一、最诡异的报错:磁盘明明还有空间

IDC 运维最怕遇到一类问题:df -h 一看磁盘还剩几十个 G,可程序一写文件就报 No space left on device(设备上没有剩余空间)。第一次遇到的运维往往会懵:空间明明够啊,怎么会写不进去?其实答案往往只有一个——inode 耗尽了

这类事故在邮件服务器、缓存服务器、session 集中存储的机器上尤其高发。本文从原理到实战,讲清楚 inode 是什么、怎么定位、怎么清理、怎么根治。

二、inode 是什么:文件系统的"户口本"

Linux 文件系统(ext4/xfs)在格式化时会把存储空间分成两大部分:一部分存数据块(文件内容),一部分存 inode(索引节点)。每个文件或目录都必须占用一个 inode,里面记录着文件的权限、属主、大小、时间戳以及数据块指针。可以粗暴地理解为:inode 是文件的"户口本",没有户口本,内容再多也落不了地

关键点在于:inode 的数量在格式化分区时就固定了(ext4 默认每 16KB 数据空间分配一个 inode)。也就是说,一个分区能存放的文件总数是写死的。如果你创建了海量的小文件(比如几百万个几 KB 的缓存文件),哪怕总容量只用了 1%,inode 也可能先被耗光。

查看 inode 使用情况的命令:

# 查看各分区 inode 使用率(IUse% 列就是 inode 使用率)
df -i

# 只看根分区
df -i /

# 查看文件系统详细 inode 信息
tune2fs -l /dev/sda1 | grep -i inode

三、快速定位:三分钟确认是不是 inode 问题

遇到"空间充足却写不进文件",按下面顺序排查:

# 第一步:确认空间确实够
df -h

# 第二步:查看 inode 使用率——重点看 IUse% 是否接近 100%
df -i

# 第三步:确认当前文件系统类型
stat -f /

如果 df -i 显示 IUse% 已经是 100%,且 Filesystem 对应挂载点就是你写文件的目录所在分区,那基本可以实锤:inode 耗尽。此时可以顺手确认一下是哪个目录在疯狂吃 inode。

四、谁在疯狂消耗 inode?三大经典元凶

元凶一:邮件队列堆积

邮件服务器(Postfix/Exim)投递失败时,退信会堆积在 /var/spool/postfix//var/spool/mqueue/ 下,一封邮件一个文件。被垃圾邮件轰炸时,几小时就能攒出几十万个小文件,直接把 inode 打满。

元凶二:缓存/临时目录的小文件

PHP 的 session 文件默认存在 /var/lib/php/sessions/,一个会话一个文件;Nginx 的 proxy_cache、fastcgi_cache 也会生成海量缓存碎片;/tmp 目录如果被程序滥用同样危险。这些目录是 inode 消耗的"重灾区"。

元凶三:日志未轮转

某些程序(尤其是自己写的脚本)往固定文件名里追加日志却从不切割,日志文件本身只有一个 inode,这倒不消耗 inode——但要注意,如果程序按时间戳每天新建一个日志文件且从不清理,几个月下来同样会累积几万个小文件。

五、清理实战:找到元凶并干掉它

定位哪个目录文件最多:

# 统计各一级目录下的文件数量(慎用,全盘扫描较慢)
for d in /*; do echo "$d: $(find $d -type f 2>/dev/null | wc -l)"; done

# 更精准:直接统计常见重灾区
find /var/spool -type f 2>/dev/null | wc -l
find /var/lib/php/sessions -type f 2>/dev/null | wc -l
find /tmp -type f 2>/dev/null | wc -l

# 找出某个目录下最老的文件,确认是否还能清理
find /var/lib/php/sessions -type f -mtime +7 | head -20

确认后按需清理,以 session 目录为例:

# 删除 7 天前的 session 文件
find /var/lib/php/sessions -type f -mtime +7 -delete

# 删除邮件队列中所有退信(先备份再删更稳妥)
find /var/spool/postfix/deferred -type f -mtime +3 -delete

# 清理 /tmp 下 3 天前的临时文件
find /tmp -type f -mtime +3 -delete

清理完再执行 df -i,确认 IUse% 降下来后,业务即可恢复写入。

六、根治方案:别等爆了才处理

方案一:建分区时规划好 inode 数量

对于已知会存海量小文件的分区,格式化时可以显式指定 inode 密度:

# ext4:每 8KB 数据分配一个 inode(默认一般是 16KB)
mkfs.ext4 -i 8192 /dev/sdb1

# xfs:按空间比例指定 inode 数量(每 512B 一个 inode 块)
mkfs.xfs -i size=512 -d agcount=4 /dev/sdb1

方案二:把"小文件重灾区"单独分区

把 session、缓存、邮件队列这类目录单独挂一个分区,并配大 inode 密度,即使爆了也只影响该分区,不会拖垮根分区和业务数据。

方案三:定时巡检脚本

#!/bin/bash
# /usr/local/bin/check_inode.sh
USE=$(df -i / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USE" -gt 85 ]; then
    echo "$(date '+%F %T') 警告: 根分区 inode 使用率已达 ${USE}%" >> /var/log/inode_warn.log
    # 可在此追加告警:mail/sms/webhook
fi

配合 crontab 每 10 分钟跑一次,做到"爆之前先报警"。

七、小结

inode 耗尽属于典型的"低频高杀伤"故障:平时遇不到,一遇到就是生产事故。核心记住三点:df -i 看 inode、小文件目录是重灾区、格式化时规划好 inode 密度。把这套排查思路记熟,下次再看到"No space left on device"就能三分钟定位、十分钟解决。