结论先行:journald 的「默认上限 4GB」只在
/var/log/journal/持久化目录下生效;一旦存储落在/run/log/journal/(内存型 tmpfs),上限只有 10% 内存,且重启即清零——而真正让磁盘翻车的是「持久化目录 + 高日志速率 + 无人设上限」三者叠加。本文复盘一次真实的 38GB 占用事故,给一套可直接抄的治理配置。
一、现象:32 台虚机里 27 台同时报警,磁盘增量全在一个「看不见」的目录
去年 11 月,我们 32 台 RHEL 9.2 业务虚机(100GB 系统盘,XFS)在 3 天内陆续有 27 台触发 nodefs.available<10% 告警。df -h 显示 / 从 71% 爬到 96%,但业务日志目录(logrotate 已配置,每文件 100MB、保留 7 份)只占 1.4GB。
逐目录 du -xh --max-depth=2 / 扫下来,大头浮出水面:/var/log/journal/ 单节点占 38.6GB,里面是 214 个 system.journal / *.journal 文件,单个最大 128MB(journald 的单文件硬上限)。业务侧没有任何应用写这个目录——写它的是 systemd 自己。
影响面:27 台节点里 6 台直接 ENOSPC,sshd 写日志失败导致 2 台 SSH 登录间歇性拒绝(会话建立时写 pam_systemd 会话记录失败),我们凌晨花了 40 分钟才用带外控制台救回来。
二、原理:journald 的存储分级与真空化机制,三个默认值各管一摊
很多人对 journald 的理解停在「系统日志都在这」,但它的存储策略是分级的,由三个参数共同决定:
1. Storage= 决定落盘位置(默认 auto,这是关键)
Storage 取值有 volatile / persistent / auto(默认)/ none。auto 的语义是:如果检测到 /var/log/journal/ 目录存在,就持久化写进去;不存在就写 /run/log/journal/(tmpfs,内存)。
反直觉点一:RHEL/CentOS 的 anaconda 安装流程默认会创建 /var/log/journal/,所以绝大多数虚机实际上跑在 persistent 模式;但如果你用自定义镜像、或手动删过这个目录,存储会静默回落到 tmpfs——日志还在(journalctl 照样能读),但重启后全丢,且上限变成 10% 内存(8GB 机器就是 800MB)。两种模式的行为差异,df -h 和 du 都看不出来,只能靠 journalctl --disk-usage 和目录是否存在来判断。
2. 上限参数的作用域不同
RuntimeMaxUse=:管 tmpfs(/run/log/journal/),默认 10% 内存SystemMaxUse=:管持久化(/var/log/journal/),默认 4GBMaxFileFileSize=:单文件上限,默认 128MB(硬编码上限,不可超过)
注意:SystemMaxUse=4GB 的「4GB」是 journald 自己认为的上限,不是硬墙。它的真空化(vacuum)是在每次写完一个新 journal 文件后才检查总占用并删最旧的文件,所以高日志速率下实际占用可以瞬时冲到上限 + 若干 128MB 文件才回落到 4GB 附近——这是我们单节点看到 38.6GB 而不是 4GB 的直接原因(详见下一节)。
3. 真空化的粒度是「文件」,不是「字节」
journalctl --vacuum-size= / --vacuum-time= / --vacuum-files= 都是按整个 journal 文件删除,不会拆文件。这意味着:如果你设 SystemMaxUse=512M,但日志速率高到单个 128MB 文件 30 秒就写满,磁盘上会长期保持 4~8 个 128MB 文件(512MB~1GB),远超你设的 512MB。上限参数的精度下限就是「一个文件」。
三、具体做法:一套可直接抄的治理配置
第一步:确认当前存储模式与真实占用
# 看存储模式(auto 下 /var/log/journal 是否存在)
ls -ld /var/log/journal
# 看 journald 自报的磁盘占用(注意:这是它的「自报」,不一定等于 du 结果)
journalctl --disk-usage
# 看真实占用(按文件)
du -h /var/log/journal/*/*.journal | sort -h | tail -20
第二步:收紧上限(/etc/systemd/journald.conf.d/override.conf,用 drop-in 不改主文件)
[Journal]
Storage=persistent
SystemMaxUse=2G
RuntimeMaxUse=512M
MaxFileFileSize=32M
# 保留 14 天,兜底
SystemMaxRetentionSec=14day
systemctl restart systemd-journald
# 立即清空到上限(不需要等下一个文件写满)
journalctl --vacuum-size=2G
MaxFileFileSize=32M 是关键:把单文件粒度从 128MB 压到 32MB,真空化的精度上限就从「+128MB」降到「+32MB」,高日志速率下磁盘占用更贴近你设的 SystemMaxUse。
第三步:给 journald 加监控(不要只盯 df)
# journald 自身暴露的指标(systemd 250+)
# 采集 /var/log/journal 的 du,与 journalctl --disk-usage 交叉验证
du -sb /var/log/journal | awk '{print $1}'
告警阈值建议:/var/log/journal 占用 > 磁盘容量的 20% 告警,> 40% 严重。
第四步:排查「谁在刷 journal」(本次事故的真正根因)
# 按 sender 统计日志量(systemd 250+ 有 --output-fields,老版本用 grep)
journalctl --since "1 hour ago" | awk '{print $5}' | sort | uniq -c | sort -rn | head -20
# 定位到某个服务疯狂刷 debug 日志后:
journalctl -u <service> -p info --since "1 hour ago" | head -50
本次事故根因:某监控 agent 在配置错误后进入「重试-失败-打日志-重试」死循环,单进程 15 分钟刷 2.3GB debug 日志,全部进了 journald。修掉 agent 配置后日志速率从 260MB/min 回落到 1.2MB/min。
四、踩过的坑与反直觉之处
journalctl --disk-usage和du对不上:journald 自报的是「它记得的文件总大小」,du是文件系统真实占用。我们事故节点上 journald 自报 3.9GB,du实测 38.6GB——因为 journald 的 vacuum 逻辑在「文件被其他进程持有」时会跳过删除,而一个journalctl -f的长期会话持有了旧文件句柄。排查时以du为准,别信自报数。- 直接
rm -rf /var/log/journal/*是错误操作:journald 进程持有这些文件的句柄,删了目录项但空间不释放(和「已删除文件仍被持有」是同一个坑)。正确做法是journalctl --rotate && journalctl --vacuum-time=1s,让 journald 自己删。 Storage=volatile不是「省磁盘」的银弹:它确实不占磁盘,但重启后所有系统日志(包括sshd、kernel、systemd的启动日志)全丢,事后排障等于瞎子。生产节点不要用它。/var独立挂载时,journald 默认不写/var/log/journal:如果/var挂在独立分区而/var/log/journal不存在,auto模式会落到 tmpfs,日志只占内存不占/var分区——你以为在持久化,实际没有。用findmnt /var确认挂载结构后再下结论。- journald 的 vacuum 是异步的、不可靠的:它依赖「写完新文件后触发」,如果日志速率极低(一天才写一个文件),vacuum 可能一天才跑一次,期间占用可以无限逼近上限。高日志速率节点必须配
--vacuum-size的定时任务(cron 每小时跑一次)兜底。
五、结论与可复用清单
- 先确认存储模式:
ls -ld /var/log/journal不存在 = tmpfs 模式,日志重启即丢,上限 10% 内存;存在 = persistent,上限SystemMaxUse(默认 4GB)。 - 上限参数不是硬墙:真空化按「文件」粒度、在「写完新文件后」触发,高日志速率下实际占用可超出设定值 2~10 倍。
- 收紧单文件粒度:
MaxFileFileSize=32M比SystemMaxUse更能控制高日志速率下的磁盘占用。 - 以
du为准,不信journalctl --disk-usage:两者在文件被持有时会产生巨大偏差。 - 删文件用
journalctl --rotate --vacuum-time=1s,不要rm -rf。 - journald 占用单独监控:
df只能告诉你「满了」,不能告诉你「谁把它撑满的」。 - 先查日志速率,再调参数:本次 38GB 的根因是一个 260MB/min 的日志风暴,参数调优只是止血,修掉刷屏进程才是根治。