第一步:确认真的满了,还是 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
}