开门见山的说,本文讲回顾容器发展历史,理清各种名词之间的关系,包含切不限于以下
- docker
- containerd
- runc
- shim
- OCI
- CRI
- ......
docker起源
最初docker是在2013年开发,使用了LXC【Linux 内核容器虚拟化】技术, LXC是linux基金会对 namespace和Cgroup的官方封装,相当于提供了更高一层的库来避免手动操作namespace和Cgroup, 然后docker使用了LXC并在基础上提供了方便的镜像管理和容器运行时管理。
后来docker自己用go语言实现了自己的LXC, 称之为 libcontainer。
最初的docker就是c/s架构, 即用户使用cli命令行作为客户端 和 docker Daemon 服务端通信,此时的docker Daemon还是一坨代码并没有做切分。
$ sudo docker version
Client:
Version: 18.03.1-ce
API version: 1.37
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:17:20 2018
OS/Arch: linux/amd64
Experimental: false
Orchestrator: swarm
Server:
Engine:
Version: 18.03.1-ce
API version: 1.37 (minimum version 1.12)
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:15:30 2018
OS/Arch: linux/amd64
Experimental: false
复制代码
docker拆分
时间来到了2015年, 此时的docker如日中天,而且也出现了越来越多的容器项目,于是由 Docker,CoreOS 和Google其他容器行业的领导者成立了OCI组织【The Open Containers Initiative】。 它维护了运行时和镜像规范。它的目的是围绕容器行业制定标准,规定了创建一个容器需要实现哪些api,至于api内部实现由项目本身决定,这样一来不同的容器项目只要实现了OCI标准就可以互相兼容。
2015 年,Docker 宣布使用轻量级的、可移植的运行时 runC,它基本上是一个直接使用 libcontainer 的命令行工具,而无需通过 Docker 引擎。runC只实现了容器运行时,并不提供镜像相关的功能。
runc 的前身是 libcontainer, runc = libcontainer + client ,所以官方说的是符合 OCI 规范的管理容器的 CLI 工具。容器这里对应的是 libcontainer。
注意,基于 OCI 标准实现的运行时,不止有 runc,还有虚拟机运行 runv: Hypervisor-based Runtime for OCI 以及 huawei-openlabl: oct 等。这就是指定标准的好处。
再注意,runc 是独立于 Docker 的,它是比 Docker 更轻量级的,而且是符合 OCI 标准化的,Docker 不具备这些。
runC 的目标是使标准容器随处可用,该项目转赠给了 OCI。
2016年,Docker搞了Swarm项目,并做了架构切分,拆分出 containerd ,让他做之前docker做的事情,剩下的Docker Daemon 专门负责上层的封装编排,containerd 从核心的 Docker 引擎中移出,并变成一个单独的守护进程。
由此Docker 从一个单一的程序转变成了一组独立的组件和项目。
可以看到图上出现了 shim 组件, 这个组件的作用其实是相当于一个中间件, 解藕containerd和底层容器运行时之间的关系。当containerd出现异常终止时不会影响正在运行的容器。
此时Docker 创建容器的步骤
-
Docker 引擎创建镜像
-
传递给 containerd
-
containerd 创建 containerd-shim
-
containerd-shim 使用 runC 运行一个容器
-
containerd-shim 允许运行时(这种情况下是 runC)在启动后推出
Client: Version: 19.03.8 API version: 1.40 Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:21:11 2020 OS/Arch: darwin/amd64 Context: default Experimental: true
Server: Engine: Version: 19.03.8 API version: 1.40 (minimum version 1.12) Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:29:16 2020 OS/Arch: linux/amd64 Experimental: true containerd: Version: v1.2.13 GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc: Version: 1.0.0-rc10 GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd docker-init: Version: 0.18.0 GitCommit: fec3683
容器编排
随着容器技术被越来越多的人使用, 能够自动化处理部署、管理、弹性伸缩、容器网络管理的需求的呼声也越来越高。
Swarm 是 Docker 自己开发的编排工具。起初是一个独立的工具,在 1.12 版本之后包含在 Docker 中。它使用 Docker CLI 创建 swarm 集群、部署和管理应用程序和服务。
公司在用的k8s就不用说了, google主导开发的容器编排项目, 初期在代码里写死了对docker的支持,随后在容器编排的竞争中战胜了swarm,成为了事实上的标准。
K8S 和 docker
早期为了支持多个容器引擎,是在Kubernetes内部对多个容器引擎做兼容,例如kubelet启动一个docker-manager的进程直接调用docker的api进行容器的创建。
后来k8s为了隔离各个容器引擎之间的差异,在docker分出containerd后,k8s也搞出了自己的容器运行时接口(CRI),CRI的出现是为了统一k8s与不同容器引擎之间交互的接口,与OCI的容器运行时规范不同,CRI更加适合k8s,不仅包含对容器的管理,还引入了k8s中Pod的概念及对Pod生命周期的管理。 k8s开始把containerd接入CRI标准。kubelet通过CRI接口调用docker-shim,进一步调用docker api。此时在每个k8s节点上kubelet大致按下图流程启动容器:
为了更好的将containerd接入到CRI标准中,k8s又搞出了cri-containerd项目,cri-containerd是一个守护进程用来实现kubelet和containerd之间的交互,此时k8s节点上kubelet大致按下图流程启动容器:
在上图中cri-containerd和containerd还是两个独立的进程,他们之间通过gRPC通信,后来在Containerd 1.1时,将cri-containerd改成了Containerd的CRI插件,CRI插件位于containerd内部,这让k8s启动Pod时的通信更加高效,此时k8s节点上kubelet大致按下图流程启动容器:
早期: kubelet --> docker-manager --> docker
中期: kubelet -CRI-> docker-shim --> docker --> containerd --> runc
中期: kubelet -CRI-> cri-containerd --> containerd --> runc
当前: kubelet -CRI-> containerd(CRI plugin) --> runc
复制代码
PS
CRI出现的背景
起初k8s在代码层面内置并且默认docker的支持, 后来出现了rkt【coreos推出的容器项目】,并且rkt也想合并到k8s代码中【是写死的那种】,可能是为了削弱docker的影响,最终这个方案通过了,支持rkt的代码就合并到了k8s中。
但是这肯定不是可持续发展的模式,后面想支持其他的容器实现再写一套代码?而且还会有各种兼容性问题,所以k8s在 1.5版本 推出了 CRI 标准。想要接入k8s的容器项目不必合并到k8s主干代码中,只需要实现CRI即可。