df -h 还剩 40%,创建文件却报 No space left on device:inode 耗尽排查

举报
yd_271851511 发表于 2026/11/04 10:10:41 2026/11/04
【摘要】 磁盘用了 60%、空间充足,touch 一个文件却报 No space left on device。df -i 一看 inode 已经 100%。这次是小文件堆积加上 tmpwatch 只删内容不删目录导致的,本文给出定位路径、两类不能直接删的目录,以及一个可以提前告警的指标。

报错是这样的:


  $ df -h /data

  Filesystem Size Used Avail Use% Mounted on

  /dev/vdb1 500G 300G 200G 60% /data


  $ touch /data/test

  touch: cannot touch '/data/test': No space left on device


空间明明有 200G。这种时候第一个要看的不是 df -h,是 df -i。


  $ df -i /data

  Filesystem Inodes IUsed IFree IUse% Mounted on

  /dev/vdb1 32768000 32768000 0 100% /data


inode 满了。


inode 是什么


ext4/xfs 在格式化的时候就把 inode 数量定死了,每个文件(包括目录、符号链接、socket)占一个 inode。inode 里存的是元数据:权限、属主、时间戳、数据块指针,唯独不存文件名。文件名在目录项里。


所以"空间够但 inode 不够"意味着:文件太多,而不是文件太大。典型的 inode 杀手有这么几类:


- 会话文件。PHP 的 session、Java 的临时上传目录,一个请求一个文件。

- 邮件队列。postfix 的 queue/active、deferred 下面动辄百万级小文件。

- 日志按小时切分且不清理,一年就是 8760 个文件乘以目录数。

- 缓存目录。我们这次就是这个:一个图片处理服务把缩略图按 md5 前两位建了两级目录,每个目录下几十万个小文件。

- 还有一种最坑的:删了文件但进程还持有 fd,inode 不会释放(这个用 df -h 能看出来,用 df -i 也能看出来,但 lsof 才找得到是谁)。


这次的具体情况


  $ find /data/cache -type f | wc -l

  29814332


三千万个文件,平均大小 9KB,总共 260GB。业务上这些缓存本来有 7 天过期,清理脚本用的是:


  find /data/cache -type f -mtime +7 -delete


看起来没问题,但有两个漏洞:


1. 只删文件不删空目录。每个 md5 前缀目录即使清空了也还在,目录本身也占 inode。三千万文件对应大约 6.5 万个目录,这部分不是主因,但会累积。

2. 更严重的是清理速度跟不上产生速度。业务高峰期每秒新建 300 个文件,清理脚本单线程每秒只能删 200 个,净增长。周末流量翻三倍,两天就攒了几百万。


怎么快速定位是哪个目录


  # 按 inode 数量排序,找出贡献最大的目录

  for d in /data/*/; do echo "$(find "$d" -xdev -type f 2>/dev/null | wc -l) $d"; done | sort -rn | head


这个命令在千万级文件上会很慢(我们跑了 20 分钟)。更快的办法是 du 的 --inodes:


  du --inodes -d 2 /data 2>/dev/null | sort -rn | head -20


du --inodes 走的是目录遍历但不 stat 每个文件,快一个数量级。xfs 上不支持 --inodes,得用 xfs_quota 或者直接 find 分批统计。


第二种情况:文件删了 inode 没释放


如果 df -i 显示 IUsed 很高,但 find 数出来的文件数远小于 IUsed,那就是有进程持着已删除文件的 fd:


  $ lsof +L1 | head

  java 12345 app 312r REG 253,17 10485760 0 1234567 /data/logs/app.log (deleted)


这种情况 df -h 也会显示空间被占,两个指标一起异常。处理方式是把持有 fd 的进程重启,或者清空 /proc/<pid>/fd 里对应的句柄(不推荐,容易把进程搞崩)。我们之前遇到过一次是 logrotate 用了 copytruncate,Java 进程的 fd 还指着老 inode,日志一直往"已删除"的文件里写,直到重启才释放。


清理时要注意的三件事


1. 不要用 rm -rf 直接怼百万级目录。rm 是同步的,一个目录几百万文件要跑几个小时,期间 IO 打满。用 rsync 的空目录同步法更快:


     mkdir /tmp/empty

     rsync -a --delete /tmp/empty/ /data/cache/01/


   rsync 的删除是批量 unlink,比 rm 快 3~5 倍。


2. 删之前确认没有进程在用。缓存目录一般没事,但如果是 tmp 目录,可能有正在上传的临时文件,删了会让业务报错。用 lsof +D 检查(慢,慎用)或者按文件 mtime 过滤。


3. 目录本身也要清。批量删完之后:


     find /data/cache -type d -empty -delete


   注意 -delete 对非空目录会失败并继续,是安全的。


根治


- 扩容 inode。ext4 可以在 mkfs 时用 -i 指定 bytes-per-inode,默认 16384(16KB 一个 inode),改成 4096 就是四倍 inode。但这是格式化时定的,线上改不了,只能换盘重建。xfs 是动态分配 inode 的,没有这个问题,所以新盘我们统一用 xfs。

- 换存储结构。三千万小文件不该放在本地文件系统。我们把缩略图迁到了 OBS,本地只留最近 24 小时的热数据,inode 占用从 100% 降到 12%,而且不再需要自己写清理脚本——桶上配个生命周期规则就行。

- 清理脚本改成多线程 + 限速,并且加一条自监控:每次跑完记录删除数量,如果连续两次删除数小于新建数就告警。


一个值得提前加的监控


node_exporter 的 node_filesystem_files_free 和 node_filesystem_files 两个指标一除就是 inode 使用率,告警阈值建议设在 80%。这个指标比磁盘使用率更早报警——我们这次磁盘才 60%,看起来非常健康,实际上已经写不进任何文件了。


顺带说一句,容器环境里 inode 更容易出问题:多个 Pod 共享节点上的同一个 overlay 文件系统,任何一个容器刷小文件都会把整台节点的 inode 吃光,表现是**同节点上所有 Pod 一起报 No space left**,但每个 Pod 自己看 df 都很正常。这种时候要去节点上执行 df -i,而不是进容器看。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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