适用场景
Linux 宿主机的根分区或 Docker 数据盘持续告警,/var/lib/docker/overlay2 占用不断增加;但镜像和容器数量并不多,普通镜像清理也没有效果。本文给出 overlay2 的归属定位、止血和治理流程。
以下路径以
/var/lib/docker为例。先用docker info --format '{{.DockerRootDir}}'确认实际目录;生产环境禁止直接删除overlay2内的文件。
现象与原因
df -h显示 Docker 所在分区接近 100%,应用写入报no space left on device;du -sh /var/lib/docker/overlay2很大,而docker system df的可回收量很小;- 镜像层、容器可写层、BuildKit 缓存和数据卷不是一类数据,概览无法解释单个层的增长;
- 常见根因是应用把临时文件、上传文件或缓存写入容器文件系统,文件删除后仍被进程打开,或构建缓存与遗留资源长期未回收。
排查步骤
1. 先区分块空间与 inode
df -hT /var/lib/docker
df -i /var/lib/docker
docker info --format 'root={{.DockerRootDir}} driver={{.Driver}}'
docker system df -v
df -hT 看块空间,df -i 看 inode。inode 100% 时,即使仍有 GB 空间也无法创建文件。docker system df -v 只用于区分镜像、容器和卷的概况,不能据此删除路径。
2. 找到持续增长的层
sudo du -xhd1 /var/lib/docker | sort -h
sudo du -xhd1 /var/lib/docker/overlay2 | sort -h | tail -n 20
sudo du -xhd2 /var/lib/docker/overlay2/<layer-id>/diff | sort -h | tail -n 20
-x 防止跨到挂载卷,diff 是容器可写层实际落盘内容。先记录大目录的 layer ID,不要做删除操作。
3. 将 layer ID 映射为容器
docker ps -aq | while read -r id; do
docker inspect --format '{{.Id}} {{.Name}} {{.GraphDriver.Data.UpperDir}}' "$id"
done | grep '/overlay2/<layer-id>/diff'
UpperDir 能把目录与容器 ID、名称对应起来。确认归属后,在容器视角寻找大文件:
docker exec <container> sh -c 'du -xhd1 / 2>/dev/null | sort -h | tail -n 20'
docker exec <container> sh -c 'find / -xdev -type f -size +500M -printf "%s %p\n" 2>/dev/null | sort -n'
第一条找顶层目录,第二条列出大于 500 MiB 的普通文件。没有 shell 的极简镜像,应通过应用配置和挂载信息判断,不要为排查修改生产镜像。
4. 检查已删除但仍打开的文件
sudo lsof +L1 | grep -E '/var/lib/docker|deleted'
sudo lsof +L1 | awk '$7 > 104857600 {print $7, $9, $1, $2}' | sort -n
+L1 表示链接数小于 1 的打开文件;第 7 列为大小,第 9 列为路径。日志轮转后旧文件仍被进程持有时,必须让对应进程优雅 reload 或重启才会真正释放空间。不要删除 /proc/<pid>/fd/* 来“腾空间”。
定位示例与修复
一次故障中,某 Java 服务的 UpperDir 占用 18 GiB;容器内 /tmp/export/ 保存着上传失败的报表,失败重试每次都会生成新文件。处置时先暂停新任务,确认旧报表可丢弃或已入库,再只删除明确无用的任务文件;随后在任务的 finally 中补上成功、失败、超时三条清理路径,并让重试复用同一任务目录。
对需要持久化的数据使用挂载卷;同时限制 stdout/stderr 的 JSON 日志:
services:
api:
image: registry.example.com/api:2026.08.07
volumes:
- app-data:/srv/app/data
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
volumes:
app-data:
max-size 是单个容器日志文件上限,max-file 是保留份数;它们不限制应用写入 /tmp 的文件,配置变更后需重建容器。
确认没有依赖已停止容器、悬空镜像和构建缓存后,再按影响从小到大回收:
docker container prune
docker image prune
docker builder prune
docker system df -v
这些命令默认要求交互确认。不要随意加入 -a 或 --volumes:前者可能删除回滚镜像,后者可能删除业务数据卷。
预防措施
- 对 Docker 数据目录同时设置容量与 inode 告警,并在 80% 前触发趋势告警;
- 每周记录
docker system df -v,关联发布和 CI 构建时间; - 临时文件必须有成功、失败、超时清理路径,持久数据必须落在卷或对象存储;
- CI 构建机单独制定 BuildKit 缓存回收策略,不与生产运行节点混用磁盘配额;
- 定期演练磁盘满场景,验证日志轮转后进程会重新打开文件。
总结
排查 overlay2 膨胀的关键是建立“目录—可写层—容器—业务文件”的归属链。先判断空间还是 inode,再映射 UpperDir、处理已删除仍打开的文件,最后修复写入位置和缓存生命周期,才能安全恢复容量并避免复发。
Discussion
评论