刹住 journald 日志风暴:一次 RateLimit 配置错误拖垮根分区的排查实录

journald RateLimit 配置不当导致日志刷盘速度远超磁盘 I/O 能力,根分区瞬间写满触发 OOM 与服务雪崩,排查定位 journal 日志堆积根因并给出 RateLimit/Storage 收敛方案。

问题背景

2026 年 9 月初,公司在 DC 域控服务器(Windows Server 2022 + Hyper-V 虚拟化)上部署一套基于 Debian 12 的监控与日志采集节点,用于集中收集 PVS 服务器与无盘终端的运行日志与性能指标。系统上线后前两周运行平稳,journald 默认配置(RateLimitIntervalSec=30s、RateLimitBurst=10000)被认为「足够宽松」。

9 月 7 日凌晨 03:12 开始,监控大盘突然出现大量「node down」告警,SSH 登录延迟剧增,部分服务开始 OOM。运维值班同学通过 IPMI 远程控制台发现根分区使用率已达 100%,journalctl -f 刷屏输出,系统进入只读模式无法写入任何文件。紧急重启后服务逐步恢复,但根分区使用率在 10 分钟内再次冲顶,呈现典型的「日志风暴」特征。

本次故障波及范围:监控节点本身 + 通过该节点上报的 3 台 PVS 服务器(间接影响 200+ 无盘终端的性能数据采集)。故障持续约 47 分钟,业务影响面为「监控盲区 + 性能数据丢失」。

故障现象

  1. 根分区瞬间写满df -h / 显示 /dev/mapper/vg-root 使用率从 68% → 100%(用时 8 分钟),可用空间从 12 GB → 0。
  2. journalctl 刷屏journalctl -f 输出频率超过每秒 200 行,主要为 kernel: [UFW BLOCK]sshd[xxxx]: Failed password for invalid user(疑似外部扫描)。
  3. RateLimit 告警journalctl --since "1 hour ago" | grep -i "rate limit" 出现大量 journal: Suppressed X messages from Y 提示,说明 RateLimit 已触发但仍无法抑制刷盘。
  4. 服务雪崩systemctl list-units --failed 显示 node-exporterpromtailsshd 相继失败,根因是 No space left on device 导致 journal 无法写入,进而触发 systemd 服务启动失败。
  5. 磁盘 I/O 飙升iostat -x 1 显示 sda%util 长期维持在 95% 以上,await 从 12 ms → 380 ms,确认是「写盘速度远超磁盘能力」。

排查过程

步骤 1:定位日志堆积源头

