docker概念简介

2022-11-30 | Tags: Docker

docker是什么

Docker是一个用于开发、交付和运行应用程序的开放平台。docker参照了航运中的集装箱概念,在航运中不关心你运输的是汽车还是棉花,它都通过集装箱将运输的流程规范成了标准化的操作。 类似的,docker提出了容器的概念,容器具备自包含的能力,将自身程序所依赖的程序和环境全部包含在了容器中,在任何操作系统的宿主机上,只要有docker环境,用户就可以在此之上运行任意容器。也就是Docker所说的“Build once, Run anywhere”。

docker不光提供了容器运行的引擎,并且在此的基础之上,提供了一系列部署工具和容器仓库(dockerhub),极大方便了容器在不同系统平台上的部署和最后的交付。

# 举个例子,安装完docker之后,通过以下命令就可以启动一个监听在宿主机8080端口的nginx服务
docker run -p 8080:80 -v /some/content:/usr/share/nginx/html:ro -d nginx
# 一个更复杂一些的例子,快速部署一个完整的gitlab
docker run -d \
  --hostname gitlab.example.com \
  --publish 443:443 --publish 80:80 --publish 22:22 \
  --name gitlab \
  --restart always \
  -v $GITLAB_HOME/config:/etc/gitlab \
  -v $GITLAB_HOME/logs:/var/log/gitlab \
  -v $GITLAB_HOME/data:/var/opt/gitlab \
  --shm-size 256m \
  gitlab/gitlab-ee:latest

对比虚拟机

两者都是虚拟化技术,容器可以对标虚拟机的概念。 不同的是容器是直接构建在宿主机的操作系统之上(主要是linux),通过资源隔离达成环境虚拟化,对于宿主机来说,容器与其他进程并无不同; 虚拟机则需要先虚拟化出相应的硬件,之后在虚拟的硬件上再运行对应的操作系统和程序。

造成的差异是:

  • 容器相比虚拟机启动速度更快,几乎没有占用额外资源和性能损失,并且可以同时运行更多的容器
  • 容器的硬件资源(CPU、内存、IO 等)调整更加灵活,可以在线进行调整
  • 容器的隔离性和安全性不如虚拟机,容器没有硬件隔离,假如通过漏洞提权之后可以更容易的突破到宿主机
  • 容器的适应度不如虚拟机,因为容器是共享宿主机内核的,所以需要高版本内核的容器是无法跑在低版本内核的宿主机系统上的,同样的arm64架构的容器也是无法跑在amd64架构的宿主机系统上的,而虚拟机就没有这个问题

概念和原理

容器(container)

简单来说,容器就是一个虚拟化的沙盒环境,这个环境与宿主机上的其他资源隔离(进程,CPU,内存,文件,网络,io等等)。因为隔离,所以每个容器都是独立的,不会相互影响。

namespace

Linux namespace 实现了以下 6 项资源隔离,基本上涵盖了一个小型操作系统的运行要素。

cgroups

cgroups 是 Linux 内核提供的一种资源控制机制,可以限制和记录任务组(进程组或线程组)使用的物理资源(包括 CPU、内存、IO 等)。

docker中容器的本质

实际上 docker容器使用了很多隔离功能(namespace,cgroups),让容器看起来像一个轻量级虚拟机在独立运行。因此容器的本质是一个linux中被限制了的,具有逻辑上独立文件系统和网络的进程。这个进程对于宿主机来说与别的进程并无本质区别,所以才能做到几乎没有性能上的损失。

镜像(image)

有了进程和物理资源,剩下要运行容器所需要的就是文件系统了。 镜像是一个只读模板,其中包含创建docker容器所需的文件系统(rootfs+依赖+程序)。当容器运行时,它会使用镜像提供的文件系统作为容器的运行文件系统,不同的镜像可以提供不同的文件系统,镜像可以由自己定制。除了文件系统之外,镜像还可以提供容器运行的环境变量,启动命令和其他容器启动的元数据。

UnionFS

UnionFS 是一种用于把多个文件系统“联合”到同一个挂载点的文件系统服务。 它可以联合了多个不同的目录,并且把他们挂载到一个统一的目录上。在这些“联合”的子目录中, 有一部分是可读可写的,但是有一部分只是可读的。当你对可读的目录内容做出修改的时候,其结果只会保存到可写的目录下,不会影响只读的目录。

举个例子:我们可以把我们的服务的源代码目录和一个存放代码修改的目录“联合”起来构成一个 联合目录。前者设置只读权限,后者设置读写权限。那么,一切对联合目录下文件的修改都只会影响那个存放修改的目录,不会污染原始的代码。

docker 镜像分层

基于UnionFS的原理,docker镜像是由一系列层构建而成的,每一层都记录了镜像构建中的对应阶段的文件变化。镜像的每一层都是只读的。这些层会按栈的方式排列,并用UnionFS原理“联合”起来。

做镜像分层最大的一个优点是资源共享:

  • 如果多个镜像都从相同的base镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像即可
  • 当镜像构建只做了顶层改动的时候,只要更新最上层即可,其余未变化的层就不用重新传输和存储

容器和镜像文件系统的统一

