2. Containerd 架构速览:gRPC API、Shim 进程、Snapshotter、Event Service
在深入运行时流水线之前,必须先理解 Containerd 的核心架构。Containerd 并非一个单体二进制,而是一组通过 gRPC 通信的模块化组件。本章将拆解四个关键模块:gRPC API 层、Shim 进程、Snapshotter 和 Event Service,它们构成了事件采样与镜像 GC 复核的底层基础。
2.1 整体架构分层
Containerd 采用 C/S 架构,控制面与数据面分离。下图展示了核心组件及其交互关系:
2.2 gRPC API:统一的控制入口
Containerd 将所有管理操作暴露为 gRPC 服务。API 定义在 github.com/containerd/containerd/api/services 目录下,主要服务包括:
| 服务名称 | gRPC 接口 | 核心方法 | 用途 |
|---|---|---|---|
| Namespace | namespaces.v1.Namespaces |
Create / List / Delete | 多租户隔离 |
| Image | images.v1.Images |
Get / List / Delete / Update | 镜像元数据管理 |
| Container | containers.v1.Containers |
Create / Get / Delete | 容器生命周期 |
| Task | tasks.v1.Tasks |
Create / Start / Kill / Exec | 运行时任务控制 |
| Events | events.v1.Events |
Subscribe (Server-Sent Stream) | 异步事件订阅 |
| Snapshot | snapshots.v1.Snapshots |
Prepare / Commit / Remove | 快照层管理 |
事件采样依赖 Events 服务的 Subscribe 方法,该方法返回一个 Server-Sent 流,客户端可以持续接收容器生命周期事件。镜像 GC 复核则通过 Image 和 Snapshot 服务获取元数据与层引用计数。
2.3 Shim 进程:容器生命周期的守护者
Shim 进程(containerd-shim)是 Containerd 架构中最具特色的设计。它位于 containerd 守护进程与容器运行时之间,承担以下职责:
- 生命周期代理: Shim 接管容器的 stdin/stdout/stderr 和退出状态,即使 containerd 重启,容器也不会受影响。
- 运行时抽象: 通过 Shim API 支持多种运行时(runc、kata、gVisor),每个容器对应一个独立的 Shim 进程。
- 事件转发: 容器退出、OOM 等事件由 Shim 捕获并上报给 containerd 的 Event Service。
- IO 多路复用: 将容器的控制台输出转换为 FIFO 或管道,供 containerd 读取。
TaskExit 事件。如果 Shim 异常退出,containerd 会丢失该事件,导致事件采样出现空洞。在生产环境中,需要监控 Shim 进程的健康状态。
2.4 Snapshotter:镜像层与容器根文件系统
Snapshotter 负责管理容器镜像的层(Layer)和容器根文件系统(RootFS)。Containerd 默认使用 overlayfs snapshotter,但也支持 devmapper、native、nydus 等。
核心概念:
- Snapshot: 一个文件系统快照,代表镜像的一层或容器的读写层。
- Parent: 快照之间的父子关系,形成层链。
- Committed Snapshot: 只读的镜像层,对应 registry 中的 blob。
- Active Snapshot: 容器的读写层,可修改。
镜像 GC 复核的核心逻辑就是遍历所有 committed snapshot,检查其引用计数:
// 伪代码:镜像 GC 复核逻辑
func reconcileImageGC(ctx context.Context, sn snapshot.Snapshotter, images []image.Image) error {
// 1. 获取所有 committed snapshots
snapshots, _ := sn.List(ctx, "sha256:")
// 2. 构建引用映射:镜像 -> 层 digest
refMap := buildReferenceMap(images)
// 3. 遍历 snapshots,标记未被引用的层
for _, s := range snapshots {
if _, exists := refMap[s.Name]; !exists {
// 未被任何镜像引用的层,标记为可回收
markForCleanup(s.Name)
}
}
return nil
}
2.5 Event Service:异步事件中枢
Event Service 是 Containerd 内部的事件发布/订阅系统。所有组件(Task、Image、Snapshot、Namespace)在状态变更时都会向 Event Service 发布事件。
事件类型示例:
| 事件名称 | 触发时机 | 关键字段 | 采样价值 |
|---|---|---|---|
TaskCreate |
容器创建 | container_id, pid, runtime | 高 |
TaskStart |
容器启动 | container_id, pid | 高 |
TaskExit |
容器退出 | container_id, exit_status, pid | 高 |
TaskOOM |
容器 OOM | container_id | 中 |
ImageCreate |
镜像拉取完成 | name, digest, size | 中 |
ImageDelete |
镜像删除 | name, digest | 中 |
SnapshotPrepare |
快照准备 | key, parent, mount | 低 |
SnapshotCommit |
快照提交 | key, name | 低 |
事件订阅使用 gRPC 流式接口,客户端调用 Subscribe 后持续接收事件:
// Go 客户端订阅事件示例
import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/events"
)
func subscribeEvents(client *containerd.Client, ctx context.Context) {
// 创建事件过滤器:只关注 Task 和 Image 事件
filter := `topic=="/tasks/*" || topic=="/images/*"`
// 订阅事件流
eventCh, errCh := client.Subscribe(ctx, filter)
for {
select {
case evt := <-eventCh:
// 处理事件:记录时间戳、提取关键字段
handleEvent(evt)
case err := <-errCh:
// 处理错误或重连
log.Errorf("event subscription error: %v", err)
return
}
}
}
2.6 模块协作:事件采样与 GC 复核的数据流
将上述四个模块串联起来,就构成了运行时流水线的数据基础: