容器句柄泄漏导致磁盘空间“虚占”,本质是进程持有已删除文件的句柄未释放,表现为df显示满而du查不到大文件;需用lsof +L1或lsof | grep deleted定位泄漏进程,通过kill -USR1等信号优雅重载日志,而非直接删文件或重启。

容器句柄泄漏导致“文件打满”,本质是进程打开了文件(尤其是日志、管道、临时文件)但未关闭,即使文件被删除或轮转,只要句柄仍被持有,磁盘空间就不会释放——表现为 df -h 显示空间耗尽,而 du -sh 却查不到对应大文件。
确认是不是句柄泄漏引发的空间虚占
别急着删日志或重启,先验证根源:
- 进宿主机,用
docker volume inspect <vol_name>找到卷实际路径(通常是/var/lib/docker/volumes/<name>/_data) - 在该路径下执行:
lsof -nP +D /var/lib/docker/volumes/<name>/_data 2>/dev/null | grep deleted
若有输出,说明有进程正占用已删除文件 - 或者更直接检查句柄泄漏痕迹:
lsof +L1 /var/lib/docker/volumes/<name>/_data
非空即证实问题存在
定位具体是哪个容器、哪个进程在“咬住”句柄
句柄不释放一定发生在某个运行中的容器内:
- 查出所有挂载该卷的容器:
docker ps --filter volume=<vol_name> --format "{{.ID}} {{.Names}}" - 进入对应容器,看其主进程 PID:
ps aux | grep -v grep - 在容器内执行:
ls -l /proc/<PID>/fd/ | grep deleted | wc -l
数值越大,泄漏越严重 - 若看到大量
pipe:[...]或anon_inode,很可能是子进程未 wait 或管道未 close 导致的 FD 泄漏
不重启容器的临时释放方案
适用于不能停服务的场景,核心是让进程主动释放旧句柄:
- 找到泄漏句柄所属的 PID(宿主机上可用
lsof -nP +D /path/to/vol/_data 2>/dev/null | grep deleted | awk '{print $2}' | sort -u) - 对常见可重载服务发信号:
• Nginx:kill -USR1 <PID>
• rsyslog / syslog-ng:kill -HUP <PID>
• Java 应用(Log4j2 启用 reconfigureOnRefresh):touch log4j2.xml - 验证是否生效:
再次运行lsof ... | grep deleted,输出应为空;
再看df -h中对应分区使用率是否明显下降
从设计和配置上根治泄漏风险
靠临时操作治标不治本,必须前置防控:
- 容器启动时强制限制日志大小:
--log-opt max-size=10m --log-opt max-file=3
避免容器自身 stdout/stderr 日志无限增长并隐式占用句柄 - 应用层必须实现健壮的日志轮转逻辑:每次 rotate 后 close 原文件句柄,并 open 新文件;禁用“始终追加不关闭”的模式
- 避免多个容器共用同一个 volume 写日志——易引发竞争、覆盖失败、句柄残留
- 关键服务加入健康检查脚本,定期扫描:
find /proc/*/fd -ls 2>/dev/null | grep deleted | wc -l
超阈值立即告警
排查本身不复杂,但容易忽略“已删除却仍被打开”这个 Linux 特性。抓住 lsof + grep deleted 这个组合,基本就能快速锁死问题源头。


















