磁盘满了,du 却找不到:被删除文件占用空间的排查

2026-09-14 · 排障

上周五下午,一台跑内部 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。