containerd 2017-03-17 开发报告解读:首个端到端镜像拉取与容器 Prometheus 指标的诞生
【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd
这篇技术文章基于 containerd 历史开发报告 docs/historical/reports/2017-03-17.md,完整梳理 containerd 2017 年 3 月 17 日周报中记录的四项里程碑:多平台测试计划、Windows 运行时移植启动、基于 Prometheus 的容器级指标导出,以及首个"端到端镜像拉取"概念验证(dist pull/dist images/ctr run)。文章同时对照当前仓库中的源码(如 core/metrics/cgroups/cgroups.go、cmd/ctr/commands/images/pull.go、client/pull.go),说明 2017 年埋下的这些能力如何在今天的 containerd 中落地演化,帮助读者理解 containerd 从"理论架构"走向"可运行系统"的关键转折点。
一、背景:containerd 周报体系与这份报告的地位
containerd 在 2017 年上半年以双周开发报告(Development Report)的形式记录进展,完整系列保存在 docs/historical/reports/ 目录下(从 2017-01-13 到 2017-06-23 共 14 份)。2017-03-17 这一期之所以关键,是因为它标志着 containerd 从"各子系统分别开发"进入了"子系统端到端串联"阶段——报告原文明确指出:
Up to this point, the relationship between subsystems has been somewhat theoretical.
(此前,各子系统之间的关系还多少停留在理论层面。)
而这份报告中的 Image Pull 章节,第一次让 fetching(内容拉取)、snapshot drivers(快照驱动)、rootfs service(根文件系统服务)、image metadata(镜像元数据)与 execution service(执行服务)五个部分在一条真实命令链中被同时调用,验证了 containerd 提出的整体模型。
二、Testing Plan:面向 ARM、Windows、Linux、Power 的 CI 战略
报告开头讨论的是测试计划(Testing Plan)。作者感谢 @gianarb 发起了 containerd 测试与 CI 计划的讨论,核心动机是让用户能够"安心地依赖 containerd"(feel secure depending on containerd)。
报告点出了 containerd 测试的特殊困难:它需要覆盖的系统平台非常多,当时已列出的支持平台包括ARM、Windows、Linux、Power,以及更多变体。这与 containerd 作为"底层容器运行时"的定位一致——它不像应用框架可以选择性地支持操作系统,而是必须跟随 OCI 生态覆盖几乎所有主流架构。报告中提到的测试计划讨论在 containerd 社区的 issue #634 中进行(原文以外链给出,此处不复述),社区贡献者可围绕 CI 平台矩阵持续补充。
从当前仓库结构看,多平台支持至今仍是主线:构建系统按平台拆分为 Makefile.linux、Makefile.darwin、Makefile.freebsd、Makefile.windows,默认路径逻辑也按平台拆分在 defaults/defaults_linux.go、defaults/defaults_darwin.go、defaults/defaults_windows.go 等文件中,印证了 2017 年"平台矩阵"这一测试难题的长期性。
三、Windows Runtime:执行代码移植启动
报告确认 Windows 运行时的执行代码移植(porting over the Windows execution code)已经启动,移植完成后还有大量测试要做,PR 即将提交。
这一条对应的是 containerd 早期"先 Linux 后 Windows"的演进路线。从源码结构看,当前仓库中 Windows 相关能力已相当完整:执行层有 pkg/cio/io_windows.go(Windows 控制台/管道 IO 处理)、pkg/os/ 下的 Windows 实现、挂载层有 core/mount/mount_windows.go、core/diff/stream_windows.go,集成测试目录中也存在 integration/client/client_windows_test.go、integration/sandbox_clean_remove_windows_test.go 等 Windows 专项用例。可以说,2017-03 这次"移植启动"的 PR 正是后来这些 Windows 实现的起点。
四、Metrics:容器级指标首次通过 Prometheus 导出
这是本报告信息量最大的部分。2017 年 3 月,团队启动了"将容器级指标通过 Prometheus 导出"的工作,报告贴出了当时的完整初始输出(以id="test"的容器为例):
containerd_container_blkio_io_service_bytes_recursive_bytes{id="test",major="8",minor="0",op="Async"} 958464 containerd_container_blkio_io_service_bytes_recursive_bytes{id="test",major="8",minor="0",op="Read"} 958464 containerd_container_blkio_io_service_bytes_recursive_bytes{id="test",major="8",minor="0",op="Sync"} 0 containerd_container_blkio_io_service_bytes_recursive_bytes{id="test",major="8",minor="0",op="Total"} 958464 containerd_container_blkio_io_service_bytes_recursive_bytes{id="test",major="8",minor="0",op="Write"} 0 containerd_container_blkio_io_serviced_recursive_total{id="test",major="8",minor="0",op="Async"} 17 containerd_container_blkio_io_serviced_recursive_total{id="test",major="8",minor="0",op="Read"} 17 containerd_container_blkio_io_serviced_recursive_total{id="test",major="8",minor="0",op="Sync"} 0 containerd_container_blkio_io_serviced_recursive_total{id="test",major="8",minor="0",op="Total"} 17 containerd_container_blkio_io_serviced_recursive_total{id="test",major="8",minor="0",op="Write"} 0 containerd_container_cpu_kernel_nanoseconds{id="test"} 1e+07 containerd_container_cpu_throttle_periods_total{id="test"} 0 containerd_container_cpu_throttled_periods_total{id="test"} 0 containerd_container_cpu_throttled_time_nanoseconds{id="test"} 0 containerd_container_cpu_total_nanoseconds{id="test"} 2.1428791e+07 containerd_container_cpu_user_nanoseconds{id="test"} 0 containerd_container_hugetlb_failcnt_total{id="test",page="1GB"} 0 containerd_container_hugetlb_failcnt_total{id="test",page="2MB"} 0 containerd_container_hugetlb_max_bytes{id="test",page="1GB"} 0 containerd_container_hugetlb_max_bytes{id="test",page="2MB"} 0 containerd_container_hugetlb_usage_bytes{id="test",page="1GB"} 0 containerd_container_hugetlb_usage_bytes{id="test",page="2MB"} 0 containerd_container_memory_active_anon_bytes{id="test"} 0 containerd_container_memory_active_file_bytes{id="test"} 659456 containerd_container_memory_cache_bytes{id="test"} 925696 containerd_container_memory_dirty_bytes{id="test"} 0 containerd_container_memory_hierarchical_memory_limit_bytes{id="test"} 9.223372036854772e+18 containerd_container_memory_hierarchical_memsw_limit_bytes{id="test"} 9.223372036854772e+18 containerd_container_memory_inactive_anon_bytes{id="test"} 73728 containerd_container_memory_inactive_file_bytes{id="test"} 266240 containerd_container_memory_kernel_failcnt_total{id="test"} 0 containerd_container_memory_kernel_limit_bytes{id="test"} 9.223372036854772e+18 containerd_container_memory_kernel_max_bytes{id="test"} 0 containerd_container_memory_kernel_usage_bytes{id="test"} 0 containerd_container_memory_kerneltcp_failcnt_total{id="test"} 0 containerd_container_memory_kerneltcp_limit_bytes{id="test"} 9.223372036854772e+18 containerd_container_memory_kerneltcp_max_bytes{id="test"} 0 containerd_container_memory_kerneltcp_usage_bytes{id="test"} 0 containerd_container_memory_mapped_file_bytes{id="test"} 577536 containerd_container_memory_oom_total{id="test"} 0 containerd_container_memory_pgfault_bytes{id="test"} 770 containerd_container_memory_pgmajfault_bytes{id="test"} 6 containerd_container_memory_pgpgin_bytes{id="test"} 651 containerd_container_memory_pgpgout_bytes{id="test"} 407 containerd_container_memory_rss_bytes{id="test"} 73728 containerd_container_memory_rss_huge_bytes{id="test"} 0 containerd_container_memory_swap_failcnt_total{id="test"} 0 containerd_container_memory_swap_limit_bytes{id="test"} 9.223372036854772e+18 containerd_container_memory_swap_max_bytes{id="test"} 1.527808e+06 containerd_container_memory_swap_usage_bytes{id="test"} 999424 containerd_container_memory_total_active_anon_bytes{id="test"} 0 containerd_container_memory_total_active_file_bytes{id="test"} 659456 containerd_container_memory_total_cache_bytes{id="test"} 925696 containerd_container_memory_total_dirty_bytes{id="test"} 0 containerd_container_memory_total_inactive_anon_bytes{id="test"} 73728 containerd_container_memory_total_inactive_file_bytes{id="test"} 266240 containerd_container_memory_total_mapped_file_bytes{id="test"} 577536 containerd_container_memory_total_pgfault_bytes{id="test"} 770 containerd_container_memory_total_pgmajfault_bytes{id="test"} 6 containerd_container_memory_total_pgpgin_bytes{id="test"} 651 containerd_container_memory_total_pgpgout_bytes{id="test"} 407 containerd_container_memory_total_rss_bytes{id="test"} 73728 containerd_container_memory_total_rss_huge_bytes{id="test"} 0 containerd_container_memory_total_unevictable_bytes{id="test"} 0 containerd_container_memory_total_writeback_bytes{id="test"} 0 containerd_container_memory_unevictable_bytes{id="test"} 0 containerd_container_memory_usage_failcnt_total{id="test"} 0 containerd_container_memory_usage_limit_bytes{id="test"} 9.223372036854772e+18 containerd_container_memory_usage_max_bytes{id="test"} 1.527808e+06 containerd_container_memory_usage_usage_bytes{id="test"} 999424 containerd_container_memory_writeback_bytes{id="test"} 0 containerd_container_per_cpu_nanoseconds{cpu="0",id="test"} 7.530139e+06 containerd_container_per_cpu_nanoseconds{cpu="1",id="test"} 4.586408e+06 containerd_container_per_cpu_nanoseconds{cpu="2",id="test"} 5.076059e+06 containerd_container_per_cpu_nanoseconds{cpu="3",id="test"} 4.236185e+06 containerd_container_pids_current{id="test"} 1 containerd_container_pids_limit{id="test"} 0这些指标覆盖了四个 cgroup 资源域:
- blkio:块设备 IO 的服务字节数与请求次数,按 major/minor 设备和 op(Async/Read/Sync/Total/Write)维度打标;
- cpu:内核态/用户态/总 CPU 时间(纳秒)、CFS 配额限流计数(throttle_periods、throttled_time)、按核的 per_cpu_nanoseconds;
- memory:匿名页/文件页的 active/inactive 状态、cache、dirty、mapped_file、page fault/PGIN/PGOUT、RSS、swap 用量与限额、hugetlb(2MB/1GB 大页)用量、OOM 计数等;
- pids:当前进程数与进程数上限。
报告给出了两条关键设计决策:
id标签即容器 ID,用户可据此过滤只关心的容器;- 采集频率完全由 Prometheus 抓取周期决定。每次
/metricsAPI 被命中时才采集容器指标,containerd 内部没有定时器——"If you never ask for metrics the collection never happens"(从不请求就从不采集)。这是一种典型的"按需付费"(pay only when you ask)设计,避免了常驻采集线程的固定开销。
报告同时预告了 PR 即将提交,以便社区讨论指标与标签命名。
在今天的源码中:TaskMonitor 插件与no_prometheus开关
2017 年这次"初始输出"对应的设计,如今已沉淀为正式的 cgroups 监控插件。在 core/metrics/cgroups/cgroups.go 中可以看到:
- 插件以
TaskMonitorPlugin类型、IDcgroups注册,并依赖EventPlugin(通过容器 create/delete 事件决定监控的 cgroup 生命周期); - 配置只有一个开关
NoPrometheus(TOML 键no_prometheus):为 false 时创建metrics.NewNamespace("container", ...)并注册到全局指标表,为 true 时仅采集不上报——这正是对"按需付费"理念的配置化体现; - 实现按 cgroup 版本分流:
cgroups.Mode() == cgroups.Unified时选择 core/metrics/cgroups/v2 的NewTaskMonitor,否则使用 v1 版本,分别读取 cgroup v1/v2 的 cgroupfs 统计文件。
另一个细节是命名空间:core/metrics/metrics.go 的init()中注册了containerd命名空间,并暴露build_info计数器(含 version、revision 两个 label),同时定义了ShimStatsRequestTimeout(io.containerd.timeout.metrics.shimstats,默认 2 秒)这个超时键——从源码结构看,这意味着当前的指标体系除了 cgroup 统计,还包含向 shim 发起 Stats RPC 的路径,这是 2017 年"直接读 cgroupfs"阶段之后的自然扩展。
需要注意一个演进差异:2017 年报告中的指标名以containerd_container_开头(如containerd_container_cpu_total_nanoseconds),而当前插件创建的命名空间前缀是container(即container_cpu_*这类名字);从源码结构看,这反映了多年间指标命名规范的调整,直接引用旧文档指标名的既有监控规则需要重新核对。
五、Image Pull:首个端到端拉取的概念验证
报告后半部分是 PR #640 带来的成果:containerd 首次实现了端到端拉取的 proof of concept。它串联了 fetching、snapshot 驱动、rootfs 服务、镜像元数据与执行服务,验证了 containerd 的子系统模型;报告也坦承存在若干待办,例如需要将部分访问逻辑移入 gRPC service。
5.1dist pull:完整拉取并展开根文件系统
dist pull是当时docker pull/git pull的对应物:对一个镜像执行完整的资源拉取,并把根文件系统展开进 snapshot 驱动。报告给出的原始输出:
$ sudo ./bin/dist pull docker.io/library/redis:latest docker.io/library/redis:latest: resolved |++++++++++++++++++++++++++++++++++++++| manifest-sha256:4c8fb09e8d634ab823b1c125e64f0e1ceaf216025aa38283ea1b42997f1e8059: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:3b281f2bcae3b25c701d53a219924fffe79bdb74385340b73a539ed4020999c4: done |++++++++++++++++++++++++++++++++++++++| config-sha256:e4a35914679d05d25e2fccfd310fde1aa59ffbbf1b0b9d36f7b03db5ca0311b0: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:4b7726832aec75f0a742266c7190c4d2217492722dfd603406208eaa902648d8: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:338a7133395941c85087522582af182d2f6477dbf54ba769cb24ec4fd91d728f: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:83f12ff60ff1132d1e59845e26c41968406b4176c1a85a50506c954696b21570: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:693502eb7dfbc6b94964ae66ebc72d3e32facd981c72995b09794f1e87bac184: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:622732cddc347afc9360b4b04b46c6f758191a1dc73d007f95548658847ee67e: done |++++++++++++++++++++++++++++++++++++++| layer-sha256:19a7e34366a6f558336c364693df538c38307484b729a36fede76432789f084f: done |++++++++++++++++++++++++++++++++++++++| elapsed: 1.6 s total: 0.0 B (0.0 B/s) INFO[0001] unpacking rootfs输出中每行对应一个 OCI 内容对象:先resolved镜像引用,再依次完成 manifest、config blob 与 6 个 layer 的下载,最后一行unpacking rootfs表明根文件系统正在展开到快照驱动——报告指出当时展开进度尚未并入状态输出,但整体已经"差不多就是今天 docker 的样子"。
5.2dist images:镜像元数据视图
拉取完成后,用dist images查看结果:
$ sudo ./bin/dist images REF TYPE DIGEST SIZE docker.io/library/redis:latest application/vnd.docker.distribution.manifest.v2+json sha256:4c8fb09e8d634ab823b1c125e64f0e1ceaf216025aa38283ea1b42997f1e8059 1.8 kB表格四列分别是引用名(REF)、镜像清单类型(TYPE)、摘要(DIGEST)与大小(SIZE)。报告特别说明:此时 SIZE 显示的是 manifest 的大小而非整个镜像的大小,后续可按需补充;同时预告了命名模型的几个待完善点——例如希望同时按 hash 限定名和 tag 版本索引同一个镜像,这些工作将在元数据存储(metadata store)开发中推进。
5.3ctr run:第一次真正跑起 redis
拉下来的镜像名可直接用于运行容器:
$ sudo ./bin/ctr run --id foo docker.io/library/redis:latest /usr/local/bin/redis-server 1:C 17 Mar 17:20:25.316 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/local/bin/redis-server /path/to/redis.conf 1:M 17 Mar 17:20:25.317 * Increased maximum number of open files to 10032 (it was originally set to 1024). ... Redis 3.2.8 (00000000/0) 64 bit ... 1:M 17 Mar 17:20:25.326 * The server is now ready to accept connections on port 6379(中间的 ASCII 艺术 banner 已省略,完整输出见原文 docs/historical/reports/2017-03-17.md。)
报告对此的定性是:"So, now we are running redis!"——一个真实的 redis 3.2.8 进程以 PID 1 的身份在容器里监听 6379 端口。同时报告诚实地指出了 PoC 的局限:必须在ctr run参数里显式写出容器命令,因为当时还没有从镜像 config(config.cmd)读取命令并转换为 OCI runtime config 的逻辑;报告判断"基础已经打好,补上这个功能应该很直接"。
5.4 对照当前代码:ctr image pull的三步语义没有变
今天对应dist pull的是ctr image pull,其命令定义在 cmd/ctr/commands/images/pull.go。命令描述中明确的三步流程——"1. Fetch all resources into containerd. 2. Prepare the snapshot filesystem with the pulled resources. 3. Register metadata for the image."——与 2017 年报告对dist pull的描述(拉取全部资源 + 展开 rootfs 到 snapshot 驱动 + 记录镜像元数据)一脉相承。
从源码看,当前实现比 2017 年的 PoC 更完整的地方包括:
- 拉取路径可选:默认走 server 端的 transfer service(
client.Transfer),--local则退回客户端本地拉取(content.Fetch),两条路径分别对应 client/pull.go 中Client.Pull与 transfer 服务的协作; - 平台选择:支持
--platform、--all-platforms与--skip-metadata,解决 2017 年报告里"命名与索引模型待完善"的遗留问题——现在可以按平台精确拉取内容与元数据; - 下载控制:
--max-concurrent-downloads通过 client/pull.go 中的transfer.WithMaxConcurrentDownloads作用于 resolver,提供并发限制; - 进度输出:
ProgressHandler(cmd/ctr/commands/images/pull.go)按父子节点构建进度树并调用DisplayHierarchy渲染层级化进度条——这正是 2017 年那种"逐项 done + 进度条"输出形态的现代化版本; - 展开参数:
--sync-fs透传给diff.WithSyncFs,在 unpack 时同步文件系统,--print-chainid则打印快照链 chain ID 用于校验。
而 2017 年 PoC 的"已知局限"(不读镜像 config、部分能力未进 gRPC service)在今天的仓库中都已兑现:ctr run相关能力由 client/task.go 与 client/container.go 承担,镜像与内容的访问全部通过 gRPC service(api/services/ 下定义)暴露给远端客户端。
六、小结:一份周报如何映射出 containerd 的演进主线
2017-03-17 这份开发报告的四个章节,恰好对应 containerd 的四条长期主线,且在当前仓库中都能找到延续:
| 2017-03-17 报告条目 | 当年状态 | 当前仓库中的延续 |
|---|---|---|
| Testing Plan(多平台 CI) | 讨论启动 | 按平台拆分的 Makefile 与集成测试(Makefile.linux、integration/client/) |
| Windows Runtime | 移植启动 | pkg/cio/io_windows.go、core/mount/mount_windows.go 等完整 Windows 实现 |
| Prometheus 容器指标 | 初始输出 + 按需采集 | core/metrics/cgroups/cgroups.go TaskMonitor 插件(cgroup v1/v2、no_prometheus开关)、core/metrics/metrics.go |
| 端到端 Image Pull PoC | dist pull/dist images/ctr run跑通 redis | cmd/ctr/commands/images/pull.go 的三步 pull 语义、client/pull.go 的 transfer 化实现 |
阅读这份历史报告的最大价值在于:它展示了 containerd 早期"先验证模型、再逐步产品化"的工程节奏——2017 年 3 月用一条dist pull+ctr run命令链证明架构可行,随后几年再把指标、Windows、元数据、gRPC 化这些报告中的"待办"逐项兑现为今天 core/ 与 client/ 下的正式模块。
【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考