VPS 磁盘 inode 用尽:小文件排查、session 清理与日志轮转设置

VPS 服务器机架旁大量微小文件颗粒堆积,容量指示条变橙

这篇文章教你如何解决一类让人困惑的故障:df -h 显示磁盘只用了 40%,但创建任何文件都报 No space left on device。遇到这种情况,问题多半不在磁盘容量,而在 inode(索引节点,文件系统中记录每个文件的元数据结构)被耗尽了。每一份邮件、每一个 PHP session 文件、每一条被切割的日志,都会占用一个 inode。小文件数量一旦突破文件系统上限,哪怕剩余空间还有几十 GB,系统也会拒绝新建文件,网站随之出现会话丢失、验证码报错、缓存写入失败等各种异常。

排查这类问题不需要重装系统,关键是按顺序做三件事:确认 inode 确实耗尽、找到小文件堆积的目录、清理源头并配置轮转防止复发。下面按实际运维中的处理顺序逐步展开。

inode 是什么,为什么磁盘没满也会写不进文件

Linux 文件系统存储文件时依赖两个独立的计量维度:一是我们熟悉的块空间(block),存放文件的实际内容;二是 inode,存放文件的权限、属主、时间戳和内容所在的块位置。也就是说,一个只有几字节的空文件,也要消耗一个 inode。df -h 统计的是块空间,df -i 统计的才是 inode,两者互不关联。

inode 总数在文件系统创建时就已固定,常见 ext4 文件系统上大约每 16 KB 空间分配一个 inode,比如一块 40 GB 的磁盘通常有 250 万个左右的 inode。日常网站文件个大、数量少,很难触顶;但以下几类负载会在短时间内生成海量小文件:

  • PHP session 文件:每个访客会话生成一个文件,站点被爬虫高频访问时一天可产生数万到数十万个。
  • 邮件队列:被植入脚本的服务器向外发信,每封邮件在队列目录留下多个小文件。
  • 日志切片:没有配置轮转时,调试日志按小时甚至按分钟切割成单个文件。
  • 缓存碎片:部分缓存插件把每个对象写成独立文件,长期累积不清理。

以一台运行 WordPress 的 Hostease VPS 为例,如果页面每秒产生 5 个新会话,一年不清理 session 目录,堆积的文件就足以耗尽 inode。判断和清理的方法在接下来的章节里逐一说明。

第一步:确认 inode 是否真的耗尽

SSH 登录服务器后先运行 df -i,输出里的 IUse% 就是各挂载点的 inode 使用率。正常站点通常在 10% 以下;一旦某行显示 100%,就坐实了 inode 耗尽。这和 VPS 的配置档位无关,哪怕是大内存的独立服务器,只要文件数量触顶同样会写不进文件,所以排查思路对所有机型通用。

确认耗尽后,下一步是定位文件堆积点。直接遍历全盘会很慢,用下面这条命令逐层统计每个一级子目录里的文件数量:

