2. Containerd 基础回顾:架构、核心组件与 CRI 接口

在深入集群节点扩容的状态同步与回滚设计之前,必须牢固掌握 Containerd 的底层架构。Containerd 并非一个简单的二进制文件,而是一组高度模块化的守护进程集合,它通过标准化的 CRI 接口与 Kubernetes 交互,并依赖 shim、snapshotter 等核心组件完成容器的完整生命周期管理。本节将系统梳理这些基础概念,为后续章节的复杂流程设计奠定技术基石。

2.1 Containerd 与 Docker 的关系

许多从业者容易混淆 Containerd 与 Docker 的边界。简单来说,Docker 是一个完整的容器化平台,而 Containerd 是 Docker 剥离出的核心容器运行时组件。自 Docker 1.11 版本起,Docker 引擎内部就已经使用 Containerd 来管理容器生命周期。在 Kubernetes 生态中,Containerd 作为 CRI 兼容的运行时,直接替代了 Docker 作为 kubelet 的底层依赖。

对比维度 Docker Containerd
定位 完整的容器平台(含 CLI、构建、编排) 轻量级容器运行时(仅核心生命周期)
对外接口 Docker API、CLI CRI(Kubernetes)、gRPC API
镜像构建 内置 Dockerfile 构建引擎 不提供构建能力,仅管理镜像拉取与存储
进程模型 dockerd + containerd + containerd-shim containerd + containerd-shim
Kubernetes 集成 通过 dockershim(已弃用) 原生 CRI 实现,无需额外适配层
关键认知:在 Kubernetes v1.24+ 版本中,dockershim 已被彻底移除。这意味着所有新集群必须使用 Containerd 或其它 CRI 兼容运行时。理解 Containerd 的架构,是掌握现代 Kubernetes 节点运维的前提。

2.2 Containerd 整体架构

Containerd 采用 C/S 架构,核心守护进程 containerd 通过 gRPC 对外暴露服务。它不直接管理容器进程,而是通过 containerd-shim 作为中间层,将容器进程与 containerd 守护进程的生命周期解耦。这种设计使得 containerd 可以重启或升级,而不影响正在运行的容器。

Containerd 核心架构组件关系 kubelet (CRI 客户端) containerd 守护进程 gRPC API | 镜像管理 | 元数据存储 | 事件系统 containerd-shim (容器A) shim (容器B) containerd-shim (容器C) runc (容器A 进程) runc (容器B) runc (容器C 进程) snapshotter overlayfs / devicemapper 镜像存储 架构设计要点: • containerd 不直接管理容器进程,通过 shim 实现守护进程与容器进程的生命周期解耦。 • 每个容器对应一个独立的 containerd-shim 进程,shim 崩溃不影响其它容器。 • snapshotter 负责管理容器镜像的层文件系统,是节点扩容时镜像预拉取的关键组件。

2.3 核心组件详解

2.3.1 containerd-shim:容器进程的守护者

shim 是 Containerd 架构中最具特色的组件。当 kubelet 通过 CRI 请求创建容器时,containerd 会为每个容器启动一个独立的 shim 进程。shim 的主要职责包括:

  • 生命周期解耦:即使 containerd 守护进程崩溃或重启,shim 仍然保持运行,确保容器进程不受影响。
  • 标准输入输出管理:shim 负责将容器的 stdout/stderr 重定向回 containerd,进而传递给 kubelet 的日志收集管道。
  • 退出码收集:当容器进程退出时,shim 捕获退出码并持久化,等待 containerd 或 kubelet 查询。
  • 运行时桥接:shim 调用底层的 runc(或其它 OCI 运行时)来实际创建和运行容器。
节点扩容关联:在集群节点扩容场景中,新节点加入集群后,kubelet 会通过 CRI 调用 containerd 创建 Pod 容器。理解 shim 的独立生命周期特性,有助于设计节点异常时的容器状态恢复策略——即使 containerd 重启,已运行的容器也不会被杀死。

2.3.2 snapshotter:容器镜像的层管理引擎

snapshotter 是 Containerd 管理容器文件系统的核心组件。它基于 Linux 的 overlayfs 或其它快照技术,实现镜像层的挂载与复用。每个容器镜像由多个只读层叠加而成,snapshotter 负责将这些层合并为一个可写层供容器使用。

快照类型 说明 节点扩容场景影响
overlayfs 默认快照器,使用内核 overlay 文件系统 镜像层共享效率高,新节点拉取镜像时只需下载缺失层
native 直接复制文件,不使用联合挂载 磁盘占用大,扩容时镜像拉取速度较慢
devmapper 基于 Device Mapper 的快照 适用于需要块设备隔离的场景,但配置复杂
stargz 支持延迟拉取的快照器(需额外插件) 可显著加速节点扩容时的镜像分发,按需加载
Snapshotter 镜像层挂载原理 容器可写层 (Container Layer) — 读写 镜像层 4 (Image Layer) — 只读 镜像层 3 (Image Layer) — 只读 镜像层 2 (Image Layer) — 只读 镜像层 1 (Image Layer / 基础层) — 只读 overlayfs 合并视图 (容器根文件系统)

2.4 CRI 接口:Kubernetes 与 Containerd 的通信桥梁

CRI(Container Runtime Interface)是 Kubernetes 定义的 gRPC 协议,用于 kubelet 与容器运行时之间的标准化通信。Containerd 通过内置的 cri-plugin 实现了完整的 CRI 接口,这也是它能够直接替代 Docker 成为 Kubernetes 默认运行时的根本原因。

2.4.1 CRI 核心接口方法

CRI 接口主要分为两类服务:RuntimeService 和 ImageService。以下是与节点扩容最相关的关键方法:

服务类型 接口方法 功能描述 扩容场景关联
RuntimeService RunPodSandbox 创建 Pod 沙箱(即容器网络命名空间) 新节点上创建 Pod 的第一步
CreateContainer 在沙箱内创建容器 扩容时调度到新节点的 Pod 会触发此调用
StartContainer 启动已创建的容器 容器状态从 Created 转为 Running
ImageService PullImage 拉取容器镜像到本地存储 新节点缺少镜像时触发,是扩容延迟的主要来源
ImageStatus 查询镜像是否存在 kubelet 判断是否需要拉取镜像

2.4.2 CRI 调用流程(节点扩容视角)

当 kubelet 接收到调度器分配的 Pod 创建请求时,会按照以下顺序调用 CRI 接口:

  1. ImageStatus 检查:kubelet 首先查询镜像是否已存在于节点本地。如果存在,跳过拉取步骤。
  2. PullImage 拉取:若镜像不存在,kubelet 调用 PullImage 方法。containerd 通过 snapshotter 将镜像层解压到本地存储。
  3. RunPodSandbox 创建沙箱:为 Pod 创建独立的网络命名空间和 cgroup 资源隔离。
  4. CreateContainer 创建容器:在沙箱内创建容器,此时容器处于 Created 状态。
  5. StartContainer 启动容器:调用 runc 启动容器进程,容器进入 Running 状态。
扩容性能瓶颈:在集群节点大规模扩容时,PullImage 是最大的性能瓶颈。如果新节点上需要拉取大量镜像(尤其是大型 AI/ML 镜像),会导致 Pod 启动时间显著延长。后续章节将详细讨论如何通过镜像预热、P2P 分发和 snapshotter 优化来缓解这一问题。

2.5 本章小结