适用场景
应用报出 No space left on device,但 df -h 显示磁盘使用率不高;Nginx 无法写入访问日志、容器无法创建临时文件、CI 构建失败,甚至 touch 一个空文件也失败。这通常不是字节空间耗尽,而是文件系统可分配的 inode 已经用完。
本文以 ext4/XFS 主机为例,给出从确认、定位到恢复与预防的完整步骤。适用于 Linux 主机、Docker 宿主机和日志/缓存文件较多的业务服务。
现象与原理
文件系统同时管理两类资源:数据块保存文件内容,inode 保存文件名、权限、属主、时间戳和数据块位置等元数据。一个普通文件、目录或软链接至少占用一个 inode;即使文件是 0 字节也一样。
因此下面两条命令必须成对看:
# 查看字节空间
df -hT
# 查看 inode 总数、已用量和可用量
df -i
典型故障现场如下:
$ df -h /var
Filesystem Type Size Used Avail Use% Mounted on
/dev/vdb1 ext4 80G 21G 55G 28% /var
$ df -i /var
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vdb1 5242880 5242880 0 100% /var
Use% 只有 28% 而 IUse% 为 100%,即可确认是 inode 耗尽。不要立刻重启服务或扩容磁盘:它们通常不能释放 inode,且会掩盖创建海量小文件的根因。
常见根因
- 应用把每次请求、每个会话或每条消息写成独立小文件;
- 缓存、缩略图、队列落盘或临时目录未设置过期清理;
logrotate配置失效,或程序自行按秒/请求切分日志;- Docker overlay2、构建缓存、测试产物或 CI 工作目录残留大量小文件;
- 文件上传或解压任务遭遇异常中断,留下几十万分片文件;
- 恶意扫描或业务缺陷持续创建空文件。
排查步骤
1. 确认故障挂载点和目录边界
先找 inode 用尽的挂载点,再在同一文件系统内统计,避免把 /proc、挂载的 NFS 或其他磁盘误算进去:
df -iP
# 以 /var 为例,只统计与 /var 同一文件系统的一级目录
find /var -xdev -mindepth 1 -maxdepth 1 -type d -printf '%p\0' \
| xargs -0 -r -n1 sh -c 'printf "%12s %s\\n" "$(find "$1" -xdev -printf . | wc -c)" "$1"' sh \
| sort -nr
-xdev 很关键:它阻止 find 穿过挂载边界。输出第一列是目录下的目录项数量,异常目录通常会非常突出。生产环境目录很大时,这个精确统计可能持续数分钟,应先从一级目录逐层缩小范围。
2. 用快速采样缩小范围
若需要先快速判断热点,可按目录层级查看 inode 占比:
for d in /var/*; do
[ -d "$d" ] || continue
printf '%10s %s\n' "$(find "$d" -xdev -type f -printf . 2>/dev/null | wc -c)" "$d"
done | sort -nr | head -20
这条命令只统计普通文件;若怀疑目录或软链接爆炸,可把 -type f 去掉。不要把 du -sh 当作替代方案,du 统计的是字节,海量空文件的目录可能只有几十 MB。
3. 找出文件名、时间和大小模式
定位到可疑目录后,抽样查看最近创建的文件与命名规律:
# 最近 24 小时新增的小文件,按时间列出前 50 个
find /var/lib/myapp/cache -xdev -type f -mtime -1 -size -4k \
-printf '%TY-%Tm-%Td %TH:%TM %10s %p\n' \
| sort | tail -50
# 统计扩展名,识别 .tmp、.part、会话文件等模式
find /var/lib/myapp/cache -xdev -type f -printf '%f\n' \
| awk -F. 'NF > 1 {print tolower($NF)}' \
| sort | uniq -c | sort -nr | head -20
若文件名含有请求 ID、时间戳或 UUID,结合应用访问日志和发布记录确定创建者。对于容器,应从宿主机映射目录开始排查,再用 docker inspect <container> 确认挂载关系;不要直接删除 overlay2 内的文件。
安全恢复方案
恢复目标是优先释放少量 inode,让关键服务恢复写入,再受控清理。删除前先停掉持续创建文件的任务,或至少暂停对应消费者/定时任务,否则清理速度可能追不上增长速度。
# 先确认删除范围和数量;此命令不做修改
find /var/lib/myapp/cache -xdev -type f -name '*.tmp' -mtime +3 -print | head -50
find /var/lib/myapp/cache -xdev -type f -name '*.tmp' -mtime +3 -printf . | wc -c
# 确认业务允许后,按批次清理 3 天前的临时文件
find /var/lib/myapp/cache -xdev -type f -name '*.tmp' -mtime +3 -delete
# 验证 inode 是否释放
df -i /var
-mtime +3 以完整 24 小时区间计算,适合保守清理。对缓存目录,建议先在预发验证 TTL;对上传、账单、审计日志等业务目录,必须先确认保留策略和备份状态。若文件仍被进程打开,删除会释放目录项和 inode,但字节空间要等句柄关闭后才回收;可用下列命令确认:
lsof +L1 | awk '$7 ~ /^[0-9]+$/ {print $7, $9}' | sort -nr | head
这里 +L1 表示链接数小于 1 的已删除文件。它主要解释“空间没有回收”,并不是 inode 耗尽的首要证据。
治理与预防
- 为缓存、会话、构建产物和临时文件定义可验证的 TTL,并通过 systemd timer、应用任务或对象存储生命周期策略执行清理。
- 对高频小文件改用批量归档、数据库/消息队列或对象存储,避免每条事件一个文件。
- 将 inode 监控纳入告警。node_exporter 可用如下 PromQL 监控可用 inode 比例:
node_filesystem_files_free{mountpoint="/var"}
/ node_filesystem_files{mountpoint="/var"} < 0.10
告警阈值宜分级:10% 预警、5% 紧急,并附上挂载点、最近 1 小时 inode 消耗速度和热点目录采样结果。
- 新建 ext4 文件系统时,根据预期文件数量规划 inode 密度;对于主要存放海量小文件的分区,评估
mkfs.ext4 -i参数。已有 ext4 文件系统通常不能在线增加 inode,XFS 也不能事后单独扩展 inode 容量,因此规划比“磁盘扩容”更重要。
总结
“磁盘还有空间却写不进去”时,先执行 df -i。确认 inode 耗尽后,使用 find -xdev 在正确挂载点内逐层统计,找出异常文件模式;先止住持续写入,再按已确认的保留策略分批清理。最后用 TTL、文件数监控和合理的文件系统规划,把一次紧急清理转化为可预防的容量治理。
Discussion
评论