磁盘显示已满但 du 找不到大文件?一次 lsof 排查实录
线上告警:df -h 显示根分区使用率 100%,但顺手执行 du -sh /* 把各目录累加一遍,总和只有 20 多个 G,跟 df 报的 45 G 完全对不上。空间到底去哪了?
一、先搞清楚差异从哪来
Linux 里“文件名”和“inode”是两件事。rm 做的只是 unlink:删掉目录项、把硬链接数减一。只要还有一个进程持有这个文件的描述符,inode 和它占用的数据块就不会被释放。于是出现两个视角的分裂:
df统计块设备的实际占用,这些“已删除但未释放”的块仍然算在里面;du是遍历目录树累加,文件已经没有目录项了,自然统计不到。
两者相减,就是被进程抱在怀里的那部分幽灵文件。常见诱因有三类:日志轮转配置不当、应用先删临时文件再继续写、以及一个 tail -f 挂在正在被清理的大日志上。
二、定位:一条命令看全
最直接的是 lsof,加 +L1 只列出硬链接数小于 1 的文件,也就是“已删除但还开着”的那批:
lsof +L1 | sort -k7 -n -r | head -20
精简镜像里往往没有 lsof,可以用 /proc 兜底:
find /proc/*/fd -ls 2>/dev/null | grep "(deleted)" | sort -k7 -n -r | head
输出第 7 列是大小,第 2 列是 PID,拿到 PID 后再用 ps -p <pid> -o pid,cmd 确认是哪个进程在写。
三、处理:别急着重启服务
如果这个进程不能随便重启(比如是生产服务),可以直接对它的 fd 做截断,空间立刻回收,进程继续持有这个被清空的“文件”:
: > /proc/<pid>/fd/<fdnum>
或者 truncate -s 0 /proc/<pid>/fd/<fdnum>。注意这是清空内容,如果里面还有你要的日志,先 cp /proc/<pid>/fd/<fdnum> /tmp/rescue.log 抢救一份再动手。
如果服务支持平滑重载,systemctl reload nginx 这类操作会让 worker 重新打开日志文件,是更干净的做法。
四、预防
- logrotate 对无法 reopen 的应用使用
copytruncate,避免“删了还在写”。 - 应用侧不要自己删日志文件,改成写固定路径、交给 logrotate 统一管理。
- 把
df与du的差值做成监控指标,超阈值直接告警,比等磁盘写满再救火体面得多。
小结:遇到 df 和 du 对不上,先怀疑“已删除未释放”,用 lsof +L1 定位,再用 truncate 或 reload 回收。整个过程不需要重启机器,也不会丢数据。
- 点赞
- 收藏
- 关注作者
评论(0)