磁盘还有 40% 却写不进文件?inode 耗尽与句柄泄漏排查实录
故障现场:磁盘还有 40%,应用却一直报 No space left on device
凌晨两点,监控告警:某业务节点上的日志采集组件批量重启,应用侧连续抛 No space left on device。登录机器第一反应是看容量:
df -h
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 50G 28G 20G 59% /
还剩 20G,59% 使用率,看起来完全正常。可 touch /var/log/test.log 依然报错。这时候很多人的第一反应是"文件系统坏了",实际上绝大多数情况是两个更朴素的原因:inode 耗尽,或者 文件句柄被删了但没释放。下面把这两条路拆开讲,都是可以直接复现、直接动手的命令。
第一条线索:df -i 看 inode,而不是 df -h
df -h 统计的是块(block)使用量,而 Linux 的 ext4/xfs 文件系统会预先分配固定数量的 inode,每个文件(哪怕 0 字节)占用一个。inode 用完了,即使有几十 G 空闲块,也创建不了新文件。
df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 3276800 3276800 0 100% /
IUse% 100%,问题定位。典型成因是某个目录堆积了海量小文件:session 文件、未轮转的日志碎片、缓存目录、上报队列的临时文件。
定位到具体目录
# 按目录统计 inode 数量,找出小文件最多的 20 个目录
for d in /*; do
[ -d "$d" ] && echo "$(find "$d" -xdev -type f 2>/dev/null | wc -l) $d"
done | sort -rn | head -20
# 更精确:进入可疑目录后逐层下钻
du -a --inodes /var 2>/dev/null | sort -rn | head -20
本次故障是 /var/spool/postfix/deferred 下有 300 多万个邮件队列碎片,单文件几百字节。确认原因后按天清理:
# 清掉 7 天前的失败队列(先统计再删,别上手就 rm)
find /var/spool/postfix/deferred -type f -mtime +7 | wc -l
find /var/spool/postfix/deferred -type f -mtime +7 -delete
# inode 立刻回落到可用
df -i /
要提醒一句:-delete 在前台删除几百万文件会长时间占用 IO,生产上建议分批,避免把磁盘 util 打满:
find /path -type f -mtime +7 -print0 | xargs -0 -n 5000 -P 2 rm -f
第二条线索:lsof +L1 揪出"已删除但未释放"的文件
另一种诡异情况:du -sh 汇总只有 20G,df 却显示 45G,对不上。差额通常就藏在"已被 unlink、但仍被进程持有句柄"的文件里——典型的日志误删:运维 rm 掉了正在写的 app.log,进程继续往这个 inode 写,空间不会归还。
# 列出所有被删除但仍被打开的文件(L1 = link count 已为 0)
lsof +L1 2>/dev/null | head -20
# 或
lsof -nP | grep '(deleted)' | sort -k7 -rn | head
输出里有进程号就直接定位到持有者。恢复空间只有两条路:让进程重新打开文件(重启或 kill -HUP、reopen 信号),或者用 /proc 里还活着的 fd 把内容捞回来再清空:
# 方式一:先安全截断,再让进程重启,避免丢日志
: > /proc/12345/fd/3
# 方式二:抢救内容后再处理
cp /proc/12345/fd/3 /tmp/rescued.log
注意 : > /proc/PID/fd/N 只是把 inode 内容置空,空间当场归还,前提是进程用追加模式写,否则文件偏移会导致空洞。
顺带排查:句柄泄漏(Too many open files)
同一类问题还有一个变种:open files 打满。现象是 accept 失败、连接建不上,报 EMFILE。三条命令定位:
# 1. 系统级上限与当前使用
cat /proc/sys/fs/file-nr # 已分配 / 空闲 / 最大
ulimit -n # 当前进程软限制
# 2. 谁占得最多
lsof -nP | awk '{print $1}' | sort | uniq -c | sort -rn | head
# 3. 单进程看 fd 明细
ls -l /proc/$(pgrep -f myapp | head -1)/fd | wc -l
如果是容器环境,还要注意 nofile 是 per-container 的,宿主机改大不等于容器里改大,需要在编排配置里显式声明:
# docker
docker run --ulimit nofile=65535:65535 ...
# k8s
securityContext:
# 通过 initContainer 或 nodes 的 sysctl 调整,容器内需用 privileged 或 hostPID 才可写
根治措施,别让它明天再犯
- 把 inode 加进监控:
df -i的 IUse% 超过 80% 就告警,比容量告警更早发现问题。 - 日志必须走轮转:logrotate 配
copytruncate或让应用支持 reopen 信号,杜绝 rm 正在写的文件。 - 临时目录加清理策略:
/tmp、上报队列、session 目录统一走定时清理脚本,按文件数与年龄双阈值。 - 版本化告警脚本:把
df -h、df -i、lsof +L1三条命令做成一个巡检脚本,出问题时一把梭出结论,省掉凌晨瞎猜的时间。
#!/bin/sh
# disk_health.sh —— 一分钟定位磁盘类故障
echo "== 容量 ==" && df -h | awk 'NR==1||$5+0>80'
echo "== inode ==" && df -i | awk 'NR==1||$5+0>80'
echo "== 已删未释放 ==" && lsof +L1 2>/dev/null | awk 'NR>1{print $1,$2,$7,$NF}' | sort -k3 -rn | head -10
磁盘问题看着唬人,实际就这三板斧:df -h 看块、df -i 看 inode、lsof +L1 看幽灵文件。三条命令覆盖了九成以上的 "No space left on device",比反复重启服务高效得多。
- 点赞
- 收藏
- 关注作者
评论(0)