系统与运维

Linux 磁盘写满排查:df、du、lsof 三板斧定位真凶

No space left on device 不代表可以随便删文件。本文给出从文件系统水位、目录逐层下钻到「已删除但仍被占用」的完整定位路径,并给出不会误伤数据的安全清理顺序。

作者:巧匠团队·8 分钟阅读·更新于 2026-08-30

第一步:确认真的满了,还是 inode 满了

先 `df -h` 看各挂载点的容量水位。重点看 Use% 到 100% 的那一行,以及它挂载在哪个目录——不同挂载点是独立的文件系统,删错地方毫无作用。

如果容量明明很空却仍报 No space left on device,八成是 inode 耗尽:海量小文件(缓存、session、邮件队列)把 inode 吃光了。用 `df -i` 验证 IUse% 是否 100%。

df -h          # 容量水位(找 100% 的挂载点)
df -i          # inode 水位(IUse% 100% = 小文件太多)

第二步:du 逐层下钻,别一上来扫根目录

定位大目录的正确姿势是「先分区,再逐层下钻」:在报满的挂载点内用 `du -xh --max-depth=1` 只看一层,比较各子目录体积后进入最大的那个,重复两三次就能锁到真凶目录。`-x` 参数很关键,它不跨文件系统,避免统计串台。

对找到了但看不懂的目录,用 `du -sh <dir>/* | sort -rh | head` 排出体积前十,通常一眼就能认出是日志、备份还是某个应用的数据目录。

du -xh --max-depth=1 /var 2>/dev/null | sort -rh | head
# 找到最大子目录后继续下钻,2~3 轮即可锁定

第三步:df 与 du 对不上?查「已删除但仍被占用」

一个经典陷阱:`df` 显示 100%,`du` 却找不到大文件。原因是进程仍持有已删除文件的句柄——文件从目录里消失了,但数据仍占着磁盘,直到进程释放或重启。

用 `lsof +L1` 列出所有「链接数为 0 但仍被打开」的文件,配合 -u 或 -c 过滤到嫌疑进程。确认后重启对应服务(或优雅 reload 日志进程)即可立刻释放空间,无需删除任何数据。

lsof +L1                      # 已删除但仍被占用的文件
lsof +L1 -u appuser            # 只看某用户的进程
# 确认后:systemctl restart <service> 释放句柄

按嫌疑程度点名四个常客

① 应用日志:增长最快,且可以用 `truncate -s 0` 清空而不中断写入进程(比 rm 更安全,rm 反而制造已删除句柄问题);② Docker:`docker system df` 看镜像/容器卷占用,`docker system prune` 清理悬空资源;③ core dump 与临时目录:`/tmp`、`/var/tmp`、`core.*`;④ 旧备份:找按日期命名的 tar/压缩包。

truncate -s 0 /var/log/app/app.log   # 清空但不打断写入进程
docker system df                     # 查看镜像/卷占用
docker system prune                  # 清理悬空镜像与停止容器

安全清理的先后顺序

推荐顺序:先 truncate 增长型日志(立竿见影且无数据风险),再清临时文件,然后 docker prune,最后才考虑删备份——删备份前必须确认还能从原始数据重建。任何时候都不要在数据库数据目录里手动删文件。

磁盘写满时连 root 登录都可能异常(shell 历史、临时文件写不了),所以应急顺序是先释放一小块空间再从容排查,而不是先花十分钟找根因。

truncate -s 0 /var/log/messages   # 应急第一刀
journalctl --vacuum-size=200M    # systemd 日志瘦身(常见大头)

事后预防:让写满不再变成事故

三件事:① 配置 logrotate 对应用日志做轮转与压缩;② 给磁盘用量加监控告警(80% 警告、90% 严重),在你发现之前先报警;③ 对会快速产生小文件的服务设置单独挂载点或定时清理任务,避免 inode 问题影响整个根分区。

/etc/logrotate.d/app 示例:
/var/log/app/*.log {
  daily
  rotate 14
  compress
  missingok
  copytruncate
}