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)。
3.4 引用计数变化流程
以下流程展示从拉取镜像到删除容器的完整引用计数变化:
- 拉取镜像: 下载所有层,每层 RefCount 初始化为 1(被镜像引用)。
- 创建容器: 为容器创建可读写层,同时镜像的每层 RefCount +1(被容器引用),变为 2。
- 删除镜像: 镜像引用的层 RefCount -1,从 2 降为 1(仅被容器引用)。
- 删除容器: 容器引用的层 RefCount -1,从 1 降为 0,层进入待回收状态。
- 垃圾回收: 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 通过快照器的引用计数管理,确保共享安全、回收及时,为垃圾回收策略提供了精确的数据依据。