3. 镜像与层基础:镜像层(Layer)的存储原理、引用计数与共享机制

理解镜像层的存储原理是掌握 containerd 垃圾回收的前提。容器镜像由多个只读层(Layer)堆叠而成,每个层代表 Dockerfile 中的一条指令。containerd 通过 快照器(Snapshotter) 管理这些层,并利用 引用计数 实现高效的磁盘空间共享。

3.1 镜像层的存储结构

每个镜像层在磁盘上对应一个独立的目录(或文件系统快照),包含该层新增、修改或删除的文件。层与层之间通过 联合挂载(OverlayFS) 或 快照链接 组合成完整的容器根文件系统。

概念 说明 磁盘表现形式
层(Layer) Dockerfile 每条指令生成的只读快照 快照器下的子目录(如 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/)
镜像(Image) 层的元数据集合,包含层列表和配置 元数据存储在 containerd 的 metadata 数据库中
容器(Container) 在镜像层之上添加一个可读写层 独立的快照,引用镜像的只读层作为父层
快照器(Snapshotter) 负责层的创建、挂载、删除和引用计数管理 插件化实现,默认使用 overlayfs
关键点: 镜像层是 不可变 的。任何对容器文件的修改都发生在顶部的可读写层,不会影响底层只读层。这一特性使得多个容器可以安全地共享同一份只读层数据。

3.2 引用计数与共享机制

containerd 的快照器为每个层维护一个 引用计数(RefCount),记录当前有多少个镜像或容器正在使用该层。当引用计数降为 0 时,该层才具备被垃圾回收的资格。

  • 共享场景: 多个容器基于同一镜像启动时,所有只读层被共享,每个容器只创建自己的可读写层。
  • 引用递增: 拉取镜像、创建容器、创建快照链时,相关层的引用计数 +1。
  • 引用递减: 删除镜像、删除容器、删除快照时,相关层的引用计数 -1。
  • 回收条件: 引用计数 == 0 且未被任何活跃快照引用。
实践建议: 不要手动删除 /var/lib/containerd 下的层目录。引用计数由 containerd 内部管理,手动删除会导致元数据不一致,引发垃圾回收失败或容器启动异常。

3.3 镜像层共享关系示意图

下图展示两个容器(App-A 和 App-B)共享同一镜像层的典型结构。只读层 L1、L2、L3 被两个容器共同引用,而每个容器拥有独立的可读写层(RW Layer)。

镜像层共享与引用计数示意图 镜像层共享与引用计数 共享只读层(RefCount = 2) L1: 基础层 RefCount: 2 L2: 依赖层 RefCount: 2 L3: 应用层 RefCount: 2 容器 App-A 可读写层(独立) 容器 App-B 可读写层(独立) 说明: • 只读层 L1、L2、L3 的 RefCount 均为 2,表示被两个容器同时引用。 • 删除容器 App-A 后,RefCount 降为 1,层仍被 App-B 使用,不会被回收。

3.4 引用计数变化流程

以下流程展示从拉取镜像到删除容器的完整引用计数变化:

  1. 拉取镜像: 下载所有层,每层 RefCount 初始化为 1(被镜像引用)。
  2. 创建容器: 为容器创建可读写层,同时镜像的每层 RefCount +1(被容器引用),变为 2。
  3. 删除镜像: 镜像引用的层 RefCount -1,从 2 降为 1(仅被容器引用)。
  4. 删除容器: 容器引用的层 RefCount -1,从 1 降为 0,层进入待回收状态。
  5. 垃圾回收: containerd 的 GC 周期扫描 RefCount == 0 的层,清理磁盘数据。
注意: 如果先删除容器再删除镜像,RefCount 变化顺序不同,但最终结果一致——所有层 RefCount 归零后才会被回收。containerd 保证引用计数的原子性,不会出现“层被误删”或“层泄漏”的情况。

3.5 快照器与层的生命周期

containerd 的快照器接口定义了层的完整生命周期操作。以默认的 overlayfs 快照器为例:

操作 快照器方法 引用计数影响 磁盘行为
拉取镜像 Prepare() 新建层,RefCount = 1 创建层目录,写入文件
创建容器 Prepare() + Mounts() 父层 RefCount +1 创建可读写层,挂载 OverlayFS
删除容器 Remove() 父层 RefCount -1 卸载 OverlayFS,删除可读写层目录
删除镜像 Remove() 层 RefCount -1 标记层为待删除(实际删除由 GC 执行)
垃圾回收 Cleanup() RefCount == 0 的层被清理 删除层目录,释放磁盘空间

3.6 层共享的磁盘收益

引用计数共享机制显著节省磁盘空间。以下是一个典型场景的量化对比:

  • 基础镜像大小: 500 MB(包含 5 个层)
  • 运行容器数量: 10 个
  • 无共享时磁盘占用: 500 MB × 10 = 5 GB
  • 有共享时磁盘占用: 500 MB(只读层共享)+ 10 × 10 MB(可读写层)≈ 600 MB
  • 节省比例: 约 88%
核心结论: 镜像层共享机制是容器技术实现“秒级启动”和“低磁盘占用”的基石。containerd 通过快照器的引用计数管理,确保共享安全、回收及时,为垃圾回收策略提供了精确的数据依据。