华为云 ECS 上 inode 耗尽排查:和磁盘空间已满的区别、定位占用与清理预防
在华为云 ECS 上,程序突然无法创建文件、包管理器报错、网站写不了会话或临时文件,日志里却经常只看到一句 No space left on device。很多人第一反应是看 df -h,结果发现磁盘空间明明还剩不少。这类情况往往不是空间用尽,而是文件系统的 inode 耗尽。inode 是文件系统给每个文件、目录、符号链接分配的索引节点,数量在部分文件系统上是有上限的;上限用尽后,即使还有空闲块,也无法再创建新文件。
下面以华为云 ECS 上常见的 Linux 镜像(Huawei Cloud EulerOS、CentOS、Ubuntu)为例,先把 inode 耗尽和磁盘空间已满分开,再按“确认哪一块分区、找出谁在占用、安全清理、做好预防”的顺序处理。文中的路径都是常见默认位置,动手前先确认本机实际路径,不要直接整目录删除。
一、先分清:空间不足还是 inode 不足
No space left on device 对应错误码 ENOSPC,磁盘块用尽和 inode 用尽都会返回它,单看这句无法判断原因。先同时看空间和 inode:
df -hT
df -iT
两份输出里都要看文件系统类型(Type)和挂载点,不要只看根分区。典型对照如下:
df -h的Use%接近 100%,df -i的IUse%不高:这是空间问题,应找大文件,或排查已删除但仍被进程打开的文件。那是另一类处理方式,本文不展开。df -h仍有空闲空间,df -i的IUse%已经 100% 或非常接近:这就是 inode 耗尽。后面按这个方向查。- 两项都很高:两边都要处理,但创建新文件会立刻失败,可以先把 inode 降下来。
可以再做一个最小验证(请换一个确认可写、且不影响业务的目录):
touch /tmp/inode_probe_$$ && rm -f /tmp/inode_probe_$$
如果这里失败,而 df -h / 仍显示有空闲,先排除目录权限问题,再把它当作该挂载点 inode 不足来排查。若 /tmp 是独立挂载,请对实际报错的那个挂载点做同样测试。
顺便看一眼内核日志,但不要把希望全放在上面:inode 用尽时,内核不一定会打出一条非常醒目的专用记录,仍以 df -i 为准。
sudo dmesg -T | tail -n 50
有两点容易误判,需要先记住:
- inode 是按文件系统统计的,不是整台机器合计。系统盘用尽时,数据盘可能仍正常;反之亦然。必须对着出问题的挂载点看。
- XFS 的 inode 是按需从空闲空间里动态分配的,
df -i里的可用数量是估算值,含义和 ext4 不完全一样。EulerOS、CentOS 的部分镜像会用 XFS,Ubuntu 系统盘多为 ext4。先看df -T,再解释IUse%。XFS 上更常见的是空间先不够;ext4 上“空间还多、inode 先满”更典型。
二、找出哪些目录在消耗 inode
du 默认按磁盘占用(字节)排序,海量小文件往往占不了多少空间,却会吃掉大量 inode,所以不要用 du -sh 来定位这个问题。GNU du 提供了按 inode 计数的参数,EulerOS、CentOS 7 及以后、Ubuntu 上一般都可用:
# -x 表示不跨越文件系统,避免把数据盘、容器挂载或远程盘算进系统盘
sudo du --inodes -x -d 1 / 2>/dev/null | sort -n
哪个顶级目录数量突出,就再往下钻一层,例如:
sudo du --inodes -x -d 1 /var 2>/dev/null | sort -n
sudo du --inodes -x -d 1 /var/lib 2>/dev/null | sort -n
du --inodes 给出的是目录树合计,适合先圈定范围。若要找出“单个目录里堆了多少文件”(会话目录、缓存目录常常是这一种),可以用:
# 按“父目录里有多少文件”排序,只统计文件,目录本身另计
# 先把范围限定在上一步圈出的目录,避免对整盘做全量 find
sudo find /var -xdev -type f -printf '%h\n' 2>/dev/null \
| sort | uniq -c | sort -nr | head -n 20
这条命令会遍历文件元数据,在文件非常多的盘上会有明显 I/O。建议先用 du --inodes 缩小范围,再对可疑目录执行;生产环境尽量选业务低峰,也可以加上 ionice -c3 降低 I/O 优先级:
sudo ionice -c3 find /var/lib -xdev -type f -printf '%h\n' 2>/dev/null \
| sort | uniq -c | sort -nr | head -n 20
如果 du --inodes 不可用,可以用下面的备选方式统计某个挂载点下一层目录的条目数(同样不要漏掉 -xdev):
for d in /var /var/log /var/lib /tmp /home /usr; do
[ -d "$d" ] || continue
printf '%s ' "$d"
sudo find "$d" -xdev \( -type f -o -type d -o -type l \) 2>/dev/null | wc -l
done
常见占用 inode 的位置包括:
- PHP 会话文件:Ubuntu / Debian 多为
/var/lib/php/sessions,EulerOS / CentOS 多为/var/lib/php/session(少一个 s),也有按版本分子目录的情况,以本机实际路径为准; - 应用自己写的缓存、缩略图、上传临时文件、按请求落地的小文件;
/tmp、/var/tmp里长期无人清理的临时文件;- 邮件队列:
/var/spool下的积压; - 软件包缓存:Ubuntu 的
/var/cache/apt,EulerOS / CentOS 的/var/cache/yum或/var/cache/dnf(这项更常表现为占空间,但小文件多时也会推高 inode); - 在系统盘上直接展开的
node_modules、多次重复解压的代码目录。
先看数量和文件年龄,再决定能不能删。不要对 du 结果里的目录直接 rm -rf。
三、安全清理:先确认,再删过期文件
清理前建议先记录当前状态,便于对照:
date
df -hT
df -iT
下面几种是比较常见、也相对好处理的来源。原则都一样:先 ls 或 find 看样本和数量,确认是过期或可重建的缓存后,再按时间条件删除。
1. PHP 会话文件
# Ubuntu / Debian
sudo find /var/lib/php/sessions -xdev -type f 2>/dev/null | wc -l
# EulerOS / CentOS
sudo find /var/lib/php/session -xdev -type f 2>/dev/null | wc -l
若数量达到几十万、上百万,多半是会话回收没有在跑(例如 session.gc_probability 为 0,且系统自带的清理定时任务被关掉)。先按时间查看,确认没有把仍在使用的会话一并清掉;下面以“超过 1 天”为例,路径请改成上一步确认过的目录:
# 先看数量,不要直接 -delete
sudo find /var/lib/php/sessions -xdev -type f -name 'sess_*' -mtime +1 | wc -l
# 确认无误后再删除
sudo find /var/lib/php/sessions -xdev -type f -name 'sess_*' -mtime +1 -delete
EulerOS / CentOS 上把路径换成 /var/lib/php/session。删完再执行一次 df -i,确认 IUse% 已经下降。Ubuntu 通常会通过 /usr/lib/php/sessionclean 做定期清理,EulerOS / CentOS 则可能依赖 systemd-tmpfiles 或 cron,清理后应检查这些任务是否还在。
2. 临时目录
sudo find /tmp /var/tmp -xdev -type f -mtime +7 | head
sudo find /tmp /var/tmp -xdev -type f -mtime +7 | wc -l
确认没有业务进程把重要文件放在这里之后,再删除过期文件。不要删除 /tmp 里仍被使用的套接字或锁文件。对不确定的文件,可以先移到一个临时目录观察,而不是直接删。
3. 软件包缓存
# Ubuntu
sudo apt-get clean
# EulerOS / CentOS 7
sudo yum clean all
# 使用 dnf 的 EulerOS / CentOS 8 及更新版本
sudo dnf clean all
4. 容器镜像层(如有)
容器运行时的存储目录里经常有大量小文件。如果确认占用主要来自 Docker 或同类运行时,应使用它们自己的清理命令(例如删除无用镜像、停止容器后的残留),不要手动删除 overlay2 或类似底层目录,否则容易把正在运行的容器写坏。这和“容器日志把磁盘写满”不是同一类问题:这里关注的是文件个数,不是某个 json-file 日志的体积。
清理后再次检查:
df -iT
touch /tmp/inode_probe_$$ && rm -f /tmp/inode_probe_$$
如果 IUse% 已经明显下降,并且可以重新创建文件,说明处理方向是对的。若数量降不下来,回到第二节,检查是否找错了挂载点,或者文件已被删除、inode 仍被进程占着。
四、已删除但仍被进程占用的 inode
文件被删除后,目录项会消失,du 和普通 find 都看不见它;只要还有进程打开着这个文件,对应的 inode 和数据块就不会释放。这在“磁盘空间对不上”时更常见,但打开的文件足够多时,同样会推高 df -i。
# 列出已删除但仍被打开的文件
sudo lsof +L1
若没有 lsof,可以按进程检查:
sudo bash -c 'for fd in /proc/[0-9]*/fd/*; do
link=$(readlink "$fd" 2>/dev/null) || continue
case "$link" in
*"(deleted)") echo "$fd -> $link" ;;
esac
done'
处理方式是重启或结束那个仍占着文件的进程,让内核回收 inode。不要去删 /proc 里的描述符,也不要在未确认的情况下对日志文件使用 truncate,以免搞乱正在写入的服务。释放后应看到 df -i 和 df -h 同时变化。
五、预防:监控 inode,并避免在系统盘堆积小文件
空间告警覆盖不到这类故障。华为云监控等产品默认的磁盘指标通常看的是容量使用率,根分区还有 30% 空间时,inode 也可能已经用尽。建议在 ECS 上单独检查 IUse%,例如每天记录一次,或在超过 80% 时告警:
#!/bin/bash
# 根据实际挂载点修改;阈值可按业务调整
threshold=80
df -P -i | awk -v th="$threshold" 'NR>1 && $1 !~ /^(tmpfs|devtmpfs|overlay)$/ {
use=$5; gsub("%","",use)
if (use+0 >= th) printf "inode high: %s %s IUse=%s\n", $1, $6, $5
}'
可以把它放到 cron 里,输出转到现有的监控或邮件通道。检查多个挂载点时,系统盘和数据盘都不要漏。
从源头上减少海量小文件,比事后清理更稳妥:
- 让 PHP 或其他会话机制的过期回收真正跑起来,不要只靠业务流量偶然触发 GC;
- 应用缓存设置目录上限和过期时间,避免按请求无限落地小文件;
- 临时文件写到
/tmp或专门的缓存目录,并靠 systemd-tmpfiles 或 cron 定期清理; - 大量小对象如果只是存储、不需要在本地 POSIX 语义下打开,可以放到对象存储,而不是堆在系统盘。
如果业务本身就必须在本地存放大量小文件,不要指望只靠给云硬盘扩容来彻底解决问题。扩容并扩展文件系统时,ext4 在新增块组时通常会带上一部分新的 inode,IUse% 有可能下降,但这既不能替代清理,也不能改变原有块组里已经定死的 inode 密度。ext4 的 inode 数量在格式化时就按“每多少字节对应一个 inode”算好了,已有文件系统无法在线改这个比例。新建数据盘时可以按文件大小预先规划,例如:
# 先确认设备名和是否为新盘,确认盘上没有需要保留的数据
# 下面只是格式化示例,设备名必须换成实际值
sudo mkfs.ext4 -i 4096 /dev/vdX
-i 4096 表示大约每 4 KiB 分配一个 inode,比常见默认值 16384 更适合大量小文件,代价是 inode 表本身会多占一些空间。如果文件普遍很大,反而应该用更大的 -i,避免浪费。格式化前必须确认设备名和盘内数据,做过格式化就无法直接恢复。
XFS 一般不需要靠调 inode 密度来应对“小文件很多”的场景,但仍然要保证有足够的空闲空间,并避免在单目录下堆积千万级条目——目录项过大同样会影响创建和查找性能。
六、几点注意事项
- 全程带上
-xdev或du -x,不要从根目录一路走进数据盘、容器存储或远程挂载。 - 先统计、后删除。会话、缓存可以按过期时间清;源代码、上传目录、数据库目录不能按“文件多”来删。
- 不要手动改动容器存储驱动的底层目录,也不要在
/proc、/sys里做清理。 - 清理后不仅看
df -i,还要确认业务进程能够重新创建文件,并检查定时清理任务是否还在生效,否则过几天会再次耗尽。 - 已有 ext4 分区无法通过简单改配置来增加 inode 密度。需要更多 inode 时,优先清理和把小文件迁到单独规划过的数据盘,而不是反复把系统盘扩得很大。
按这个顺序处理,可以把“空间还在、文件却创建失败”的问题从误判里分开,并在下次发生前先看到 IUse% 的爬升。
- 点赞
- 收藏
- 关注作者
评论(0)