磁盘还有空间却报 No space left on device?可能是 inode 用完了
磁盘还剩 80% 的空闲,df -h 看着一切正常,可一写入文件就报 No space left on device。写个小文件不行,重启服务报错,连 touch 都失败——这是每个运维迟早会撞上的一次「空间谜案」。问题多半不在容量,而在 inode。
Linux 文件系统里有两套互相独立的上限:一套管数据块(block),一套管 inode。df -h 只看第一套,所以你看到还有空间;df -i 看的是第二套,它可能早就顶到了 100%。这篇文章先讲清两者的区别,再教你怎么定位是哪个目录吃光了 inode、怎么安全清掉、怎么让它不再复发。
df -h 和 df -i:两笔互相独立的账
一个文件落到磁盘上,要占用两样东西:数据块存内容,inode 存「户口」。inode 里装着文件大小、权限、属主、时间戳、指向数据块的指针——但注意,文件内容本身不在这里。每创建一个文件、目录、符号链接,都要消耗一个 inode,哪怕那个文件是 0 字节。
问题就出在这:block 和 inode 是两笔独立的账,任何一笔花完都会报 No space left on device,报错还一模一样,只能靠现象区分:
- 数据块用完 → 大文件写不进去,但还能建空文件;
df -h的 Use% 逼近 100%。 - inode 用完 → 任何新文件(包括空文件和目录)都建不了,但已有文件还能继续追加写;
df -h可能才用了三成,df -i的 IUse% 却是 100%。
所以第一件事永远是两条命令一起看。下面这台就是典型的 inode 打满,注意 df -h 和 df -i 的反差:
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 59G 18G 39G 32% /
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 3932160 3920000 12160 100% /磁盘才用了三成,inode 却一个不剩。inode 总数是在格式化(mkfs)时定死的,ext4 默认按每 16KB 一个 inode 的比例分配,所以 59G 的盘约分到 390 万个 inode。事后不能随便加——想增加总数,只能备份数据、用 mkfs.ext4 -N <数量> 指定比例重新格式化。动手前先想好怎么恢复:本地加密过的数据换盘后要靠密钥才能重新解锁,见 本地加密与数据恢复说明。所以正确思路不是「加 inode」,而是「找出吃掉它们的文件并清掉」。
谁吃光了 inode:定位小文件大户
绝大多数 inode 耗尽,罪魁都是海量小文件。几十万个几 KB 的文件,占的数据块没多少,inode 却一个不落——上面那台机器就是 18G 的实际数据,塞了 390 万个文件。典型来源有几个:
- PHP 的 session 文件(
/var/lib/php/sessions/下的sess_开头),垃圾回收(gc)没配好,越积越多; - 邮件队列里堆积的退信(
/var/spool/exim、/var/spool/postfix); /tmp、/var/tmp里的临时文件,systemd-tmpfiles没清或脚本只写不删;- 应用缓存、
node_modules、npm/yarn 缓存里成千上万的小文件; - 某个写坏的 cron 任务,每秒建一个文件还从不清理。
定位小文件用 inode 视角的 du 最直接(GNU coreutils 8.22+ 支持 --inodes),输出直接告诉你哪个目录 inode 占得多:
$ du --inodes -x -s /var/* 2>/dev/null | sort -rn | head
1523310 /var/lib
840021 /var/lib/php/sessions
610204 /var/cache
83110 /var/log/var/lib/php/sessions 一个目录就占掉 84 万个 inode,元凶基本锁定。如果 coreutils 版本旧、没有 --inodes,就按目录数文件个数,完全不管文件大小:
find /var -xdev -type f | cut -d/ -f3 | sort | uniq -c | sort -rn | head看到某个目录文件数异常,再进去确认:
find /var/lib/php/sessions -type f | wc -lls 一下会看到目录里塞满了 sess_ 开头的文件,很多是几天甚至几周前的——session 清理机制根本没生效。
安全清掉:别直接 rm 一大把
清这些文件要谨慎,两个坑最常见。
第一,别用 rm /var/lib/php/sessions/* 这种通配符——几十万个文件会撑爆 shell 的参数列表(ARG_MAX),而且展开本身就很慢。用 find 的 -delete 或配合 xargs 分批处理:
find /var/lib/php/sessions -type f -name 'sess_*' -mtime +1 -delete-mtime +1 只删一天前的旧 session,正在用的不碰,相对安全。动手前先跑一遍不带 -delete 的版本,看看会删多少、范围对不对,再真正执行。
第二,删完 df -i 还是满的——多半是「删了却没释放」。Linux 里,只要还有进程握着某个文件(哪怕已经 unlink),它的 inode 和 block 就一直占着。用 lsof 看:
$ lsof +L1 | grep -v COMMAND | head
php-fpm 1234 www-data 3w REG 8,1 0 0 12345 /var/lib/php/sessions/sess_abc (deleted)+L1 列出所有已被删除但仍被打开的文件,NLINK 一列是 0 就是这类。找到那个进程,重启它或对对应路径 > /path/to/file 截断,inode 立刻回来。删文件前先 lsof 扫一遍,能少走很多弯路。
根治:让 inode 不再默默打满
清掉这一次是治标,根治要做三件事。
给 session 和临时文件的清理机制配好。PHP 的 gc 靠
session.gc_maxlifetime(多久算过期,默认 1440 秒)和session.gc_probability/gc_divisor(多久触发一次,默认 1/100)配合工作;很多 inode 打满的服务器,根源就是gc_probability被设成了 0,gc 从不运行。Debian/Ubuntu 系还多一层保险:/etc/cron.d/php会定时跑sessionclean清理过期 session,确认这个 cron 还在、没有因为 PHP 升级被停掉。systemd 的systemd-tmpfiles会定时清/tmp和/var/tmp,确认对应单元在正常跑。告警同时盯两套指标。监控里只加了磁盘使用率是不够的,
df -i的 inode 使用率同样要加阈值——80% 预警、90% 严重。很多半夜的故障,都是 inode 先悄悄逼近 100% 而无人知晓。找出那个持续制造小文件的应用。如果 inode 增长曲线很陡,说明有程序在不停建小文件——多半是缓存策略写错了,或者临时文件只建不删。定位到具体目录后,回到业务代码或配置里解决,比隔三差五手删强。
判断 inode 还有多少余量、总数多大,可以看:
df -i
tune2fs -l /dev/sda1 | grep -iE 'inode (count|size)'tune2fs 能直接看到文件系统的 inode 总数(Inode count)和单个 inode 大小(Inode size,ext4 默认 256 字节)。这也解释了为什么不能无脑把 inode 调大——每个 inode 都要占 256 字节的元数据空间,数量翻倍,元数据占的空间也跟着翻倍。
下次再遇到 df -h 正常却写不进文件,先跑 df -i——这是区分两类资源的第一步,也是最容易被跳过的一步。block 满和 inode 满的解法完全不同:前者删大文件、扩盘或做轮转,后者清海量小文件、修好它们的产生机制。方向错了,删再多也没用。
远程排查时用 Termark 这类 SSH 客户端连上去,df -i、du --inodes、find 直接在终端里跑,把结果复制给同事也顺手。这类「资源被不知不觉打满」的问题,本质都是存储策略——数据该存哪、存多久、谁来清理,客户端本地也有同样的问题,见 数据存储路径。