2. Containerd 架构基础:核心组件、gRPC 接口与插件机制

Containerd 并非一个简单的二进制文件,而是一个由多个守护进程、gRPC 接口和可插拔模块构成的工业级容器运行时。理解其分层架构是进行参数动态注入与流水线集成的理论前提。本章将拆解其核心组件、通信协议与扩展机制。

2.1 核心组件分层

Containerd 采用 C/S(客户端/服务端) 架构,主要分为三层:客户端层(CLI/API)、核心守护进程层(containerd) 和 底层执行层(shim/runc)。各层职责清晰,通过 gRPC 或 Unix Socket 通信。

层级 组件 职责说明
客户端层 ctr / nerdctl / Kubelet 提供用户交互命令或 Kubernetes CRI 调用入口,将请求序列化为 gRPC 消息。
核心守护进程 containerd 管理镜像拉取、存储、容器生命周期、快照(snapshotter)和元数据存储。
执行层 containerd-shim + runc 每个容器对应一个 shim 进程,负责接管容器标准输入输出、退出状态,并调用 runc 创建容器。
架构要点:Shim 进程是 Containerd 实现“无守护进程残留”的关键。即使 containerd 主进程重启,shim 依然能保持容器运行,并将状态重新注册给新的 containerd 实例。
Containerd 核心组件分层架构 Containerd 核心组件分层架构 客户端层(Client Layer) ctr CLI | nerdctl | Kubelet (CRI) 核心守护进程层(containerd Daemon) gRPC Server | Metadata Store | Snapshotter | Content Store Task Manager | Image Service | Lease Manager 执行层(Shim + Runtime) containerd-shim (per container) | runc / kata-runtime 容器进程(PID Namespace / Cgroups)

2.2 gRPC 接口体系

Containerd 内部通信全面基于 gRPC,使用 Protocol Buffers 定义服务接口。主要分为两大类:面向用户的通用 API 和 面向 Kubernetes 的 CRI API。

2.2.1 通用 gRPC 服务

通用 API 定义在 github.com/containerd/containerd/api/services 目录下,核心服务包括:

  • Namespace Service:管理容器命名空间隔离。
  • Image Service:镜像拉取、推送、删除、列表。
  • Container Service:容器创建、更新、删除、查询。
  • Task Service:容器进程启动、停止、暂停、恢复、获取状态。
  • Snapshot Service:管理文件系统快照(OverlayFS 层)。
  • Content Service:管理 Blob 内容存储(镜像层数据)。
  • Lease Service:资源租约管理,防止 GC 回收正在使用的资源。

2.2.2 CRI(Container Runtime Interface)

Kubelet 通过 CRI 插件与 Containerd 交互。Containerd 内置了 cri 插件,将 CRI 请求转换为内部 gRPC 调用。核心接口包括:

CRI 接口 对应方法 功能描述
RuntimeService RunPodSandbox / StopPodSandbox 管理 Pod 沙箱(Sandbox)生命周期,通常对应一个 pause 容器。
RuntimeService CreateContainer / StartContainer 在沙箱内创建并启动业务容器。
ImageService PullImage / ListImages 镜像拉取与查询,支持私有仓库认证。
参数注入切入点:在流水线集成测试中,我们通常通过 ctr 或 nerdctl 调用通用 API 创建容器,并在 CreateContainerRequest 的 spec 字段中注入自定义参数(如环境变量、挂载点、Linux Capabilities)。CRI 接口则用于模拟 Kubelet 行为,验证 Pod 级别的参数传递。

2.3 插件机制与扩展点

Containerd 采用 插件(Plugin) 架构,几乎所有核心功能都以插件形式注册到主进程中。插件通过 containerd 配置文件(/etc/containerd/config.toml)进行启用、禁用或参数配置。

2.3.1 内置插件类型

插件类型 注册名称 功能说明
Snapshotter overlayfs / native / devmapper 管理容器镜像层与容器读写层文件系统。
Runtime io.containerd.runc.v2 调用 runc 创建容器,支持 v1/v2 shim。
CRI io.containerd.grpc.v1.cri 实现 Kubernetes CRI 接口,将 CRI 请求转为内部调用。
Transfer io.containerd.transfer.v1 镜像传输(拉取/推送)的流式处理。
GC io.containerd.gc.v1 垃圾回收未使用的镜像层与容器元数据。

2.3.2 插件注册与配置示例

插件在启动时通过 config.toml 加载。以下是一个典型的插件配置片段,用于启用 CRI 并指定 pause 镜像:

version = 2
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    sandbox_image = "registry.k8s.io/pause:3.9"
    [plugins."io.containerd.grpc.v1.cri".containerd]
      default_runtime_name = "runc"
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
        runtime_type = "io.containerd.runc.v2"
注意:插件名称必须与 Containerd 内部注册的 ID 完全一致。修改配置后需要重启 containerd 服务。在流水线中,我们常通过动态修改 config.toml 并发送 SIGHUP 信号实现热加载(部分插件支持)。
Containerd 插件注册与 gRPC 调用流程 插件注册与 gRPC 调用流程 Kubelet / ctr gRPC Server Plugin Manager CRI Plugin Snapshotter Runtime Plugin GC Plugin 底层执行:Shim 进程 → runc → 容器 OverlayFS 挂载 | Cgroups 限制 | Namespace 隔离 每个插件独立注册,gRPC Server 根据请求类型路由到对应插件处理

2.4 架构设计对参数注入的影响

理解上述架构后,可以总结出参数动态注入的几个关键约束与机会:

  • gRPC 是唯一入口:所有参数必须通过 gRPC 请求的 CreateContainerRequest 或 UpdateContainerRequest 传递,无法绕过。
  • 插件隔离性:不同插件(如 CRI 与通用 API)处理参数的方式略有差异,CRI 插件会额外处理 Pod 级别的安全上下文。
  • Shim 持久化:参数一旦通过 shim 传递给 runc,便写入容器配置(config.json),后续修改需通过 UpdateTask 接口或重建容器。
  • Snapshotter 无关性:参数注入通常不涉及快照层,除非需要动态修改根文件系统挂载选项。
实践建议:在流水线集成测试中,优先使用 ctr run --env 或 nerdctl run --cap-add 验证参数传递路径,再通过 CRI 接口模拟 Kubelet 行为,确保两种路径均能正确注入。