早上刚到公司,告警群弹出一条:某台Web服务器根分区使用率 91%。你心里一紧——这类问题很特殊,它不像 CPU 高那样可以慢慢查,磁盘一旦真正写满(100%),MySQL 会拒绝写入、Docker 容器会异常退出、SSH 甚至可能登不进去,业务会在几分钟内全面瘫痪。但反过来,慌乱中直接 rm -rf 又极容易删错文件,把事故变成灾难。所以磁盘告警的正确处置姿势是"先定位、再清理、后根治":先搞清楚到底是谁在吃空间,然后按安全等级从低风险到高风险逐步清理,最后建立自动告警与轮转机制,让这件事不再半夜发生。本文给出一套可照抄的完整方案。
一、先别急着删:三分钟定位空间去哪了
看到 90% 就动手删文件是最危险的做法。磁盘问题的复杂性在于:你看到的"已用空间"和文件系统里能找到的文件大小之和,经常对不上。最典型的情况是文件已被 rm 删除,但仍有进程持有它的文件描述符,空间不会释放。因此定位要分两步走。
# 第一步:看整体,确认是哪个分区告急,同时注意 inode 使用率
df -h
df -i
# 第二步:从根目录逐层统计,只看一层深度,快速锁定大目录
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -20
# 第三步:交互式下钻(ncdu 需要 yum install ncdu / apt install ncdu)
ncdu /
# 第四步:查"已删除但仍被进程占用"的文件,这是最容易遗漏的一类
lsof +L1
lsof | grep -i deleted
# 第五步:按大小直接找大文件(> 500MB)
find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20
关于 du 与 df 结果不一致:如果 df 显示用了 90%,但 du / 加起来只有 40%,差额几乎肯定是"被删除但仍被进程持有"的文件。用 lsof | grep deleted 找到对应进程 PID,重启该进程即可释放空间;如果进程不能重启,可以用 truncate 把它持有的文件截断(见下一节),无需重启也能立刻回收空间。这类情况在 Nginx、Java 应用、rsyslog 上极其常见——有人觉得"日志我都删了怎么空间没回来",原因就在这里。
另外务必执行 df -i。inode 耗尽时现象和磁盘满完全一样(No space left on device),但 df -h 看起来还有大量剩余空间。邮件服务器、缓存目录、容器环境里堆积海量小文件时,inode 往往先于容量耗尽。
二、常见元凶与对应清理手段
不同"吃空间大户"的安全清理方式完全不同,用错方法可能直接搞坏服务。下表按风险从低到高排列,建议严格从上往下执行。
| 占用来源 | 典型路径 | 清理方式 | 风险 |
|---|---|---|---|
| 系统日志 journal | /var/log/journal | journalctl --vacuum-size=500M | 低 |
| YUM/APT 缓存 | /var/cache/yum、/var/cache/apt | yum clean all / apt clean | 低 |
| 已轮转的旧日志 | /var/log/*.gz、*.1 | find -mtime +30 -delete | 低 |
| 临时文件 | /tmp、/var/tmp | 清理 30 天前的文件 | 中,确认无进程在用 |
| 正在写入的大日志 | /var/log/xxx.log | truncate 截断,禁止直接 rm | 中,操作方式要对 |
| Docker 资源 | /var/lib/docker | docker system prune -a --volumes | 高,会删掉未运行镜像 |
| MySQL binlog | /var/lib/mysql | PURGE BINARY LOGS BEFORE | 高,影响主从与恢复 |
| core dump | /var/lib/systemd/coredump | 确认后删除 | 中,可能含排查线索 |
2.1 正在写的日志:用 truncate 而不是 rm
这是最应该记住的一条纪律。对于 Nginx、Tomcat、业务程序正在写入的日志文件,直接 rm 有两个后果:一是空间不会释放(进程仍持有句柄),二是进程继续往一个"已消失"的文件写,日志彻底丢失且无法恢复。正确做法是把文件截断为 0 字节,保留 inode 不变:
# 正确:截断文件,进程可继续写入,空间立刻释放 truncate -s 0 /var/log/nginx/access.log : > /var/log/myapp/app.log # 等价写法 # 错误示范(不要这么做) # rm -f /var/log/nginx/access.log # 如果已经误删,可用 /proc 找回并截断(PID 与 fd 号从 lsof 输出里取) ls -l /proc/1234/fd | grep deleted : > /proc/1234/fd/3
2.2 journal、包缓存与 Docker 的安全清理
# systemd 日志限制到 500MB(立即生效) journalctl --vacuum-size=500M # 或按时间保留 7 天 journalctl --vacuum-time=7d # 永久生效:编辑 /etc/systemd/journald.conf,设 SystemMaxUse=500M sed -i 's/^#SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf systemctl restart systemd-journald # 包管理器缓存 yum clean all # RHEL/CentOS apt-get clean # Debian/Ubuntu # Docker:先看看各类型占用多少,再决定删什么 docker system df docker system prune # 删停止的容器、悬空镜像、无用网络 docker system prune -a # 额外删除所有未被容器引用的镜像(谨慎) docker volume prune # 删除未被引用的数据卷(危险,先确认)
Docker 的 prune -a 与 volume prune 务必谨慎:前者会删掉所有当前没在跑的镜像,重新拉取要花时间;后者可能删掉数据库容器的数据卷。执行前先用 docker system df -v 看清楚,并确认镜像仓库可拉取、数据卷有备份。
2.3 MySQL binlog 的正确清理
MySQL 的 binlog(二进制日志)在开启主从或做过备份策略的机器上可能占几十上百 GB。清理必须走 MySQL 命令,直接 rm 文件会导致 mysql-bin.index 与实际文件不一致,MySQL 重启失败。
# 先看当前保留情况 SHOW BINARY LOGS; SHOW VARIABLES LIKE 'binlog_expire_logs_seconds'; SHOW VARIABLES LIKE 'expire_logs_days'; # 清理某个时间点之前的 binlog(MySQL 5.7) PURGE BINARY LOGS BEFORE '2024-10-01 00:00:00'; # 或按文件名清理 PURGE BINARY LOGS TO 'mysql-bin.000120'; # 设置自动过期:MySQL 8.0 用秒,5.7 用天 SET GLOBAL binlog_expire_logs_seconds = 604800; # 7 天 # 写入配置文件永久生效 /etc/my.cnf # [mysqld] # binlog_expire_logs_seconds = 604800
三、自动告警:让磁盘在 85% 时就被发现
清理只能救急,真正解决问题的是提前告警。核心原则是分两级:超过 85% 发预警(白天处理即可),超过 90% 发紧急告警(立即响应)。为什么是 85% 起步?因为大文件写入需要连续空间,且数据库、容器在接近满时行为会异常,留出 10%-15% 的缓冲才能争取到处置时间。
方案一:Prometheus 告警规则(推荐,已在用 Prometheus 的情况下)
groups:
- name: disk.rules
rules:
# 预警:使用率超过 85% 持续 30 分钟
- alert: DiskUsageWarning
expr: |
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 > 85
for: 30m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} 挂载点 {{ $labels.mountpoint }} 使用率 {{ $value | printf \"%.1f\" }}%"
# 紧急:使用率超过 90% 持续 10 分钟
- alert: DiskUsageCritical
expr: |
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 > 90
for: 10m
labels:
severity: critical
# 趋势预测:按近 6 小时增速,10 天内会写满
- alert: DiskWillFillIn10Days
expr: |
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 10*86400) < 0
for: 1h
labels:
severity: warning
annotations:
summary: "按当前增速,{{ $labels.mountpoint }} 将在 10 天内写满"
# inode 也要单独告警
- alert: InodeUsageHigh
expr: |
(1 - node_filesystem_files_free{fstype!~"tmpfs|overlay"}
/ node_filesystem_files{fstype!~"tmpfs|overlay"}) * 100 > 85
for: 30m
labels:
severity: warning
方案二:没有 Prometheus 时的纯 Shell 脚本 + crontab。小团队几台机器用这个更轻。
#!/bin/bash
# /opt/scripts/disk_check.sh —— 磁盘使用率检查与邮件告警
WARN=85
CRIT=90
MAILTO="ops@yourdomain.com"
df -P -h | awk 'NR>1 {gsub("%","",$5); print $5, $6, $1}' | while read used mount dev; do
# 跳过内存文件系统
case "$dev" in tmpfs|devtmpfs|overlay|/dev/loop*) continue;; esac
if [ "$used" -ge "$CRIT" ]; then level="CRITICAL"
elif [ "$used" -ge "$WARN" ]; then level="WARNING"
else continue; fi
top5=$(du -h --max-depth=2 "$mount" 2>/dev/null | sort -hr | head -6)
{
echo "级别: $level"
echo "主机: $(hostname) 时间: $(date '+%F %T')"
echo "分区: $dev 挂载点: $mount 使用率: ${used}%"
echo ""
echo "== 占用最大的目录 =="
echo "$top5"
} | mail -s "[$level] $(hostname) 磁盘 ${used}% - $mount" "$MAILTO"
done
# 加入定时任务,每 30 分钟检查一次
# */30 * * * * /bin/bash /opt/scripts/disk_check.sh >/dev/null 2>&1
脚本里带上"占用最大的目录"这个细节非常实用——收到邮件时不用登录就能初步判断是谁在吃空间,处置速度能快一倍。
四、根治:让磁盘不再涨到 90%
清理和告警都是被动的,长效机制有三件事要做。
第一,配置 logrotate。Linux 自带 /etc/logrotate.conf 与各应用的 /etc/logrotate.d/*,确认业务日志都纳入了轮转。一份典型的配置:每天轮转、保留 14 份、压缩、轮转后通知进程重开日志(postrotate 里的 kill -USR1)。用 logrotate -d /etc/logrotate.d/nginx 做一次干跑调试,确认没有问题。
第二,设定各组件的保留策略:MySQL binlog 7 天、journal 500MB、Docker 镜像只保留在用版本、监控数据(Prometheus)保留 15 天、应用日志按量分级(错误日志保留久、访问日志保留短)。
第三,分离与扩容。把日志、数据、备份放到独立的数据盘或独立分区,避免日志暴涨把根分区拖垮——这是最容易被忽视但收益最大的一条。云服务器的云盘通常支持在线扩容,扩完后按文件系统类型执行 growpart + resize2fs(ext4)或 xfs_growfs(xfs)即可生效。
五、标准处置流程
- 收到告警后先跑
df -h与df -i,确认是容量问题还是 inode 问题,以及具体是哪个挂载点。 - 跑
du -h --max-depth=1 /挂载点 | sort -hr | head,锁定占用最大的目录。 - 跑
lsof | grep deleted,确认是否存在"已删未释放"的文件,若有则重启对应进程或截断/proc/PID/fd/N。 - 按风险从低到高清理:先 journal 与包缓存,再旧日志,再临时文件。绝不一开始就动
/var/lib/docker或数据库目录。 - 对正在写入的日志用
truncate -s 0,严禁rm。 - 清理后再次
df -h确认已降到安全线(建议降到 80% 以下,留足缓冲)。 - 排查增长原因:是日志没轮转、备份没清理、还是业务量突增?不搞清楚原因,几天后必然复发。
- 补齐 logrotate 配置与保留策略,并确认告警规则已覆盖该分区(含 inode)。
小结
磁盘使用率到 90% 是运维最常见也最考验基本功的告警。处理的关键不在"删",而在"先看清再动手":用 du 定位目录、用 lsof | grep deleted 揪出隐形占用、用 df -i 排除 inode 问题;清理时按 journal → 包缓存 → 旧日志 → 临时文件 → Docker → 数据库的顺序由低风险到高风险推进;正在写的日志一律用 truncate 截断而非 rm;告警要设 85% 预警、90% 紧急两级,并加一条 predict_linear 趋势预测提前发现快速增长。避坑清单:一是 rm 正在写的日志导致空间不释放且日志丢失;二是手动删除 MySQL binlog 文件导致 MySQL 无法启动;三是 docker system prune -a --volumes 误删数据卷;四是只监控容量不监控 inode;五是清理完不查原因,问题反复出现。把 logrotate 与保留策略配好,再配合趋势告警,磁盘问题基本可以从你的值班清单里消失。
关键词:磁盘使用率90%、磁盘空间清理、磁盘告警、logrotate、inode耗尽、truncate释放空间
企业QQ咨询




