Docker引擎

108 阅读3分钟

Docker使用的是传统的client-server模式,用户通过DockerClient与Docker daemon建立通信,并将请求发送给后者 image.png Docker 引擎由如下主要的组件构成:Docker 客户端(Docker Client)、Docker 守护进程 (Docker daemon)、containerd 以及 runc。它们共同负责容器的创建和运行

image.png

Docker 首次发布时,Docker 引擎由两个核心组件构成:LXC 和 Docker daemon。 Docker daemon 是单一的二进制文件,包含诸如 Docker 客户端、Docker API、容器运行 时、镜像构建等
LXC 提供了对诸如命名空间(Namespace)和控制组(CGroup)等基础工具的操作能力, 它们是基于 Linux 内核的容器虚拟化技术。然而LXC作为核心的组件依赖于外部工具,这会给项目带来巨大风险,甚至影响其发展;其次,LXC是基于Linux的,这对于一个立志于跨平台的项目来说是个问题。
Docker公司后续自研Libcontainer的目标是成为与平台无关的工具,可基于不同内核为 Docker 上层提供必要的 容器交互功能,在Docker 0.9 版本中,Libcontainer 取代 LXC 成为默认的执行驱动。

目前 Docker 引擎的架构示意图如下图所示

image.png runc 是 OCI 容器运行时规范的参考实现,runc 实质上是一个轻量级的、针对 Libcontainer 进行了包装的命令行交 互工具,runc 生来只有一个作用——创建容器。
containerd主要任务是容器的生命周期管理——start | stop | pause | rm....

启动一个新的容器

针对如下启动命令

docker container run --name ctr1 -it alpine:latest sh
  1. 当使用 Docker 命令行工具执行如上命令时,Docker 客户端会将其转换为合适的 API 格式, 并发送到正确的 API 端点
  2. 一旦 daemon 接收到创建新容器的命令,它就会向 containerd 发出调用。daemon 已经不再包含任何创建容器的代码了。daemon 使用一种 CRUD 风格的 API,通过 gRPC 与 containerd 进行通信。
  3. containerd 将 Docker 镜像转换为 OCI bundle,并让 runc 基于此创建一个新的容器
  4. runc 与操作系统内核接口进行通信,基于所有必要的工具(Namespace、CGroup 等) 来创建容器。容器进程作为 runc 的子进程启动,启动完毕后,runc 将会退出

image.png

shim

shim 是实现无 daemon 的容器(用于将运行中的容器与 daemon 解耦,以便进行 daemon 升级等操作)不可或缺的工具。
前面提到,containerd 指挥 runc 来创建新容器。事实上,每次创建容器时它都会 fork 一 个新的 runc 实例。不过,一旦容器创建完毕,对应的 runc 进程就会退出。因此,即使运行上百个容器,也无 须保持上百个运行中的 runc 实例。
一旦容器进程的父进程 runc 退出,相关联的 containerd-shim 进程就会成为容器的父进程。 作为容器的父进程,shim 的部分职责如下。

  1. 保持所有 STDIN 和 STDOUT 流是开启状态,从而当 daemon 重启的时候,容器 不会因为管道(pipe)的关闭而终止。
  2. 将容器的退出状态反馈给 daemon。