根本原因是df默认统计文件系统级可用空间,未考虑inode耗尽、root保留空间(通常5%)及已删除但被进程占用的文件;需同步运行df -i检查inode,并用lsof +L1或find /proc/*/fd -ls | grep deleted定位“隐形”占空间文件。

df 命令的精度陷阱:为什么 df -h 显示还有 10% 却报 “No space left on device”
根本原因不是显示不准,而是 df 默认统计的是 **文件系统级可用空间**,不考虑 inode 耗尽、root 用户保留空间(通常是 5%)、或被已删除但进程仍占用的文件(/proc/*/fd/ 下的 deleted 文件)。
-
df -i必须同步检查 —— 尤其在大量小文件场景(如日志归档、容器镜像层),Inodes先用完很常见 - 普通用户看到的 “Available” 是扣除了 root 保留空间后的值;但某些服务(如
nginx、dockerd)以 root 身份运行,实际可用空间比df -h显示的Avail还多约 5% - 用
lsof +L1或find /proc/*/fd -ls 2>/dev/null | grep deleted找出“看不见却占空间”的已删文件
用 inotifywait 监控关键目录写入暴增(比定时轮询更准)
定时脚本(如每 5 分钟跑一次 df)只能发现“已经满了”,而 inotifywait 可捕获 CREATE、WRITE、MOVED_TO 等事件,在磁盘还没满时就识别出异常写入源。
- 只监控高风险路径:
/var/log、/tmp、/var/lib/docker/overlay2(Docker 主机)、/data(业务数据盘) - 避免递归过深:用
-m(monitor 模式)+--format '%w%f %e'输出路径和事件,配合awk统计单位时间内的写入频次 - 注意权限:若监控目录属其他用户(如
www-data),需用对应用户运行或加sudo,否则收不到事件
inotifywait -m -e create,modify,attrib /var/log --format '%w%f %e' | \
awk '{print $1}' | \
awk '{count[$1]++}
NR%60==0 {for (i in count) if (count[i] > 50) print "ALERT: " i " written " count[i] " times in last minute"; delete count}'Shell 脚本预警阈值设置:别只写 90%,要分层 + 排除临时波动
硬编码 if [ $(df / | awk 'NR==2 {print }' | sed 's/%//') -gt 90 ] 会误报。真实环境必须加缓冲、分等级、跳过瞬时峰值。
- 连续 3 次超过阈值才触发(用文件记录上次时间戳和次数)
- 设置两级阈值:
85%发 warning(钉钉/企业微信),95%发 critical(短信 + 电话) - 排除已知临时目录:比如
/tmp上的systemd-private-*目录,用df --exclude-type=tmpfs或过滤挂载点 - 对 LVM 逻辑卷,用
lvs替代df查看 LV 使用率,避免因 ext4 预分配导致的误判
Python 脚本整合 Prometheus + Alertmanager 实现自动分级告警
纯 Shell 难以支撑多维度判断(如:空间增长速率 + inode 使用率 + 特定目录子目录数量),Python 更适合做决策中枢。
- 用
psutil.disk_usage(path)获取精确字节级数据,比subprocess.run(["df"])更稳定 - 计算 10 分钟内
/var/log的大小变化率:(current_size - size_10m_ago) / 10 / 60,超 5MB/s 触发日志风暴告警 - 暴露为 Prometheus metrics:用
prometheus_client库注册disk_used_percent、inode_used_percent、log_dir_growth_rate_bytes - Alertmanager 配置中用
absent()检测采集中断,避免“假阴性”——磁盘没满,但监控进程挂了
from prometheus_client import Gauge, start_http_server import psutildisk_gauge = Gauge('disk_used_percent', 'Disk usage percent', ['mount']) inode_gauge = Gauge('inode_used_percent', 'Inode usage percent', ['mount'])
def collect_disk_stats(): for part in psutil.disk_partitions(): if part.fstype in ('ext4', 'xfs', 'btrfs'): try: usage = psutil.disk_usage(part.mountpoint) disk_gauge.labels(mount=part.mountpoint).set(usage.percent)
注意:psutil 不直接提供 inode,需调用 os.statvfs
except (OSError, FileNotFoundError): pass真正容易被忽略的,是 保留空间策略与服务身份的错配:比如把
/var/lib/postgresql放在默认保留 5% 的 xfs 分区上,而 PostgreSQL 进程以postgres用户运行(非 root),它就无法使用那 5%,等于实际可用空间直接少了 5%。这种细节,只有查xfs_info和id -u postgres对齐后才能确认。










