1. 版本号变换
v1.11.0 --- 14 Apr 2016
v1.11.1 --- 27 Apr 2016
v1.11.2 --- 2 Jun 2016
v1.12.0 --- 29 Jul 2016
v1.12.1 --- 19 Aug 2016
v1.12.2 --- 12 Oct 2016
v1.12.3 --- 27 Oct 2016
v1.12.4 --- 13 Dec 2016
v1.12.5 --- 16 Dec 2016
v1.12.6 --- 11 Jan 2017
v1.13.0 --- 19 Jan 2017
v1.13.1 --- 9 Feb 2017
v17.03.0-ce --- 2 Mar 2017
v17.03.1-ce --- 28 Mar 2017
v17.04.0-ce --- 6 Apr 2017
v17.05.0-ce --- 6 May 2017
......
v18.05.0-ce --- 26 Apr 2018
v18.06.0-ce --- 19 Jul 2018
v18.06.1-ce --- 22 Aug 2018
v18.09.0 --- 8 Nov 2018
v18.09.1 --- 10 Jan 2019
......
v19.03.0 --- 23 Jul 2019
.......
v19.03.5 --- 15 Nov 2019
2. 架构变换
2.1. 最开始
Docker 首次发布时,Docker 引擎由两个核心组件构成:LXC 和 Docker daemon。
- Docker daemon 是一个单一的二进制文件,包含了诸如 Docker 客户端、Docker API、容器运行时、镜像构建等。
- LXC 提供了对诸如命名空间(Namespace)和控制组(CGroup)等基础工具的操作能力,它们是基于 Linux 内核的容器虚拟化技术。
2.2. LXC 变成 Libcontainer
Docker 公司开发了名为 Libcontainer 的自研工具,用于替代 LXC。Libcontainer 的目标是成为与平台无关的工具,可基于不同内核为 Docker 上层提供必要的容器交互功能。在 Docker 0.9 版本中,Libcontainer 取代 LXC 成为默认的执行驱动。
2.2.1. Docker 1.2.0 架构讲解
2.3. 拆解 daemon --- 现在的架构
Docker daemon 的整体性带来了越来越多的问题:难于变更、运行越来越慢。之后 Docker 公司开始努力拆解这个大而全的 Docker daemon 进程,并将其模块化。这项任务的目标是尽可能拆解出其中的功能特性,并用小而专的工具来实现它。这些小工具可以被替换,也可以被第三方拿去用于构建其他工具。
这遵循了 UNIX 中得以实践并验证过的一种软件哲学:小而专的工具可以组装成大型工具。
Docker 1.11.0 版本发布后 Docker Daemon 的架构由原来一个模块,被拆分成了 4 个模块:docker、containerd、docker-containerd-shim、docker-runc。关于容器执行和容器运行时的代码已经完全从 daemon 中移除,并重构为小而专的工具,比如容器运行代码就用 runc 实现了。runc 是 OCI 容器运行时标准的参考实现,它的目标就是与 OCI 规范保持一致。
OCI 定义两个容器相关的规范:镜像规范和容器运行时规范。这两个规范在 2017 年 7 月发布了 1.0 版本。但是在 1.0 版本发布之前,Docker 引擎就已经遵循该规范实现了部分功能。
2.3.1. 各个组件
2.3.1.1. daemon
当越来越多的功能从 daemon 中拆解出来并模块化之后,daemon 的主要功能就剩镜像管理、镜像构建、REST API、身份验证、安全、核心网络、卷以及编排等。
2.3.1.2. containerd
Docker daemon 的功能进行拆解后,所有的容器执行逻辑被重构到了 containerd 中。它的主要任务是容器的生命周期管理(start|stop|pause|rm),它可以指挥与 OCI 兼容的容器运行时来创建容器,默认情况下使用 runc。除此之外, containerd 组件还需要确保 Docker 镜像能够以正确的 OCI Bundle 的格式传递给 runc。
containerd 最初被设计为轻量级的小型工具,仅用于容器的生命周期管理。然而,随着时间的推移,它被赋予了更多的功能,比如镜像管理。containerd 额外的功能往往都是模块化的、可选的,便于自行选择所需功能。这样,像 kubernetes 这样的项目在使用 containerd 时,可以自己选择需要包含的功能。
containerd 在 Linux 中是以 daemon 的方式运行(从 1.11 版本之后就这样使用了,因为 containerd 是在 1.11 版本中被拆解出来的)。
containerd 是由 Docker 公司开发,并捐献了云原生计算基金会(Cloud Native Computing Foundation,CNCF)。2017 年 12 月发布了 1.0 版本,具体的发布信息可见 Github 中的 containerd/containerd。
2.3.1.3. shim
containerd 指挥 runc 来创建新容器。那么每次创建容器时都会 fork 一个新的 runc 实例。一旦容器创建完毕,对应的 runc 进程就会退出。一旦容器进程的父进程 runc 退出之后,相关联的 containerd-shim 进程就会成为容器的父进程。因此,即使运行上百个容器,也无须保持上百个运行中的 runc。
shim 的部分职责主要如下:
- 保持所有 STDIN、STDOUT 流是开启状态,所以当 daemon 重启的时候,容器不会因为管道的关闭而终止,这样的好处就是对 daemon 的维护和升级工作不会影响到运行中的容器。
- 将容器的退出状态反馈给 daemon。
2.3.1.4. runc
runc 是 OCI 容器运行时规范的参考实现,Docker 公司参与了规范的制定与 runc 的开发。runc 实质上一个轻量级的、针对 Libcontainer 进行了包装的命令行交互工具。
runc 只有一个作用那就是创建容器,它使用与 OCI 兼容的 bundle 来启动容器。runc 是一个 CLI 包装器,实质上就是一个独立的容器运行时工具。因此可以直接下载它并基于源码编译出一个二进制文件,即可拥有一个全功能的 runc。
runc 所在的那一层也称为 OCI 层,关于 runc 的发布信息见 Github opencontainers/runc 库。
上述提到的组件,在 Linux 中系统中都由单独的二进制文件来实现,具体包括 dockerd(Docker daemon)、containerd(containerd)、container-shim(shim)、docker-runc(runc)。通过 ps 命令可以看到上述这些组件的进程,只是有些进程只有在运行容器的时候才可见。
2.3.2. 启动一个新的容器的过程
当使用 Docker 命令行工具执行如下命令时,Docker 客户端会将其转换为合适的 API 格式,并发送到正确的 API 端点。
docker container run -it alpine sh
API 是在 daemon 中实现的(该 API 是一套功能丰富、基于版本的 REST API(也就是 HTTP API),这套 API 已经成为了 Docker 的标志,并且被行业接受成为事实上的容器 API)。
那么,一旦 daemon 接收到创建容器的命令,它就会向 containerd 发出调用(daemon 已经不再包含任何创建容器的代码了)。daemon 使用一种 CRUD 风格的 API,通过 gRPC 与 containerd 进行通信。
这套功能丰富、基于版本的 REST API 已经成为 Docker 的标志,并且被行业接收成为事实上的容器 API。
daemon 进程会监听 /var/run/docker.sock 这个 Unix 套接字文件,来获取来自客户端的 Docker 请求。
contaienrd 并不负责创建容器,而是指挥 runc 去创建容器。container 将 Docker 镜像转换为 OCI bundle,并让 runc 基于此创建一个新的容器。
runc 与操作系统内核接口进行通信,基于所有必要的工具(Namespace、cGroup 等)来创建容器。容器作为 runc 的子进程启动,启动完毕后,runc 将会退出。
整个启动过程如下图所示:
简单来说,docker daemon 就相当于一个服务器,接收客户端传来的数据,之后服务将这个数据发送给 containerd ,由 containerd 来创建管理容器。 daemon 其实就相当于一个接受者,实际管理容器的是 containerd。
2.3.3. 总结
将所有的用于启动、管理容器的逻辑和代码从 daemon 中移除,意味着容器运行时与 Docker daemon 实现了解耦(有时称之为无守护进程的容器)。这样的好处就是对 Docker daemon 的维护和升级工作不会影响到运行中的容器(在最开始的那个版本中,所有容器运行时的逻辑都在 daemon 中实现,启动和停止 daemon 都会导致宿主机上所有运行中的容器被杀掉,这在生产环境中是一个大问题)。
2.3.4. 小记
- containerd 其实就是负责创建和管理容器的,虽然真正的创建工作由 runc 负责
- runc 实质上一个轻量级的、针对 Libcontainer 进行了包装的命令行交互工具。
2.4. 巨人的肩膀
- 《深入浅出 Docker》