磁盘满了,先别急着删
磁盘告警时最容易犯的错,是直接 rm -rf /var/log/*,结果空间一点没释放,日志还丢了。
原因下面会说,先按这个顺序走一遍,通常几分钟就能定位到是谁在吃空间。
第一步:分清是空间满了还是 inode 满了
df -h # 各挂载点的空间使用率
df -i # 各挂载点的 inode 使用率输出示意:
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 99G 94G 0 100% /
/dev/vdb1 500G 120G 355G 26% /dataFilesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 6553600 6553600 0 100% /
/dev/vdb1 32768000 1200000 31568000 4% /data这两种"满"报错信息一样(都是 No space left on device),但排查方向完全不同:
df -h的Use%到 100% —— 空间真的被数据占满了;df -i的IUse%到 100% —— 空间还有富余,但文件数量到顶了,同样写不进新文件。
小文件特别多的机器(session、缓存碎片、邮件队列)经常是后一种。
第二步:用 du 逐层下钻
du -xh --max-depth=1 / | sort -rh | head -20输出示意:
45G /var
12G /home
3.1G /usr-x 很关键 —— 不加它会跨到其它挂载点,看到的数字是虚的。
定位到大目录后逐层往下:
du -xh --max-depth=1 /var | sort -rh | head -20
du -sh /var/* | sort -rh | headdu 在大目录上会跑很久,赶时间可以直接看已知的几个大户:/var/log、/var/lib/docker、/var/cache、/home、/tmp。
用 du 时有三个地方容易看错:
-x别省,否则会把挂载在下层的盘也算进来;- 硬链接默认只算一次,要按"每个链接都占一份"来统计得加
-l; - 稀疏文件按实际占用算,所以
du常常远小于ls -l显示的大小 —— 这是正常的,不是漏算。
第三步:df 和 du 对不上 —— 已删除但没释放的文件
这是最经典也最容易被忽略的情况:df 说占用 90%,du 加起来只有 30%。
原因是有进程还持有已被 rm 掉的文件句柄,空间自然不释放;du 遍历目录树看不到它,df 统计块设备能看到它。
lsof +L1 # link count 为 0 的文件,也就是"已删除但被打开"
lsof | grep deleted | head输出示意:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
nginx 1183 root 2w REG 253,1 20.4G 0 131091 /var/log/nginx/access.log (deleted)输出里有 PID 和 FD,处理方式二选一:
# 方式一:重启对应服务(日志类最常见,重启后自动释放)
systemctl restart nginx
# 方式二:不重启,把该句柄截断为 0(对日志文件有效)
: > /proc/1183/fd/2注意必须是 : >(截断),而不是 rm —— 删掉 /proc 下的路径不解决任何问题,空间依旧不释放。
这也是为什么开头那个 rm -rf /var/log/* 没用:文件被删了,但写日志的进程还开着句柄。
第四步:inode 耗尽的排查
df -i # 先确认是不是 inode 满了
find / -xdev -type f 2>/dev/null | wc -l # 全盘文件总数
# 找出哪个目录下文件最多
find / -xdev -type f 2>/dev/null | sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head输出示意:
812340 /var/lib/php/sessions
42117 /var/spool/postfix/deferred
3106 /var/log常见元凶是大量小文件:session 文件、缓存碎片、/var/spool 下的邮件队列、被按秒切割的日志。
确认后整目录删除比逐个删快得多,rm -rf /path/to/dir 之后重建空目录即可。
第五步:几个稳定的空间大户
1. systemd 日志
journalctl --disk-usage # 当前占用多少
journalctl --vacuum-size=200M # 收缩到 200M
journalctl --vacuum-time=7d # 只保留最近 7 天想永久限制,改 /etc/systemd/journald.conf,然后 systemctl restart systemd-journald:
SystemMaxUse=500M
SystemMaxFileSize=50M2. 包管理器缓存与旧内核
apt-get clean && apt-get autoremove --purge # Debian / Ubuntu
yum clean all # CentOS / RHEL
package-cleanup --oldkernels --count=2 # CentOS 保留最近 2 个内核3. Docker
docker system df # 分开看:镜像 / 容器 / 数据卷 / 构建缓存
docker system prune -a # 清掉未使用的镜像与构建缓存(会删东西,看清提示再确认)第六步:按时间清理历史文件
只删够老的文件,别碰正在写的。养成先干跑再真删的习惯:
# 先干跑:只打印清单,确认没有误伤
find /var/log -type f -name "*.log" -mtime +30 -print
# 确认后再真删
find /var/log -type f -name "*.log" -mtime +30 -delete-mtime +30 表示修改时间在 30 天以前。目录里文件极多时,find -delete 比 rm -rf 稳,不会因为参数过长而失败。
第七步:确实不够用了 —— 在线扩容
排查完发现没有可清理的,那就扩。LVM 下三步,第 3 步不能省,否则卷大了文件系统还是不认识新空间:
vgs # 先看卷组里还有多少 VFree 可分配
lvs # 看现有逻辑卷与大小
lvextend -L +10G /dev/mapper/vg0-root # 给逻辑卷加 10G
# 让文件系统认识新空间,命令按文件系统类型区分
resize2fs /dev/mapper/vg0-root # ext4:参数是设备
xfs_growfs / # XFS:参数是挂载点,不是设备两个容易踩的点:
- ext4 支持在线扩容,也支持缩容(但要先卸载);
- XFS 只能扩、不能缩,选文件系统时就要想清楚。
清理时的几个坑
- 别删正在写的日志文件。
rm之后进程仍持有句柄,空间不释放、内容也没了。要么truncate -s 0 file,要么先停服务。 rm大文件不等于立刻释放空间。删除只是解除链接,真正回收取决于还有没有进程打开着它。du小于df是正常的。文件系统元数据、被删句柄、稀疏文件都会造成差异;只有差得离谱时才需要按第三步查。- 别在业务高峰做全盘
du或find,这两条命令都会把磁盘 IO 打满,反而拖垮服务。
预防
日志轮转配好,比事后救火省事得多。/etc/logrotate.d/myapp:
/var/log/myapp/*.log {
daily
rotate 7
compress
missingok
notifempty
copytruncate # 复制后截断原文件,进程不用重启(正好绕开句柄占用问题)
}另外三条:
- journald 上限按上面的配置写死;
- 监控不能只看空间,
df -i一起报; - 容器日志单独限大小:
--log-opt max-size=50m --log-opt max-file=3。
总结
- 先分清是空间满还是 inode 满:
df -h和df -i都要看; du -xh逐层下钻,-x别跨挂载点,注意硬链接与稀疏文件的影响;df与du差得多 →lsof +L1找已删除但被占用的文件,用: > /proc/PID/fd/N截断;- 稳定大户按顺序清:journald、包管理器缓存、旧内核、Docker、应用日志;
- 清理历史文件先
-print干跑,确认后再-delete; - 真的不够用就扩容,LVM 三步走完记得
resize2fs或xfs_growfs; - 长期靠 logrotate 与各项上限配置,别等告警了才动手。