如果说 runc 是容器的”引擎”,那 containerd 就是容器的”操作系统”——它管理镜像的拉取和解压、容器的创建和启动、任务的监控和重启、事件的发布和订阅。Docker 用它管理容器生命周期,Kubernetes 通过 CRI 接口与它交互,cloud-native 生态的每一个角落都有它的身影。
containerd 的设计哲学是简洁且可组合——它只做容器运行时该做的事,不做编排(那是 Kubernetes 的事),不做网络(那是 CNI 的事),不做存储(那是 CSI 的事)。这种”只做一件事并做好”的设计,让 containerd 成为了云原生基础设施的坚实底座。
containerd 的诞生源于 Docker 架构的一次关键拆分。早期的 Docker 是一个单体架构——docker daemon 同时负责镜像管理、容器生命周期、网络配置、存储挂载,所有功能耦合在一个进程中。2016 年,Docker 将容器生命周期管理拆分为独立的 containerd 项目,docker daemon 只保留上层 API 和构建功能。2017 年,containerd 加入 CNCF 孵化,2019 年成为 CNCF 毕业项目。从 Docker 的”内部组件”到”行业标准容器运行时”,containerd 完成了身份的蜕变——它不再只是 Docker 的附属,而是 Kubernetes、云平台、边缘计算等场景的默认选择。理解这段历史,有助于理解 containerd 的设计哲学:它”只做容器运行时该做的事”,因为它的诞生就是为了从 Docker 的”大而全”中剥离出”小而美”。
前置知识
- Ch06 runc 源码分析:containerd 调用 runc 创建容器,理解 runc 是理解 containerd 的前提
- Ch05 OCI 规范详解:containerd 遵循 OCI 规范管理镜像和容器
- gRPC 基础:containerd 的所有 API 通过 gRPC 暴露
containerd 的设计哲学是”可组合”——它通过插件体系扩展功能,核心只做容器生命周期管理。这种设计让它既轻量又灵活。
本章将深入 containerd 的架构设计,理解它如何在 runc 之上构建工业级容器管理能力。
一、containerd 整体架构
1.1 架构全景
1.2 核心组件
| 组件 | 职责 | 存储后端 |
|---|---|---|
| Content Store | 存储镜像层和配置的原始内容 | 文件系统 |
| Metadata Store | 存储镜像、容器、快照的元数据 | BoltDB |
| Snapshot Service | 管理文件系统快照(OverlayFS) | overlayfs/btrfs/zfs |
| Diff Service | 计算文件系统层之间的差异 | — |
| Runtime Service | 管理容器运行时(shim+runc) | — |
| Event Service | 发布/订阅容器事件 | — |
1.3 containerd 的数据流
二、gRPC API
2.1 API 服务列表
containerd 通过 gRPC 暴露以下核心服务:
| 服务 | 功能 | 关键方法 |
|---|---|---|
| Images | 镜像管理 | List, Get, Create, Delete |
| Containers | 容器管理 | List, Get, Create, Delete, Update |
| Tasks | 任务管理 | Create, Start, Kill, Delete, Wait, Exec |
| Content | 内容管理 | List, Info, Read, Write, Delete |
| Snapshots | 快照管理 | Prepare, Commit, Mounts, Remove |
| Diff | 差异计算 | Compare, Apply |
| Leases | 租约管理 | Create, Delete, List |
| Events | 事件订阅 | Subscribe |
| Introspection | 自省 | Plugins, ServerInfo |
2.2 镜像管理 API
// containerd 镜像管理示例package main
import ( "context" "fmt" "log"
containerd "github.com/containerd/containerd/v2/client")
func main() { // 1. 连接 containerd client, err := containerd.New("/run/containerd/containerd.sock") if err != nil { log.Fatal(err) } defer client.Close()
ctx := context.Background()
// 2. 拉取镜像 image, err := client.Pull(ctx, "docker.io/library/nginx:latest", containerd.WithPullUnpack, // 自动解压 ) if err != nil { log.Fatal(err) } fmt.Printf("Pulled image: %s\n", image.Name())
// 3. 创建容器 container, err := client.NewContainer(ctx, "my-nginx", containerd.WithImage(image), containerd.WithNewSnapshot("my-nginx-snapshot", image), containerd.WithNewSpec( containerd.WithImageConfig(image), containerd.WithHostNamespace(containerd.NetworkNamespace), ), ) if err != nil { log.Fatal(err) } defer container.Delete(ctx, containerd.WithSnapshotCleanup)
// 4. 创建并启动任务 task, err := container.NewTask(ctx, containerd.NewIO(os.Stdin, os.Stdout, os.Stderr)) if err != nil { log.Fatal(err) } defer task.Delete(ctx)
if err := task.Start(ctx); err != nil { log.Fatal(err) }
// 5. 等待任务退出 status, err := task.Wait(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Task exited with status: %d\n", <-status)}2.3 容器管理 API
# 使用 ctr 命令行操作 containerd
# 镜像操作ctr images pull docker.io/library/nginx:latestctr images listctr images inspect docker.io/library/nginx:latest
# 容器操作ctr containers create docker.io/library/nginx:latest mynginxctr containers list
# 任务操作ctr tasks start mynginxctr tasks listctr tasks kill mynginxctr tasks attach mynginx
# 快照操作ctr snapshots listctr snapshots prepare my-snapshotctr snapshots commit my-snapshot my-committed-snapshot三、镜像管理
3.1 镜像拉取流程
containerd 拉取镜像的完整流程:
- 解析引用:将
nginx:latest解析为完整的 registry 路径 - 获取 Manifest:从 Registry 拉取镜像 Manifest
- 下载 Blob:按需下载 Config 和 Layer Blob
- 存储 Content:将 Blob 存储到 Content Store
- 解压 Layer:将 Layer 解压到 Snapshot Service
- 注册 Image:在 Metadata Store 中注册镜像
# 查看 containerd 的内容存储ls /var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/
# 查看已下载的 blobctr content list
# 查看快照ctr snapshots list3.2 Snapshot Service
Snapshot Service 管理容器的文件系统快照,基于 OverlayFS 实现:
| 操作 | 说明 | OverlayFS 对应 |
|---|---|---|
| Prepare | 创建可写快照 | 创建 upperdir + merged |
| Commit | 提交可写快照为只读 | 将 upperdir 转为 lowerdir |
| Mounts | 获取挂载点 | 返回 overlay mount 选项 |
| Remove | 删除快照 | 删除 upperdir/lowerdir |
# 查看 containerd 的 OverlayFS 快照ls /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/
# 查看快照的元数据ctr snapshots list
# 查看容器的 OverlayFS 挂载mount | grep overlay3.3 镜像层与快照的映射
四、任务管理
4.1 Container vs Task
containerd 区分 Container 和 Task 两个概念:
| 概念 | 说明 | 生命周期 |
|---|---|---|
| Container | 容器的配置和元数据 | 持久化(直到删除) |
| Task | 容器的运行实例 | 临时(进程退出即结束) |
# Container 可以没有 Task(已停止的容器)ctr containers list# NAME IMAGE# mynginx nginx:latest
ctr tasks list# TASK PID STATUS# (空——容器未运行)
# 启动 Taskctr tasks start mynginxctr tasks list# TASK PID STATUS# mynginx 12345 RUNNING4.2 Task 的创建流程
// containerd 的 Task 创建流程(简化)func (c *container) NewTask(ctx context.Context, ioCreator IOCreator) (Task, error) { // 1. 获取容器的 rootfs 挂载点 mounts, err := c.snapshotter.Mounts(ctx, c.snapshotID)
// 2. 准备 OCI Bundle bundle := &Bundle{ ID: c.id, Path: c.bundlePath, Rootfs: mounts, Spec: c.spec, }
// 3. 启动 containerd-shim shim, err := shim.Start(ctx, bundle, c.runtime) if err != nil { return nil, err }
// 4. 通过 shim 创建 Task task, err := shim.Create(ctx, &task.CreateRequest{ ID: c.id, Bundle: bundle.Path, Rootfs: mounts, Terminal: true, Stdin: "/dev/null", Stdout: "/dev/null", Stderr: "/dev/null", })
return task, nil}五、插件体系
5.1 containerd 的插件架构
containerd 采用插件架构,所有核心功能都是插件:
# 查看 containerd 的插件列表ctr plugins list
# 输出示例:# TYPE ID PLATFORMS STATUS# io.containerd.content.v1 content - ok# io.containerd.snapshotter.v1 overlayfs linux/amd64 ok# io.containerd.diff.v1 walking linux/amd64 ok# io.containerd.runtime.v2 task/shim - ok# io.containerd.service.v1 diff - ok# io.containerd.service.v1 images - ok# io.containerd.service.v1 containers - ok# io.containerd.service.v1 tasks - ok# io.containerd.grpc.v1 containers - ok# io.containerd.grpc.v1 content - ok# io.containerd.grpc.v1 diff - ok# io.containerd.grpc.v1 images - ok# io.containerd.grpc.v1 introspection - ok# io.containerd.grpc.v1 leases - ok# io.containerd.grpc.v1 namespaces - ok# io.containerd.grpc.v1 snapshots - ok# io.containerd.grpc.v1 tasks - ok# io.containerd.cri.v1 cri - ok5.2 插件类型
| 插件类型 | 示例 | 功能 |
|---|---|---|
| Snapshotter | overlayfs, btrfs, zfs, native | 文件系统快照管理 |
| Diff | walking, plugin | 层差异计算 |
| Runtime | task/shim, io.containerd.runc.v2 | 容器运行时 |
| Service | images, containers, tasks, content | 核心服务 |
| GRPC | containers, content, diff, images | gRPC API |
5.3 自定义 Snapshotter 示例
// 自定义 Snapshotter 插件(简化骨架)package mysnapshotter
import ( "context" "github.com/containerd/containerd/v2/plugins/snapshots")
type mySnapshotter struct { root string}
func NewSnapshotter(root string) snapshots.Snapshotter { return &mySnapshotter{root: root}}
func (s *mySnapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { // 创建可写快照 return nil, nil}
func (s *mySnapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { // 提交快照为只读 return nil}
func (s *mySnapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { // 返回挂载点 return nil, nil}
func (s *mySnapshotter) Remove(ctx context.Context, key string) error { // 删除快照 return nil}六、事件系统
6.1 事件类型
containerd 通过事件系统发布容器生命周期事件:
| 事件 | 触发时机 |
|---|---|
ImageCreate | 镜像创建 |
ImageDelete | 镜像删除 |
ContainerCreate | 容器创建 |
ContainerDelete | 容器删除 |
TaskCreate | 任务创建 |
TaskStart | 任务启动 |
TaskExit | 任务退出 |
TaskOOM | 任务 OOM |
SnapshotPrepare | 快照准备 |
SnapshotCommit | 快照提交 |
# 订阅 containerd 事件ctr events
# 输出示例:# ENVELOPE TIMESTAMP TOPIC EVENT# 1 2026-08-14T10:00:00Z /containers/create {"id":"mynginx",...}# 2 2026-08-14T10:00:01Z /tasks/create {"id":"mynginx",...}# 3 2026-08-14T10:00:01Z /tasks/start {"id":"mynginx",...}6.2 事件在 Kubernetes 中的应用
Kubernetes 通过 CRI 接口订阅 containerd 事件,感知容器状态变化:
// CRI 事件处理(简化)func (c *criService) handleEvent(event *eventtypes.Event) { switch event.Topic { case "/tasks/exit": // 容器退出,更新 Pod 状态 c.handleContainerExit(event) case "/tasks/oom": // 容器 OOM,记录事件 c.handleContainerOOM(event) }}七、CRI 插件
7.1 CRI 接口
Kubernetes 通过 CRI(Container Runtime Interface)与 containerd 交互:
# containerd 的 CRI 配置version = 2
[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" runtime_engine = "" runtime_root = ""
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" # gVisor
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata] runtime_type = "io.containerd.kata.v2" # Kata Containers7.2 CRI 与 containerd 的交互
containerd 的 gRPC socket(/run/containerd/containerd.sock)默认没有认证机制。任何能访问该 socket 的进程都可以创建、删除容器和拉取镜像。在生产环境中,务必通过文件权限限制 socket 的访问,或启用 TLS 认证。Kubernetes 节点上,只有 kubelet 和 root 用户应该有权访问该 socket。
八、动手实践
8.1 用 Go 客户端操作 containerd
package main
import ( "context" "fmt" "log" "os" "syscall"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/pkg/namespaces")
func main() { ctx := namespaces.WithNamespace(context.Background(), "default")
client, err := containerd.New("/run/containerd/containerd.sock") if err != nil { log.Fatal(err) } defer client.Close()
// 拉取镜像 image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack, ) if err != nil { log.Fatal(err) } fmt.Printf("Pulled: %s\n", image.Name())
// 创建容器 container, err := client.NewContainer(ctx, "my-redis", containerd.WithImage(image), containerd.WithNewSnapshot("redis-snap", image), containerd.WithNewSpec( containerd.WithImageConfig(image), ), ) if err != nil { log.Fatal(err) } defer container.Delete(ctx, containerd.WithSnapshotCleanup)
// 创建任务 task, err := container.NewTask(ctx, containerd.NewIO(os.Stdin, os.Stdout, os.Stderr)) if err != nil { log.Fatal(err) } defer task.Delete(ctx)
// 启动任务 if err := task.Start(ctx); err != nil { log.Fatal(err) }
// 等待退出 statusC, err := task.Wait(ctx) if err != nil { log.Fatal(err) }
// 发送 SIGTERM task.Kill(ctx, syscall.SIGTERM) status := <-statusC fmt.Printf("Redis exited: %v\n", status)}8.2 containerd 调试技巧
# 查看 containerd 日志journalctl -u containerd -f
# 查看 containerd 的 gRPC 调试端点curl --unix-socket /run/containerd/containerd.sock http://localhost/v2/
# 查看容器详情ctr containers inspect mynginx
# 查看任务详情ctr tasks inspect mynginx
# 查看镜像层ctr images inspect docker.io/library/nginx:latest | jq .rootfs
# 查看快照信息ctr snapshots inspect mynginx-snap
# 查看内容信息ctr content inspect sha256:abc123...九、本章小结
上一章理解了runc 源码与容器创建流程。
| 组件 | 职责 | 关键接口 |
|---|---|---|
| Content Store | 存储镜像 blob | content API |
| Metadata Store | 存储元数据 | BoltDB |
| Snapshot Service | 文件系统快照 | snapshotter API |
| Runtime Service | 容器运行时 | task API |
| Event Service | 事件发布订阅 | events API |
| CRI Plugin | Kubernetes 集成 | CRI API |
containerd 的插件架构使其非常灵活——你可以替换 Snapshotter(如使用 stargz 延迟拉取)、替换 Runtime(如使用 gVisor/Kata)、添加自定义插件。这种可组合性是 containerd 被广泛采用的关键。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






