2. Containerd 架构速览:gRPC API、Shim 进程、Snapshotter、Event Service

在深入运行时流水线之前,必须先理解 Containerd 的核心架构。Containerd 并非一个单体二进制,而是一组通过 gRPC 通信的模块化组件。本章将拆解四个关键模块:gRPC API 层、Shim 进程、Snapshotter 和 Event Service,它们构成了事件采样与镜像 GC 复核的底层基础。

2.1 整体架构分层

Containerd 采用 C/S 架构,控制面与数据面分离。下图展示了核心组件及其交互关系:

Containerd 核心架构组件交互图 客户端 (ctr / kubelet) gRPC API (Socket) Containerd 核心守护进程 (containerd) Namespace Service Image Service Event Service Metadata Snapshotter (overlayfs) Shim 进程 (containerd-shim) 容器运行时 (runc / kata) 容器进程 (PID Namespace)
架构要点: 客户端通过 gRPC 与 containerd 守护进程通信;守护进程内部通过 Event Service 发布异步事件;Snapshotter 管理镜像层文件系统;Shim 进程作为容器生命周期代理,隔离 containerd 与运行时。

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 读取。
事件采样关键点: Shim 进程在容器退出时发送 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
        }
    }
}
生产注意事项: Event Service 使用内存中的发布/订阅模型,不持久化事件。如果订阅者消费速度慢于事件产生速度,事件会被丢弃(channel 满)。事件采样系统必须实现背压控制和断线重连逻辑。

2.6 模块协作:事件采样与 GC 复核的数据流

将上述四个模块串联起来,就构成了运行时流水线的数据基础:

事件采样与镜像 GC 复核数据流 Shim 进程 容器退出 / OOM Event Service 发布 TaskExit 事件 事件采样器 记录 / 告警 / 审计 gRPC API List Snapshots / Images Snapshotter 遍历层引用计数 GC 复核器 标记 / 清理孤儿层 流水线整合 事件采样器从 Event Service 获取实时容器事件;GC 复核器通过 gRPC API 查询 Snapshotter 和 Image Service, 定期比对层引用关系,回收未被引用的镜像层。两者独立运行,但共享同一份元数据。
架构启示: 理解这四个模块的职责边界,是编写可靠事件采样器和镜像 GC 复核工具的前提。Shim 进程的健壮性影响事件完整性;Snapshotter 的层链结构决定 GC 复核算法复杂度;Event Service 的背压机制影响采样系统设计。