containerd 最初只是 Docker 内部拆分出来的一个组件,如今已经成长为 Kubernetes 生态事实上的标准容器运行时。当 Kubernetes 1.24 正式移除 dockershim 之后,containerd 几乎成了所有主流发行版的默认选择,从 kubeadm 部署的裸集群到云厂商的托管服务,都在向它靠拢。这种变化不只是换了个底层工具那么简单,它重塑了整个容器技术栈的分工方式,也影响着镜像构建、监控排查、集群运维的每一个环节。

containerd 为什么能胜出:架构层面的答案
要理解 containerd 的成功,得先看它做了什么样的取舍。Docker 是一套完整的容器平台,包含镜像构建、卷管理、网络、API 网关等大量功能;而 containerd 只聚焦在运行时这一层,负责拉取镜像、管理容器生命周期、挂载文件系统。这种克制的设计让它足够轻量,也足够稳定。
containerd 的核心架构分为三层:最底层是 containerd-shim 进程,每个容器对应一个 shim,容器退出时 shim 负责回收状态并上报,这样即使 containerd 主进程重启,运行中的容器也不受影响;中间层是核心守护进程,通过 gRPC 暴露 API;上层则是面向 Kubernetes 的 CRI 插件,直接实现 runtime.v1 接口。这个插件化设计是关键——Kubernetes 通过 CRI(Container Runtime Interface)与运行时通信,containerd 内置 CRI 支持后,kubelet 不再需要任何中间转换层。
相比之下,Docker 走的是 dockershim 加 Docker Engine 再加 containerd 的三层路径,链路长、组件多、故障点也多。Kubernetes 社区维护 dockershim 的成本一直很高,而 containerd 的 CRI 插件由 containerd 社区自己维护,责任边界清晰。从工程角度看,砍掉冗余链路是迟早的事。
实际收益:性能、资源与稳定性的量化对比
切换到 containerd 最直观的感受是资源占用的下降。Docker Daemon 本身要占用几百 MB 内存,而 containerd 主进程通常只占几十 MB。对于大规模集群,每个节点省下来的内存乘以节点数,就是一笔可观的成本。下面是一组在相同配置节点上粗略测量的对比数据:
| 运行时 | 守护进程内存 | 容器启动延迟 | Pod 启动耗时(典型) |
|---|---|---|---|
| Docker + dockershim | 约 300MB 以上 | 较长,链路三层 | 较慢 |
| containerd | 约 50MB | 更短,直连 CRI | 明显更快 |
启动速度的提升在高并发扩容场景尤其明显。kubelet 调用 containerd 的 CRI 接口是直接的过程调用,没有 Docker API 的额外转换开销。当集群需要在短时间内拉起数百个 Pod 时,containerd 的镜像拉取并行度和容器创建吞吐都表现更好。此外,由于链路缩短,日志和事件的可观测性也更强,排查问题时不需要在多个守护进程之间来回对照。
运维方式的变化:从 docker 命令到 ctr 和 crictl
运行时统一之后,运维习惯也得跟着调整。过去大家习惯用 docker ps、docker logs 排查问题,现在这些命令在纯 containerd 节点上行不通了。containerd 自带两个工具:ctr 是面向开发调试的底层客户端,功能原始但能直接操作 containerd API;crictl 则是 Kubernetes 官方推荐的运行时调试工具,命令风格与 docker 命令接近,上手成本低。
# 查看 Kubernetes 管理的容器(类似 docker ps) crictl ps # 查看节点上的镜像列表 crictl images # 拉取镜像并导出 ctr -n k8s.io images pull docker.io/library/nginx:1.25 ctr -n k8s.io images export nginx.tar docker.io/library/nginx:1.25 # 查看 containerd 自身的命名空间 ctr namespaces list
这里有个容易踩的坑:containerd 有命名空间的概念,Kubernetes 默认使用 k8s.io 命名空间。如果你用 ctr 拉取镜像时不加 -n k8s.io,kubelet 是看不到这个镜像的。很多初学者发现镜像明明已经导入了节点,Pod 却仍然报 ImagePullBackOff,原因往往就在这里。理解这一点需要回到 containerd 的设计初衷——它本来就是给上层平台用的,命名空间机制让不同平台可以在同一个运行时上隔离各自的资源。
另一个变化是镜像加速配置的写法。Docker 时代大家改 /etc/docker/daemon.json,containerd 则需要修改 /etc/containerd/config.toml,为 registry.k8s.io 或 docker.io 配置 mirror 端点,然后重启 containerd 服务。配置文件格式从 JSON 换成了 TOML,结构也更加层次化,初次接触时建议先用 containerd config default 导出默认配置再逐项修改。
对生态的深远影响:标准化与去中心化的双刃剑
containerd 成为标准,本质上加速了 OCI 规范的落地。镜像格式遵循 OCI Image Spec,运行时遵循 OCI Runtime Spec,这意味着任何符合规范的镜像都能在任何符合规范的运行时上运行,可移植性问题被永久性地解决了。镜像构建工具因此迎来了百花齐放:BuildKit、Kaniko、Buildah 等工具都不再依赖 Docker Daemon,可以在 Kubernetes 集群内部以 Pod 的方式完成构建,这对 CI/CD 流水线的设计影响深远。
同时,沙箱运行时生态也建立在 containerd 的基础上。通过 RuntimeClass 机制,Kata Containers、gVisor、Firecracker 这些安全容器方案都以 containerd shim 的形式接入,形成 host process 容器之外的第二套隔离体系。可以说 containerd 提供了一个稳定的运行时底座,上层的创新不再需要重新发明轮子。
当然,标准化也有代价。Docker 在开发者本地的体验依然优秀,docker build 和 docker run 的简洁性是 ctr 无法替代的,所以现在常见的实践是本地开发用 Docker 或 Podman,生产集群用 containerd,两者通过相同的 OCI 镜像格式衔接。理解每一层工具的定位,比纠结要不要彻底抛弃 Docker 更有价值。容器技术的分层已经非常清晰:OCI 定义标准,containerd 实现运行时,Kubernetes 负责编排,各司其职,这大概就是这场演变留给我们最重要的启示。
containerdKubernetes容器运行时修改时间:2026-09-06 21:32:37