4. 容器与快照基础:容器根文件系统(snapshot)的创建、挂载与生命周期

在 containerd 中,容器的根文件系统并非完整的操作系统镜像副本,而是通过 快照(Snapshot) 机制构建的轻量级、分层视图。理解快照的创建、挂载与生命周期,是掌握磁盘容量释放与垃圾回收的前提。本节将深入剖析这一核心机制。

4.1 快照(Snapshot)的核心概念

快照是 containerd 对容器镜像层和容器读写层的一种抽象管理单元。它基于 OverlayFS 或类似联合文件系统技术,实现了以下关键特性:

  • 分层复用:多个容器可以共享相同的只读镜像层,仅在各自的读写层记录变更。
  • 写时复制(Copy-on-Write, CoW):对容器根文件系统的修改仅在写入时复制到上层,极大节省磁盘空间。
  • 高效创建:创建新容器时,无需复制整个文件系统,只需创建一个指向已有层的轻量级快照。
关键术语:在 containerd 中,“快照”通常指代一个文件系统层或视图,而非传统意义上的时间点备份。其核心价值在于 共享 与 隔离 的平衡。

4.2 快照的层级结构:从镜像层到容器层

一个容器的根文件系统由多层快照组成,形成一个从底向上、从只读到可写的层级链。下图展示了典型的四层结构:

容器根文件系统快照层级结构 容器根文件系统快照层级结构 容器读写层 (RW Layer) 容器运行时的所有写入操作 镜像顶层 (Image Top Layer) Dockerfile 最后一条指令 镜像中间层 (Image Middle Layer) 中间构建指令 镜像基础层 (Image Base Layer) FROM 指令指定的基础镜像 说明:只读层(蓝色)可被多个容器共享;读写层(红色)为每个容器独有

4.3 快照的创建与挂载流程

当 containerd 启动一个容器时,快照的创建与挂载遵循以下步骤:

  1. 解析镜像:从镜像仓库拉取或本地缓存中获取镜像的 manifest 和层列表。
  2. 准备快照链:为镜像的每一层创建只读快照,并建立父子关系。
  3. 创建容器快照:在镜像顶层之上,创建一个可读写的容器快照(即 active snapshot)。
  4. 挂载文件系统:使用 OverlayFS 将只读层与读写层联合挂载到容器的 rootfs 路径。
  5. 启动容器进程:容器进程以挂载后的 rootfs 作为根文件系统运行。
挂载命令示例(简化):containerd 内部通过类似以下命令实现联合挂载:
mount -t overlay overlay \
  -o lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs:\
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/2/fs:\
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/3/fs,\
upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4/fs,\
workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4/work \
  /run/containerd/io.containerd.runtime.v2.task/default/<container-id>/rootfs

4.4 快照的生命周期状态

containerd 的快照管理器中,每个快照都处于以下生命周期状态之一:

状态 描述 磁盘占用 可回收性
Active 当前被容器使用的读写快照,容器进程正在运行 持续增长(写入时复制) 不可回收
Committed 已提交的只读快照,通常对应镜像层或已停止容器的快照 固定大小 若未被引用则可回收
View 只读视图快照,用于临时访问,不参与写操作 无额外占用 可随时释放
Removed 已被标记删除,但可能仍有其他快照依赖 待释放 依赖解除后回收

4.5 快照的引用计数与垃圾回收基础

containerd 通过 引用计数(Reference Count) 机制管理快照的生命周期。每个快照维护一个计数器,记录有多少上层快照或容器正在使用它。当引用计数降为零时,该快照即成为垃圾回收的候选对象。

常见陷阱:即使容器已停止,其读写层快照仍可能被保留(Committed 状态),直到被显式删除或触发垃圾回收。这是磁盘空间泄漏的常见原因之一。

下图展示了快照之间的引用关系与回收判定逻辑:

快照引用关系与回收判定 快照引用关系与回收判定 容器A (Active) 容器B (Active) 快照A (Active) 快照B (Active) 共享镜像层1 (Committed, 引用计数=2) 共享镜像层0 (Committed, 引用计数=2) 回收条件:仅当引用计数为0时,快照才可被安全删除并释放磁盘空间

4.6 快照存储目录结构

containerd 默认将快照数据存储在 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ 目录下。每个快照对应一个数字 ID 的子目录,其内部结构如下:

/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/
├── 1/
│   ├── fs/          # 快照的文件系统内容
│   └── work/        # OverlayFS 工作目录(仅读写层有)
├── 2/
│   ├── fs/
│   └── work/
├── 3/
│   └── fs/          # 只读层无 work 目录
├── metadata.db      # 快照元数据数据库(boltdb)
└── ...
磁盘容量分析切入点:通过检查 metadata.db 中的引用计数和快照大小,可以精确识别哪些快照占用了大量磁盘空间且不再被引用,这是后续垃圾回收策略的核心数据来源。

4.7 快照生命周期对磁盘容量的影响

快照的生命周期直接决定了磁盘空间的占用模式。下表总结了不同阶段的空间特征:

阶段 空间占用特征 典型场景 对GC的影响
容器运行中 读写层持续增长,镜像层固定 生产服务运行 不可回收
容器已停止 读写层变为Committed,不再增长 任务型容器执行完毕 若未被其他容器引用,可回收
镜像已删除 镜像层快照仍可能被其他容器引用 清理旧镜像 需检查引用计数
孤立快照 无任何容器或镜像引用 异常中断或清理残留 优先回收目标