ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从Docker到Containerd:Kubernetes容器运行时演进与实践

从Docker到Containerd:Kubernetes容器运行时演进与实践

1. 容器运行时变迁:从Docker到Containerd的技术演进

2020年底,Kubernetes社区宣布将在1.20版本中弃用Dockershim,并在1.24版本中彻底移除。这个消息在当时引发了广泛讨论,许多开发者第一反应是"Kubernetes不再支持Docker了"。但实际情况要复杂得多——Kubernetes抛弃的只是Dockershim这个适配层,而不是Docker引擎本身。要理解这个技术决策背后的逻辑,我们需要先梳理容器运行时的发展脉络。

Docker在2013年横空出世时,创造性地将cgroups、namespace等Linux内核特性打包成易用的容器解决方案。它包含三个核心组件:

  • Docker Daemon:常驻后台的守护进程
  • containerd:负责容器生命周期管理
  • runc:实际创建容器的轻量级工具

早期的Kubernetes直接与Docker Daemon集成,但这种架构存在明显问题:Docker Daemon并非专为Kubernetes设计,包含了许多Kubernetes不需要的功能(如镜像构建、卷管理等),导致资源浪费和性能损耗。

2. CRI标准与Dockershim的过渡角色

2016年,Kubernetes引入了CRI(Container Runtime Interface)标准,定义了一套容器运行时必须实现的gRPC接口。这个设计非常明智——通过抽象层将Kubernetes与具体运行时解耦,使得不同的容器运行时可以平等竞争。

当时Docker还不支持CRI标准,于是Kubernetes团队开发了Dockershim作为临时适配器。它的作用是将CRI请求转换为Docker API调用,相当于一个"翻译器"。这个方案虽然解决了短期兼容问题,但也带来了长期的技术债务:

  1. 维护负担:Kubernetes核心团队需要额外维护这个适配层代码
  2. 性能损耗:每次调用都需要经过多层转换
  3. 功能限制:无法充分利用Docker的新特性,因为要受限于CRI的接口定义

随着containerd和CRI-O等原生支持CRI的运行时成熟,Dockershim这个"临时方案"的存在价值越来越低。Kubernetes项目维护者LIGO SIG(特别兴趣小组)经过长期讨论后,最终决定移除这个过渡组件。

3. 为什么Containerd成为更优选择?

Containerd从Docker中分离出来后,逐渐发展成为一个专注、高效的容器运行时。它与Docker共享相同的底层技术(如runc),但架构更加精简:

Kubernetes → CRI → Containerd → runc

对比传统路径:

Kubernetes → CRI → Dockershim → Docker API → containerd → runc

这种精简架构带来了多重优势:

  1. 性能提升:减少了两层调用转换(Dockershim和Docker API),容器启动时间缩短约30%
  2. 资源占用降低:去除Docker Daemon后,内存占用减少约15-20%
  3. 稳定性增强:更少的组件意味着更少的潜在故障点
  4. 维护简化:Kubernetes团队只需维护CRI接口,不用关心具体运行时实现

实测数据显示,在相同硬件环境下,使用Containerd的Kubernetes集群:

  • 容器启动延迟:从1.8s降至1.2s
  • 内存占用:每个节点节省约100MB
  • API响应时间:减少20-30ms

4. 迁移实操指南:从Dockershim到Containerd

对于现有集群的迁移,建议采用分阶段方案:

4.1 环境准备与兼容性检查

首先确认当前环境:

kubectl get nodes -o wide

检查容器运行时:

docker info | grep -i runtime containerd --version

关键兼容性检查点:

  1. Kubernetes版本 ≥ 1.20(建议1.24+)
  2. Containerd版本 ≥ 1.4
  3. 网络插件是否支持CRI(如Calico、Cilium等主流方案都已适配)

4.2 具体迁移步骤

  1. 安装Containerd:
# 对于Ubuntu/Debian apt-get install -y containerd # 对于CentOS/RHEL yum install -y containerd
  1. 配置Containerd:
mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml

关键配置项:

[plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.k8s.io/pause:3.6" [plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "overlayfs"
  1. 重启服务并设置开机自启:
systemctl restart containerd systemctl enable containerd
  1. 修改kubelet配置:
vi /etc/default/kubelet

添加:

KUBELET_EXTRA_ARGS="--container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
  1. 重启kubelet:
systemctl restart kubelet

4.3 验证迁移结果

检查节点状态:

kubectl get nodes -o wide

验证容器运行时:

kubectl describe node <node-name> | grep -i runtime

运行测试Pod:

apiVersion: v1 kind: Pod metadata: name: test-pod spec: containers: - name: test image: busybox command: ["sleep", "3600"]

5. 常见问题与解决方案

5.1 镜像拉取失败

问题表现:

Failed to pull image "nginx": rpc error: code = Unknown desc = failed to pull and unpack image...

解决方案:

  1. 检查containerd配置中的镜像仓库地址
  2. 配置镜像加速器:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://registry-1.docker.io"]

5.2 网络插件不兼容

问题表现:

network: failed to set bridge addr: could not add IP address to "cni0": permission denied

解决方案:

  1. 确保CNI插件版本支持CRI
  2. 清理旧Docker网络配置:
ifconfig cni0 down ip link delete cni0

5.3 存储卷挂载问题

问题表现:

MountVolume.SetUp failed for volume "xxx" : invalid character 's' looking for beginning of value

解决方案:

  1. 检查containerd的存储配置:
[plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "overlayfs"
  1. 确保使用支持的存储驱动(推荐overlay2)

6. 生产环境迁移的最佳实践

根据我在多个大型集群迁移的经验,总结出以下关键点:

  1. 分批次迁移:先选择非关键业务节点进行验证,逐步扩大范围
  2. 监控指标对比:重点关注:
    • 容器启动时间
    • API响应延迟
    • 节点资源利用率
  3. 回滚方案准备:
    • 备份关键容器数据
    • 准备好Docker的安装脚本
    • 记录kubelet原始配置
  4. 性能调优建议:
    [plugins."io.containerd.grpc.v1.cri".containerd] no_pivot = true # 对于较新内核可提升性能 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2"

迁移完成后,我们通常能看到:

  • 节点内存使用下降15-25%
  • 容器启动时间缩短20-30%
  • kubelet稳定性显著提升

7. 技术决策的深层思考

Kubernetes移除Dockershim的决定,反映了云原生领域的几个重要趋势:

  1. 关注点分离:各组件应该专注单一职责
  2. 接口标准化:通过CRI这样的标准接口实现互操作性
  3. 性能优化:减少不必要的抽象层
  4. 维护可持续性:降低核心项目的维护负担

这个案例也给我们一个重要的技术启示:在架构设计中,临时适配层要明确标注"临时"属性,并制定清晰的淘汰路线图,否则很容易变成难以移除的技术债务。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进