Docker的架构
Docker 在 1.11 之前主要是通过 docker daemon 来处理 client 的请求,容器的相关操作都是通过 docker daemon 来完成。从 1.11 之后,并不是简简单单的通过 docker daemon 来处理了,它集成了 Containerd、RunC 等多个组件。这些组件之间相互协作来完成客户端请求和容器管理。
现在的架构图如下:
下面对这些组件进行一一说明。
Dockerd
Dockerd 是一个守护进程,它可以通过 TCP 和 UNIX Domain Socket 两种途径接收客户端的 HTTP 请求,这些请求都是 Restful 风格。也就是说 Dockerd 是面向用户的,它是对容器操作相关的 API 的上层封装。
Containerd
Containerd 对外提供 gRPC 形式的 API,API 的定义中不再包含于集群、编排等相关功能,但是它也不是简单的将 Docker API 照搬过来,而是进行了更细粒度的抽象,并且还实现了监控管理、多租户的接口,方便外部应用利用这套 API 来实现高效和定制容器管理功能。
Containerd-shim
在默认情况下,Docker 守护进程在停止容器的时候会发送 SIGTERM 信号,而容器进程有可能错误的忽略该信号,为了能够正确的处理系统信号等相关特性,通过 Containerd-shim 来保证能够正确处理各种信号,所以每个容器都会对应一个 Containerd-shim 实例。它对外的接口是 ttRPC。
RunC
OCI 定义了容器运行时标准,runC 是 Docker 按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。runC 是从 Docker 的 libcontainer 中迁移而来的,实现了容器启停、资源隔离等功能。Docker 默认提供了 docker-runc 实现,事实上,通过 containerd 的封装,可以在 Docker Daemon 启动的时候指定 runc 的实现。
runc 不是以守护进程的方式来执行,它会将容器的运行配置和状态数据记录在 json 文件中,当 Containerd 要求 Runc 执行例如停止、暂停容器等操作,它会根据容器 ID 在配置好的路径下找到该 json 文件,然后再利用 json 中记录的容器进程 PID 以及 CGroup 文件路径等作为参数来调用操作系统 API,并完成自己的任务。
由 RunC 启动的容器进程的标准输入和标准输出被重定向到管道中,并在 Contained 中被关联到 FIFO 文件中,这些 FIFO 文件是在 Dockerd 中创建的,并通过 gRPC 请求将它们的路径名传递到 Contained 中。