磁盘满了却找不到大文件?inode、deleted 文件和那些幽灵占用

Linux · 6 分钟阅读· 11 次阅读

这是运维里最经典的一种困惑:

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        40G   40G     0 100% /

$ sudo du -sh /*
8.2M    /bin
120M    /etc
...
合计:16G

40G 的盘,文件只占 16G,剩下 24G 去哪了?

有四种可能,按出现频率排。

一、已删除但被进程占用的文件(最常见)

Linux 里,文件被删除(unlink)后,只要还有进程持有它的 fd,磁盘块就不会释放。

sudo lsof +L1

输出里 SIZE/OFF 就是被占用的空间:

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NLINK NODE NAME
nginx    1234 root   12w   REG  253,1  18.4G    0  131074 /var/log/nginx/access.log (deleted)

这里 18.4G 就是这么没的——日志被 rm 了,但 nginx 还开着句柄。

正确处理方式不是 kill 进程,是让它重新打开文件:

# nginx
sudo nginx -s reopen

# 通用做法:清空而不是删除
sudo truncate -s 0 /var/log/nginx/access.log
# 或者
sudo sh -c 'echo > /var/log/nginx/access.log'

echo > 会保留 inode,进程继续写同一个文件,空间会立刻释放(因为文件被截断了)。

如果进程已经不在了,重启服务也可以。

这也是为什么日志轮转要用 copytruncate 或者发信号重开,而不是直接 rm。

二、inode 耗尽

df -h 显示还有空间,但创建文件报 No space left on device:

$ df -i /
Filesystem     Inodes  IUsed  IFree IUse% Mounted on
/dev/sda1     2621440 2621440      0  100% /

inode 用完了。每个文件(包括空文件)占一个 inode。

常见原因:

  • 大量的 session 文件(PHP 的 sess_*)
  • 邮件队列积压
  • 缓存目录里几百万个小文件

定位:

# 找出哪个目录文件最多
sudo find / -xdev -type f | sed 's#/[^/]*$##' | sort | uniq -c | sort -rn | head -20

注意 -xdev 不跨挂载点,否则会扫到 NAS 上去,跑几小时。

清理小文件时不要用 rm -rf 直接删几百万个,会很慢且 IO 打满。更快的方式:

# 先移动到同分区的临时目录,再后台删
mkdir /tmp/trash
find /var/lib/php/sessions -type f -mtime +1 -exec mv {} /tmp/trash/ \;
rm -rf /tmp/trash &

三、ext4 的保留块

ext4 默认给 root 保留 5% 的空间,防止用户写满后系统无法启动:

$ sudo tune2fs -l /dev/sda1 | grep -i reserved
Reserved block count:     1310720

40G 的盘,5% 就是 2G。这 2G 在 df 里算作 used,但普通用户用不了。

如果是纯数据盘(不是系统盘),可以降到 1%:

sudo tune2fs -m 1 /dev/sdb1

这条对 4T 的盘能省出 160G。但不要对 / 做,系统盘保留 5% 是有意义的。

四、挂载点被覆盖

如果某个目录上挂了一个空盘,挂载之前那个目录里原来的文件还在,但你看不见了——它们被「盖住」了。

典型场景:系统装在 / 上,/home 单独分区。某次 /home 没挂上,你看到的是 / 上那个空的 /home;或者反过来,你把数据写到了未挂载的 /home,然后挂载后数据「消失」。

检查:

findmnt
lsblk -f

把服务停掉,umount 一下,看那个目录里是不是有东西。

这个坑很隐蔽,因为 df 和 du 的数字都不会异常,只是你看到的和实际存的不一样。

排查顺序总结

# 1. 确认是空间还是 inode
df -h; df -i

# 2. 找被删但仍占用的文件
sudo lsof +L1 | awk '$7 ~ /^[0-9]/ {print $7, $9}' | sort -hr | head

# 3. 正常找大文件
sudo du -xhd1 / | sort -hr | head -15

# 4. 找大文件具体位置
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null

# 5. 检查保留块
sudo tune2fs -l $(findmnt -no SOURCE /) | grep -i reserved

# 6. 检查挂载覆盖
findmnt --verify

一个真实事故

某次 /var 满了,告警在凌晨三点。

du -sh /var/* 加起来 3G,df 显示 20G 满。lsof +L1 发现 syslog-ng 占着 16G 的已删除文件。

根因是 logrotate 配置里写了 create 但没写 postrotate 通知 syslog-ng 重开文件,于是日志被 rotate 后,进程还在往旧 inode 写,每次 rotate 就漏掉一份,累积了三个月。

修复是加 postrotate /usr/bin/killall -HUP syslog-ng,并临时 truncate 释放空间。

从这件事之后,我给每台机器都加了一条监控:lsof +L1 里已删除文件的总大小超过 1G 就告警。 这比 df 告警更早发现问题。

评论 (0)

还没有评论,来说两句吧~

发表评论

验证码

提交后需站长审核,通过后才会显示。