容器运行时快照泄漏:containerd的overlayfs孤立快照问题
概述
在容器化和 Kubernetes 集群的运维中,存储管理是保障系统可靠性和控制成本的关键一环。本文描述了一个在特定版本的 containerd 运行时中发现的严重问题:使用 overlayfs 快照驱动时,在容器(Pod)被正常删除后,其对应的可读写(RW)快照层会残留在磁盘目录和容器元数据的 BoltDB 数据库中,但与之相关的所有挂载引用都已消失,导致这些快照成为“孤立资源”。这类孤立快照不仅无法被 ctr、crictl 等标准工具发现或清理,还会持续消耗宝贵的磁盘空间,且缺乏有效的监控手段,是一个典型的“静默故障”和资源泄漏场景,对 SRE 的容量规划和系统可靠性构成了直接挑战。
核心概念
要理解此问题,需要明确以下几个核心概念:
-
OverlayFS 快照:containerd 使用快照管理器来构建容器的文件系统层。OverlayFS 是一种常见的联合文件系统,它将多个目录(层)合并为一个统一视图。在运行一个可写容器时,containerd 会创建一个 RW 快照(UpperDir),它基于一个或多个只读(RO)的镜像层(LowerDir)之上。这个 RW 快照是容器写入数据的地方,通常包含
fs/(文件系统视图)和work/(内部工作目录)子目录。 -
Containerd 快照管理:containerd 通过其快照插件(如
overlayfs)管理快照的生命周期。每个快照都有唯一的数字 ID 和关联的元数据,存储在一个嵌入式的 BoltDB 数据库(通常是meta.db)中。这些元数据包括快照名称、父快照引用、子快照列表以及引用计数(snapshot.ref)。 -
孤立资源(Orphan):在理想情况下,删除一个容器应触发链式反应:容器终止 -> 停止所有关联进程 -> 卸载所有文件系统挂载点 -> 从 BoltDB 中移除快照元数据 -> 删除磁盘上对应的快照目录。孤立资源指的是上述清理流程不完整时产生的残留物。在本问题中,表现为 BoltDB 记录和磁盘文件均存在,但任何活跃的容器或容器运行时的 API 都不再引用它们。
实践
以下详细描述了该泄漏问题的具体表现和诊断方法,基于 issue 中提供的案例(快照 ID 1384)。
1. 磁盘残留确认 快照目录存在于配置的快照存储路径下,且包含完整的文件结构,占据了实际的磁盘空间(案例中为 1021MB)。
ls /data/k8s/.../io.containerd.snapshotter.v1.overlayfs/snapshots/1384/
# fs/ work/
du -sh /data/k8s/.../io.containerd.snapshotter.v1.overlayfs/snapshots/1384/
# 1021M .
2. BoltDB 元数据残留
使用 strings 等工具直接查询 BoltDB 文件,可以看到该快照的完整元数据树,包括其父快照(parent)和子快照(children)的引用,表明它曾是镜像层链中的一个中间层。
strings /data/k8s/.../io.containerd.metadata.v1.bolt/meta.db | grep -A2 -B2 '/1384/'
# 输出显示完整的快照元数据,包括 name, parent, children 等字段。
3. 运行时引用丢失
在 /proc/self/mountinfo(或其他挂载信息源)中,找不到任何指向该快照目录(1384)的 upperdir 或 lowerdir 引用。这证明在内核的 VFS 层,已经没有任何活动挂载点使用这个快照层。
grep 'upperdir.*/1384/' /proc/self/mountinfo
# (空输出)
4. 工具链无法感知与清理 所有容器生态系统的标准管理工具都对此孤立快照“视而不见”:
ctr -n k8s.io snapshot ls | grep 1384:不显示。ctr -n k8s.io snapshot info 1384:报告“未找到”。crictl rmi --prune:命令成功执行,但并未清理此快照(它主要处理未被引用的镜像层,而非容器 RW 层)。ctr snapshot prune:同样无效。
5. 交叉验证方法
为验证此现象,可以对比一个健康的活跃快照(如案例中的 76747)。在 /proc/self/mountinfo 中能清晰看到其作为 upperdir 被挂载,同时也能通过 ctr 工具查询到其信息。这种“磁盘-数据库-挂载点-工具”四路交叉验证法是诊断此类问题的关键。
SRE 要点
与可靠性、可观测性、事件响应的关联
- 可靠性与容量管理:此问题直接导致容量泄漏。随着时间推移,泄漏的快照会累积,耗尽磁盘空间,可能导致节点上新的容器无法创建(
ENOSPC错误),影响服务可用性。SRE 需要建立存储使用监控(如节点disk_space_usage指标),但此问题绕过了标准的监控和清理流程。 - 可观测性盲点:由于标准工具无法列出或清理这些快照,它们成了可观测性盲区。SRE 无法通过常规仪表盘或运维脚本获取准确的、可回收的存储空间信息。这增加了故障排查的复杂度和响应时间。
- 事件响应:当集群出现存储压力时,SRE 事件响应流程可能因无法定位和清理这些“幽灵”资源而受阻。需要依赖非标准、高风险的手段(如手动
rm -rf)进行干预,增加了操作风险。
最佳实践与常见反模式
- 最佳实践:
- 定期执行健康检查:编写自定义脚本,定期交叉比对快照目录 (
ls)、BoltDB 内容(使用strings或专用库)和容器运行状态,识别潜在的孤立快照。 - 预留运维窗口与监控:在监控中增加对
snapshots目录总大小的独立监控,并设置告警阈值,以便在问题恶化前介入。 - 升级与关注:密切关注 containerd 官方对该 issue 的修复进展,及时升级到包含修复补丁的版本。
- 谨慎手动清理:如果必须手动清理,应在维护窗口进行,并遵循严格步骤:先确认无挂载引用 -> 尝试用
ctr snapshot rm --sync-> 如失败,最后才考虑rm -rf磁盘目录和清理 BoltDB(后者需专业操作,风险极高)。
- 定期执行健康检查:编写自定义脚本,定期交叉比对快照目录 (
- 常见反模式:
- 忽视告警:忽视存储空间持续增长但无明显业务增长的告警。
- 依赖标准清理命令:错误地认为
crictl rmi --prune或ctr snapshot prune能解决所有存储泄漏问题。 - 缺乏根治方案:频繁进行手动
rm操作而不推动从根本上(升级 containerd 或等待官方修复)解决问题。
相关指标(SLI/SLO/SLA)
- SLI (服务级别指标):节点可用磁盘空间百分比;容器创建失败率(因
ENOSPC导致)。 - SLO (服务级别目标):集群节点可用磁盘空间 > 15%;因存储问题导致的容器创建失败率 < 0.1%。
- SLA (服务级别协议):内部服务协议可规定,基础设施提供方需确保节点存储的可用性,并承诺在发生此类已知泄漏问题时,在约定时间内(如 4 小时)提供缓解方案(如清理脚本或升级计划)。此问题可作为修订未来 SLA 中相关条款的依据。