当创建一个新容器时,会在最上层镜像的文件系统之上添加一个新的读写层,这一层通常被称为“容器层”。对正在运行的容器所做的所有更改,例如写入新文件、修改现有文件和删除文件,都写入这个薄的读写容器层。 在这样的框架下,容器和镜像的文件系统被巧妙统一了起来,它们的主要区别在于容器的顶层是读写“容器层”,而镜像是只读层。对容器中文件的所有写入都存储在这个“容器层”中,当容器被删除时,“容器层”也被删除,底层镜像则保持不变。

因为每个容器都有自己的读写“容器层”,并且所有更改都存储在这个容器层中,所以多个容器可以共享同一个底层镜像并且能拥有自己的数据状态。

Copy On Write

那么,对最上层的修改是怎么实现的呢?

写时复制(Copy On Write)是一种获得高效率共享和复制文件的策略。如果一个文件或目录存在于镜像内的较低层,而上一层(包括可写层)需要对其进行读取访问,则它只是在上一层第一次需要修改文件时(构建镜像或运行容器时),将文件复制到该层并进行修改。这最小化了I/O和镜像中每个层的大小。

这个策略和linux中进程fork时的内存写策略是一致的。

架构

docker整体架构就比较简单了,是一个经典的C/S架构,具体可以看下图

  • Docker daemon(dockerd)监听Docker API请求并负责管理和运行docker对象,如镜像,容器等。
  • Docker client(docker)是用户与Docker交互的主要方式。当使用docker run等命令时,客户端将使用Docker API将这些命令发送给dockerd,由dockerd执行。docker提供了相当丰富的命令用户管理,控制和监控各种docker中的资源。
  • Docker registry存储docker镜像,提供对docker镜像的存储,管理能力。这个是docker比较有创意的一个组件,让docker突破了私有集群的界限,能更方便快捷的分享自己的镜像,也促大大成了docker生态的繁荣。Docker Hub是任何人都可以使用的公共registry,而使用docker搭建私有registry也是一件很简单的事。当使用docker pull或docker run命令时,docker将从配置的registry中拉取对应镜像。当使用docker push命令时,对应镜像将被推送到配置的registry。

生态

容器规范

容器不光是 Docker,还有其他容器,比如 CoreOS 的 rkt。为了保证不同容器之间能够兼容, Docker、CoreOS、Google等公司共同成立了一个叫 Open Container Initiative(OCI) 的组织,其目是制定开放的容器规范:runtime spec 和 image format spec。有了这两个规范,不同组织和厂商开发的容器能够在不同的 runtime 上运行。这样就保证了容器的可移植性和互操作性。

runtime

runtime 是容器真正运行的地方。runtime 需要跟操作系统 kernel 紧密协作,为容器提供运行环境。这里的runtime可以粗略理解为对内核中namespace,cgroups和控制脚本等工具进行包装的中间件,用于抹平不同内核中能力的差异,目前有以下几种runtime:

  • lxc 是 Linux 上老牌的容器 runtime。Docker 最初也是用 lxc 作为 runtime。
  • runc 是 docker 自己开发的容器 runtime,符合 oci 规范,也是现在 Docker 的默认 runtime。在runc之后,docker官方为了更好的适应oci规范,后来又开源了更高层抽象的containerd。
  • rkt 是 CoreOS 开发的容器 runtime,符合 oci 规范,因而能够运行 Docker 的容器。

管理工具

容器管理工具顾名思义就是用户用来管理容器的工具。容器管理工具对内与 runtime 交互,对外为用户提供操作接口。

我们用户口中说的docker一般就是指的 docker engine。docker engine 包含架构中的 deamon 和 client 两个部分。

除docker之外,还有lxc runtime对应的lxd,rkt runtime对应的rkt cli。

定义工具

容器定义工具允许用户定义容器的内容和属性,这样容器就能够被保存,共享和重建。

  • docker image 是 docker 容器的模板,dockerfile 是创建 docker image的命令序列描述文件。
  • ACI (App Container Image) 与 docker image 类似,只不过它是由 CoreOS 开发的 rkt 容器的 image 格式。

Registry

容器是通过 image 创建的,需要有一个仓库来统一存放 image,这个仓库就叫做 Registry。这里的标准就是Docker Registry。

容器 OS

由于有容器 runtime,几乎所有的 Linux、MAC OS 和 Windows 都可以运行容器。但这不并妨碍容器 OS 的问世。容器 OS 是专门运行容器的操作系统。与常规 OS 相比,容器 OS 通常体积更小,启动更快。因为是为容器定制的 OS,通常它们运行容器的效率会更高。目前几乎各大linux发行版都有相对应的容器版本。

容器编排引擎

基于容器的应用一般会采用微服务架构。在这种架构下,应用被划分为不同的组件,并以服务的形式运行在各自的容器中,通过 API 对外提供服务。为了保证应用的高可用,这些容器会组成集群,集群中的容器会根据业务需要被动态地创建、迁移和销毁。

这就是容器编排引擎要干的工作,所谓编排(orchestration),通常包括容器管理、调度、集群定义健康监测和服务发现等。通过容器编排引擎,容器被有机的组合成微服务应用,实现业务需求。

  • docker swarm 是 Docker 开发的容器编排引擎。
  • mesos 是一个通用的集群资源调度平台,mesos 与 marathon 一起提供容器编排引擎功能。
  • kubernetes 是 Google 领导开发的开源容器编排引擎,同时支持 Docker 和 CoreOS 容器。

目前kubernetes已经成为了事实上的容器编排引擎标准,并且在渐渐移除对docker engine的使用。