磁盘满了却找不到大文件?inode、deleted 文件和那些幽灵占用
这是运维里最经典的一种困惑:
$ 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)
还没有评论,来说两句吧~
发表评论