CentOS服务器突然报错“No space left on device”,但用df -h查看磁盘空间明明还有大量剩余,这种情况十有八九是inode耗尽了。inode是文件系统用来存储文件元数据的结构,每个文件、目录、软链接都会消耗一个inode。即使磁盘空间充足,一旦inode数量用尽,系统就无法再创建任何新文件或目录。

这个问题在CentOS系统中尤为常见,因为默认的ext4或xfs文件系统在格式化时会根据分区大小自动计算inode数量。如果你的服务器上存在海量小文件,比如邮件队列、缓存文件、日志切片、PHP session文件等,inode耗尽只是时间问题。更隐蔽的是,某些程序在inode耗尽后并不会明确提示原因,而是直接抛出模糊的系统错误,让排查变得困难。

如何快速判断是不是inode耗尽

当你遇到无法创建文件的情况时,第一步就是确认inode使用情况。执行以下命令:

df -i

这条命令会列出所有挂载分区的inode总数、已用数量、可用数量和使用百分比。如果某个分区的IUse%达到100%,那问题根源就是inode耗尽。与之对比,df -h显示的是磁盘空间使用率,两者是完全独立的资源。

还有一个更直接的验证方法,尝试手动创建一个空文件:

touch /tmp/test_file

如果返回“No space left on device”,但df -h显示磁盘有空间,那基本可以锁定是inode问题。此时你甚至无法创建目录、无法写入日志,SSH连接可能也会受到影响,因为sshd需要创建临时文件。

找出吃掉inode的元凶

确认inode耗尽后,下一步就是定位哪些目录占用了大量inode。使用以下命令可以按目录统计inode使用量:

