containerd 容易被看成一团扁平的软件,因为它同时站在几条容器叙事的交叉处。Docker 借它落地,Kubernetes 节点常常依赖它,项目自己也把它界定为行业标准的容器运行时。[1] 这套说法很容易把人的理解拉向一条直线:一个守护进程、一种运行时、一个容器真正“发生”的地方。文档给出的图景更有层次。把 containerd 放回边界管理这一位置,它会清楚得多。不可变镜像内容与已挂载的文件系统分开,容器元数据与可运行进程分开,高层守护进程与真正负责启动容器的底层运行时逻辑也分开。[1][2][3][4][5]

这层理解在 2026 年更重要,因为项目还在继续加固这些接缝,同时让它们保持可见。到 2026-05-03T12:35:47Z UTC 为止,GitHub 上最新的正式发布是 containerd 2.3.0,发布时间是 2026-04-30。[9] 发布策略文件说明,项目现在按四个月节奏发 minor 版本,并和 Kubernetes 发布节奏对齐;支持矩阵又把 2.3 标成当前的 LTS 分支,并列出对 Kubernetes 1.36 的支持。[8] 这些信息带出的重点很具体:在节点运行时、快照器、shim 与 CRI 兼容层仍要不断演进的地方,containerd 依旧处在主干位置。

图像说明:题图来自 Wikimedia Commons 的真实服务器机柜照片。[11] 这张图放在这里很准确,因为 containerd 最有力量的地方正是主机层的存储、运行时套接字与节点服务。若把视线放在光滑的 logo 或终端界面上,重心就会偏到别的层面。

第一条边界:container 元数据与可运行状态分开

containerd 最有用的一条澄清,也最容易被匆匆带过:在 containerd 里,containertask 属于两种对象。features 文档把 container 写成元数据对象,OCI runtime specification、镜像与根文件系统引用都挂在这里;task 则是由 container 派生出来、真正进入运行态的对象。[2] 这条区分看上去很细,真正进入运维或平台实现语境时,分量很大。

一旦团队把“container”直接等同于“正在跑的进程”,镜像配置、rootfs 准备、运行时选择与活跃进程监管就会被压在一层里,很多判断也会跟着发糊。containerd 有意把这些动作拆开。客户端先创建 container 对象,把 image config 接上,再指定 snapshot,后面才把 task 物化出来。[2] 这样的顺序让它很适合嵌进更大的系统里。守护进程没有承担完整的终端用户体验,它先提供一块干净的承接面,让上层系统在执行发生之前把状态放稳。[1][2]

也正因为如此,containerd 与“所有便利都装进同一个开发者入口”的工具形态之间天然有距离。README 写得很明白:它的设计目标是嵌进更大的系统里,区别于直接面向终端用户的工具。[1] 这句话带出来的是架构立场。Docker、nerdctl、Kubernetes 之类的工具能安稳地压在它上面,依靠的正是它把底层执行模型收得足够窄。[1][2]

第二条边界:content store 与 snapshotter 各管一件事

containerd 最干净的一条技术分界,落在 内容文件系统状态 之间。历史架构文档把 content 组件写成按哈希寻址的不可变内容存储,把 snapshot 组件写成负责解包镜像层、生成文件系统快照的部分。[3] 当前 snapshotter 文档沿用的是同一个分工,只是换成更面向运维的表述:snapshotter 管理容器文件系统快照,默认是 overlayfs,也可以换成 nativebtrfszfserofs,还可以接外部 snapshotter 插件。[4]

这条边界之所以关键,是因为镜像数据与已挂载的根文件系统原本就属于两类工作。content store 回答的是“这个 blob 在不在、摘要能不能对上”;snapshotter 回答的是“在这台主机上,根文件系统该怎样摆出来给工作负载使用”。一旦把这层关系看清,很多 containerd 行为就会显得顺理成章。拉取镜像,没有把存储策略永久锁死。把内容解到 overlayfs 是一种主机级选择,切到远程或外部 snapshotter 又是另一种主机级选择。[2][4]

这也解释了项目为什么能在同一个守护进程周围容纳这么多存储路径。features 文档明确保留了通过 gRPC 接外部 snapshot plugin 的能力,plugins 文档又把 proxy plugin 与内建 plugin 放在一套共同的加载模型里。[2][5] 顺着这个结构看,snapshotter 根本谈不上边角扩展,它本来就是 containerd 承认“不同主机与不同性能目标需要不同文件系统机制”的主要接口之一。

这条边界的外溢影响,如今已经跑到了 containerd 社群之外。Docker 关于 containerd image store 的官方文档写得很直接:一旦启用这条路径,Docker Engine 用的是 snapshotter 这套模型,区别于旧式 storage driver 语言。[10] 这是一条很好的旁证,因为它说明 snapshotter 已经从运行时内部细节,走到了运维人员会直接碰到的抽象层上。[10]

第三条边界:daemon、shim 与 runtime engine 分层

