Docker与Containerd使用区别

一、前言

Kubernetes1.24 版本里弃用并移除 docker shim,这导致 1.24 版本开始不在支持 docker 运行时。大部分用户会选择使用 Containerd 做为Kubernetes运行时。

PS: docker-ce 底层就是 Containerd

使用 Containerd 时,kubelet 不需要通过 docker shim 调用,直接通过 Container Runtime Interface (CRI) 与容器运行时交互。减少调用层,并且也减少很多bug产生。

下面来讲讲 docker 与 Containerd 使用有那些方面不同。

二、名词解释

CRI 大家在看到 CRI 时就会想起 OCI,这两个名词概念容易混淆。

CRI:容器运行时接口 container runtime interface,CRI 中定义了容器和镜像两个接口,实现了这两个接口目前主流的是:CRI-O、Containerd。(目前 PCI 产品使用的即为 Containerd)。

其主要的作用:

  • 1、针对容器操作的接口,包括容器的创建、启动和停止等

  • 2、针对镜像的操作,拉去、删除镜像等

  • 3、针对 podsandbox(容器沙箱环境)

  • 4、以上全是接口

OCI:开放容器标准 open container initiative,OCI 中定义了两个标准:容器运行时标准 和 容器镜像标准,实现了这一标准的主流是:runc(也即我们日常说的 Docker)、Kata-Container。

主要作用,制作容器:

  • 1、容器镜像制作内容,即 ImageSpec

  • 2、容器需要接收哪些指令,即 runtimeSpec

三、Dockershim

Dockershim 作用:把外部收到的请求转化成 Docker Daemon 能听懂的请求,让 Docker Daemon 执行创建、删除等容器操作。

  • 1、Kubelet 通过 CRI 接口(gRPC)调用 dockershim,请求创建一个容器。CRI 即容器运行时接口,这一步中,Kubelet 可以视作一个简单的 CRI Client,而 dockershim 就是接收请求的 Server。目前 dockershim 的代码其实是内嵌在 Kubelet 中的,所以接收调用就是 Kubelet 进程。

  • 2、dockershim 收到请求后,转化成 Docker Daemon 能听懂的请求,发到 Docker Daemon 上请求创建一个容器。

  • 3、Docker Daemon 早在 1.12 版本中就已经将针对容器的操作移到另一个守护进程 containerd 中,因此 Docker Daemon 仍然不能帮我们创建容器,而是要请求 containerd 创建一个容器。

  • 4、containerd 收到请求后,并不会自己直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让 containerd-shim 去操作容器。是因为容器进程需要一个父进程来做诸如收集状态,维持 stdin 等 fd 打开等工作。而假如这个父进程就是 containerd,那每次 containerd 挂掉或升级,整个宿主机上所有的容器都得退出了。而引入了 containerd-shim 就规避了这个问题(containerd 和 shim 并不是父子进程关系)。==

  • 5、我们知道创建容器需要做一些设置 namespaces 和 cgroups,挂载 root filesystem 等等操作,而这些事该怎么做已经有了公开的规范,那就是 OCI。它的一个参考实现叫做 runC。于是,containerd-shim 在这一步需要调用 runC 这个命令行工具,来启动容器。

  • 6、runC 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程,负责收集容器进程的状态,上报给 containerd,并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理,确保不会出现僵尸进程。

通过上面来看,Docker Daemon 和 dockershim 看上去就是两个不干活的东西,Kubelet 为啥不直接调用 containerd 呢?其实和容器历程有关,这里不在阐述。

尽管现在已经有 CRI-O,containerd-plugin 这样更精简轻量的 Runtime 架构,但 dockershim 这一套作为经受了最多生产环境考验的方案,迄今为止仍是 Kubernetes 默认的 Runtime 实现。不过 Containerd 逐渐被人们所知晓。

四、展望

虽然未来 Kubelet 删除 dockershim 支持,但并不说明 Docker 马上就不能在 Kubernetes 中使用,目前容器市场 Docker 还是占用很大的比例。这中间会有一个过渡期,大家可以关注 Containerd 或者 Podman。Centos8 开始,仓库源默认容器已经从 Docker 切换为 Podman。

五、自己总结:

kubelet --> (回掉集成的dockershim) --> containerd --> containerd-shim --> runC --> container(创建完成后runC就消失,containerd-shim接管container)

六、Docker 和 Containerd 常用命令比较

镜像相关操作 Docker Containerd
显示本地镜像列表 docker images crictl images
下载镜像 docker pull crictl pull
上传镜像 docker push
删除本地镜像 docker rmi crictl rmi
查看镜像详情 docker inspect IMAGE-ID crictl inspect IMAGE-ID
容器相关操作 Docker Containerd
显示容器列表 docker ps crictl ps
创建容器 docker create crictl create
启动容器 docker start crictl start
停止容器 docker stop crictl stop
删除容器 docker rm crictl rm
查看容器详情 docker inspect crictl inspect
attach docker attach crictl attach
exec docker exec crictl exec
logs docker logs crictl logs
stats docker stats crictl stats
Pods相关操作 Docker Containerd
显示POD列表 crictl pods
查看POD详情 crictl inspectp
运行POD crictl runp
停止POD crictl stopp

七、容器日志和相关参数配置差异

功能 Docker Containerd
存储路径 如果 Docker 作为 K8S 容器运行时,容器日志的落盘将由 docker 来完成,保存在类似/var/lib/docker/containers/$CONTAINERID 目录下。Kubelet 会在 /var/log/pods 和 /var/log/containers 下面建立软链接,指向 /var/lib/docker/containers/$CONTAINERID 该目录下的容器日志文件。 如果 Containerd 作为 K8S 容器运行时, 容器日志的落盘由 Kubelet 来完成,保存至 /var/log/pods/$CONTAINER_NAME 目录下,同时在 /var/log/containers 目录下创建软链接,指向日志文件。
配置参数 在 docker 配置文件中指定:"log-driver": "json-file", "log-opts": {"max-size": "100m","max-file": "5"} 方法一:在 kubelet 参数中指定:--container-log-max-files=5 --container-log-max-size="100Mi"方法二:在 KubeletConfiguration 中指定:"containerLogMaxSize": "100Mi", "containerLogMaxFiles": 5
容器日志保存到数据盘 把数据盘挂载到 "data-root"(缺省是 /var/lib/docker)即可。 创建一个软链接 /var/log/pods 指向数据盘挂载点下的某个目录 或者 通过挂载目录,把 /var/log/pods 目录挂载到数据盘上。

八、CNI 网络

功能 Docker Containerd
谁负责调用 CNI Kubelet 内部的 docker-shim Containerd 内置的 cri-plugin(containerd 1.1 以后)
如何配置 CNI Kubelet 参数 --cni-bin-dir--cni-conf-dir Containerd 配置文件(toml):[plugins.cri.cni] bin_dir = "/opt/cni/bin" conf_dir = "/etc/cni/net.d"