根本原因是应用进程持有已删除文件的句柄导致磁盘空间未释放;需用lsof验证,可通过信号重载日志进程或重启容器释放空间,并应通过日志轮转、限制日志大小等预防。
这个问题本质是:容器长期运行时,应用进程打开了卷内文件但未关闭句柄(如日志轮转失败、程序异常挂起),即使文件被删除或覆盖,只要句柄仍被持有,磁盘空间就不会真正释放——linux 的“已删除但仍在使用”状态(unlinked but held open)。
确认是否为句柄未释放导致的虚占
先验证是不是这个原因,而不是卷本身数据膨胀:
- 用 docker volume inspect <vol_name> 查看卷实际路径(通常是
/var/lib/docker/volumes/<name>/_data) - 进入该目录,执行:
lsof +L1 /var/lib/docker/volumes/<name>/_data
——若输出非空,说明有进程正占用已删除文件 - 或者更直接:
lsof -nP +D /var/lib/docker/volumes/<name>/_data 2>/dev/null | grep deleted
临时释放空间(不重启容器)
如果确认是句柄问题,且无法立即重启容器,可尝试以下安全操作:
- 找到占用句柄的进程:
lsof -nP +D /var/lib/docker/volumes/<name>/_data 2>/dev/null | grep deleted | awk '{print $2}' | sort -u - 对每个 PID,查看其打开的已删文件:
ls -la /proc/<PID>/fd/ | grep deleted - 若确定该进程可重载(如 Nginx、rsyslog、某些 Java 日志框架),发送信号触发日志 reopen:
• Nginx:kill -USR1 <PID>
• rsyslog:kill -HUP <PID>
• Log4j2(启用 reconfigureOnRefresh):touch 配置文件触发重载
根治方法:从容器设计和运行时控制入手
避免下次再发生,关键在预防:
- 容器内应用必须实现日志 rotate 并自动 close 文件句柄;禁用“始终追加不轮转”的日志模式
- 启动容器时限制日志大小:
--log-opt max-size=10m --log-opt max-file=3 - 避免将 volume 直接挂载给多个长期运行且频繁写日志的容器共用——易引发竞争与句柄残留
- 对关键服务,在健康检查中加入空间监控脚本,发现 _data 下
/proc/*/fd中 deleted 条目突增即告警
终极兜底:安全重启容器
当句柄僵死、应用无重载机制、或已影响服务时,最可靠的方式仍是可控重启:
- 先 docker stop --time=30 <container> 给应用优雅退出时间
- 观察 lsof ... | grep deleted 是否清零;未清零说明进程未完全释放,需查应用 shutdown 流程
- 再 docker start ——新进程会新建干净句柄,旧空间立即归还

















