df 说磁盘满了、du 却找不到大文件:被删除却没释放的空间

举报
yd_242458115 发表于 2026/11/05 13:19:14 2026/11/05
【摘要】 一次磁盘 100% 告警,du 加起来只有 30G。原因是文件被 rm 了但进程还握着 fd。讲清 inode 与硬链接的回收条件、三种定位方法(lsof +L1、遍历 /proc/*/fd、grep deleted)、重启与 truncate 两种处理,以及 logrotate 的 copytruncate 怎么从根上避免。

监控告警磁盘 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,或让程序支持重开日志
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。