for i in /*; do echo $i; find $i -type f 2>/dev/null | wc -l; done

这个脚本会遍历根目录下的每个一级目录,统计其中包含的文件数量。执行后你会看到类似这样的输出:

/bin
1523
/boot
327
/dev
0
/etc
18472
/home
89
/var
3847291
/tmp
1203847

很明显,/var和/tmp目录下的文件数量异常高。接下来可以进一步深入这两个目录,逐层排查:

find /var -type f | wc -l
du --inodes -d 2 /var | sort -rn | head -20

du --inodes这个参数非常实用,它能以inode数量而非磁盘空间大小来排序,快速定位到问题目录。通常你会发现以下目录是inode消耗大户:

/var/spool/postfix/maildrop - Postfix邮件队列堆积

/var/spool/clientmqueue - sendmail的邮件队列

/var/lib/php/session - PHP会话文件

/tmp - 各类临时文件

/var/cache - 应用程序缓存

/var/log/journal - systemd日志的二进制文件

清理邮件队列释放inode

邮件队列是inode耗尽的头号元凶之一。如果服务器上运行着Postfix或sendmail,大量未发送的邮件会堆积在队列目录中。检查邮件队列大小:

postqueue -p | tail -1

如果显示有数千封邮件在队列中,可以批量清理。先查看队列中有哪些邮件:

postqueue -p

然后根据情况选择清理方式。删除所有队列中的邮件:

postsuper -d ALL

如果只想删除特定发件人的邮件:

mailq | grep "user@domain.com" | awk '{print $1}' | postsuper -d -

对于sendmail,清理队列的命令是:

rm -rf /var/spool/clientmqueue/*

清理完成后,再次用df -i确认inode使用率是否下降。如果邮件队列不是必须的服务,建议直接停用或配置邮件中继,避免问题复发。

处理PHP Session文件堆积

PHP默认的session存储方式是文件,每个访客会话都会在/tmp或/var/lib/php/session目录下创建一个session文件。如果PHP的垃圾回收机制配置不当,这些文件会无限累积。一个日活较高的网站,几天内就能产生数十万甚至上百万个session文件。

查看session目录的文件数量:

ls -1 /var/lib/php/session | wc -l

如果数量惊人,可以手动清理过期的session文件。PHP的session文件过期时间由session.gc_maxlifetime控制,默认是1440秒(24分钟)。但PHP的垃圾回收是概率触发的,不一定及时执行。可以手动删除修改时间超过24小时的文件:

find /var/lib/php/session -type f -mtime +1 -delete

如果session目录文件数量实在太大,find命令可能会很慢,可以使用更高效的方式:

cd /var/lib/php/session && ls -f | head -100000 | xargs rm -f

长期解决方案是调整PHP配置,在php.ini中设置:

session.gc_probability = 1
session.gc_divisor = 100
session.gc_maxlifetime = 1440

这样每次请求有1%的概率触发垃圾回收。对于高流量站点,还可以考虑将session存储切换到Redis或Memcached,从根本上解决文件型session的inode消耗问题。

清理systemd日志文件

CentOS 7及更高版本使用systemd-journald管理日志,它的二进制日志文件存储在/var/log/journal目录下。与传统的文本日志不同,journald会产生大量小文件,长期运行后可能占用数十万个inode。

查看journal日志占用的磁盘空间和文件数量:

journalctl --disk-usage
find /var/log/journal -type f | wc -l

清理journal日志可以按时间或大小限制:

journalctl --vacuum-time=7d
journalctl --vacuum-size=500M

第一条命令保留最近7天的日志,第二条限制日志总大小不超过500MB。也可以直接删除整个journal目录(需要重启systemd-journald服务):

rm -rf /var/log/journal/*
systemctl restart systemd-journald

如果不需要持久化日志,可以配置journald仅使用内存存储。编辑/etc/systemd/journald.conf:

Storage=volatile
RuntimeMaxUse=100M

然后重启服务:

systemctl restart systemd-journald
清理/tmp目录的临时文件

/tmp目录是另一个inode消耗重灾区。很多程序会在这里创建临时文件,但并不是所有程序都会主动清理。可以设置定时任务自动清理过期文件:

find /tmp -type f -atime +7 -delete
find /tmp -type d -empty -delete

更稳妥的做法是配置tmpwatch或systemd-tmpfiles来管理。CentOS默认安装了tmpwatch,可以手动执行:

tmpwatch --mtime --all 240 /tmp

这条命令会删除/tmp下修改时间超过240小时的文件。要设置自动清理,创建/etc/cron.daily/tmp-clean:

#!/bin/bash
/usr/sbin/tmpwatch --mtime --all 168 /tmp

赋予执行权限:

chmod +x /etc/cron.daily/tmp-clean
处理大量小文件的通用策略

如果inode消耗来自业务数据本身,比如图片服务器上存储了海量缩略图、日志服务器上的细粒度日志文件,就需要从架构层面解决。一种有效的方式是将多个小文件打包存储,比如使用tar归档、SQLite数据库或对象存储来替代直接的文件系统存储。

对于必须使用文件系统的场景,可以考虑在格式化分区时手动指定inode大小。使用mkfs.ext4的-i参数可以设置每个inode对应的字节数:

mkfs.ext4 -i 4096 /dev/sdb1

这个例子中每4096字节分配一个inode,比默认的16384更密集,能创建更多inode。但要注意,更小的-i值意味着inode表本身会占用更多磁盘空间。对于xfs文件系统,inode是动态分配的,默认情况下inode数量受限于分区的25%空间,通常不会耗尽,但如果确实遇到了,可以在挂载时使用imaxpct参数调整:

mount -o imaxpct=50 /dev/sdb1 /data

这允许inode占用最多50%的分区空间。

建立inode监控告警机制

解决当前问题只是第一步,防止复发同样重要。建议在监控系统中添加inode使用率的监控项。如果你使用Zabbix,可以添加自定义监控项:

UserParameter=inode.use[*],df -i $1 | tail -1 | awk '{print $$5}' | tr -d '%'

在Nagios或Icinga中,check_disk插件默认就支持inode检查:

check_disk -w 15% -c 10% -p / -W 80% -K 90%

其中-W和-K参数分别对应inode的警告和临界值。对于使用Prometheus的现代化环境,node_exporter会自动暴露inode指标,可以直接配置告警规则。

如果没有专业监控工具,一个简单的cron脚本也能起到预警作用:

#!/bin/bash
INODE=$(df -i / | tail -1 | awk '{print $5}' | tr -d '%')
if [ $INODE -gt 90 ]; then
    echo "Inode usage is at ${INODE}%" | mail -s "Inode Warning" admin@example.com
fi
特殊情况:inode耗尽导致服务无法启动

如果inode已经100%耗尽,某些服务可能无法正常启动,因为启动过程需要创建PID文件或临时文件。此时需要先手动释放少量inode。可以删除一些确定不需要的文件,或者清空某些日志文件(注意是清空不是删除,删除文件需要创建目录条目,而清空不需要额外inode):

cat /dev/null > /var/log/some-large-log.og

或者找到一些可以安全删除的空文件、过期缓存:

find /tmp -type f -size 0 -delete
find /var/cache -type f -name "*.cache" -delete

释放出少量inode后,立即启动关键服务,然后按照前面的步骤系统性地清理。

总结排查流程

面对CentOS系统inode耗尽问题,标准处理流程是:先用df -i确认inode使用情况,然后用du --inodes逐层定位问题目录,根据目录类型采取对应清理措施,最后建立监控防止复发。整个过程不需要重启服务器,对业务影响可以降到最低。

inode耗尽虽然不像磁盘空间满那样常见,但一旦发生,排查难度更高,因为它的表象和磁盘满完全一样。把inode监控纳入日常运维体系,是每个CentOS服务器管理员的必修课。