
containerd 开发报告深度解读2017 年 5 月的镜像推送、Go 客户端与测试体系建设【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd本文基于 docs/historical/reports/2017-05-26.md 这篇历史开发报告展开。它以第一视角记录了 containerd 在 2017 年 5 月冲刺功能完备feature complete的关键节点dist工具新增镜像推送能力、首个 Go 客户端初具雏形、集成测试体系开始落地以及命名空间namespace与事件events两大待办功能。阅读本文你将掌握这段时期内 containerd 镜像推送命令的设计思路与用法、客户端拉取镜像 → 生成 OCI spec → 创建容器 → 运行任务的完整编程范式并能结合当前仓库源码理解这些早期设计如何演化为今天 client、pkg/oci、core/events 等核心包。项目状态功能完备冲刺与 API 稳定性规划报告开篇给出了 2017 年 5 月 26 日的项目全景containerd 整体已非常接近功能完备最后的几项能力如命名空间支持正在收尾。报告提到当时已有多个下游消费者基于 containerd 做实现包括 Kubernetes 的 CRI、Docker 执行引擎、Swarmkit 与 Linuxkit。从研发节奏上看团队希望在当月月底达成功能完备将 6 月留给API 质量打磨与客户端需求确认——具体工作包括错误码error codes整理以及重新审视 protobuf 定义protos确保产出一套能在 containerd 1.x 整个生命周期内稳定支持的 API。这一决策对后来的架构影响深远今天的 api 目录下仍然以.proto定义 生成的.pb.go形式组织服务接口如 api/services/containers、api/services/content正是当年API 先行、长期稳定路线的延续。Image push向 registry 推送镜像与对象报告的核心技术内容之一是给dist工具新增了push与push-object两个命令push将 manifest 及其全部关联对象层、config 等推送到 registrypush-object将 content store 中的单个 blob 推送到 registry。这两个命令在当时位于dist工具下而在当前仓库中它们的后继实现分别落在 cmd/ctr/commands/images/push.goctr images push与 cmd/ctr/commands/content/content.goctr content push-object。push 命令的关键语义报告明确了几个在当时很实用的设计点无需预先打 tagpush命令不要求先给镜像打 tag只需指定远程名称与本地名称。若两者相同则只传一个参数即可。当前实现中local 时会回退为local ref见 cmd/ctr/commands/images/push.go#L114-L116正是这一语义的延续。远程名称可省略对象标识符即可以不写 Docker 风格的 tag 或 digest此时可用 manifest digest 作为对象标识符重新拉取 manifest。只推送已存在的镜像push 不会凭空创建镜像 manifest创建新镜像需要另行计算层 diff、生成 config 并组装 manifest。当前 push.go 的Description依然保留了同样的约束The image manifest must exist before push.推送示例与输出解读报告给出了向本地 registry 推送 ubuntu 镜像的完整示例。dist image list先展示本地镜像REF、TYPE、DIGEST、SIZE 四列随后dist push localhost:5000/ubuntu docker.io/library/ubuntu:latest依次推送 manifest、各 layer 与 config并实时输出进度条、总耗时与吞吐$ dist image list REF TYPE DIGEST SIZE docker.io/library/ubuntu:latest application/vnd.docker.distribution.manifest.v2json sha256:382452f82a8bbd34443b2c727650af46aced0f94a44463c62a9848133ecb1aa8 44.7 MiB $ dist push localhost:5000/ubuntu docker.io/library/ubuntu:latest manifest-sha256:382452f82a8bbd34443b2c727650af46aced0f94a44463c62a9848133ecb1aa8: done || layer-sha256:cf9722e506aada1109f5c00a9ba542a81c9e109606c01c81f5991b1f93de7b66: done || layer-sha256:b6f892c0043b37bd1834a4a1b7d68fe6421c6acbc7e7e63a4527e1d379f92c1b: done || layer-sha256:55010f332b047687e081a9639fac04918552c144bc2da4edb3422ce8efcc1fb1: done || layer-sha256:3deef3fcbd3072b45771bd0d192d4e5ff2b7310b99ea92bce062e01097953505: done || config-sha256:ebcd9d4fca80e9e8afc525d8a38e7c56825dfb4a220ed77156f9fb13b14d4ab7: done || layer-sha256:2955fb827c947b782af190a759805d229cfebc75978dba2d01b4a59e6a333845: done || elapsed: 6.5 s total: 44.7 M (6.9 MiB/s)值得说明的是这里的时序数据显示为当年示例环境下的单次运行结果实际耗时取决于网络与 registry 性能不应理解为固定指标。示例本身清晰展示了推送流程的两阶段先 resolve 出 manifest 描述符再按依赖顺序上传所有关联 blob。当前ctr push的演进如今ctr images push的命令定义保留了当年核心语义并增加了更丰富的控制项见 cmd/ctr/commands/images/push.go#L47-L79--manifest按 digest 指定要推送的 manifest--platform仅推送特定平台的内容--max-concurrent-uploaded-layers限制并发上传的层数--local绕过 transfer service直接从本地客户端推送此时才支持--skip-verify、--tlscacert等 registry 标志--allow-non-distributable-blobs允许推送标记为不可分发的 blob。非--local路径默认走 transfer service 的client.Transfer调用见 push.go#L137而push-object则要求remote object type三个参数其中对象以 digest 形式给出见 cmd/ctr/commands/content/content.go#L516-L534。Client让 containerd 用起来不像远程守护进程报告用较大篇幅阐述了客户端的设计动机由于会有多个下游消费者使用同一套 API团队希望减少使用者的重复代码并提供一个感觉不到是在和远程守护进程打交道的稳定客户端 API。容器对多数人来说已经足够复杂containerd 客户端要做的就是把底层系统细节文件系统操作、tar 解包、rootfs 搭建全部接管让构建平台变得毫不费力。这一设计哲学在今天的 client 包中依然清晰可见New通过 gRPC 连接 containerd 实例默认超时 10 秒见 client/client.go#L104-L113所有请求都经由context携带命名空间等元数据上层使用者无需关心 socket 与协议细节。创建客户端client, err : containerd.New(address, containerd.WithNamespace(docker)) if err ! nil { return err } defer client.Close()这里WithNamespace(docker)将后续所有操作限定在docker命名空间内。在当时这是新引入的命名空间 API 的客户端入口当前仓库中命名空间可以通过 client_opts.go 的WithDefaultNamespace(ns string)在客户端级别设置也可以借助 pkg/namespaces 提供的WithNamespace(ctx, namespace)在 context 级别更细粒度地控制见 pkg/namespaces/context.go。从 DockerHub 拉取并解包镜像// pull unpack the image to your snapshot ( overlayfs default ) of choice image, err : client.Pull(ctx, docker.io/library/redis:alpine, containerd.WithPullUnpack) if err ! nil { return err }WithPullUnpack指示在拉取完成后将镜像解包到快照器默认 overlayfs中。当前实现中该选项等价于设置RemoteContext.Unpack true见 client/client_opts.go#L161-L167client/pull.go 的Pull方法会据此解析快照器名称、查询其能力并创建解包器unpacker把各层 diff 应用到快照见 client/pull.go#L92-L119。拉取期间客户端还会自动创建 leasec.WithLease以保护下载内容不被 GC 回收。生成 OCI 运行时规范// generate the spec based on the image that we pulled spec, err : containerd.GenerateSpec(containerd.WithImageConfig(ctx, image)) if err ! nil { return err }在 2017 年 5 月GenerateSpec与WithImageConfig还是客户端包的一部分如今它们已经下沉到 pkg/oci/spec.go 与 pkg/oci/spec_opts.go。GenerateSpec会先生成一份默认 spec再依次应用传入的SpecOpts见 pkg/oci/spec.go#L67-L82WithImageConfig则读取镜像的 config 对象将其中声明的环境变量、工作目录、Entrypoint/CMD 等合并进 OCI spec见 pkg/oci/spec_opts.go#L365-L368。这一步是把镜像翻译成运行时配置的关键桥梁。基于镜像创建容器// create the container with a persistent ReadWrite layer based on the image and spec container, err : client.NewContainer(ctx, redis, spec, containerd.WithNewRootFS(redis-rootfs, image)) if err ! nil { return err } defer container.Delete(ctx)WithNewRootFS为容器分配一个基于镜像的可读写read-write根文件系统快照。当前仓库中这一职责由WithNewSnapshot(id, image, ...)承担它根据镜像的 diff IDs 计算 chain ID 作为父快照随后调用快照器的Prepare创建可写层并把SnapshotKey与镜像名写回容器元数据见 client/container_opts.go#L240-L282。与之对应的WithNewSnapshotView则创建只读视图View。容器创建本身在 client/client.go 的NewContainer中完成id 在命名空间内必须唯一见 client/client.go#L338-L377。运行容器// use the current processs stdio task, err : container.NewTask(ctx, containerd.Stdio) if err ! nil { return err } defer task.Delete(ctx) pid : task.Pid() // start the redis process if err : task.Start(ctx); err ! nil { return err } task.Kill(ctx, syscall.SIGTERM) status, err : task.Wait(ctx) os.Exit(status)这段代码展示了任务生命周期管理的核心范式NewTask创建任务实例使用当前进程的 stdio并携带CreateTaskRequest中的 stdin/stdout/stderr 配置见 client/container.go#L226-L246随后Start启动、Kill发信号、Wait等待退出状态。报告强调这套 API 让用户可以放心使用defer与常规控制流无需做复杂的文件系统操作、处理 tar 文件或自行搭建 rootfsAPI 按生命周期切分用户可以直接观察运行中容器的各阶段状态而不必注册 hooks 和回调。Testing标准 Go testing 驱动的集成测试报告指出随着首个客户端合入团队同步启动了集成测试工作基于 Travis CI 运行完整的集成测试且编写测试只需标准 Go testing 包无需引入其他测试框架。此外客户端被特意设计为可 mock——既便于 containerd 自身测试打桩也便于客户端用户为自己的代码做模拟测试。这一传统延续至今仓库的 integration/client 目录集中存放了大量面向真实 containerd 实例的集成测试如container_test.go、image_test.go、snapshot_test.go而 core 各包也都配套了单元测试例如 core/events/exchange、pkg/namespaces 均有对应_test.go文件。client包通过Service接口抽象各类 gRPC 服务使得测试中可以注入 mock 服务实现。Whats Next事件与命名空间的收官报告末尾列出了发布前的最后两块拼图Events事件被视为功能完备前最后一个大特性Namespaces命名空间当时已进入实现阶段初始服务 spec 已设计完成预期数周内完成。从今天仓库的视角回看这两块都已经完整落地事件系统ctr events命令可以直接订阅并展示 containerd 事件见 cmd/ctr/commands/events/events.go事件以Envelope形式封装时间戳、命名空间、主题与typeurl.Any载荷见 core/events/events.go#L26-L32底层由Publisher与Subscriber接口抽象core/events/exchange/exchange.go 提供了进程内实现。命名空间cmd/ctr/commands/namespaces/namespaces.go 提供了ctr namespace create、ctr namespace list等管理命令API 层有 api/services/namespaces 的 gRPC 服务定义pkg/namespaces 则负责 context 与命名空间的绑定、环境变量回退等辅助逻辑见 pkg/namespaces/context.go。此外守护进程侧的事件发布命令也支持--namespace标志见 cmd/containerd/command/publish.go。结语从 2017 年 5 月看 containerd 的架构基因这份 2017 年 5 月的开发报告虽然篇幅不长却浓缩了 containerd 的几条核心架构基因且全部在今天的主干代码中仍然成立镜像内容与推送以 content store 为中心管理 blobmanifest 与对象分离、按需推送的设计演化为今天的 cmd/ctr/commands/images/push.go 与 transfer service 体系客户端体验减少重复代码、隐藏底层系统细节、可 mock 可测试的目标成就了今天 API 稳定、被广泛集成的 client 包功能完备的边界事件与命名空间从待办变为默认能力构成了多租户隔离与可观测性的基础。对读者而言这份报告既是理解 containerd 早期演进的一手史料也是对照当前源码pkg/oci、client、core/events、pkg/namespaces观察设计如何延续与落地的最佳入口。【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考