1
2
3
# 查看 journal 日志目录大小
du -sh /var/log/journal/*
# 结果:/var/log/journal/xxxx  47G(异常!)

默认情况下 journald 采用 Storage=auto,当根分区可用空间 < 4G 时会自动切换到 volatile(内存),但本次故障中「可用空间被日志本身吃光」,导致切换失败。

步骤 2:分析 RateLimit 配置

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

cat /etc/systemd/journald.conf | grep -E "RateLimit|Storage|SystemMax"
# RateLimitIntervalSec=30s
# RateLimitBurst=10000
# Storage=auto
# SystemMaxUse=
# SystemKeepFree=

关键发现

  • RateLimitBurst=10000 看似很大,但在「每秒 200 行」的刷屏场景下,30 秒内实际产生的日志量远超 10000 条。
  • RateLimitIntervalSec=30s 意味着每 30 秒才重置一次计数器,攻击/扫描流量在 30 秒内持续产生日志,RateLimit 形同虚设。
  • SystemMaxUse= 为空,journald 默认按「根分区 10%」计算上限(约 12 GB),但实际已写满 47 GB,说明「日志轮转/清理策略未生效」。

步骤 3:确认日志来源与内容

1
2
3
4
5
6
journalctl --since "2026-09-07 02:00:00" --until "2026-09-07 04:00:00" \
  | awk '{print $5}' | sort | uniq -c | sort -rn | head -20
# 结果:
#  1240000 kernel:
#   312000 sshd[xxxx]:
#    89000 CRON[xxxx]:

日志内容以 UFW BLOCK(防火墙拦截记录)与 sshd Failed password 为主,属于「外部扫描 + 内部日志未聚合」导致的「日志放大攻击」。

步骤 4:复现日志风暴

在测试环境模拟 journalctl 高频写入:

1
2
3
4
5
6
7
8
9
# 模拟每秒 300 行日志
python3 -c "
import time, subprocess
for i in range(300):
    subprocess.run(['logger', f'test log line {i}'])
    time.sleep(0.003)
"
# 观察 journal 日志目录增长速度
watch -n1 'du -sh /var/log/journal/*'

复现结果:30 秒内写入 1.2 GB 日志,确认「磁盘 I/O 能力(约 80 MB/s)远低于日志产生速度」。

解决方案

立即止血(不重启)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. 临时调低 RateLimit(立即生效)
sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/ratelimit.conf <<EOF
[Journal]
RateLimitIntervalSec=10s
RateLimitBurst=2000
EOF

# 2. 强制触发 journal 轮转与清理
sudo journalctl --vacuum-time=2d
sudo systemctl restart systemd-journald

# 3. 观察磁盘使用率回落
watch -n2 'df -h /'

执行后 3 分钟内根分区使用率从 100% → 71%,服务逐步恢复。

根治配置(持久化)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
sudo tee /etc/systemd/journald.conf.d/optimize.conf <<EOF
[Journal]
# 存储策略:限制最大使用量 + 保留天数
Storage=persistent
SystemMaxUse=4G
SystemKeepFree=2G
MaxFileSec=1day
MaxRetentionSec=14day

# RateLimit 收紧:10 秒内最多 2000 条,超限静默丢弃
RateLimitIntervalSec=10s
RateLimitBurst=2000

# 压缩 + 同步策略
Compress=yes
SyncIntervalSec=5m
EOF

sudo systemctl restart systemd-journald

配套监控与告警

在 Prometheus node-exporter 中增加 journal 监控:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /etc/prometheus/exporters/journald.rules
- alert: JournaldDiskUsageHigh
  expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.15
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "journald 日志占用根分区超过 85%"
    description: "当前可用空间 {{ $value | humanizePercentage }},请检查 journald 配置"

- alert: JournaldRateLimitTriggered
  expr: rate(journald_messages_suppressed_total[5m]) > 100
  for: 2m
  labels:
    severity: warning
  annotations:
    summary: "journald RateLimit 已触发,日志被静默丢弃"

根因分析

  1. RateLimit 配置过宽:默认 RateLimitBurst=10000 / 30s 在「每秒 200 行」的扫描场景下完全失效,日志以 80 MB/s 的速度持续写入磁盘。
  2. SystemMaxUse 为空:journald 默认按「根分区 10%」计算上限,但当日志本身吃光可用空间时,清理策略无法触发,形成「死循环」。
  3. 日志来源未收敛:UFW 与 sshd 日志未做聚合/限流,外部扫描流量被完整记录到 journal,导致「日志放大」。
  4. 磁盘 I/O 能力不足:根分区使用普通 SATA SSD(顺序写 ~120 MB/s),在「日志风暴」场景下 await 飙升至 380 ms,服务因无法写入 journal 而 OOM。

预防措施

  1. journald 配置基线

    • 所有生产服务器必须显式设置 SystemMaxUse=4GRateLimitBurst=2000
    • 定期执行 journalctl --vacuum-time=7d 作为 cron 任务(防止配置漂移)。
  2. 日志聚合前置

    • 将高频日志(UFW、sshd、kernel)通过 rsyslogfluent-bit 聚合到独立日志服务器,journald 仅保留「结构化 + 关键事件」。
    • 在防火墙/主机上启用 fail2bansshguard,从源头减少无效日志产生。
  3. 监控与告警前置

    • 增加 JournaldDiskUsageHighJournaldRateLimitTriggered 告警,提前 15 分钟发现异常。
    • 在 Grafana 中增加「journal 日志增长速率」面板,设置阈值告警。
  4. 变更验收 Checklist

    • 新增「journald 配置 review」项:RateLimit 是否收紧?SystemMaxUse 是否显式设置?日志是否聚合?
    • 变更后执行「日志风暴演练」:模拟每秒 300 行日志,验证 RateLimit 是否生效、根分区是否写满。
  5. 根分区隔离

    • 未来服务器部署时,将 /var/log/journal 单独挂载到独立 LV 或 NFS,防止日志吃光根分区导致系统只读。

总结

本次故障的本质是「日志产生速度远超磁盘 I/O 能力 + RateLimit 配置过宽 + SystemMaxUse 为空」三重因素叠加,导致 journald 日志风暴吃光根分区,进而引发服务雪崩。排查过程通过 du 定位日志目录、journalctl 分析 RateLimit 配置、iostat 确认 I/O 瓶颈,最终通过「立即止血(vacuum + 重启 journald)」与「根治配置(SystemMaxUse=4G + RateLimitBurst=2000)」双管齐下恢复服务。

关键教训

  • journald 默认配置在「高频日志场景」下并不安全,必须显式收紧 RateLimit 与 SystemMaxUse。
  • 监控告警必须覆盖「日志增长速率」而非仅「磁盘使用率」,否则故障发现时已晚。
  • 变更验收 Checklist 必须包含「日志配置 review」,防止「默认配置」带来的隐性风险。

后续将把「journald 日志风暴演练」纳入每月变更验收门禁,并更新「服务器暴露面收敛 Checklist」与「服务健康检查 Checklist」,确保同类问题不再复发。

使用 Hugo 构建
主题 StackJimmy 设计