云原生容器运行时【免费下载链接】cri-oOpen Container Initiative-based implementation of Kubernetes Container Runtime Interface项目地址https://gitcode.com/gh_mirrors/cr/cri-o点击查看免费下载导读本文以 cri-o 仓库中 vendored 的 containerd/typeurl v2 包为研究对象系统讲解它在 Go 服务间通过 ttrpc/gRPC 传输任意结构化数据时如何完成类型注册、protobufAny编解码的整套机制。文中将结合 types.go 与 types_gogo.go 的源码实现并对照 cri-o 在 Kata 类虚拟化运行时runtimeVM中对该库的真实调用链帮助读者理解Register、MarshalAny、UnmarshalAny等 API 的底层原理掌握在自有项目中安全复用这套类型管理方案的方法。一、typeurl 是什么为“跨进程传输任意类型”而生的 Go 包typeurl 是 containerd 官方子项目Apache 2.0 许可见 LICENSE定位一句话即可概括管理“被编码类型”的注册、序列化marshaling与反序列化unmarshaling的 Go 包。它解决的核心场景是当类型需要通过 ttrpc/gRPC API 在进程间传递并最终被序列化为 protobuf Any 消息时需要一套统一的“类型↔URL↔编码”映射机制。typeurl 恰好承担了这件事为每个参与传输的类型注册一个唯一的 TypeURL根据类型特性自动选择编码方式protobuf 或 JSON反序列化时根据 TypeURL 自动还原出具体 Go 类型。从源码注释看包内定义了一个自有的Any接口GetTypeUrl() stringGetValue() []byte其目的在 types.go 中写得很明确wed like to have our own to hide the underlying protocol buffer implementations from containerd clients——即用自有抽象隐藏底层 protobuf 实现避免 containerd 客户端被具体实现细节绑架。在 cri-o 中该库v2 v2.2.3见 go.mod被用于与 containerd 的 ttrpc shim 接口通信是实现 Kata 类虚拟化容器运行时支持的关键一环。二、核心概念protobuf Any、TypeUrl 与 Value要理解 typeurl必须先理解它编码的载体——protobufAny。包文档 doc.go 给出了其 proto 定义message Any { string type_url 1; bytes value 2; }Any是一个可以“装下任意数据”的消息由两个字段构成字段类型含义type_urlstring唯一标识value中序列化消息类型的 URL/资源名valuebytes符合type_url所指示类型的、合法序列化后的消息字节type_url是区分不同Any内容的“身份证”typeurl 库的全部工作正是围绕这些 URL 的管理展开注册、解析、回查从而实现内容的“自动”编解码。三、类型注册Register 与 URL 路径的拼接规则任何类型在使用前必须先注册。注册通常在包的init()函数中完成这是 typeurl 约定的惯用法见 doc.go 中的示例func init() { typeurl.Register(Foo{}, Foo) }3.1 变参路径拼接Register的签名是func Register(v interface{}, args ...string)types.go。args是可变参数内部通过path.Join(args...)拼接成最终的 URL 路径。例如 doc.go 中来自github.com/containerd/containerd/client包的注册示例const prefix types.containerd.io major : strconv.Itoa(specs.VersionMajor) typeurl.Register(specs.Spec{}, prefix, opencontainers/runtime-spec, major, Spec)这会最终映射为types.containerd.io/opencontainers/runtime-spec/1/Spec这样的复合 URL。将版本号major拼入 URL 是一种典型的兼容性策略类型名即使不变携带版本信息的 URL 也能让对端识别消息格式的演进。3.2 注册的约束与冲突保护注册表是包级全局的registry make(map[reflect.Type]string)并用sync.RWMutex保护types.go。Register有几个关键行为注册键是去指针后的反射类型tryDereferencetypes.go要求入参必须是指针若传入非指针会直接panic(v is not a pointer to a type)注册时则取t.Elem()即指针所指类型本身同一类型重复注册如果两次注册的 URL 相同静默返回如果 URL 不同直接 panic——panic(fmt.Errorf(type registered with alternate path %q ! %q, et, p))防止同一类型被映射到两个不同路径造成歧义。3.3 TypeURL 查询TypeURL(v interface{}) (string, error)types.go用于查询某类型对应的 URL查询优先级为本地注册表命中直接返回未注册但实现了proto.Message接口的类型返回t.ProtoReflect().Descriptor().FullName()即 proto 消息的完整名遍历各handler如 gogoHandler由 handler 给出 URL全部落空则返回ErrNotFound包装错误。四、编码MarshalJSON 兜底 protobuf 优先 gogo 扩展MarshalAny(v interface{}) (Any, error)types.go是编码入口其类型分派逻辑非常清晰输入类型编码方式已是Any原样返回避免重复序列化verbatim实现了proto.Message使用proto.Marshal按 protobuf 编码被 handler如 gogoHandler匹配使用 handler 提供的 marshaller以上皆否回退到json.Marshal这意味着typeurl 对任意 Go 数据结构都可用只要它能被序列化为 JSON即使没有 proto 定义也能编码进Anydoc.go 明确说明了这一点。编码结果是一个anyType结构体同时携带解析出的typeURL和编码后的value字节。随后MarshalProto(from Any) *anypb.Anytypes.go把自有的Any转换成标准库google.golang.org/protobuf/types/known/anypb.AnyMarshalAnyToProto(from interface{}) (*anypb.Any, error)则是“任意值 → anypb.Any”的一步到位封装。五、解码Unmarshal按 URL 反查类型并还原解码入口是UnmarshalAny(any Any) (interface{}, error)types.go内部委托给UnmarshalByTypeURL(typeURL, value)最终落到核心函数unmarshaltypes.go反查类型getTypeByUrltypes.go先在本地注册表按 URL 查找未命中则尝试protoregistry.GlobalTypes.FindMessageByURL再未命中则交给各 handler 的GetType。全部失败返回type with url %s: ErrNotFound。构造目标实例未提供目标类型时用reflect.New(t)创建新实例提供out参数时会校验out的类型 URL 与消息 URL 是否一致不一致则报cant unmarshal type %q to output %q。按类型分派解码protobuf 消息走proto.Unmarshal匹配 handler 的走 handler 的 unmarshaller否则回退json.Unmarshal。另外两个面向“指定目标类型”的解码 API 也值得关注UnmarshalTo(any Any, out interface{}) error与UnmarshalAny等价但由调用方提供目标实例UnmarshalToByTypeURL(typeURL, value, out) error进一步拆开 URL 与字节适合调用方已持有 URL 的场景。配套的类型判定工具是Is(any Any, v interface{}) booltypes.go将v的 TypeURL 与any的 TypeURL 比对用于快速判断Any中的内容是否属于某类型。六、gogoproto 支持与no_gogo构建标签默认情况下typeurl 在标准 Google protobuf 之外还内置了对 gogoproto 消息的支持README Optional 一节说明了这一点。其实现位于 types_gogo.go文件首行即为构建约束//go:build !no_gogo即默认启用 gogo 支持只有显式添加no_gogo构建标签如go build -tags no_gogo时才将其剔除。其实现方式是注册一个gogoHandlertypes_gogo.go到全局handlers切片中为实现了gogoproto.Message接口的类型提供Marshallergogoproto.Marshal编码Unmarshallergogoproto.Unmarshal解码TypeURLgogoproto.MessageName(pm)生成 URLGetTypegogoproto.MessageType(url)反查类型。正因为有了这套 handler 机制接口定义见 types.gotypeurl 的编解码流程具备良好的可扩展性——任何实现了handler接口的第三方编解码器都可以被追加注册而不必改动核心流程。这对仍在使用旧版 gogo protobuf 生态的老项目兼容意义重大。七、在 cri-o 中的真实应用runtimeVM 与 ttrpc shim 通信理解了 API 后再看 cri-o 如何在实际生产代码中使用它。cri-o 对 Kata 类虚拟化运行时的支持实现在 internal/oci/runtime_vm.go 与 internal/oci/runtime_vm_linux.gotypeurl 在其中承担了三类职责7.1 启动时注册 OCI 运行时规范类型在newRuntimeVM中internal/oci/runtime_vm.gocri-o 模仿 containerd 的做法注册了 OCI 运行时规范的核心类型const prefix types.containerd.io major : strconv.Itoa(rspec.VersionMajor) typeurl.Register(rspec.Spec{}, prefix, opencontainers/runtime-spec, major, Spec) typeurl.Register(rspec.Process{}, prefix, opencontainers/runtime-spec, major, Process) typeurl.Register(rspec.LinuxResources{}, prefix, opencontainers/runtime-spec, major, LinuxResources) typeurl.Register(rspec.WindowsResources{}, prefix, opencontainers/runtime-spec, major, WindowsResources)源码注释也坦诚地记录了当时的临时性设计FIXME: We need to register those types for now, but this should be defined as a specific package that would be shared both by CRI-O and containerd——即这类注册应最终收敛为 CRI-O 与 containerd 共享的独立包而非各自注册。7.2 将运行时配置编码为 Any 传给 shim在CreateContainer中internal/oci/runtime_vm.go当管理员为 runtime handler 配置了runtime_config_path时cri-o 会把配置封装进runtimeoptions.Options经typeurl.MarshalAny编码后转换为标准anypb.Any随创建任务请求一起传给 containerd shimruntimeOptions : runtimeoptions.Options{ ConfigPath: r.handler.RuntimeConfigPath, } marshaledOtps, err : typeurl.MarshalAny(runtimeOptions) if err ! nil { return err } opts protobuf.FromAny(marshaledOtps)此外runtime_vm.go 处typeurl.MarshalAny(pSpec)用于将容器进程规范编码进创建任务runtime_vm.go 处则将运行结果再次编码后返回——可见MarshalAny贯穿了“下发配置 → 下发进程规范 → 回传结果”的完整通信链路。7.3 解码 shim 返回的统计信息在ContainerStats中internal/oci/runtime_vm_linux.gocri-o 接收 shim 通过task.Stats返回的Any类型统计数据用typeurl.UnmarshalAny还原出具体类型statsData, err : typeurl.UnmarshalAny(resp.GetStats()) if err ! nil { return nil, err } m, ok : statsData.(*cgroupsV1.Metrics) if ok { return metricsV1ToCgroupStats(ctx, m), nil } else { m, ok : statsData.(*cgroupsV2.Metrics) if ok { return metricsV2ToCgroupStats(ctx, m), nil } else { return nil, errors.New(unknown stats type) } }这里还体现了一个非常实用的工程技巧由于宿主机与虚拟机内guest VM的 cgroup 版本可能不一致不能假定收到的统计类型因此先用UnmarshalAny还原再通过类型断言*cgroupsV1.Metrics/*cgroupsV2.Metrics做双版本兼容处理。这正是 typeurl “根据 TypeURL 自动还原具体类型”能力带来的灵活性——调用方无需事先约定消息版本靠运行时类型断言即可优雅降级。八、最佳实践小结结合 README 说明与 cri-o 的实际用法在自有项目中落地 typeurl 时建议遵循在init()中集中注册所有需要跨进程传输的类型注册参数必须是指针URL 中携带版本信息如types.containerd.io/opencontainers/runtime-spec/1/Spec便于消息格式演进与多版本共存避免重复注册冲突同一类型只注册一次不同路径会触发 panic编码优先级可预期proto.Message走 protobufgogoproto 消息由 gogo handler 处理其余回退 JSON——因此非 proto 的普通结构体也能直接使用只要可被 JSON 序列化解码时善用类型断言如 cri-o 对 cgroup v1/v2 统计的处理所示UnmarshalAny返回interface{}后先断言再分支可优雅兼容对端版本的差异需要与旧 gogo 生态互通时保持默认构建若项目完全不依赖 gogoproto可通过no_gogo构建标签裁剪该支持。九、参考文件索引包说明文档vendor/github.com/containerd/typeurl/v2/README.md核心实现注册、编解码、Any 接口、handler 机制vendor/github.com/containerd/typeurl/v2/types.go详细使用示例与设计意图 vendor/github.com/containerd/typeurl/v2/doc.gogogoproto 支持与no_gogo构建标签实现vendor/github.com/containerd/typeurl/v2/types_gogo.gocri-o 中的实际调用类型注册与编码internal/oci/runtime_vm.gocri-o 中的实际调用统计信息解码internal/oci/runtime_vm_linux.go依赖版本声明go.mod赞分享云原生容器运行时【免费下载链接】cri-oOpen Container Initiative-based implementation of Kubernetes Container Runtime Interface项目地址https://gitcode.com/gh_mirrors/cr/cri-o点击查看免费下载相关推荐LinuxKit 中的 containerd/typeurl v2基于 protobuf Any 的类型注册与编解码实战解析LinuxKit 中的 containerd/typeurl v2基于 protobuf Any 的类型注册与编解码实战解析 导读 typeurl 是 con操作系统云原生容器运行时Atmosphère 1.8.0 适配 Switch 19.0.0 固件升级指南Atmosphère 1.8.0 适配 Switch 19.0.0 固件升级指南 Atmosphère 1.8.0 版本新增了对 Switch 系统 19.0.固件操作系统嵌入式系统编程containerd typeurl 包深度解析Go 语言中 protobuf Any 类型的注册、编解码与 gRPC 应用实践containerd typeurl 包深度解析Go 语言中 protobuf Any 类型的注册、编解码与 gRPC 应用实践 本指南围绕 containe测试云原生质量保障上一篇如何用Resemble Enhance实现AI语音降噪5分钟让嘈杂录音秒变专业音频下一篇如何使用ParanoidAndroid开发者必备的字符串加密指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考