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 实例。
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 信号实现热加载(部分插件支持)。
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 行为,确保两种路径均能正确注入。