for d in /*; do echo "$(find $d -xdev 2>/dev/null | wc -l) $d"; done | sort -rn | head

找到占用最多的目录后再往下钻一层,重复执行,通常两三轮就能锁到具体目录。实际运维中最常见的三个堆积点是 /var/lib/php/sessions/(Debian/Ubuntu)或 /var/lib/php/session/(CentOS)、/var/spool/postfix/(邮件队列)、以及应用自己的缓存目录。如果 IUse% 是 100%,连 find 遍历本身都可能因为无法创建临时文件而变慢,先看下一节的清理动作再回来继续定位也是可以的。

第二步:清理 PHP session 堆积

session 文件是中文站点最常见的 inode 杀手。PHP 默认按概率触发垃圾回收,站点访问量大或配置偏低时,大量过期 session 会长期滞留。确认目录位置后,先删除修改时间超过 24 小时的过期会话文件:

# Debian/Ubuntu
find /var/lib/php/sessions/ -type f -name 'sess_*' -mtime +1 -delete

# CentOS / AlmaLinux
find /var/lib/php/session/ -type f -name 'sess_*' -mtime +1 -delete

inode 排查流程示意图

清理后再次运行 df -i 观察 IUse% 是否回落。删除百万级小文件可能需要几分钟,期间负载会短暂升高,建议放在业务低峰执行。更彻底的做法是让 PHP 自己定期回收:在 php.ini 中确认 session.gc_probability=1、session.gc_divisor=100、session.gc_maxlifetime=1440,表示每百次请求约有 1 次触发回收,回收超过 1440 秒未活动的会话。如果站点会话量特别大,也可以直接安排一条 cron,每天凌晨清理一次过期 session,比依赖请求触发更可控。

session 文件堆积对比图

清理完 session 之后,我们再来看另一类同样高发的堆积源——日志文件,以及如何用轮转机制让它不再失控。

第三步:日志轮转防止复发

日志本身体积不小,通常先撑爆的是磁盘空间;但某些框架的调试日志会按请求或按小时切割,一天生成几千个文件,inode 先于容量触顶。清理只是一次性手段,长期防复发要靠 logrotate(Linux 系统自带的日志轮转工具,按规则自动切割、压缩并删除旧日志)。

大部分发行版已为系统日志预置了轮转规则,自定义应用的日志需要单独添加配置。在 /etc/logrotate.d/ 下新建一个文件,例如 myapp,写入:

/var/www/myapp/storage/logs/*.log {
    daily
    rotate 7
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
}

逐项解释关键参数:daily 表示每天切割一次,量大时可改为 hourly(需配合 cron.hourly);rotate 7 表示保留最近 7 份,更早的自动删除,这样日志文件数量有硬上限;compress 与 delaycompress 组合让最近一份不压缩、其余压缩,方便排查当天问题;copytruncate 在复制后截断原文件,适合应用一直持有着日志文件句柄、无法通过重命名触发轮转的场景。写好配置后用 logrotate -d /etc/logrotate.d/myapp 做一次 dry-run 验证语法,再等轮转周期确认文件按预期切割。

邮件队列也值得顺手检查:postqueue -p 查看队列,若发现数万封退信堆积,多半是站点被植入发信脚本,要先排查 web 目录里的可疑 PHP 文件,再用 postsuper -d ALL deferred 清空延迟队列。安全排查可参考服务器安全加固指南。

真实案例:一个 session 目录吃掉 180 万 inode

某外贸站点突然出现用户无法登录、购物车清空的现象,df -h 显示磁盘只用了 38%。运维登录后 df -i 显示 / 分区 IUse% 已到 100%,总计约 190 万个 inode 全部占满。按前文方法逐层定位,最终发现 /var/lib/php/sessions/ 下堆了 183 万个 session 文件——站点此前做过一次爬虫攻击高峰,每个被访问页面都创建了新会话,而 PHP 的垃圾回收配置里 session.gc_probability=0,等于彻底关闭了自动回收。

处理过程分三步:先执行上面的 find -delete 命令清理过期会话,IUse% 在 6 分钟内从 100% 降到 21%;再把 gc_probability 恢复为 1 并把 session.gc_maxlifetime 调到 7200 秒,兼顾回收效率与在线用户时长;最后为业务日志目录补上 logrotate 规则,把文件数量锁定在 7 份以内。之后再未复发。

这个案例说明 inode 问题几乎总能在源头上找到配置缺陷。要持续掌握这类服务器运维知识,可以订阅 Hostease 博客的网站优化专栏,里面有性能与稳定性相关的系列实践文章。

建立日常监控,避免再次被打措手不及

inode 耗尽是渐进过程,从 80% 涨到 100% 往往只要几天。与其等故障报警,不如把检查纳入日常巡检。最简单的做法是一条 cron 加一个阈值判断,例如每天早上 8 点执行:

#!/bin/bash
USAGE=$(df -i / | awk 'NR==2 {gsub(/%/,""); print $5}')
if [ "$USAGE" -ge 80 ]; then
    echo "inode usage ${USAGE}% on $(hostname) at $(date)" | mail -s "inode alert" ops@example.com
fi

这个脚本的逻辑是提取根分区的 inode 使用率,超过 80% 就发告警邮件,给你的处理窗口至少留出一到两天。监控指标建议同时覆盖三层数据:磁盘块空间(df -h)、inode(df -i)、以及文件堆积目录的 find 计数,三者一起看才能区分是”大文件吃空间”还是”小文件吃 inode”。阈值建议设在 80% 而不是 95%,给清理动作留出缓冲时间。

如果你使用的是 Hostease 的 WordPress 主机,主机环境自带自动化运维与 24/7 中文技术支持,这类系统层面的文件清理和监控可以交由主机商协助处理,把精力留在业务本身。

服务器连接告警铃铛与通知邮件的监控架构示意图

总结

总结一下完整的处理链条:先用 df -i 区分磁盘空间问题和 inode 问题,再用逐层 find 计数锁定堆积目录;session 清理用 find -mtime +1 -delete 配合 php.ini 的垃圾回收参数治本,日志用 logrotate 的 daily 加 rotate 7 把文件数量锁进硬上限,邮件队列则要先查入侵源头再清队列;最后以 80% 阈值的 cron 告警兜底。我们建议你现在就登录一台在用服务器跑一次 df -i,把现状记录下来,再按第二、三步把 session 清理和日志轮转配置补齐——这两项配置各花不到十分钟,却能避免下一次突如其来的 No space left on device。如果你需要,Hostease 的中文技术支持也可以协助检查服务器环境的文件系统健康状态。

发表评论