深色模式
容器运行时 Containerd 与 CRI 详解
摘要:本文面向生产 SRE,讲清 Kubernetes 与容器运行时之间的"契约"——CRI,以及事实标准运行时 containerd 的内部架构、shim 模型与常见运维坑。覆盖版本:Kubernetes v1.28+(CRI v1 自 v1.26 起强制)。
适用版本与前提
- Kubernetes:v1.28+(自 v1.26 起 kubelet 仅支持 CRI v1 API;dockershim 已在 v1.24 移除)
- 运行时:containerd 1.7+/2.x 或 CRI-O(本文以 containerd 为主)
- 前提:理解 OCI runtime(runc)、Pod、CNI 基本概念
背景与问题:为什么需要 CRI
早期 Kubernetes 直接内嵌了 Docker 集成(dockershim)。但 Docker 并非为 K8s 设计,中间多一层适配既笨重又难维护。于是社区抽象出 CRI(Container Runtime Interface)——一组 kubelet 与运行时之间的 gRPC 接口(RuntimeService 管 Pod/容器生命周期,ImageService 管镜像)。Kubernetes 只认 CRI,运行时只要实现 CRI 即可被 kubelet 使用。dockershim 在 v1.20 宣布废弃、v1.24 正式移除。
版本相关
自 Kubernetes v1.26 起,kubelet 仅支持 CRI v1 API。若运行时不支持 v1,kubelet 将无法注册为节点。v1.24/v1.25 仍兼容 CRI v1alpha2 等旧版本;升级前请确认运行时版本对应的 CRI API 版本。
CRI 是什么:kubelet 与运行时的契约
kubelet 不直接调 runc,而是通过 CRI 与运行时对话。CRI 把 K8s 语义翻译成运行时操作:
- PodSandbox:对应一个 Pod 的沙箱(pause 容器 + 共享的 network/IPC namespace)。
- Container:沙箱内的具体容器。
- ImageService:拉取/列出/删除镜像。
kubelet 调用流程(简化):RunPodSandbox → PullImage → CreateContainer → StartContainer → 周期 ListContainers/ContainerStatus 做调和。
containerd 架构
containerd 是一个插件化守护进程:content store、snapshotter、metadata、runtime v2、CRI、transfer、events 都是插件,客户端通过 gRPC 访问它们。核心服务按"生命周期责任"划分:
| 服务 | 责任 |
|---|---|
| Content Store | 按 digest 存不可变 blob(镜像层、manifest、config) |
| Image Service | 把名字映射到 OCI descriptor,管理镜像元数据 |
| Snapshotter | 创建/提交文件系统快照(默认 overlayfs) |
| Container | 持久化容器元数据(spec、image、runtime、snapshot key) |
| Task | 通过 runtime v2 与 shim 管理进程级执行 |
| Lease | 保护工作流期间的 content/snapshot/metadata 不被 GC |
| CRI plugin | 把 K8s CRI 调用翻译成 containerd 服务调用 |
命名空间隔离
containerd 的命名空间(如 k8s.io)与 Linux namespace 无关,它只是对元数据做逻辑分区。Kubernetes 用 k8s.io,ctr 默认用 default,Docker 用 moby。所以 ctr images ls 看不到 K8s 拉的镜像,但底层 content 可按 digest 共享。
shim:为什么 containerd 重启不影响运行中的容器
这是 containerd 最值得理解的设计。containerd-shim-runc-v2 是每容器一个的小进程:
- 它作为容器的父进程(容器的 init 是 shim 的子进程,而非 containerd 的子进程);
- 负责容器 stdio 转发、退出状态回收、向 containerd 上报状态;
- 通过 ttrpc(比 gRPC 更轻量的本地协议)与 containerd 通信。
因此 containerd 守护进程崩溃或升级重启时,shim 仍存活、容器不中断;containerd 重启后重新连上已有 shim 即可。这就实现了"升级运行时不杀容器"。
containerd 关键路径速查
- 配置:
/etc/containerd/config.toml(Linux);Windows 为C:\Program Files\containerd\config.toml - 数据根:
/var/lib/containerd/(meta.db、blobs、snapshots) - 状态:
/run/containerd/ - CRI socket(默认):
/run/containerd/containerd.sock - 调试 CLI:
ctr(不稳定,仅调试)、crictl(CRI 标准工具,运行时无关)
生产实践:配置 containerd + CRI
步骤 1:确认 CRI 插件已启用
containerd 默认可能禁用 CRI 插件(尤其包管理器安装时)。检查并启用:
bash
# 查看配置是否禁用了 cri
grep disabled_plugins /etc/containerd/config.toml
# 若 cri 出现在 disabled_plugins 中,移除它后重载
sudo systemctl restart containerd1
2
3
4
2
3
4
步骤 2:配置 systemd cgroup driver(与 kubelet 一致)
cgroup driver 必须和 kubelet 保持一致,否则容器无法启动或资源统计错乱。
containerd 1.x 写法:
toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true1
2
3
2
3
containerd 2.x 写法(配置项路径变了):
toml
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true1
2
3
2
3
版本相关
containerd 2.x 把 CRI 配置路径从 io.containerd.grpc.v1.cri 改为 io.containerd.cri.v1.runtime / io.containerd.cri.v1.images。照搬 1.x 的 config.toml 到 2.x 会导致配置不生效。升级 containerd 大版本时务必重写配置,可用 containerd config default > /etc/containerd/config.toml 重新生成后逐项覆盖。
步骤 3:配置 kubelet 指向运行时端点
bash
# kubeadm 集群通常用此配置;非 kubeadm 可显式指定
# /var/lib/kubelet/config.yaml
containerRuntimeEndpoint: unix:///run/containerd/containerd.sock1
2
3
2
3
版本相关
Kubernetes v1.28 起支持 alpha 的 cgroup driver 自动探测;v1.37 在 KubeletCgroupDriverFromCRI feature gate 开启且运行时支持 RuntimeConfig CRI RPC 时,kubelet 自动从运行时探测 cgroup driver 并忽略自身 --cgroup-driver。老版本 containerd(1.y 及以下)不支持该 RPC,会回退到自己配置的值;v1.38 将移除该回退,老 containerd 配新 kubelet 会失败。请以目标版本为准。
常见失败模式
- cgroup driver 不一致:kubelet 用 systemd、运行时用 cgroupfs(或反之),容器 OOM/无法启动、kubelet 报
failed to create cgroup。务必对齐。 - cri 插件被禁用:kubelet 启动卡在
connect: no such file or directory或rpc error: code = Unavailable,crictl info失败。检查disabled_plugins。 - 镜像仓库拉取慢/限流:大集群并发拉同一镜像易被 registry 限流,应配镜像缓存(如 registry 镜像、节点本地 cache 或
imagePullSecrets)。 - 容器 crash loop 后 containerd 配置不兼容:装 CNI 后某些发行版自带
config.toml有冲突参数,重置为containerd config default再覆盖可解。
回滚与清理
生产危险
修改 containerd 配置或升级运行时会重启 containerd,进而短暂影响节点上新容器的创建(已运行容器因 shim 存活不受影响,但 kubelet 对该节点状态可能抖动)。操作前应先 kubectl cordon 并规划维护窗口。
bash
# 回滚:保留旧配置备份,改回后重载
sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak.$(date +%F)
# 编辑恢复后
sudo systemctl restart containerd
# 用 crictl 验证运行时连通
sudo crictl info1
2
3
4
5
6
2
3
4
5
6
安全与合规
- 运行时以 root 运行,能操作主机内核特性(namespace、cgroup)。务必配合 Pod
SecurityContext、seccomp、AppArmor、限制allowedUnsafeSysctls。 - 镜像来源:只从可信 registry 拉取,配合 admission(如 ImagePolicyWebhook、Sigstore 签名校验)。
- crictl / ctr 是节点级后门:限制能登录节点的人员与能力,审计节点操作。
性能、容量与成本
- snapshotter 默认 overlayfs;高 I/O 场景可考虑
overlaybd、stargz(懒加载)等以加速大镜像冷启动,但各有兼容与存储开销代价。 - 镜像层按 digest 去重,content store 跨容器共享,可显著降低节点磁盘占用。
- 运行时本身开销很小(远低于业务容器);调优重点在镜像拉取并发、磁盘、与 kubelet 的 CRI 调用延迟。
可观测性
crictl ps/crictl pods:查看运行时视角的容器与沙箱(区别于docker ps,且运行时无关)。ctr -n k8s.io containers ls/tasks ls:深入 containerd 内部状态(调试用)。- 指标:containerd 暴露 gRPC 指标(需配
metrics地址),关注 CRI 调用延迟、镜像拉取耗时。
替代方案与权衡
- CRI-O:只实现 CRI,无额外 daemon API/CLI,组件更少、攻击面更小,是 OpenShift 默认运行时。与 containerd 在功能上等价,选谁多由发行版默认决定。
- Docker Engine + cri-dockerd:dockershim 移除后若仍需用 Docker,需额外跑
cri-dockerd适配器。仅当你强依赖 Docker 独有行为(如某些构建工具)时考虑,否则直接上 containerd 更简洁。 - containerd vs 其他 OCI runtime:runc 是默认;需要强隔离可用 Kata Containers / gVisor(通过 runtime class 切换),代价是启动慢、资源开销高。
FAQ
Q:containerd 重启,我的 Pod 会丢吗? A:不会。shim 独立于 containerd 存活,容器进程继续运行,containerd 重启后重新接管。
Q:kubelet 能同时用两个运行时吗? A:可以,通过 RuntimeClass 指定不同运行时(如 runc 与 Kata),按工作负载选择。
参考资料
- Container Runtimes - Kubernetes 官方文档,访问日期:2026-10-08。
- Chapter 10: containerd Architecture - The containerd Book,访问日期:2026-10-08。
- containerd 项目仓库与 CRI 插件说明 - GitHub,访问日期:2026-10-08。
- containerd Glossary(shim / runtime v2 / snapshotter 定义),访问日期:2026-10-08。