news 2026/9/13 9:00:01

containerd Shim Capabilities 机制详解:通过 Bootstrap 扩展声明运行时能力

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
containerd Shim Capabilities 机制详解:通过 Bootstrap 扩展声明运行时能力

containerd Shim Capabilities 机制详解:通过 Bootstrap 扩展声明运行时能力

【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd

导读

Shim Capabilities 是 containerd 运行时 v2 架构中一套能力协商机制:shim 在启动时通过BootstrapResult附加类型化扩展(typed extension),主动告诉 containerd 自己能够独立完成哪些挂载类型(mount type)与挂载变换(transform),从而让 mount manager 不再重复代劳。本文以 docs/shim-capabilities.md 为主体,结合 bootstrap.proto、mount.proto 与 core/runtime/v2/task_mounts.go 等源码,讲解该扩展的协议格式、注册表语义、链式变换的"后缀规则"以及旧注解的迁移路径,帮助 shim 开发者与容器运行时集成方正确声明自身能力。


一、能力声明的载体:BootstrapResult 扩展

在 containerd 2.3 引入的引导协议(bootstrap protocol)中,shim 的start子命令从 stdin 读取BootstrapParams,向 stdout 写出BootstrapResultBootstrapResult除了携带监听地址(address)与通信协议(protocol,可取ttrpcgrpc)之外,还预留了一个可选字段:

