服务器磁盘满了才报警,已经晚了
做站长这些年,最吓人的一条报警不是"CPU 打满",也不是"网站 502",而是凌晨收到监控短信:磁盘使用率 100%。那一刻你做的所有事情都变成救火——Nginx 日志写不进去,MySQL 拒绝新连接,Session 落不了盘,后台直接白屏。更麻烦的是,你根本不知道是哪个文件在悄悄长大。这一切本来是可以提前几周,甚至几个月就发现的。
问题在于,大多数人只监控了"磁盘使用率"这一个数字,而磁盘不是一夜之间满的。真正该盯的是三件事:磁盘的 SMART 健康状态(盘会不会突然坏)、inode 使用率(文件数量是否异常膨胀)、以及增长趋势(按当前速度还能撑几天)。这篇就从这三个角度,把一套不依赖商业软件的磁盘健康监控方案讲透。
第一层:SMART,看盘有没有在"撑不住"
SMART 是硬盘固件自带的健康自检,绝大多数机械盘和 SSD 都支持。工具是 smartmontools,先装再跑:
apt install smartmontools
smartctl -a /dev/sda输出很长,但真正需要长期盯的字段没几个。机械盘重点看三项:Reallocated_Sector_Ct(重映射扇区数,不为 0 且持续增长说明盘面在坏)、Current_Pending_Sector(待映射扇区,有值就是危险信号)、以及最关键的 SMART overall-health self-assessment test result,只要不是 PASSED,就该准备换盘了。
SSD 的看点和机械盘不同。SSD 要盯 Wear_Leveling_Count、Media_Wearout_Indicator 或 Percentage_Used(剩余寿命百分比),以及 Uncorrectable_Error_Cnt。一块写满寿命的 SSD 不会立刻坏,但写入性能会断崖式下降,表现为数据库频繁超时。此外 SSD 的 SMART 属性 ID 各家厂商定义不同,靠属性名而不是数字去认,才是稳妥的。
光看静态快照没用,真正有价值的是定期跑自检并留存结果。可以在 crontab 里安排每周一次短自检、每月一次长自检:
# 每周日凌晨 3 点短自检(几秒到几分钟)
0 3 * * 0 /usr/sbin/smartctl -t short /dev/sda
# 每月 1 号凌晨 4 点长自检(可能几十分钟到数小时)
0 4 1 * * /usr/sbin/smartctl -t long /dev/sda自检结果在 smartctl -a 的"Self-test execution status"和下方日志区能看到。更推荐的做法是用 smartd 守护进程,它常驻后台,一旦关键属性超过阈值会自动写 syslog 甚至发邮件:
# /etc/smartd.conf
/dev/sda -a -o on -S on -s (S/../.././02|L/../../6/03) -m admin@example.com -M exec /usr/share/smartmontools/smartd-runner第二层:被忽视的 inode 使用率
磁盘没满但写不进文件,罪魁祸首往往是 inode 用尽。每个文件(哪怕 0 字节)都要占用一个 inode,而 inode 数量在格式化时就固定了。邮件队列堆积、Session 文件爆炸、缓存目录产生几十万个小文件,都会先耗尽 inode,再"看起来一切正常"地让服务挂掉。所以监控磁盘的同时,必须监控 inode:
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 6553600 400123 6153477 7% /一旦 IUse% 超过 80% 就该排查了。定位哪个目录文件最多,用这条组合命令:
for d in /var/*; do echo "$(find $d -xdev -type f 2>/dev/null | wc -l) $d"; done | sort -rn | head常见的 inode 杀手是 /var/spool/postfix/maildrop(定时任务报错堆积的信件)、/var/lib/php/sessions、以及各类 cache 目录。清理时要先确认业务无依赖,别把正在用的 Session 一锅端了。
第三层:增长趋势,比当前水位更重要
"磁盘用了 75%"这个数字本身不吓人,吓人的是它上周才 60%。趋势监控的思路很简单:每天记录一次使用量,算出日均增长,再看按这个速度几天会满。一个轻量脚本就够用:
#!/bin/bash
# /usr/local/bin/disk-trend.sh
LOG=/var/log/disk-trend.log
DATE=$(date +%F)
USED=$(df --output=pcent / | tail -1 | tr -d ' %')
AVAIL_MB=$(df --output=avail -m / | tail -1 | tr -d ' ')
echo "$DATE used=${USED}% avail=${AVAIL_MB}MB" >> $LOG
# 取最近 7 天做线性外推
awk '{
d=$1; split($2,a,"="); split(a[2],b,"%"); u=b[1];
if (NR==1) {first_d=d; first_u=u} last_d=d; last_u=u
} END {
cmd="date -d \""last_d"\" +%s"; cmd | getline lastts; close(cmd)
cmd="date -d \""first_d"\" +%s"; cmd | getline firstts; close(cmd)
days=(lastts-firstts)/86400; if (days<=0) days=1;
rate=(last_u-first_u)/days;
if (rate>0.3) printf "WARN 日均增长 %.2f%%,按此速度约 %.0f 天后满盘\n", rate, (100-last_u)/rate;
}' $LOG把结果接进你的告警通道(邮件、Telegram Bot、企业微信机器人皆可),增长率超过阈值就提醒。这种"提前预警"比"满了才报"有价值得多,因为它给了你安排扩容、清理或迁移的时间窗口。
用 df 之外的工具把空间去向看清楚
当空间已经异常时,最快的定位工具是 ncdu 和 du 的组合。ncdu 交互式界面适合在服务器上手动翻,du 适合脚本化:
apt install ncdu
ncdu /
# 脚本化找大目录
du -xhd1 / 2>/dev/null | sort -rh | head -15
# 找单个大文件
find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null记住加 -xdev,否则跨挂载点会把别的分区也统计进来,得到的数字全是错的。日志类目录通常是重灾区,配合前面的 logrotate 思路做切割和保留,能从源头控制磁盘增长速度。
长期保命:LVM 与在线扩容的余量
再好的监控也只是"发现",真正让你从容的是"有余地"。装系统时如果用了 LVM,扩容就是在线、无损的:
# 云盘先在控制台扩容后
growpart /dev/vda 3
pvresize /dev/vda3
lvextend -l +100%FREE /dev/vg0/root
resize2fs /dev/vg0/root # ext4;xfs 用 xfs_growfs /如果当初没上 LVM,就只能停机做分区调整,风险和成本都高得多。这也是很多老站长在重建服务器时坚持用 LVM 的原因——多留的这点抽象,救的是若干次半夜扩容的命。
把三层监控落到一张每日巡检表
在落地巡检之前,还有一个常被忽略的坑值得单独说:日志和临时文件的增长速度往往由业务代码决定,而业务代码你又很难保证它每次都写对。真正稳妥的做法是给这些高风险目录单独挂一个小分区或独立逻辑卷,这样即便它被写爆,也只会撑满自己那一块,不会连累根分区导致整机失联。这是无数站长用"整个根分区被 /var/log 写满"的代价换来的经验。
另一个务实的补充是学会用 find 做定时清理。很多业务自己不做日志轮转,你就可以用一条定时任务兜底,把超过 N 天的旧文件删掉:
# 每天清理 30 天前的应用日志(注意 -delete 与 -mtime 的判断顺序)
find /var/log/myapp -type f -name '*.log.*' -mtime +30 -delete
# 更稳的写法:先打印确认,再删除
find /var/log/myapp -type f -mtime +30 -print用 find 删文件一定要先跑 -print 确认清单,再换成 -delete,否则一个路径写错就可能误删仍在使用的文件。这是运维里少数几个"手滑一次就造成生产事故"的操作之一。
最后给一份可直接照着做的巡检清单。每天看:df -h 与 df -i 的使用率、inode 使用率、disk-trend 脚本输出的增长趋势。每周看:smartctl 短自检结果、SMART 关键属性是否有变化。每月看:长自检结果、大盘增长率复盘、以及是否有可以归档清理的历史数据。
磁盘故障从来不是"突然发生",而是"突然发现"。把 SMART、inode、增长趋势这三层监控搭起来,你就能从"救火队长"变回那个可以安心睡觉的站长。