磁盘还有 40% 却写不进文件?inode 耗尽与句柄泄漏排查实录

举报
yd_243555368 发表于 2026/11/08 09:05:48 2026/11/08
【摘要】 df -h 显示还剩 20G,应用却持续报 No space left on device。本文从 df -i 定位 inode 耗尽、用 lsof +L1 揪出已删除未释放的幽灵文件,再带出 Too many open files 的句柄泄漏排查,给出可直接复制的命令、清理脚本与四项根治措施。

故障现场:磁盘还有 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 才可写

根治措施,别让它明天再犯

  1. 把 inode 加进监控:df -i 的 IUse% 超过 80% 就告警,比容量告警更早发现问题。
  2. 日志必须走轮转:logrotate 配 copytruncate 或让应用支持 reopen 信号,杜绝 rm 正在写的文件。
  3. 临时目录加清理策略:/tmp、上报队列、session 目录统一走定时清理脚本,按文件数与年龄双阈值。
  4. 版本化告警脚本:把 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",比反复重启服务高效得多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。