2. 主流容器运行时对比:containerd、CRI-O、Docker(通过 dockershim)的架构差异、性能特点与选型建议

在 Kubernetes 生态中,容器运行时是负责拉取镜像、创建容器、管理容器生命周期的核心组件。自 Kubernetes v1.24 正式移除 dockershim 后,社区对容器运行时的选择变得更加聚焦。本章从架构差异、性能特点与选型建议三个维度,对 containerd、CRI-O 以及已退役的 Docker(通过 dockershim)进行深度对比。

2.1 架构差异对比

三种运行时的核心差异在于:组件耦合度、CRI 适配层实现方式以及守护进程模型。下图展示了三者在 Kubernetes 节点中的架构层级关系。

三种容器运行时在 Kubernetes 节点中的架构对比 kubelet CRI 接口 (gRPC) containerd 内置 CRI 插件 (cri) containerd-shim + runc CRI-O 原生 CRI 实现 conmon + runc / crun Docker Engine dockershim (已移除) docker-containerd-shim OCI 兼容运行时 (runc / crun / kata) 架构要点: • containerd 和 CRI-O 均直接实现 CRI gRPC 接口,无需额外适配层。 • Docker 通过 dockershim 将 CRI 请求转换为 Docker API 调用,增加一层代理开销。
架构关键差异:containerd 和 CRI-O 是“原生 CRI 运行时”,而 Docker 是“通用容器引擎 + 适配层”。dockershim 的移除标志着 Kubernetes 不再为通用引擎的适配层买单。

2.2 性能特点对比

性能差异主要体现在容器启动延迟、镜像拉取效率、内存占用和并发吞吐量四个维度。以下表格基于社区基准测试数据(Kubernetes Node Performance Test)整理。

性能指标 containerd CRI-O Docker + dockershim
容器启动延迟 (P99) ~120ms ~135ms ~180ms
镜像拉取吞吐量 高(并发层优化好) 中(依赖底层存储驱动) 中(Docker 层序列化开销)
常驻内存占用 ~30MB ~35MB ~120MB(含 dockershim)
并发 Pod 创建速率 ~50 Pod/s ~45 Pod/s ~30 Pod/s
CRI 调用延迟 低(原生 gRPC) 低(原生 gRPC) 中(API 转换开销)
存储驱动兼容性 overlay2, devicemapper overlay2, aufs overlay2, aufs, devicemapper
性能调优提示:在 containerd 中,通过调整 [plugins."io.containerd.grpc.v1.cri".containerd] 下的 snapshotter 和 discard_unpacked_layers 参数,可以进一步降低镜像解压延迟。CRI-O 则可通过 conmon 的 --exit-delay 参数优化容器退出时的资源回收速度。

2.3 选型建议与场景匹配

选型不能只看性能数字,还需考虑社区活跃度、生态兼容性、运维复杂度和安全需求。以下决策树和表格可帮助团队快速定位。

容器运行时选型决策树 选择容器运行时 是否仅用于 Kubernetes 集群? 是 containerd 推荐:性能均衡,社区最活跃 否 Docker 开发/CI 环境首选 需要强隔离或机密计算? 是 CRI-O + kata 否 containerd

2.4 选型建议总结

  • 生产环境 Kubernetes 集群:首选 containerd。它是 CNCF 毕业项目,社区活跃度最高,性能均衡,与 Kubernetes 集成最紧密。推荐版本 v1.7.x 以上。
  • 安全敏感场景(多租户、金融、边缘):推荐 CRI-O + crun/kata。CRI-O 架构更精简,配合 crun(C 语言重写的 OCI 运行时)可进一步降低启动延迟;结合 Kata Containers 实现硬件虚拟化隔离。
  • 开发/CI 环境:可继续使用 Docker。虽然 dockershim 已移除,但通过 cri-dockerd 适配器仍可接入 Kubernetes,且 Docker 的开发者体验和调试工具链更成熟。
  • 边缘计算/资源受限节点:推荐 CRI-O。其内存占用略低于 containerd,且不依赖 Docker 的复杂组件栈,适合 2C4G 以下的节点。
避坑指南:不要在生产环境同时部署多个容器运行时。每个节点只能注册一个 CRI 运行时给 kubelet。混合部署会导致 kubelet 无法正确识别 Pod 状态,引发调度混乱。迁移运行时前,务必在测试集群验证 kubectl drain 和 kubelet 重启 流程。

2.5 迁移路径参考

如果当前集群仍在使用 Docker + dockershim,建议按以下步骤迁移至 containerd:

  1. 评估兼容性:检查所有工作负载是否依赖 Docker 特有功能(如 docker exec、docker logs 的特定参数)。
  2. 节点打标与排水:使用 kubectl cordon 和 kubectl drain 逐节点下线。
  3. 安装 containerd:通过包管理器或二进制安装 containerd v1.7+,配置 /etc/containerd/config.toml 启用 CRI 插件。
  4. 更新 kubelet 配置:修改 /var/lib/kubelet/kubeadm-flags.env 中的 --container-runtime=remote 和 --container-runtime-endpoint=unix:///run/containerd/containerd.sock。
  5. 重启 kubelet:systemctl restart kubelet,验证节点状态为 Ready。
  6. 逐步上线:每次迁移 1-2 个节点,观察集群稳定性后再批量操作。
# containerd 核心配置示例(/etc/containerd/config.toml)
version = 2
[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.k8s.io/pause:3.9"
  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "overlayfs"
    default_runtime_name = "runc"
    discard_unpacked_layers = true
  [plugins."io.containerd.grpc.v1.cri".registry]
    config_path = "/etc/containerd/certs.d"
迁移验证命令:迁移完成后,使用 crictl info 查看运行时版本和 CRI 插件状态;使用 crictl pods 和 crictl ps 验证 Pod 和容器列表是否正常。