搭建DevOps企业级仿真实验环境:012容器运行时 containerd 详解
引言
在 Kubernetes 集群中,真正负责创建和运行容器进程的并不是 kubelet 本身,而是容器运行时。随着 Kubernetes 从 v1.20 开始逐步弃用 dockershim,**containerd** 已经成为社区推荐的默认容器运行时。它从 Docker 项目中独立出来,由 CNCF 孵化并毕业,以轻量、稳定、模块化的特性支撑着无数生产集群。
然而,许多从 Docker 迁移过来的开发者对 containerd 仍感到陌生:它与 Docker 到底有哪些不同?它的进程模型是怎样的?如何配置和调试?本文将围绕这些问题,从 CRI 接口标准出发,深入解析 containerd 的架构分层,并通过实际操作在 Ubuntu 22.04 上完成部署、镜像拉取、容器创建以及进程分析,最后对比 containerd 与 Docker 的本质差异,帮助读者建立起对容器运行时的清晰认知。
一、容器运行时在 Kubernetes 中的定位
Kubernetes 调度的是容器化应用,而容器运行时就是负责执行这些容器的软件层。为了避免 kubelet 与特定的运行时绑定,K8s 定义了 **CRI(Container Runtime Interface)**——一套标准的 gRPC 接口,使得任何实现了 CRI 的运行时都可以无缝接入。
kubelet 通过 CRI 向运行时发出如下指令:
- 创建和管理 Pod 沙箱(pause 容器 + 网络命名空间)
- 创建、启动、停止和删除业务容器
- 拉取、列表、删除镜像
这种可插拔的架构意味着,只要替换 CRI 实现,我们就可以在 containerd、CRI-O 甚至其他定制运行时之间自由切换,而不必修改 kubelet 自身。调用流程可以简化为:
|
Plain Text |
关键操作对应到 CRI 方法:RunPodSandbox 创建沙箱、CreateContainer 在沙箱内创建容器、StartContainer 启动进程、PullImage 拉取镜像等。
二、containerd 的演进与架构分层
containerd 最初是 Docker 引擎的底层容器管理器,2016 年从 Docker 项目分离并捐赠给 CNCF,2019 年成为毕业项目。如今它定位为行业标准的容器运行时,原生支持 CRI 插件,架构高度模块化。
其内部可以划分为五个核心层次:
|
层级 |
组件 |
功能 |
|
API 层 |
GRPC API |
对外暴露容器与镜像管理接口,包括 CRI 和原生 API |
|
核心层 |
Core |
管理对象元数据、命名空间、事件流、任务生命周期 |
|
快照层 |
Snapshotter |
管理联合文件系统(overlayfs、device-mapper 等),为容器提供 rootfs |
|
运行时层 |
Runtime |
通过 shim 调用 runc 或其他 OCI 运行时创建容器 |
|
存储层 |
Storage |
基于内容寻址存储(content store)管理镜像层和 manifests |
所有层级通过清晰的 API 边界解耦,开发者可以针对快照、运行时等单独扩展,例如替换成 katacontainers 或 gVisor 等安全运行时。
三、进程模型:containerd-shim 的关键角色
在 containerd 的进程树中,最值得关注的组件是 **containerd-shim-runc-v2**。其设计目的是让 runc 在完成容器创建后可以退出,而容器进程继续运行,避免 containerd 自身直接成为所有容器进程的父进程。
运行时调用链为:
|
Plain Text |
我们可以用 pstree 观察实际的进程关系:
|
Bash |
输出示例:
|
Plain Text |
shim 的作用可以归纳为三点:
- 作为容器的父进程,持续收集退出码和状态;
- 保持容器的标准输入输出流连接;
- 当容器退出时向 containerd 报告,自身随后也退出。
这种设计使得 containerd 守护进程的重启不会影响到已运行的容器,整体稳定性大幅提升。
四、containerd 与 Docker 的渊源与区别
传统 Docker 引擎的架构可以看作:
|
Plain Text |
Docker 是一个“全家桶”式的平台,除了 containerd 还包含了 dockerd 守护进程、命令行工具、镜像构建(BuildKit 集成)、Docker Compose 等大量功能。
而 containerd 独立运行时,去掉了 dockerd 这一层,调用路径变得极为简洁:
|
Plain Text |
两者的关键区别可以总结如下:
|
维度 |
containerd |
Docker |
|
定位 |
聚焦容器运行时与镜像管理 |
完整开发到部署平台 |
|
进程模型 |
单一守护进程 + shim |
dockerd + containerd + shim |
|
CRI 支持 |
内置原生 CRI 插件 |
曾依赖外部 dockershim(已移除) |
|
镜像构建 |
无原生 docker build,需配合 BuildKit 或 nerdctl |
内置 build 命令 |
|
默认 CLI |
ctr(调试用,功能基础) |
docker(用户友好,生态丰富) |
|
资源占用 |
较低,仅运行时核心 |
较高,包含完整平台组件 |
从 Kubernetes 的视角看,推荐 containerd 的原因非常明确:去除不必要的中介层,减少故障面,提升整体稳定性。Docker 虽然功能强大,但在 K8s 集群中,构建镜像等功能早已被 CI/CD 流水线接管,节点上仅需纯粹的容器运行时即可。
五、实训:在 Ubuntu 22.04 上部署 containerd
接下来我们在真实环境中完成 containerd 的安装与配置。
环境准备:一台 Ubuntu 22.04 节点,拥有 root 权限,可访问外网(如已安装 Docker 可保留用于后续对比)。
- 安装 containerd
|
Bash |
- 生成并修改默认配置
默认配置文件位于 /etc/containerd/config.toml。生成一份标准配置并启用 systemd cgroup 驱动:
|
Bash |
为何一定要启用 SystemdCgroup = true?Kubernetes 推荐使用 systemd 作为节点的 cgroup 管理器,如果 containerd 仍使用 cgroupfs,就会在同一节点上存在两层 cgroup 管理器,极易引起资源竞争和冲突。该选项位于配置文件中的:
|
TOML |

