问题背景
生产环境 Linux 服务器磁盘告警是运维日常最常见的告警之一。传统经验是「df -h 看满、du -sh 定位大目录」,但真实故障远比这复杂:df 显示 98% 已满,du 却只算出 30GB;删掉 60GB 日志后空间不释放;df -i 100% inode 耗尽,df -h 却显示还有空间;日志轮转配置正确却仍堆积海量小文件……这些「经典但不常见」的场景往往让一线工程师手足无措,排错耗时数小时甚至跨天。
本文基于真实生产案例,提炼出一套可复用的 四步磁盘疑难排查方法论,并附带止血 Checklist 与根治 Checklist。目标是把「碰运气式排查」变成「流程化作战」。
故障现象
典型表现(真实案例汇总):
- df/du 不一致:
df -h显示/已用 98%(仅剩 2GB),但du -sh /*累加只到 30GB,差距 60GB+。 - 删除大文件后空间不释放:
rm -f /var/log/app/*.log后 df 依然 98%,进程仍在向已删除文件句柄写数据。 - inode 耗尽(df -i 100%):
df -h显示还有 40% 空间,但新建文件报「No space left on device」,ls卡死或极慢。 - 日志轮转失效:配置了 logrotate + copytruncate,但 Java 进程仍持续写旧文件,产生海量小文件导致 inode 打满。
- overlay2 /var/lib/docker 目录暴涨:Docker 宿主机 overlay2 目录 inode 耗尽,容器无法创建(df -i 100%)。
这些现象的共同点是:文件系统元数据与用户态视图脱节,需要从内核、进程、文件句柄三个层面同时看。
排查过程
第一步:确认「谁在说谎」——df vs du 差异定位
|
|
关键判断:
- 如果
df -h满但du不满 → 极大概率是「已删除但仍被进程打开的文件」(幽灵文件)。 - 如果
df -i100% 而df -h未满 → inode 耗尽,需重点排查小文件海啸。
第二步:定位幽灵文件(已删除但仍占用空间)
|
|
真实案例:Java 收集进程把 62GB 的旧日志文件句柄一直 hold 着,NLINK=0,truncate -s 0 /proc/<pid>/fd/<fd> 可瞬间释放 60GB 空间。
第三步:inode 耗尽专项排查
|
|
常见根因:
- Docker overlay2 目录海量小文件(容器日志未轮转 + 镜像层未 GC)
/var/spool/clientmqueue(sendmail 队列)/var/log/journalsystemd 日志未限制大小- 应用日志目录缺少 logrotate 或 copytruncate 配置错误
第四步:日志轮转与进程打开文件句柄交叉验证
|
|
解决方案
止血 Checklist(5 分钟内恢复容量)
-
幽灵文件释放(最常见):
1 2 3 4# 找到最大已删除文件句柄 lsof +L1 | awk '$NF ~ /deleted/ {print $(NF-1), $NF}' | sort -k1 -n | tail -5 # 针对性 truncate(零停机) truncate -s 0 /proc/<pid>/fd/<fd> -
inode 紧急释放:
1 2 3 4# 清理 Docker 构建缓存与无用镜像 docker system prune -af --volumes # 清理 systemd journal journalctl --vacuum-time=7d -
临时挂载新盘扩容(当根分区彻底满了):
1 2 3 4mkdir /mnt/tmp mount /dev/nvme1n1p1 /mnt/tmp rsync -aXv /var/log/ /mnt/tmp/log/ mount --bind /mnt/tmp/log /var/log
根治 Checklist(永久杜绝同类问题)
-
日志轮转必须配置 copytruncate(针对 Java/Node 等不重开文件的进程):
1 2 3 4 5 6 7 8 9 10/var/log/app/*.log { daily rotate 30 compress delaycompress missingok notifempty copytruncate # ← 关键 create 0644 app app } -
Docker 日志驱动限制 + 自动清理:
1 2 3 4 5 6 7{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }定时任务:
docker system prune -af --volumes --filter "until=168h" -
监控双指标(df + inode):
- Prometheus node_exporter 同时暴露
node_filesystem_avail_bytes与node_filesystem_files_free - 告警规则:
node_filesystem_avail_bytes / node_filesystem_size_bytes < 0.1或node_filesystem_files_free < 100000
- Prometheus node_exporter 同时暴露
-
sudoers 与应用最小权限收敛(防止误操作导致日志目录 inode 爆满)。
根因分析
根本原因始终是文件系统状态与进程视图不同步:
df读的是 VFS superblock(已分配块数)du读的是目录树(可见文件)- 已删除但仍打开的文件,其 inode 的 block 仍被标记占用,直到最后一个文件描述符关闭
- inode 耗尽是目录项(dentry)与 inode 分配表的资源耗尽,与数据块无关
理解这三层语义,就能解释 90% 的「诡异磁盘问题」。
预防措施
-
部署前 Checklist:
- 所有日志文件必须配置 logrotate(含 copytruncate)
- Docker 宿主机必须限制 json-file 日志大小
- 根分区预留至少 20% 缓冲 + inode 监控
-
变更管理:
- 任何涉及日志目录的变更必须执行
lsof +L1巡检 - 大版本升级前执行
df -i基线比对
- 任何涉及日志目录的变更必须执行
-
可观测性:
- 所有服务器同时上报「磁盘空间使用率」与「inode 使用率」两条曲线
- 建立「df - du 差值」监控面板,差值 > 5GB 立即告警
总结
Linux 磁盘疑难问题从来不是「空间满了」这么简单,而是元数据、进程句柄、日志轮转三者三角关系的失衡。本文提出的四步方法论(确认差异 → 定位幽灵文件 → inode 专项 → 日志轮转验证)已在生产环境多次验证,可将平均排查时间从 2 小时缩短到 15 分钟。
核心口诀:
先看 df -i,再看 lsof +L1,最后看 logrotate 是否真的 copytruncate。
把这套 Checklist 做成运维 SOP,磁盘类 P1 告警即可「按图索骥、分钟级止血」。