磁盘满了,du 却找不到:被删除文件占用空间的排查
上周五下午,一台跑内部 API 的机器开始报写入失败。登上去看,根分区已经 100%。这种情况通常不难处理,找到大文件删掉就行。但这次有点不一样。
现象:两个数字对不上
先用 df 看整体占用:
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 40G 0 100% /
然后用 du 从根目录往下统计,排除掉 /proc 这类虚拟文件系统:
$ sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -5
1.2G /opt
2.8G /usr
3.1G /home
11G /var
19G /
文件加起来只有 19G,而分区显示用了 40G。有 20 多个 G 凭空消失了。
原因:文件删了,空间没释放
在 Linux 里,删除文件实际上只是删除了目录中指向 inode 的那条记录。如果此时还有进程持有这个文件的句柄,inode 和数据块就不会被回收,直到最后一个句柄关闭。du 是沿着目录树统计的,自然看不到这类“已删除但仍在使用”的文件;而 df 读的是文件系统层面的块使用量,所以两者出现了差距。
最常见的场景是日志:有人手动 rm 了一个很大的日志文件,但写日志的进程并不知道,继续往那个已经没有名字的文件里写。
定位:用 lsof 找出来
$ sudo lsof -nP +L1 | grep -i deleted
api-server 2187 app 3w REG 252,1 22548103168 0 131204 /var/log/api/access.log (deleted)
+L1 表示只列出链接数小于 1 的文件,也就是已经从目录中删除的文件。结果很清楚:api-server 进程的 3 号文件描述符还开着一个 21G 的 access.log。后来翻操作记录才知道,前一天有同事看到日志太大,直接删了,但没有重启服务,也没有配置日志轮转。
如果机器上没有装 lsof,也可以直接看 /proc:
$ sudo find /proc/*/fd -ls 2>/dev/null | grep '(deleted)'
处理:不重启也能释放
最直接的办法是重启这个进程,句柄关闭后空间会立刻回收。但当时是工作时间,这个服务不方便随便重启。好在可以通过 /proc 直接把那个文件截断为零:
$ sudo truncate -s 0 /proc/2187/fd/3
执行后再看 df,可用空间立刻回到了 21G。进程本身完全没有感知,继续正常写日志。
注意:截断会丢失这个文件里的全部内容。如果日志还有排查价值,先 cp /proc/2187/fd/3 /some/other/disk/ 备份出来再截断。
之后做了什么
- 给
/var/log/api/加了 logrotate 配置,按天轮转、保留 14 份、压缩旧文件,并在轮转后向进程发送信号让它重新打开日志文件。 - 在团队文档里写明:清理正在被写入的日志,用
truncate -s 0或: > file,不要用rm。 - 监控里把磁盘告警阈值从 95% 调到了 85%,留出处理时间。
这类问题一旦遇到过就很好认:只要 df 和 du 的结果差距明显,第一反应就应该是 lsof +L1。