记一次 journald 日志风暴:systemd-journald 配置错误导致根分区刷爆的排查实录

journald Storage=auto + SystemMaxUse 未生效,日志持续写入根分区导致 df 告警,排查发现配置被 override 覆盖。

问题背景

某生产服务器(CentOS 7.9,systemd 219)在凌晨 03:17 触发 Zabbix 告警:「根分区使用率 92%(阈值 85%)」。运维值班人员通过 SSH 登录后发现 /var/log/journal 目录已占用 47GB,且仍在持续增长。正常情况下该服务器日志量每日约 200-300MB,根分区(/)总容量 100GB,理论上不应在 4 小时内被日志占满。

此前一周该服务器刚完成「journald 配置优化」变更(工单 #OPS-2026-0918),变更内容包括将 Storage 从 volatile 调整为 auto,并设置 SystemMaxUse=2G、RuntimeMaxUse=500M。变更后监控显示日志目录大小稳定在 1.8GB 左右,变更验收通过。但 5 天后再次出现日志风暴,根因指向配置被 override 机制覆盖。

故障现象

  1. 磁盘空间告警:03:17 Zabbix 触发根分区使用率告警,SSH 登录后 df -h / 显示:

    1
    2
    
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/sda2       100G   92G  8.0G  92% /
    

    /var/log/journal 目录实际占用 47GB(du -sh /var/log/journal)。

  2. journald 进程 CPU 飙升:top 显示 systemd-journal 进程 CPU 使用率 180%(双核),且 journalctl --disk-usage 命令卡死 30 秒以上才返回结果。

  3. 日志写入异常:journalctl -n 100 能正常输出最近 100 条日志,但 -f 跟进模式下日志条目每秒约 1200 条,且大量重复的 audit: type=1400 SELinux AVC denied 消息(源头是某容器化应用误配 SELinux 策略)。

  4. 配置检查异常:journalctl --verify 报错「File corruption detected at …」,且 systemctl status systemd-journald 显示服务处于 active (running) 但有 3 次 Watchdog timeout 重启记录。

排查过程

步骤 1:确认日志增长源头

首先定位日志增长最快的 unit:

1
2
3
4
5
6
journalctl --disk-usage
# 输出:Archived and active journals take up 47.2G in the file system.

# 查看各 unit 日志大小排序
journalctl --output=short-precise --no-pager | \
  awk '{print $5}' | sort | uniq -c | sort -rn | head -20

发现 audit 相关的日志条目占比超过 78%,且时间戳集中在 02:58-03:15 之间每秒约 800-1200 条。

步骤 2:检查 journald 配置与 override 机制

按变更记录检查 /etc/systemd/journald.conf:

1
2
3
4
5
6
[Journal]
Storage=auto
SystemMaxUse=2G
RuntimeMaxUse=500M
SystemMaxFileSec=1week
MaxRetentionSec=4weeks

但 systemctl cat systemd-journald 显示实际生效配置与预期不符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/systemd/journald.conf
[Journal]
Storage=auto
SystemMaxUse=2G
...

# /etc/systemd/journald.conf.d/99-custom.conf  ← 意外出现
[Journal]
Storage=persistent
SystemMaxUse=50G
RuntimeMaxUse=10G

该 99-custom.conf 文件创建时间为 2026-09-20 14:22,恰好是「journald 配置优化」变更的第 3 天。经核查,该文件由另一运维同事在处理「日志归档需求」时手动创建,未走变更流程。

步骤 3:验证 override 优先级与生效顺序

systemd 配置加载顺序为:

  1. /etc/systemd/journald.conf(主配置)
  2. /etc/systemd/journald.conf.d/*.conf(按文件名 ASCII 排序)
  3. /run/systemd/journald.conf.d/*.conf(运行时配置,优先级最高)
  4. /usr/lib/systemd/journald.conf.d/*.conf(包自带配置,优先级最低)

99-custom.conf 的 99- 前缀使其在 ASCII 排序中排在最后,因此覆盖了主配置中的 SystemMaxUse=2G。同时 Storage=persistent 强制 journald 将日志写入 /var/log/journal 而非 /run/log/journal(tmpfs),导致根分区持续被写入。

步骤 4:定位日志风暴的业务源头

确认配置问题后,进一步排查日志来源:

1
2
3
# 查看 audit 日志的进程上下文
journalctl _TRANSPORT=audit --since "2026-09-26 02:50:00" | \
  grep "type=1400" | head -5

输出显示 SELinux AVC denied 消息来自容器 app-frontend(Kubernetes Pod),该容器在 02:58 因滚动更新触发大量文件访问,SELinux 策略误配导致每秒产生约 800 条 audit 日志。

步骤 5:验证 journald Watchdog 与文件损坏

journalctl --verify 报错的根因是 journal 文件在磁盘空间耗尽前被 journald 强制截断,导致索引损坏:

1
2
3
journalctl --verify --file /var/log/journal/*/system.journal
# 输出:Invalid object at 0x3f8a2c1b0
#       File corruption detected at 0x3f8a2c1b0 (length 4096)

解决方案

立即止血(不重启服务)

  1. 临时限制 journald 写入:

    1
    2
    3
    
    # 通过 systemd-run 临时覆盖配置
    systemctl kill --signal=SIGUSR1 systemd-journald
    # 触发 journald 立即执行日志轮转与裁剪
    
  2. 手动清理历史日志(保留最近 7 天):

    1
    2
    
    journalctl --vacuum-time=7d
    # 输出:Vacuuming done, freed 41.3G of archived journals
    
  3. 删除异常 override 配置:

    1
    2
    3
    
    rm -f /etc/systemd/journald.conf.d/99-custom.conf
    systemctl daemon-reload
    systemctl restart systemd-journald
    

根治措施

  1. 规范化 journald 配置(删除所有 override,统一主配置):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # /etc/systemd/journald.conf
    [Journal]
    Storage=auto
    SystemMaxUse=2G
    RuntimeMaxUse=500M
    SystemMaxFileSec=1week
    MaxRetentionSec=4weeks
    ForwardToSyslog=no
    Audit=yes
    
  2. 建立配置变更门禁:

    • 所有 /etc/systemd/*.conf.d/ 目录变更必须走变更流程
    • 变更单需包含「配置加载优先级验证」环节(systemctl cat 截图)
    • 新增自动化检查脚本(每日 06:00 执行):
      1
      2
      3
      4
      5
      6
      
      #!/bin/bash
      # check_journald_override.sh
      if ls /etc/systemd/journald.conf.d/*.conf 2>/dev/null | grep -q .; then
        echo "[WARN] journald override 配置存在,请检查"
        ls -l /etc/systemd/journald.conf.d/
      fi
      
  3. 修复 SELinux 策略(业务侧):

    • 为 app-frontend 容器生成自定义 SELinux 策略:
      1
      2
      
      ausearch -m avc -ts recent | audit2allow -M myapp
      semodule -i myapp.pp
      
    • 或在 Deployment 中添加 securityContext.seLinuxOptions 放行必要权限。
  4. 监控增强:

    • 新增 Prometheus 指标 node_filesystem_avail_bytes 阈值告警(<15% 触发 P2)
    • 新增 journalctl --disk-usage 每日巡检脚本,输出到运维群

根因分析

直接原因:/etc/systemd/journald.conf.d/99-custom.conf 的 SystemMaxUse=50G 覆盖了主配置的 2G 限制,且 Storage=persistent 强制日志写入根分区而非 tmpfs,导致 SELinux AVC 日志风暴直接耗尽根分区。

间接原因:

  1. 变更流程缺失「配置 override 检查」环节,允许运维人员在 /etc/systemd/*.conf.d/ 目录创建高优先级配置文件。
  2. journald 的 Storage=auto 行为依赖 /var/log/journal 目录是否存在:若目录不存在则使用 volatile(tmpfs),若存在则使用 persistent。变更时未清理旧目录,导致配置切换后日志持续写入根分区。
  3. SELinux 策略误配导致 audit 日志量激增 40 倍,叠加 journald 配置错误,4 小时内产生 47GB 日志。

配置加载优先级陷阱:systemd 的 *.conf.d/ 目录机制设计初衷是「局部覆盖」,但文件名 ASCII 排序导致 99-*.conf 总是最后加载,极易产生「看似主配置生效,实际被 override」的隐蔽故障。

预防措施

  1. 配置即代码:将 /etc/systemd/journald.conf 纳入 Ansible/ SaltStack 管理,禁止手工修改 *.conf.d/ 目录。
  2. 变更 checklist 增加「override 扫描」:
    • 变更前执行 systemctl cat <unit> 并截图存档
    • 变更后执行 systemd-analyze cat-config <unit> 验证最终生效配置
  3. 监控前置:
    • 新增告警规则:journalctl --disk-usage | awk '{print $7}' > 5G 触发 P2 告警
    • 新增日志速率监控:journalctl --since "1 minute ago" | wc -l > 1000 触发 P3 告警(防日志风暴)
  4. 定期演练:每季度执行一次「journald 日志轮转与裁剪演练」,验证 journalctl --vacuum-time 与 SystemMaxUse 的实际效果。
  5. SELinux 策略治理:建立「容器 SELinux 策略清单」,所有新上线容器必须通过 audit2allow 生成策略并 review,避免 AVC denied 日志泛滥。

总结

本次故障的根因是「配置 override 机制被忽视 + 变更流程缺失 override 检查」,导致看似「已优化」的 journald 配置实际被高优先级文件覆盖。journald 的 Storage=auto 与 SystemMaxUse 在 override 场景下表现隐蔽,极易产生「配置已生效但实际未生效」的错觉。

教训:

  • systemd 的 *.conf.d/ 目录是「双刃剑」:方便局部定制,但优先级陷阱极易导致配置漂移。
  • 任何涉及「日志/监控/告警」的配置变更,必须在变更单中明确「配置加载验证」环节。
  • 监控指标应覆盖「配置实际生效值」(如 journalctl --disk-usage),而非仅依赖「配置已下发」。

延伸阅读:

使用 Hugo 构建
主题 Stack 由 Jimmy 设计