![]()
- 启动并验证服务
|
Bash # 根目录确保 rw |
确认 containerd 服务已正常运行,并且监听了 Unix Socket。

六、使用 ctr 操作 containerd 原生接口
ctr 是 containerd 自带的命令行工具,直接与原生 gRPC API 交互,不经过 CRI。它适合底层调试。
常用操作示例:
|
Bash
sudo ctr -n k8s.io c ls # 列出镜像 sudo ctr -n k8s.io i ls # 修复 DNS sudo chattr -i /etc/resolv.conf echo "nameserver 223.5.5.5" | sudo tee /etc/resolv.conf echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf sudo systemctl restart systemd-resolved # 拉取 nginx 镜像(使用毫秒镜像) sudo ctr -n k8s.io image pull docker.1ms.run/library/nginx:alpine sudo ctr -n k8s.io run -d --net-host docker.1ms.run/library/nginx:alpine test-nginx 方案 B:前台测试(用完自动删除) sudo ctr -n k8s.io run --rm --net-host docker.1ms.run/library/nginx:alpine test-nginx
sudo ctr -n k8s.io container ls |

在 containerd 的概念模型中,**container** 是一个静态的配置对象,而 task 代表实际运行中的进程实例。同一个 container 可以对应多个 task(例如容器重启后会产生新的 task)。
七、使用 crictl 通过 CRI 接口调试
crictl 是 Kubernetes 社区提供的 CRI 调试工具,它沿着与 kubelet 完全相同的 gRPC 路径与运行时交互,这对于验证 CRI 实现是否正常工作非常有帮助。
- 安装 crictl
|
Bash |
- 创建连接配置 /etc/crictl.yaml
|
Bash |
- 拉取镜像并创建 Pod 沙箱
|
Bash |
![]()
创建沙箱配置文件 pod-config.json:
|
JSON |
运行沙箱(即 pause 容器)并获取其 ID:
|
Bash |
创建业务容器配置 container-config.json:
|
JSON |
创建并启动容器:
|
Bash |
验证容器已运行:
|
Bash |

通过 crictl,我们可以看到容器已经以 Pod 为单位组织:每个 Pod 先有一个 pause 容器(沙箱),然后业务容器加入该沙箱共享网络和 IPC 命名空间。这与 kubelet 创建 Pod 的过程完全一致。
ctr 与 crictl 的使用场景对比:
- ctr:直接操作 containerd 原生接口,无 Pod 概念,适合运行时底层调试;
- crictl:遵循 CRI 标准,有 Pod 概念,适合模拟 kubelet 行为、验证 K8s 节点运行时健康度。
八、containerd 与 Docker 进程模型对比实战
如果我们手头有一个同时安装了 Docker 的节点,可以直观对比两者的进程树。
Docker 环境:运行一个 nginx 容器
|
Bash |
典型输出会看到 dockerd 调用 containerd,再调用 containerd-shim 和 nginx。
纯净 containerd 环境:如前述,只有
|
Plain Text |
显然 containerd 路径更短,没有 dockerd 这个额外进程。这意味着少了一个守护进程的内存开销,也减少了因 dockerd 自身故障导致容器不可用的风险。
此外,镜像构建是 containerd 不能原生覆盖的场景。containerd 没有 docker build 命令,但可以配合 BuildKit 或 **nerdctl**(一个兼容 Docker CLI 风格的工具)来构建镜像,形成完整的容器工作流。
九、containerd 完整工作流程与日志分析
让我们从 CRI 请求的角度把整个容器启动流程串起来:
- crictl runp 发送 RunPodSandbox 请求 → containerd API 层接收。
- Core 创建 sandbox 元数据,Snapshotter 为 pause 容器准备 rootfs(基于 overlayfs 快照)。
- Runtime 层创建 containerd-shim 进程,shim 调用 runc 启动 pause 容器。
- 若有 CNI 配置,此时会配置 Pod 的网络命名空间。
- crictl create 发送 CreateContainer,在指定 sandbox 内创建容器元数据,Snapshotter 为业务容器准备 rootfs。
- crictl start 发送 StartContainer,shim 调用 runc 启动业务进程,并把它加入到 pause 的网络命名空间中。
我们可以通过日志验证这一流程:
|
Bash |
关键日志行示例:
- PullImage "nginx:alpine" —— 镜像拉取开始
- content digest: sha256:... —— 镜像清单校验通过
- CreateContainer within sandbox ... —— 容器创建
- StartContainer for ... returns sandbox ID ... —— 容器启动
- shim disconnected —— 容器退出时 shim 断开连接
错误日志通常以 level=error 开头,包含运行时、快照或插件故障的详细信息,是日常排障的重要入口。
容器运行时是 Kubernetes 的“发动机”,掌握它是成为可靠集群管理员的必经之路。希望本文能让你向着透明化、可控化的运维目标再近一步。
本文为“搭建DevOps企业级仿真实验环境”系列的一部分,所有内容均基于实际硬件环境(32核64线程 / 128G内存 / 6T硬盘)编写,力求贴近真实企业部署场景。
欢迎各位 DevOps、SRE 爱好者,在评论区留言交流探讨,互相学习。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)