runtime-v2 文档最有力的一点,在于它彻底拆开了“containerd 直接启动容器”这层误解。文档写得很清楚:containerd 自己没有直接拉起容器。它先协调镜像内容、rootfs 布局与配置,再调用一个 runtime shim;shim 在 socket 上监听,再通过 ttRPC 处理生命周期操作。[5] shim 后面还可以再调用独立的 runtime engine,例如 runc,由后者真正完成 create、start、stop。[5]

这正是项目的核心骨架。containerd 负责持久的控制面角色,shim 负责贴近执行侧的 API 面,runtime engine 负责平台相关的进程工作。[5] 同一份文档还把关系继续推开:shim 与容器之间未必是一对一。一个 shim 可以管理多个容器,io.containerd.runc.v2 这条实现会借助 CRI sandbox label,把同一个 Kubernetes pod 里的容器归到一起。[5] 这个设计很重要,因为它让 containerd 能同时容纳普通单容器流程与 pod 这种多容器执行边界,同时保留它们在系统里的差异。

发布与升级路径也在强调这条分层是真正的主线,有别于历史包袱。2.0 迁移说明写明了老的 Runtime V1 与 Runc V1 shim 支持已经移除,用户要向 io.containerd.runc.v2 与新的 shim 模型迁移。[7] 到 2.3,start 路径又加入了基于 protobuf 的 bootstrap protocol,旧机制只作为过渡兼容继续保留。[5] 这套动作合在一起,意思很明确:containerd 仍在继续加固 shim 合约,因为项目把运行时分层当作长期资产来看。

CRI 是 Kubernetes 适配层,位置非常明确

放到 Kubernetes 语境里,containerd 的层次感会更直观。CRI 架构文档说,cri plugin 与 kubelet 同处一台节点,接收 CRI 请求,负责借助 CNI 配置 pod 网络,再调用 containerd 内部能力创建 sandbox container、拉取镜像、启动应用容器。[6] 这条调用链已经包含了大量编排责任,也正因为此,把 CRI 单独立出来反而让整个系统更容易读。

2.0 文档里的变化进一步说明了这一点。sandbox service 已经稳定,sandboxed CRI 默认开启,CRI 路径也切到了基于 sandbox controller 的 pod-sandbox 支持上。[7] 也就是说,项目正在更明确地承认:pod、sandbox、设备注入与 runtime 适配各自值得有名字清楚的接口。对于 Kubernetes 节点运维来说,这正是 containerd 保持可读性的原因:kubelet 说 CRI,CRI plugin 处理 pod 级事务,containerd 下层再把 content、snapshot 与 task execution 摆在有次序的结构里。[5][6][7]

团队真正该带走的判断

从工程落点看,containerd 最强的适配场景是容器 底座:Kubernetes 节点、嵌入式运行时、特殊 snapshotter 栈、替代 shim,或者任何需要稳定执行核心的主机级工具。[1][2][5][8] 若期待的是一套直接面向开发者、从镜像到交互都已经收拾好的产品形态,往往还需要另一层工具站在上面。这属于同一套设计节制留下的自然结果,不宜被读成产品缺口。

若要给这篇文章找一个最直接的反证条件,也很简单:一旦这些边界失去可信度,containerd 的说服力就会迅速下降。content storage 与 rootfs 摆放混在一起,snapshotter 选择就会带来更高风险;container 对象与 task 失去区分,客户端的分阶段建模也会松动;shim 若退成偶然存在的一层,运行时多样性与活跃进程管理就会一起变得难以维护。到 2026 年,文档与发布姿态给出的信号正好朝着相反方向展开。containerd 仍然重要,正因为它在每一层都反复提醒同一件事:运行时栈要保持稳固,每个组件最好只守住自己那一类工作。[1][2][3][4][5][6][7][8][10]

来源

  1. containerd README:项目定位、嵌入式使用姿态、生命周期范围、registry 支持与 CRI 概览。
  2. containerd docs/features.md:container 元数据与 task 的区分、namespace、根文件系统、client 行为,以及外部 snapshot plugin 支持。
  3. containerd 历史架构说明:content、metadata、snapshot、event 与 runtime execution 的高层分工。
  4. containerd snapshotter 文档:内建 snapshotter 选项、外部 snapshotter 接口,以及 mount target 行为。
  5. containerd docs/runtime-v2.md:shim/runtime-engine 模型、ttRPC socket 流程、命名 runtime 解析、pod 级 shim 分组,以及 2.3 bootstrap protocol。
  6. containerd CRI 架构文档:kubelet 到 CRI 的调用路径、CNI 接线、sandbox 创建与应用容器启动顺序。
  7. containerd 2.0 说明:sandbox service 稳定化、CRI 默认路径调整、shim 移除项与升级相关 breaking changes。
  8. containerd RELEASES.md:发布节奏、支持周期、LTS 策略与 Kubernetes 兼容矩阵。
  9. containerd GitHub v2.3.0 发布页:本文写作时的最新版本与发布时间锚点。
  10. Docker Docs,《containerd image store with Docker Engine》:snapshotter 与 containerd image store 模型在下游运维场景中的直接呈现。
  11. Wikimedia Commons,《File:Servers in a Rack.jpg》:本文题图所用服务器机柜照片的来源页。