Ubuntu 根分区 inode 被打满的排查过程:定位 /var/cache/fontconfig 暴涨问题
1. 问题现象
在 Ubuntu 服务器上执行:
df -i /
发现根分区 inode 使用率达到 100%:
文件系统 Inodes 已用(I) 可用(I) 已用(I)% 挂载点
/dev/sda2 15556608 15556606 2 100% /
这表示系统并不是单纯“磁盘容量满了”,而是 inode 被耗尽。
inode 可以理解为文件系统中“文件/目录的索引节点”。每创建一个文件、目录、软链接,都会消耗一个 inode。
所以即使 df -h 显示磁盘还有空间,只要 inode 满了,系统仍然会报:
No space left on device
常见表现包括:
- 无法创建新文件
- 程序写日志失败
- apt、pip、npm 等工具异常
- Python / Node / Docker / 浏览器自动化程序异常
- 系统服务启动失败
2. 初始确认
先同时查看磁盘容量和 inode:
df -h /
df -i /
重点看 df -i /:
df -i /
如果 已用(I)% 是 100%,就说明 inode 已经被打满。
3. 第一轮定位:查看哪个目录消耗 inode
一开始尝试直接查多个目录:
sudo du -x --inodes -d1 /var /tmp /home /root /python 2>/dev/null | sort -n
但是这个命令卡住了。
原因是:系统中已经有上千万个小文件,du --inodes 需要递归遍历所有文件,遇到海量文件目录时会非常慢。
因此不要一次扫太大范围,而是分目录排查。
4. 排查 /python
因为当前业务目录在 /python,先怀疑是爬虫或脚本生成了大量文件。
执行:
sudo du -x --inodes -d1 /python 2>/dev/null | sort -n | tail -30
结果类似:
2 /python/dbs
3 /python/__pycache__
5 /python/scrapydweb
8 /python/eggs
26 /python/logs
2388 /python/scrapyd2
5478 /python/Python-3.10.12
5484 /python/Python-3.9.16
11597 /python/scrapyd
25017 /python
结论:
/python 只占用了 25017 个 inode
而根分区总共用了约 1555 万个 inode,所以 /python 不是主因。
5. 排查 /var/lib
继续排查:
timeout 60s sudo du -x --inodes -d1 /var/lib 2>/dev/null | sort -n | tail -30
结果类似:
12206 /var/lib
结论:
/var/lib 也不是主因
6. 分拆排查 /var 下的目录
因为直接扫描 /var 卡住,所以改成逐个目录查:
for d in /var/log /var/cache /var/tmp /var/spool /var/backups /tmp /root /home /opt /usr; doecho "===== $d ====="timeout 60s sudo du -x --inodes -d1 "$d" 2>/dev/null | sort -n | tail -20
done
关键输出:
===== /var/cache =====
14757403 /var/cache/fontconfig
14757689 /var/cache
结论非常明确:
/var/cache/fontconfig 占用了约 1475 万个 inode
这就是根分区 inode 被打满的直接原因。
7. 为什么不能在 /var/cache/fontconfig 里用 ll
进入目录后执行:
ll
会卡住。
原因是:
ll = ls -l
它会尝试读取目录中所有文件,并对每个文件执行 stat 获取详细信息。
当目录里有几百万、上千万个文件时,ll 会像“假死”一样卡住。
在这种目录中不要执行:
ll
ls -l
rm *
rm -rf *
尤其不要执行:
rm -rf *
因为文件太多时可能触发:
Argument list too long
正确做法是使用 find 分批处理。
8. 安全查看目录中的少量文件
如果只是想看几个样本,不要用 ll,可以用:
timeout 5s ls -U /var/cache/fontconfig 2>/dev/null | head -30
或者:
timeout 5s find /var/cache/fontconfig -xdev -maxdepth 1 -type f -printf '%f\n' 2>/dev/null | head -30
查看文件属性样本:
timeout 10s find /var/cache/fontconfig -xdev -maxdepth 1 -type f -print0 2>/dev/null \
| head -z -n 20 \
| xargs -0 -r stat -c 'user=%U group=%G size=%s mtime=%y file=%n'
9. 初步清理 /var/cache/fontconfig
因为 /var/cache/fontconfig 是 fontconfig 字体缓存目录,正常情况下可以删除,系统后续会重新生成。
先退出目录:
cd /
分批删除,比如先删 10 万个文件:
sudo bash -c 'find /var/cache/fontconfig -xdev -maxdepth 1 -type f -print0 2>/dev/null | head -z -n 100000 | xargs -0 -r rm -f'
查看 inode 是否释放:
df -i /
如果有效,再删 100 万个:
sudo bash -c 'find /var/cache/fontconfig -xdev -maxdepth 1 -type f -print0 2>/dev/null | head -z -n 1000000 | xargs -0 -r rm -f'
或者循环删除:
for i in $(seq 1 10); doecho "===== batch $i ====="sudo bash -c 'find /var/cache/fontconfig -xdev -maxdepth 1 -type f -print0 2>/dev/null | head -z -n 500000 | xargs -0 -r rm -f'df -i /
done
也可以一次性删除目录内所有文件:
sudo ionice -c3 nice -n 19 find /var/cache/fontconfig -xdev -mindepth 1 -type f -delete
参数说明:
ionice -c3
表示尽量降低 I/O 优先级,避免影响系统。
nice -n 19
表示降低 CPU 调度优先级。
10. 清理后的结果
清理后再次查看:
df -i /
结果变为:
文件系统 Inodes 已用(I) 可用(I) 已用(I)% 挂载点
/dev/sda2 15556608 807773 14748835 6% /
说明 inode 已经从 100% 降到 6%,系统恢复正常。
11. 发现问题仍在继续:文件还在增加
清理之后发现 /var/cache/fontconfig 里仍然不断有文件增加。
这说明有进程正在持续触发 fontconfig 生成缓存。
为了观察增长情况,可以执行:
watch -n 5 'df -i /; echo; sudo find /var/cache/fontconfig -xdev -maxdepth 1 -type f 2>/dev/null | wc -l'
如果文件数量持续上涨,就说明问题源头还在运行。
12. 初步查看谁在使用 fontconfig
先用 lsof 看谁打开了 /var/cache/fontconfig:
sudo lsof -nP 2>/dev/null | grep '/var/cache/fontconfig' | head -50
曾看到类似输出:
gnome-shell gdm mem REG /var/cache/fontconfig/xxx-le64.cache-7
注意:
mem REG
表示该进程把文件映射到内存使用,通常是读取,并不一定是写入。
所以 gnome-shell 只是使用字体缓存,不一定是元凶。
过滤掉 mem:
sudo lsof -nP 2>/dev/null \
| awk '$9 ~ "^/var/cache/fontconfig/" && $4 !~ /^mem/ {print}' \
| head -100
13. 使用 auditd 精准抓写入进程
为了精准定位是谁在写 /var/cache/fontconfig,安装 auditd:
sudo apt update
sudo apt install -y auditd
确认工具存在:
which auditctl
which ausearch
添加监听规则:
sudo auditctl -D
sudo auditctl -a always,exit -F dir=/var/cache/fontconfig -F perm=wa -k fontconfig_write
参数说明:
-F dir=/var/cache/fontconfig
监听这个目录。
-F perm=wa
监听写入和属性变更。
-k fontconfig_write
给这条规则打一个 key,方便后续检索。
等待一段时间后查看日志:
sudo ausearch -k fontconfig_write -i | tail -200
14. 最终定位到元凶
audit 日志中出现了关键证据:
proctitle=node /python/scrapyd/xxx/xx/xxx.js
comm=node
exe=/usr/bin/node
cwd=/python
pid=988264
ppid=874718
同时能看到它在不断操作 /var/cache/fontconfig:
name=/var/cache/fontconfig/xxxx-le64.cache-7.NEW nametype=CREATE
syscall=openat success=yes ... O_RDWR|O_CREAT|O_CLOEXECname=/var/cache/fontconfig/xxxx-le64.cache-7 nametype=CREATE
syscall=rename success=yesname=/var/cache/fontconfig/xxxx-le64.cache-7.LCK nametype=DELETE
syscall=unlink success=yes
最终结论:
node /python/scrapyd/xxxx/xx/xxxx.js
正在持续触发 fontconfig 缓存生成,是 /var/cache/fontconfig 文件暴涨、inode 被打满的直接元凶。
15. 停止元凶进程
先查看相关进程:
pgrep -af 'sdenv.js|node'
停止指定脚本:
sudo pkill -TERM -f '/python/scrapyd/xxxxx/xx/xxx.js'
如果没有停止:
sudo pkill -9 -f '/python/scrapyd/xxx/xxx/xxx.js'
再次确认:
pgrep -af 'xxx.js|node'
查看 inode:
df -i /
查看 fontconfig 文件数量:
sudo find /var/cache/fontconfig -xdev -maxdepth 1 -type f 2>/dev/null | wc -l
16. 查父进程
audit 日志中显示:
pid=988264
ppid=874718
查看父进程:
ps -fp 874718
查看进程树:
pstree -ap 874718
也可以查所有相关进程:
pgrep -af 'scrapyd|python|xxxx.js|node'
如果父进程是 Scrapyd 或 Python worker,说明是爬虫任务间接启动了这个 Node 脚本。
17. 取消 auditctl 监听
定位完成后,关闭 audit 规则,避免 audit 日志继续增长。
清空所有临时规则:
sudo auditctl -D
确认规则是否清空:
sudo auditctl -l
如果输出为空,或者没有 fontconfig_write,说明监听已经取消。
如果只想删除这一条规则,可以先查看规则:
sudo auditctl -l
然后删除对应规则:
sudo auditctl -d always,exit -F dir=/var/cache/fontconfig -F perm=wa -k fontconfig_write
不过临时排障场景下,最简单的是:
sudo auditctl -D
18. 再次清理 fontconfig
停止元凶进程后,再清理残留缓存:
cd /
sudo ionice -c3 nice -n 19 find /var/cache/fontconfig -xdev -mindepth 1 -type f -delete
重新生成正常字体缓存:
sudo fc-cache -r -v
查看 inode:
df -i /
19. 为什么 Node 脚本会触发 fontconfig
fontconfig 是 Linux 下的字体配置和缓存系统。以下场景容易触发它生成缓存:
- Puppeteer
- Playwright
- Chromium / Chrome
- node-canvas
- sharp
- SVG / PDF / 图片渲染
- 服务端截图
- 浏览器指纹模拟
- 字体指纹相关逻辑
- 动态创建字体目录
- 每次任务创建新的临时浏览器 profile
本次出现大量类似文件:
bcdcb5d5-bff2-4888-a17b-b81202409208-le64.cache-7
1da7dc5a-7b9d-4364-97c6-c8c0e0b8a74d-le64.cache-7
cdd7ba25-f32b-407a-93e4-583a73b042e0-le64.cache-7
这些文件名呈 UUID 风格,说明程序可能不断制造新的字体目录、缓存目录或浏览器环境,导致 fontconfig 不断认为“发现了新字体目录”,于是持续生成新的缓存文件。
20. 检查 sdenv.js
进入目录:
cd /python/scrapyd/xxx/xx
搜索关键字:
grep -nE 'canvas|font|Font|puppeteer|playwright|chromium|chrome|sharp|pdf|svg|tmp|cache|userDataDir|FONTCONFIG|XDG' sdenv.js
搜索整个目录:
grep -RniE 'canvas|font|puppeteer|playwright|chromium|chrome|sharp|userDataDir|FONTCONFIG|XDG' /python/scrapyd/xxxx/xxx 2>/dev/null | head -100
如果是 Node 项目,可以查看依赖:
cd /python/scrapyd/xxx/xx
npm ls canvas puppeteer playwright sharp 2>/dev/null
21. 预防方案
21.1 不要以 root 直接运行 Node 脚本
当前 audit 日志中显示:
uid=root
exe=/usr/bin/node
建议不要让爬虫或 Node 脚本直接以 root 运行。
可以创建普通用户运行爬虫任务,降低影响范围。
21.2 固定缓存目录
运行 Node 前设置独立缓存目录:
mkdir -p /tmp/sdenv-cache/xdg
mkdir -p /tmp/sdenv-cache/fontconfigexport XDG_CACHE_HOME=/tmp/sdenv-cache/xdg
export FONTCONFIG_PATH=/etc/fonts
然后运行:
node /python/scrapyd/xxxx/xxx/sdenv.js
但这不一定能完全阻止 /var/cache/fontconfig 写入,因为系统 fontconfig 配置可能仍然指向 /var/cache/fontconfig。
21.3 如果使用 Chromium / Playwright / Puppeteer
要注意:
- 不要每次创建随机
userDataDir后不清理 - 不要让临时 profile 长期堆积
- 限制并发数量
- 任务结束后删除临时目录
- 固定 cache 目录或使用临时目录并定时清理
示例:
--user-data-dir=/tmp/chrome-profile-xxxx
--disk-cache-dir=/tmp/chrome-cache-xxxx
任务结束后:
rm -rf /tmp/chrome-profile-xxxx /tmp/chrome-cache-xxxx
21.4 给 inode 做监控
临时监控:
watch -n 5 'df -i /; echo; sudo find /var/cache/fontconfig -xdev -maxdepth 1 -type f 2>/dev/null | wc -l; echo; pgrep -af "sdenv.js|node|chrome|chromium"'
长期可以写成脚本:
#!/bin/bashwhile true; dodatedf -i /echo "fontconfig file count:"find /var/cache/fontconfig -xdev -maxdepth 1 -type f 2>/dev/null | wc -lecho "related processes:"pgrep -af 'sdenv.js|node|chrome|chromium|playwright|puppeteer'echo "------"sleep 30
done
保存为:
monitor_inode.sh
运行:
bash monitor_inode.sh
22. 本次排障用到的核心命令汇总
查看 inode
df -i /
查看磁盘空间
df -h /
按目录统计 inode
sudo du -x --inodes -d1 /python 2>/dev/null | sort -n | tail -30
sudo du -x --inodes -d1 /var/lib 2>/dev/null | sort -n | tail -30
分目录排查
for d in /var/log /var/cache /var/tmp /var/spool /var/backups /tmp /root /home /opt /usr; doecho "===== $d ====="timeout 60s sudo du -x --inodes -d1 "$d" 2>/dev/null | sort -n | tail -20
done
查看 fontconfig 文件数
sudo find /var/cache/fontconfig -xdev -maxdepth 1 -type f 2>/dev/null | wc -l
安全查看少量文件
timeout 5s ls -U /var/cache/fontconfig 2>/dev/null | head -30
分批删除 fontconfig 缓存
sudo bash -c 'find /var/cache/fontconfig -xdev -maxdepth 1 -type f -print0 2>/dev/null | head -z -n 1000000 | xargs -0 -r rm -f'
一次性删除 fontconfig 缓存
sudo ionice -c3 nice -n 19 find /var/cache/fontconfig -xdev -mindepth 1 -type f -delete
查看相关进程
pgrep -af 'python|node|chrome|chromium|playwright|puppeteer|fc-cache|gnome-shell|gdm'
查看谁打开 fontconfig 文件
sudo lsof -nP 2>/dev/null | grep '/var/cache/fontconfig' | head -50
过滤掉 mem 映射
sudo lsof -nP 2>/dev/null \
| awk '$9 ~ "^/var/cache/fontconfig/" && $4 !~ /^mem/ {print}' \
| head -100
安装 auditd
sudo apt update
sudo apt install -y auditd
添加 audit 监听
sudo auditctl -D
sudo auditctl -a always,exit -F dir=/var/cache/fontconfig -F perm=wa -k fontconfig_write
查询 audit 日志
sudo ausearch -k fontconfig_write -i | tail -200
取消 audit 监听
sudo auditctl -D
sudo auditctl -l
停止元凶进程
sudo pkill -TERM -f '/python/scrapyd/xxxx/xxx/sdenv.js'
如果不停止:
sudo pkill -9 -f '/python/scrapyd/xxxx/xxx/sdenv.js'
查看父进程
ps -fp 874718
pstree -ap 874718
重新生成字体缓存
sudo fc-cache -r -v
23. 最终结论
本次问题不是磁盘容量不足,而是根分区 inode 被打满。
直接原因:
/var/cache/fontconfig
目录中生成了约 1475 万个字体缓存文件。
最终通过 auditd 定位到写入进程:
node /python/scrapyd/xxx/xx/sdenv.js
该 Node 脚本持续触发 fontconfig 创建缓存文件,导致 inode 被迅速耗尽。
处理步骤:
- 使用
df -i /确认 inode 打满; - 使用
du --inodes分目录定位; - 发现
/var/cache/fontconfig占用约 1475 万 inode; - 使用
find分批清理缓存文件; - 安装并使用
auditd监听写入; - 通过
ausearch定位到node /python/scrapyd/xxx/xxx/xxx.js; - 停止该进程;
- 清理残留缓存;
- 取消 audit 监听;
- 后续检查
sdenv.js中的字体、canvas、puppeteer、playwright、chromium、sharp、PDF、SVG 等相关逻辑。
核心经验:
inode 100% 时,不要只看 df -h。
要用 df -i 和 du --inodes 找海量小文件。
遇到千万级目录,不要 ls -l,不要 rm *。
用 find、xargs、auditd 分析和处理。
