这篇做完你能得到什么
你会拿到一套可复用的排查流程:先判断是"容量满"还是"inode 满",再从根目录一路收缩到具体目录和文件,最后用安全的方式清理日志、Docker 占用和僵尸文件。按顺序执行完,你能回答三个问题:谁把盘吃满了、能不能删、删完为什么空间没回来。
前置条件
- 一台 Linux 服务器,具备 sudo 或 root 权限
- 常用命令可用:
df、du、find、sort、truncate(多数发行版自带) - 建议装两个可选工具:
ncdu(交互式目录浏览)、lsof(查看被进程占用的文件) - 心里有数:清理是有风险的操作,涉及数据库文件、审计日志、容器数据卷时先确认再动手
第一步:先分清是空间满还是 inode 满
很多人上来就删大文件,但如果盘满是因为 inode 耗尽(文件数量太多,但每个文件都很小),删大文件一点用都没有。
```bash
看容量
df -hT
看 inode
df -i
```
阅读方式:
df -hT关注 Use% 这一列,找到接近 100% 的挂载点,记下它的挂载路径(比如/、/var、/data)和文件系统类型。df -i关注 IUSE%,如果它接近 100% 而df -h还有空间,那就是 inode 问题,直接跳到第七步。- 两个都满,先按容量处理,再处理 inode。
注意:df 显示的是挂载点,不是目录。比如 /var 单独挂了一块盘,那你该排查的是 /var,而不是整个 /。
第二步:从根目录往下收缩,找到大目录
假设满的是 /。逐层下钻,每层只扫当前文件系统(-x 参数),避免翻进其他挂载盘拖慢速度,也用 nice/ionice 降低对线上服务的影响。
```bash
第一层:根目录下各目录占多大
sudo nice -n 19 ionice -c3 du -xhd1 / 2>/dev/null | sort -h
```
看到 /var 特别大,就继续下钻:
```bash
sudo du -xhd1 /var 2>/dev/null | sort -h
sudo du -xhd1 /var/log 2>/dev/null | sort -h
```
du -x 表示不跨文件系统,-d1 表示只统计一层,-h 是人类可读单位。sort -h 按体积排序,最大的排在最后。
如果装了 ncdu,可以直接交互式翻:
```bash
sudo ncdu -x /
```
方向键进入目录,d 删除(谨慎使用),q 退出。它比反复敲 du 直观。
常见大户:/var/log、/var/lib/docker、/var/cache、/home、/tmp、/var/spool。
第三步:捞出具体的大文件
目录定位到之后,直接找大文件:
```bash
找出当前文件系统里大于 500M 的文件,按大小倒序
sudo find / -xdev -type f -size +500M -printf '%s\t%p\n' 2>/dev/null | sort -nr | head -20
```
把 %s 换成可读格式也行:
```bash
sudo find /var -xdev -type f -size +200M -exec ls -lh {} + 2>/dev/null | sort -k5 -hr | head -20
```
几个高频目标:
```bash
老日志
sudo find /var/log -xdev -type f -mtime +30 -size +100M -exec ls -lh {} + 2>/dev/null
缓存目录(Debian/Ubuntu)
sudo du -xhd1 /var/cache/apt 2>/dev/null
崩溃转储
sudo ls -lh /var/crash /var/lib/systemd/coredump 2>/dev/null
```
确认某个文件确实没用了再删。拿不准就先挪到别的盘:
```bash
sudo mv /var/log/old-app.log /data/archive/
```
第四步:处理"删了文件但空间没回来"
这是磁盘清理里最经典的一个坑:用 rm 删掉了日志,df 却一点没变。原因是某个进程还持有这个文件的句柄,inode 没被释放。
先找出这些"幽灵文件":
```bash
方法一:lsof
sudo lsof -nP +L1 2>/dev/null | head -30
方法二:没有 lsof 时,直接看 /proc
sudo find /proc/[0-9]*/fd -lname '*deleted*' -printf '%p -> %l\n' 2>/dev/null | head -30
```
输出里的路径形如 /proc/12345/fd/7,其中 12345 是进程号。
两种处理方式:
1. 重启或重载对应服务,句柄释放,空间自动归还。比如 nginx 重新打开日志文件:
```bash
sudo nginx -s reopen
```
2. 不想重启服务,就通过 /proc 把文件截断为零:
```bash
sudo truncate -s 0 /proc/12345/fd/7
或者
sudo sh -c ': > /proc/12345/fd/7'
```
只对普通文件有效,socket、管道会报错。
顺带记住这条经验:清理正在被写入的日志,用 truncate -s 0 或 : > file,不要用 rm。 不然服务会继续往一个已经"不存在"的文件里写,空间不会释放,直到重启。
第五步:日志清理与轮转
日志通常是可以安全清理的部分,但方式要对。
systemd journal:
```bash
看占用
journalctl --disk-usage
只保留最近 500M
sudo journalctl --vacuum-size=500M
只保留最近 7 天
sudo journalctl --vacuum-time=7d
```
想让它以后自动控制,编辑 /etc/systemd/journald.conf:
```ini
[Journal]
SystemMaxUse=1G
SystemMaxFileSize=100M
MaxRetentionSec=2week
```
然后重启:
```bash
sudo systemctl restart systemd-journald
```
传统日志文件: 用 logrotate,别手动删。在 /etc/logrotate.d/ 下加一个配置文件,例如:
```
/var/log/myapp/*.log {
daily
rotate 7
missingok
notifempty
compress
delaycompress
copytruncate
}
```
copytruncate 适合那些不方便重开日志文件的应用(它先复制再清空原文件);如果应用支持信号重载,用 postrotate 发信号更干净。先干跑验证语法:
```bash
sudo logrotate -d /etc/logrotate.d/myapp
```
包管理缓存:
```bash
Debian / Ubuntu
sudo apt-get clean
RHEL / CentOS / Rocky
sudo dnf clean all
或
sudo yum clean all
```
第六步:Docker 占用
/var/lib/docker 很容易悄悄涨到几十上百 G。先看统计:
```bash
docker system df
docker system df -v
```
输出会分开列出镜像、容器、数据卷、构建缓存各占多少。清理按需选择:
```bash
清理停止的容器、未使用的网络、悬空镜像、构建缓存
docker system prune
更彻底:连未被任何容器使用的镜像一起清
docker system prune -a
顺带清理未被使用的数据卷(危险,确认没有重要数据)
docker system prune -a --volumes
```
注意:prune 是不可逆的,-a 会删掉所有没在运行的容器正在使用的镜像,--volumes 会删掉未被挂载的数据卷。执行前先跑一次 docker system df -v 看清楚。
单个容器的日志文件如果特别大,可以直接定位:
```bash
sudo find /var/lib/docker/containers -name '*-json.log' -size +100M -exec ls -lh {} + 2>/dev/null
```
截断它(容器不用重启):
```bash
sudo truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.log
```
要治本,配置日志驱动上限。编辑 /etc/docker/daemon.json:
```json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
```
```bash
sudo systemctl restart docker
```
这个配置只对新创建的容器生效,已有的容器需要重建才会应用。
另外,不要直接 rm -rf /var/lib/docker,那会破坏 Docker 的元数据,可能导致守护进程起不来。要清就通过 docker 命令清。
第七步:inode 用尽
df -h 有空间、df -i 却 100%,说明文件数量太多。典型场景:PHP session 文件、邮件队列、缓存目录、海量小文件的数据集、node_modules、容器 overlay 层。
先找出哪个目录文件最多:
```bash
GNU coreutils 支持 --inodes,按 inode 数量排序
sudo du -x --inodes -d1 / 2>/dev/null | sort -n
不支持 --inodes 时用 find 统计
for d in /var/*; do
[ -d "$d" ] && printf '%s\t%s\n' "$(sudo find "$d" -xdev 2>/dev/null | wc -l)" "$d"
done | sort -n | tail -20
```
定位到目录后,先预览再删:
```bash
预览:删之前一定先 print 看看
sudo find /var/lib/php/sessions -type f -mtime +7 -print | head -50
确认无误后再执行
sudo find /var/lib/php/sessions -type f -mtime +7 -delete
```
Docker 场景下,/var/lib/docker/overlay2 层数多、小文件多,用 docker system prune 通常能一次性回收大量 inode。
第八步:验证与收尾
清理完复查:
```bash
df -hT
df -i
docker system df
journalctl --disk-usage
```
如果 du 加起来只有 20G,df 却显示 100G 已用,还可能是这几个原因:
- ext4 默认给 root 预留 5% 空间,非 root 用户看到的可用空间会少一截。查看预留量:
```bash
sudo tune2fs -l /dev/sda1 | grep -i 'reserved block count'
```
数据盘可以调小(比如 1%),系统盘不建议动:
```bash
sudo tune2fs -m 1 /dev/sdb1
```
仅适用于 ext2/3/4。
- 有目录被挂载点"盖住"了:
/var/lib/mysql挂了盘,但原目录里的文件还在下面。需要mount --bind到别处才能看到。 - LVM 快照、thin pool、btrfs 快照占用了空间,
du看不到。
常见坑与排错
rm 后空间不释放。 有进程还开着文件句柄,回到第四步,用 lsof +L1 或 /proc 处理,最省事的办法是重启对应服务。
删日志后服务报错或不再写日志。 大概率是删的时候句柄还连着,应用以为文件还在。改用 truncate -s 0 或让 logrotate 的 copytruncate 处理。
docker system prune 之后空间没降多少。 检查是不是还有运行中的容器在写、有数据卷没清、或者构建缓存没被回收。再跑一次 docker system df -v 看明细。
扫描目录扫描到一半卡住。 多半是跨挂载点扫到了网络盘或慢速盘。加 -x(du -x、find -xdev),并用 nice -n 19 降优先级。
find ... -delete 删错东西。 养成先 -print 后 -delete 的习惯,中间隔着一次肉眼核对。通配符删除永远不要在 / 下裸跑。
inode 清了但没降。 有些文件被进程 hold 住,同样是句柄问题,重启相关服务后再看 df -i。
清理完一会儿又满。 说明有东西在持续写入,常见是应用 debug 日志、容器日志没限额、journald 没配上限、定时任务输出全打到文件。这时候要去配置轮转和限额,而不是每周手动清一次。
下一步建议
1. 加上监控告警。 用 node_exporter + Prometheus 采集 node_filesystem_avail_bytes 和 node_filesystem_files_free,容量和 inode 各设一条阈值告警,在 80% 就触发,别等到 100%。
2. 把日志治理成常规动作。 journald 配上限、应用日志接 logrotate、容器日志驱动配 max-size,这三件事做完,日志类问题能减少很多。
3. 定期巡检脚本。 把 df -h、df -i、docker system df、journalctl --disk-usage 打包成一个巡检脚本,配合定时任务把结果发到运维群或邮件。
4. 规划磁盘分层。 系统盘和数据盘分开,数据盘用 LVM 或云盘,方便在线扩容。云厂商的磁盘扩容方式以官方文档当前版本为准,扩完还要记得扩容文件系统本身。
5. 沉淀一份清理清单。 把每台机器上"哪些目录可以删、保留多久、删之前要确认什么"写成文档,下次半夜收到告警时照着执行,比现场判断快得多。
