1. 课程导论:为什么需要垃圾回收与磁盘释放?
在 Kubernetes 集群中,容器运行时(Container Runtime)是承载 Pod 生命周期的核心组件。以 containerd 为例,它负责拉取镜像、创建容器、管理容器文件系统。然而,随着业务的持续部署、版本迭代与故障恢复,节点磁盘空间会迅速被以下三类数据吞噬:
- 悬空镜像层(Dangling Layers):镜像更新后,旧层不再被任何镜像或容器引用,但仍残留在磁盘上。
- 已退出容器的读写层(Read-Write Layers):容器退出后,其上层可写层(snapshotter 挂载点)未被及时清理。
- 临时挂载与快照元数据(Snapshots & Metadata):containerd 的 snapshotter 维护大量中间快照,长期积累导致元数据膨胀。
若不引入有效的垃圾回收(Garbage Collection, GC)策略,磁盘容量将逐步逼近上限,最终引发 Eviction(驱逐)、Pod 创建失败 甚至 节点 NotReady 等生产事故。
1.1 容器镜像与层文件系统带来的磁盘压力
容器镜像采用 分层联合文件系统(OverlayFS / AUFS / devicemapper) 设计。每个镜像由多个只读层(Layer)叠加而成,容器启动时在最上层添加一个可写层。这种设计虽然实现了存储复用与快速分发,但也带来了独特的磁盘管理挑战:
| 压力来源 | 产生原因 | 磁盘影响 |
|---|---|---|
| 镜像层堆积 | 频繁构建/拉取新版本镜像,旧层未被 GC 回收 | 每层数 MB~数百 MB,累计可达数十 GB |
| 容器可写层残留 | 容器退出后未执行 docker rm 或 crictl rm |
每个容器可写层占用 100MB~数 GB(日志、临时文件) |
| 快照元数据膨胀 | snapshotter 维护大量已删除容器的快照记录 | 元数据文件(bolt.db)可达 GB 级,影响查询性能 |
| 临时拉取缓存 | 镜像拉取过程中产生的临时层与索引文件 | 拉取失败或中断后残留,占用额外空间 |
关键指标: 在生产环境中,一个运行 30 天的 32 节点 Kubernetes 集群,若未配置 GC,平均每个节点因悬空镜像和退出容器可写层浪费 15~25 GB 磁盘空间。这相当于 2~3 个完整业务镜像的体积。
1.2 磁盘压力引发的连锁故障
当节点磁盘使用率超过阈值(通常为 85%~95%),kubelet 会触发 节点压力驱逐(Node Pressure Eviction)。以下是典型的故障演进路径:
- 镜像拉取失败:磁盘空间不足,containerd 无法写入新层,Pod 启动卡在
ImagePullBackOff。 - 容器 OOM 与写延迟:文件系统元数据操作变慢,容器日志写入阻塞,引发健康检查失败。
- kubelet 驱逐 Pod:kubelet 根据
evictionHard配置驱逐低优先级 Pod,导致服务抖动。 - 节点 NotReady:磁盘 I/O 完全阻塞,kubelet 心跳上报超时,节点从集群中脱离。
⚠️ 常见误区: 很多运维人员认为“只要节点磁盘够大(如 200GB SSD)就不需要 GC”。实际上,镜像层和容器可写层的增长速度远超预期——一次 CI/CD 流水线触发 10 次镜像构建,就可能产生 50+ 悬空层。GC 不是“可选项”,而是 节点稳定性的基础保障。
1.3 垃圾回收的核心目标
containerd 的垃圾回收机制围绕以下三个目标设计:
- 回收悬空镜像层:删除未被任何镜像或容器引用的层,释放只读层空间。
- 清理已退出容器的可写层:根据容器状态与保留策略,删除不再需要的快照。
- 压缩元数据存储:定期对 snapshotter 的元数据库进行碎片整理与空间回收。
下图展示了 containerd 垃圾回收在节点存储体系中的位置与作用范围:
1.4 课程内容预览
本课程将深入 containerd 的垃圾回收实现,涵盖以下核心模块:
| 章节 | 核心内容 | 实践产出 |
|---|---|---|
| 2~4 | 镜像垃圾回收原理与参数调优 | 配置 image_gc_high_threshold / low_threshold |
| 5~7 | 快照垃圾回收与容器可写层清理 | 手动触发 crictl rmi --prune 与 snapshot GC |
| 8~10 | 元数据压缩与 bolt.db 碎片整理 | 使用 containerd metadata gc 命令 |
| 11~13 | 生产级 GC 策略设计 | 结合 kubelet 驱逐与 Prometheus 监控 |