containerd fully leverages the OCI runtime specification1, image format specifications and OCI reference implementation (runc).
containerd includes a daemon exposing gRPC API over a local UNIX socket. The API is a low-level one designed for higher layers to wrap and extend. Containerd uses RunC to run containers according to the OCI specification.
▌docker-shim:
/usr/bin/docker-containerd-shim
每启动一个容器都会起一个新的docker-shim的一个进程.
他直接通过指定的三个参数来创建一个容器:
- 容器id
- boundle目录(containerd的对应某个容器生成的目录,一般位于:/var/run/docker/libcontainerd/containerID)
- 运行是二进制(默认为runc)来调用runc的api(比如创建容器时,最后拼装的命令如下:runc create 。。。)
他的作用是:
- 它允许容器运行时(即 runC)在启动容器之后退出,简单说就是不必为每个容器一直运行一个容器运行时(runC)
- 即使在 containerd 和 dockerd 都挂掉的情况下,容器的标准 IO 和其它的文件描述符也都是可用的
- 向 containerd 报告容器的退出状态
前两点尤其重要,有了它们就可以在不中断容器运行的情况下升级或重启 dockerd(这对于生产环境来说意义重大)。
▌runc (OCI reference implementation)
/usr/bin/docker-runc
OCI定义了容器运行时标准OCI Runtime Spec support (aka runC),runC是Docker按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。
runC是从Docker的libcontainer中迁移而来的,实现了容器启停、资源隔离等功能。
Docker默认提供了docker-runc实现,事实上,通过containerd的封装,可以在Docker Daemon启动的时候指定runc的实现。
我们可以通过启动Docker Daemon时增加–add-runtime参数来选择其他的runC现。例如:
docker daemon --add-runtime "custom=/usr/local/bin/my-runc-replacement"
▌Docker、containerd, containerd-shim和runc之间的关系
他们之间的关系如下图:
我们可以通过启动一个Docker容器,来观察进程之间的关联。
▌通过docker 而通过runc来启动一个container的过程
▌查看进程信息
利用docker top命令,可以让我们从宿主机操作系统中看到容器的进程信息。
[root@VM-4-17-centos ~]# docker top centos6-2
UID PID PPID C STIME TTY TIME CMD
root 4962 4948 0 16:24 pts/0 00:00:00 /usr/sbin/sshd -D
▌查看子进程信息
[root@VM-4-17-centos containerd]# ps aux | grep 4948
root 4948 0.0 0.0 12212 3696 ? Sl 16:24 0:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc
root 27040 0.0 0.0 115932 1004 pts/0 S 18:32 0:00 grep --color=auto 4948
▌查看进程树
pstree -l -a -A 4948 -p
输出结果如下:
[root@VM-4-17-centos containerd]# pstree -l -a -A 4948 -p
docker-containe,4948 -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc
|-sshd,4962 -D
|-{docker-containe},4949
|-{docker-containe},4950
|-{docker-containe},4951
|-{docker-containe},4952
|-{docker-containe},4953
|-{docker-containe},4954
`-{docker-containe},1593
虽然pstree命令截断了命令,但我们还是能够看出,
当Docker daemon启动之后,dockerd和docker-containerd进程一直存在。
当启动容器之后,docker-containerd进程(也是这里介绍的containerd组件)会创建docker-containerd-shim进程,其中的参数460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd就是要启动容器的id。
最后docker-containerd-shim子进程,已经是实际在容器中运行的进程(既sleep 1000)。
docker-containerd-shim另一个参数,是一个和容器相关的目录/var/run/docker/containerd/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd,里面的内容有:
[root@VM-4-17-centos containerd]# ll /var/run/docker/containerd/460d688239304172f39bb9586bfc5959e0c3db64e7c3a0937f1003f94408ebbd
total 0
prwx------ 1 root root 0 Nov 3 16:24 init-stdin
prwx------ 1 root root 0 Nov 3 16:24 init-stdout
其中包括了容器配置和标准输入、标准输出、标准错误三个管道文件。
▌docker-shim
docker-shim是一个真实运行的容器的真实垫片载体,
每启动一个容器都会起一个新的docker-shim的一个进程,
他直接通过指定的三个参数:
- 容器id,
- boundle目录(containerd的对应某个容器生成的目录,一般位于:/var/run/docker/libcontainerd/containerID),
- 运行时二进制(默认为runc)
- 调用runc的api创建一个容器(比如创建容器:最后拼装的命令如下:runc create 。。。。。)
▌RunC
OCI定义了容器运行时标准,
runC是Docker按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。
runC是从Docker的libcontainer中迁移而来的,实现了容器启停、资源隔离等功能。
Docker默认提供了docker-runc实现,事实上,通过containerd的封装,可以在Docker Daemon启动的时候指定runc的实现。
▌CRI:
kubernetes在初期版本里,就对多个容器引擎做了兼容,因此可以使用docker、rkt对容器进行管理。
以docker为例,kubelet中会启动一个docker manager,通过直接调用docker的api进行容器的创建等操作。
在k8s 1.5版本之后,kubernetes推出了自己的运行时接口api–CRI(container runtime interface)。cri接口的推出,隔离了各个容器引擎之间的差异,而通过统一的接口与各个容器引擎之间进行互动。
与oci不同,cri与kubernetes的概念更加贴合,并紧密绑定。
cri不仅定义了容器的生命周期的管理,还引入了k8s中pod的概念,并定义了管理pod的生命周期。
在kubernetes中,pod是由一组进行了资源限制的,在隔离环境中的容器组成。而这个隔离环境,称之为PodSandbox。在cri开始之初,主要是支持docker和rkt两种。其中kubelet是通过cri接口,调用docker-shim,并进一步调用docker api实现的。
如上文所述,docker独立出来了containerd。kubernetes也顺应潮流,孵化了cri-containerd项目,用以将containerd接入到cri的标准中。