df 说磁盘满了、du 却找不到大文件:被删除却没释放的空间
监控告警磁盘 100%,登上去一看,df -h 显示根分区 50G 全满。可我用 du -sh /* 挨个加起来只有 30G,剩下 20G 凭空消失。查了半小时没找到大文件,最后发现:空间被删了,但没被释放。
一、为什么删了还不释放
Linux 的文件由两部分组成:目录项(文件名)和 inode(真正指向数据块)。rm 做的是把目录项摘掉、把 inode 的硬链接计数减一。只有当硬链接计数归零、并且没有任何进程还打开着这个文件时,内核才会真正回收数据块。
所以只要有一个进程握着这个文件的 fd,空间就一直被占着。这时 du 扫的是目录树,那个名字已经不存在了,自然统计不到;df 问的是文件系统本身,它知道块还占着。两个工具的数据源不同,这就是对不上的根源。
二、三种定位方法
1. lsof 直接找已删除但仍打开的文件
lsof -nP +L1
+L1 表示“链接数小于 1”,正好就是这类文件。输出里 SIZE/OFF 列就是它当前占用的字节数。也可以按关键字过滤:
lsof -nP | grep -i deleted | awk '{print $1, $2, $7, $9}' | sort -k3 -n -r | head -20
2. 不想装 lsof,直接看 /proc
for pid in /proc/[0-9]*; do
for fd in $pid/fd/*; do
target=$(readlink "$fd" 2>/dev/null) || continue
case "$target" in
*"(deleted)"*)
size=$(stat -Lc %s "$fd" 2>/dev/null) || continue
echo "$size $pid $target"
;;
esac
done
done | sort -rn | head -20
第一列是字节数,第二列是 PID。文件名末尾带 (deleted) 就是目标。
3. 确认到底是哪个进程
拿到 PID 后用 ps -o pid,comm,args -p <PID> 看它是什么。常见的三类:
- 日志文件被
rm了,但服务还开着原 fd 在写(最常见) - 数据库的临时文件、WAL 文件
- 容器里被删除的镜像层,被守护进程占着
三、怎么处理
方案 A:重启进程(最稳)
进程退出时内核自动回收所有 fd,空间立刻释放。缺点是业务会中断,适合能重启的服务。
方案 B:不重启,直接清空
如果那个 fd 还指向一个真实路径(没被删除),可以原地截断:
truncate -s 0 /path/to/file.log
等价写法是 : > /path/to/file.log。注意 不能 用 rm 再 touch,那样新文件是新的 inode,老进程还写着老的,空间一点不释放。
如果文件已经被删除、只剩一个带 (deleted) 的 fd,那就必须重启进程,没法从外部截断。
四、预防:日志轮转的 copytruncate
这个坑绝大多数是日志轮转造成的。如果程序自己不重开日志文件(不支持 SIGHUP 重载),logrotate 默认的 create 模式会 rename 老文件、新建一个,老文件仍被进程占着,磁盘就慢慢涨上去。
/var/log/myapp/*.log {
weekly
rotate 8
compress
delaycompress
missingok
notifempty
copytruncate
}
copytruncate 的做法是复制一份再原地截断原文件,进程的 fd 依然有效,磁盘也释放了。代价是复制那一瞬间可能丢几行日志——这是它在“程序不支持重开文件”时的必要妥协。
五、监控口径:看 df,别只看 du
容量告警一定要用 df 的 Use%,因为 du 天生看不到被删除仍占用的块;而排查“哪个目录大”才用 du。两个数不一样时,先怀疑删除未释放,而不是怀疑 du 算错了。
六、容器里的差异
容器共享宿主内核,df 看到的是宿主文件系统。在容器里 du 找不到空间、宿主上 lsof 却能看到,多半是容器内进程占着已删除的文件。定位要在宿主上做,进容器看 /proc 是看不到别的命名空间的 fd 的。
小结
df和du对不上,先查“删除未释放”,而不是查权限或隐藏文件- 定位用
lsof -nP +L1,没装 lsof 就遍历/proc/*/fd - 能重启就重启,不能重启且文件还在就
truncate -s 0 - 日志类问题从根上解决:轮转配置加
copytruncate,或让程序支持重开日志
- 点赞
- 收藏
- 关注作者
评论(0)