都对。df 问的是文件系统「已分配了多少块」,du 是遍历目录树把看得见的加起来。当两者对不上,说明有东西正占着一块已经没有名字的空间。下面是找出它的方法。
1. 先确认差异确实存在
$ clai 对比一下 df 报告的 / 和 du 在那里实际找到的→ df -h / && du -shx / 2>/dev/nullFilesystem Size Used Avail Use% Mounted on/dev/nvme1n1p2 915G 871G 44G 96% /348G /
占用 871 GB,找到 348 GB。这个差额就是全部问题所在。-x 能防止 du 跑进其他挂载的文件系统,把自己的结果算大。
2. 追字节之前先看 inode
$ clai 检查是不是 inode 用完了而不是空间用完了→ df -i /Filesystem Inodes IUsed IFree IUse% Mounted on/dev/nvme1n1p2 124960768 3634368 121326400 3% /
如果 IUse% 到了 100% 而 Use% 没有,说明你有几百万个小文件,inode 一个不剩。这是另一个问题:磁盘有空间,但文件系统已经没地方登记新文件了。
3. 找出已删除但仍被打开的文件
$ clai 显示已经被删除但仍被进程打开的文件→ sudo lsof +L1COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAMEpython3 23435 app 3u REG 259,2 1000000000 0 41231 /var/log/app/debug.log (deleted)
特征是 NLINK 0:已经没有任何目录项指向这个文件,所以 du 看不见它,但在最后一个文件描述符关闭之前,磁盘块一直被占着。通常就是某人 rm 了日志,却没有重启正在往里写的服务。
4. 把空间收回来
$ clai 重启那个还占着已删除文件的服务→ sudo systemctl restart app.service
重启进程会关闭描述符,内核立刻释放这些块。df 会在那一瞬间掉下来——不用卸载,也不用重启机器。
5. 如果现在没法重启
$ clai 清空进程 23435 在描述符 3 上占着的那个已删除文件→ sudo truncate -s 0 /proc/23435/fd/3
通过 /proc/<pid>/fd/<n> 写入,可以在不杀掉进程的前提下清空它仍在写的文件。进程保留描述符继续记日志,你立刻拿回空间。
6. 另一个经典:挂载点盖住了下面的文件
$ clai 检查是否有东西挂载在某个下面还有文件的目录上→ sudo mount --bind / /mnt/root && du -shx /mnt/root/var14G /mnt/root/var
如果在挂载磁盘之前就往 /var/data 写过东西,那些文件仍然留在根文件系统上,占着透过挂载点谁也看不到的空间。对 / 做一次 bind 挂载就能把它们暴露出来。
坑
- 用 root 运行
du。 以普通用户身份运行时,它会静默跳过读不了的目录,于是你把权限问题当成了幽灵去追。 lsof +L1需要权限才能看到其他用户的描述符。不加sudo得到的是一份简短且具有欺骗性的干净列表。- 不要再删一次活动日志。 正是这个动作制造了眼下的局面。对在写的日志用
truncate -s 0,或者给 logrotate 配上copytruncate。
相关问题
为什么 rm 之后空间没回来? 在 Unix 里,unlink 删除的是名字而不是数据。只有最后一个打开的描述符关闭时块才会释放——对一个运行中的服务来说,那就等于「永远不会」,除非它重启。
du 和 df 差了百分之几,有问题吗? 没有。那是预留块(ext4 默认给 root 留 5%)加上文件系统元数据。只有大缺口才值得排查。
不用 lsof 能找到吗? 能:sudo ls -l /proc/*/fd 2>/dev/null | grep deleted 通过进程表显示同样的信息。
另请参阅
不用在凌晨三点翻 lsof 的参数了——描述问题,命令交给 CliAI 写。一行命令安装。