message BootstrapResult { ... // Optional: Typed detail describing what this shim instance is able to // do, so that containerd can adjust its own behavior. repeated Extension extensions = 6; }

每个Extension内部就是一个google.protobuf.Any,shim 可以将任意类型化的结构化数据打包进去。containerd 侧的解析逻辑如下(见 core/runtime/v2/task_mounts.go):

func mountCapabilitiesExtension(bootstrap *bootapi.BootstrapResult) (*apitypes.MountCapabilities, error) { var caps apitypes.MountCapabilities found, err := bootstrap.FindExtension(&caps) if err != nil { return nil, err } if !found { return nil, nil } return &caps, nil }

向后兼容的关键设计

containerd 会忽略无法识别的扩展类型。这意味着 shim 可以无条件地附加扩展:当它运行在一个不认识该扩展的旧版 containerd 上时,旧守护进程只是继续保持原有行为,不会因此出错。这一点在bootstrap.proto的注释中亦有明确约定("containerd MUST ignore an extension whose type it does not recognize"),相关测试可见 core/runtime/v2/shim_test.go 中的TestBootstrapParamsUnknownExtension

扩展的查找与注册

BootstrapResult携带的extensions字段是注册表式(registry-style)的设计:shim 通过AddExtension附加,containerd 通过FindExtension按类型 URL 查找,整个过程对协议本身零侵入。引入一种新的能力类型不需要修改核心协议,只需要双方约定一个新的类型 URL。


二、MountCapabilities 扩展详解

当前文档注册表中的第一种、也是截至本文写作时文档记录的能力扩展是containerd.types.MountCapabilities。它描述的是:shim 自己执行了某些挂载类型或变换,mount manager 不得再代为执行。典型场景是 VM 类运行时——它可以把磁盘镜像文件直接传给 guest,而不是让宿主机先建立 loop 设备,从而获得更优的性能。

2.1 Proto 定义与字段语义

定义见 api/types/mount.proto:

message MountCapabilities { // Types are the mount types the sender performs itself, such as "erofs" or // "loop". These are base mount types, with any transform prefixes removed. repeated string types = 1; // Transforms are the mount transforms the sender applies itself, such as // "format", "mkfs" or "mkdir". // // A transform is named on its own, without the "/<mount-type>" suffix that // appears in a mount type, so "format" covers the mount types "format/bind" // and "format/mkdir/overlay" alike. repeated string transforms = 2; }

两个字段的命名规则非常关键:

字段取值示例语义
types"erofs""loop"基础挂载类型,必须去掉变换前缀。即只写overlay,不写format/mkdir/overlay
transforms"format""mkfs""mkdir"变换名本身,不带/<mount-type>后缀。声明format即同时覆盖format/bindformat/mkdir/overlay等一切以format/开头的挂载链

由于两个字段都是开放字符串(open ended strings),未来新增挂载类型或变换同样不需要修改协议。

2.2 空扩展的含义

附加一个两个字段都为空的MountCapabilities扩展,表示shim 除了普通系统挂载之外不处理任何额外内容——mount manager 将照常完成全部工作。这在源码中表现为:mountClaimOpts遍历caps.Typescaps.Transforms,为空时不会产生任何激活选项(见 core/runtime/v2/task_mounts.go)。

2.3 containerd 侧的翻译

containerd 会把扩展内容翻译为 mount manager 的激活选项(activation options):

opts := make([]mount.ActivateOpt, 0, len(caps.Types)+len(caps.Transforms)) for _, t := range caps.Types { opts = append(opts, mount.WithAllowMountType(t)) } for _, t := range caps.Transforms { opts = append(opts, mount.WithAllowTransform(t)) }

typesmount.WithAllowMountTypetransformsmount.WithAllowTransform。这两个选项定义在 core/mount/manager.go:

  • WithAllowMountType(mountType):即使存在该类型的自定义 handler,这些挂载也不应被执行——除非为了支撑后续挂载而必须执行;
  • WithAllowTransform(transform):该变换由调用者自己执行,mount manager 不执行——除非后续挂载依赖它。

翻译的正确性由 core/runtime/v2/task_mounts_test.go 中的TestTaskMountControllerActivate覆盖:例如声明Types: ["erofs", "loop"]Transforms: ["format", "mkfs"]后,断言AllowMountTypes == ["erofs", "loop"]AllowTransforms == ["format", "mkfs"]


三、链式变换的"后缀规则":只能认领链的后缀

这是 MountCapabilities 语义中最容易踩坑、也最需要理解的部分。

3.1 变换从外向内执行

在 docs/mounts.md 描述的挂载管理模型中,变换通过前缀链式书写:<transformer1>/<transformer2>/<mount-type>,例如format/mkdir/overlay。变换的执行顺序是从外向内(outside-in):外层变换的输出是内层变换的输入。因此一个被认领(claimed)的变换只有作为其所在链的后缀时才会被尊重

format/mkdir/overlay为例:

  • 认领mkdir:mount manager 会先执行format,然后把mkdir/overlay交还给 shim 自行完成;
  • 只认领format:什么都不发生,因为mkdir的输入依赖format已经运行完毕,mkdir无法在format未运行的情况下执行;
  • 一个想自己执行format的 shim,必须把链中format之后的所有变换一并认领,即同时认领formatmkdir

这套行为在 mount manager 的规划逻辑中有精确的测试用例(core/mount/manager/plan_test.go):

测试用例激活选项行为
什么都不认领manager 执行整条链(applyCount = 2)
认领最内层mkdirWithAllowTransform("mkdir")manager 执行到format为止,mkdir/overlay留给调用者(applyCount = 1)
只认领最外层formatWithAllowTransform("format")不被尊重,manager 仍执行整条链(applyCount = 2)
两个变换都认领WithAllowTransform("format")+WithAllowTransform("mkdir")manager 一个都不执行(applyCount = 0)
认领完整字面类型WithAllowMountType("format/mkdir/overlay")等价于认领链中所有变换(applyCount = 0)

TestPlanActivationClaimedGapInMiddle还覆盖了三变换链format/mkfs/mkdir/overlay中认领formatmkdir、而中间的mkfs未被认领的情形:由于mkfs未认领,它强制 manager 执行到它为止(applyCount = 2),只有它之后的mkdir才真正留给调用者。这一"中间有空缺(gap)则空缺之前都归 manager"的语义与文档描述完全一致。

3.2format变换的特殊性

format变换负责用 Go 模板解析挂载参数,例如{{ mount 0 }}会引用 mount manager 内部挂载点(参见 docs/mounts.md 中source/target/mount/overlay四个模板值)。这些模板引用的挂载点是mount manager 内部私有的状态,shim 无法凭空构造,因此:

shim 只能在覆盖链剩余部分的完整后缀中认领format,绝不能单独认领format

单独认领format在语义上不可能成立——format的输出是内层变换的输入,如果format由 shim 完成,那么后续变换的输入对 manager 而言是不存在的挂载点,链条无法继续。


四、迁移路径:废弃的 runtime-allow-mounts 注解

MountCapabilities扩展是在containerd 2.4中加入的,它取代了已废弃的运行时信息注解containerd.io/runtime-allow-mounts。为了平滑迁移:

  • 扩展优先:只要 shim 在 bootstrap 结果中附加了MountCapabilities扩展,就以扩展为准,完全不 consult 旧注解(测试"extension present takes precedence, legacy is not consulted"明确验证了这一点);
  • 注解兜底:未附加扩展的 shim,containerd 仍会去查询其RuntimeInfo注解作为迁移路径;
  • 已知例外io.containerd.runc.v2io.containerd.runhcs.v1这两个运行时确定从未设置过该注解,因此会直接跳过查询(见 core/runtime/v2/task_mounts_deprecated.go 的deprecatedNoAnnotationRuntimes映射,测试"well known default runtimes are never queried for the annotation"亦有覆盖)。

旧注解的值是一个逗号分隔列表,其中<transform>/*形式的条目表示变换,其余表示挂载类型,解析逻辑见deprecatedParseAllowedMounts

func deprecatedParseAllowedMounts(v string) *apitypes.MountCapabilities { caps := &apitypes.MountCapabilities{} for entry := range strings.SplitSeq(v, ",") { if entry == "" { continue } if transform, ok := strings.CutSuffix(entry, "/*"); ok { caps.Transforms = append(caps.Transforms, transform) continue } caps.Types = append(caps.Types, entry) } return caps }

例如注解值block,format/*会被解析为Types: ["block"]Transforms: ["format"]。此外,旧路径对成功查询结果做了进程级缓存(sync.Map,runtime 名 → capabilities),失败查询不缓存以便下次重试;当旧注解被命中时,containerd 会记录一条 warn 日志,提示 shim 应改用MountCapabilities扩展。

安全失败方向

无论扩展解析失败(例如类型 URL 匹配但二进制内容损坏)、旧注解查询失败,还是完全没有声明,mountClaimOpts都遵循**"什么都不认领"的安全方向**:即 mount manager 承担全部挂载工作,而不是冒险把挂载留给可能没有实现它的 shim。测试用例"a malformed extension claims nothing rather than failing""a failed legacy lookup claims nothing rather than failing"均验证了这一点。


五、工作流程与整体关系

将上述机制放入完整链路中看:

  1. containerd 启动 shim(containerd-shim-xxx start),通过 stdin 传递BootstrapParams
  2. shim 完成初始化后向 stdout 写出BootstrapResult,其中extensions字段附带上containerd.types.MountCapabilities
  3. containerd 读取结果,通过FindExtension提取扩展,若存在则由mountClaimOpts翻译为WithAllowMountType/WithAllowTransform激活选项;
  4. taskMountController.Activate在激活任务 rootfs 时带上这些选项调用 mount manager(见 core/runtime/v2/task_mounts.go),并把ActivationInfo.System(仍需 shim 自行完成的挂载)返回给 shim;
  5. shim 在 rootfs 中就位后启动容器。

关键点在于:shim 必须先于其挂载被激活而启动,这样它的能力声明才能被纳入激活决策(docs/mounts.md 的 "Support with containerd shims" 一节明确说明:"The shim is started before its mounts are activated, so that what it advertises can be taken into account.")。此外,若挂载 manager 插件未配置(manager == nil),Activate会原样返回 rootfs,能力协商自然退化为无操作。


六、给 shim 开发者的实操建议

综合文档与源码,为 shim 附加能力扩展时请遵循以下要点:

  1. 协议层面:在写出的BootstrapResult中调用AddExtension(&apitypes.MountCapabilities{...});扩展机制由pkg/shim自动处理,新协议失败时会回退到旧机制,无需 shim 自行判断。
  2. 命名规则types只写基础挂载类型(如erofsloopblock),transforms只写变换名(如formatmkfsmkdir),不要写format/mkdir/overlay这样的完整链式类型(除非你想表达"整条链都归我"——此时认领完整字面类型与认领全部变换等价)。
  3. 后缀规则:认领变换时务必从链的最内层(最后一个变换)开始认领;想自己执行format,就必须同时认领其后所有变换。只认领链中间的变换不会生效。
  4. format特殊对待:由于format依赖 manager 内部挂载点的模板解析,它只能在覆盖整条链剩余部分的后缀中被认领,绝不能单独认领。
  5. 迁移:新 shim 一律使用MountCapabilities扩展;仍在使用containerd.io/runtime-allow-mounts注解的 shim 应尽快迁移(注解按<transform>/*区分变换),迁移完成前 containerd 2.4+ 会继续兼容查询,但io.containerd.runc.v2io.containerd.runhcs.v1除外。

参考资料

  • docs/shim-capabilities.md:本文主体,能力扩展注册表与语义的权威定义
  • docs/runtime-v2.md:引导协议(bootstrap protocol)与 shim 编写总览
  • docs/mounts.md:mount manager、挂载类型与变换、以及与运行时的关系
  • api/runtime/bootstrap/v1/bootstrap.proto:BootstrapParams/BootstrapResult/Extension定义
  • api/types/mount.proto:MountCapabilities消息定义
  • core/runtime/v2/task_mounts.go:扩展解析与激活选项翻译的实现
  • core/runtime/v2/task_mounts_deprecated.go:废弃注解的迁移查询与解析
  • core/runtime/v2/task_mounts_test.go 与 core/mount/manager/plan_test.go:能力翻译与后缀规则的测试验证
  • core/mount/manager.go:WithAllowMountType/WithAllowTransform激活选项定义

【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:53:50

基于51单片机与考毕兹振荡器的微亨级电感测量方案

简介&#xff1a;一份基于51单片机的电感测量设计资料包&#xff0c;面向单片机课程设计、电子竞赛及初学电感测量原理的开发者。项目采用考毕兹三点式振荡电路&#xff0c;通过测量振荡频率换算出0.1&#xff5e;10uH量程的电感值&#xff0c;涵盖proteus仿真、原理图、流程图…

作者头像 李华
网站建设 2026/9/13 8:53:42

国产大模型选型实战:按场景拆解Hy4、GLM-5.3、Kimi K3与DeepSeek-V4

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 8:51:30

ESP32引脚分配避坑指南:复用冲突、电源域与型号差异全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华