上一篇 下一篇 分享链接 返回 返回顶部

香港服务器的 Debian 11:我如何把 systemd + journald 调到“不丢一条日志”

发布人:Minchunlin 发布时间:2025-08-25 10:05 阅读量:768


上周五,推广活动 23:00 准点打爆,我们边盯 Grafana,边看 Nginx 的 5xx 抖了一下。我第一反应是翻日志,可 journalctl 里出现了一条刺眼的提示:

“Suppressed 14322 messages from PID 1227 (api.service)”

那一刻我就知道,问题不在业务先,在我这条日志链路上。

这篇就是我后来“补课”的完整记录:在 Debian 11(Bullseye,systemd v247)上,如何把 systemd 与 journald 结合起来,在高流量场景既不丢日志、又不把机器拖垮。不管你是第一次摸 journald 的新手,还是准备给线上拧螺丝的老手,我尽量把坑和细节都摊开。

现场环境与基线

机房/网络

  • 机房:葵涌 KCDC,双路市电 + N+1 UPS
  • 上联:10G 两路(HKIX + 运营商专线),BGP 入口限速 9.5 Gbps
  • 峰值(事故当晚):7.8 Gbps,HTTP RPS 峰到 320k req/s(多机摊)

香港服务器(单台)

项目 参数
机型 1U 定制(相当于 Dell R650 级别)
CPU Intel Xeon Silver 4314 × 2(32C/64T)
内存 256GB DDR4-3200
系统盘 2 × 480GB SATA SSD(RAID1)
日志盘 2 × 1.92TB NVMe U.2(RAID1,XFS)
网卡 Intel X710 10GbE × 2(LACP)
OS Debian 11 (Bullseye),systemd v247
容器 Docker 24(daemon 日志驱动改 journald)

服务进程(与日志相关)

api.service(Go) / edge-ngx.service(OpenResty) / queue-worker.service(Rust)

集中式日志:一台“汇聚节点”,跑 systemd-journal-remote

每台业务机跑 systemd-journal-upload 主动推送

当晚症状

journald 告警:Suppressed N messages(典型 RateLimit 触发)

/run/log/journal 使用率飙升(内存环形区),持久目录未开启

rsyslog 默认装上形成双写(journald → rsyslog → 文件),I/O 抖

目标与方案

目标

  • 零丢失:在极端突发时刻不压死关键业务日志
  • 低开销:CPU、I/O 可控,不给 P99 延迟添堵
  • 可回放/可聚合:跨机查询、追踪一条请求链路
  • 可观测:丢弃/限流要可见、可报警

方案总览(文字拓扑)

应用(stdout/stderr)→ systemd(接 stdout,打上 Unit/CGroup 元数据)→ journald(持久化、限流、压缩、配额)→ journal-upload(TLS 推送)→ 汇聚机 journal-remote(集中存储/查询)

关键点:

  • 把日志入口统一到 systemd(不是散落到各自文件)
  • journald 持久化到 NVMe,合理限流/配额
  • 只保留一种链路(禁用无意义的 syslog 双写)
  • 对关键服务单独放宽/关闭限流,对“话痨服务”降噪

实操一步步

1)开启持久存储并做好权限

journald 默认可能只把日志扔在内存(/run/log/journal)。先启持久目录:

sudo mkdir -p /var/log/journal
sudo chown root:systemd-journal /var/log/journal
sudo chmod 2755 /var/log/journal

2755 和 systemd-journal 组很重要,否则你会发现普通用户/采集侧读不到。

2)为日志盘单独挂载(可选但强烈建议)

NVMe RAID1,XFS:

# 假设阵列设备是 /dev/md3
sudo mkfs.xfs -f /dev/md3
echo '/dev/md3 /var/log/journal xfs noatime,nodiratime,logbufs=8,logbsize=256k 0 2' | sudo tee -a /etc/fstab
sudo mount -a

XFS 在 journald 这种小对象写入场景通常比 ext4 更稳,noatime 能少些脏写。

3)调 journald.conf

编辑 /etc/systemd/journald.conf(没有就创建),这套是我在 300k+ RPS 下跑通的保守参数:

[Journal]
# 存储与压缩
Storage=persistent
Compress=yes
Seal=yes
SplitMode=uid

# 刷盘与速率
SyncIntervalSec=5m
RateLimitIntervalSec=5s
RateLimitBurst=20000

# 空间与单文件大小(根据你日志盘大小调整)
SystemMaxUse=8G
SystemKeepFree=5G
SystemMaxFileSize=256M
RuntimeMaxUse=1G
RuntimeKeepFree=256M
RuntimeMaxFileSize=64M

# 仅保留一条链路,避免 journald -> rsyslog -> 文件产生双写抖动
ForwardToSyslog=no
ForwardToKMsg=no
ForwardToConsole=no
ForwardToWall=no

# 限制级别:丢弃 debug 噪音(必要时再开)
MaxLevelStore=info
MaxLevelKMsg=notice

应用后重启:

sudo systemctl restart systemd-journald
sudo systemctl status systemd-journald -l

为什么不是把 RateLimit 直接关掉?

关掉(设置为 0)确实“不丢”,但极端情况下会把 journald 本身打到高 CPU。我的策略是全局放宽(5s/20000),关键服务单独放开(下一步)。

4)让服务把日志直接打进 journal,并做“分级限流”

以 api.service 为例(Go 应用,stdout 打日志):

sudo systemctl edit api.service

加入 drop-in:

[Service]
# 入口统一:把 stdout/stderr 都进 journal
StandardOutput=journal
StandardError=journal

# 便于检索
SyslogIdentifier=api

# 关键服务不做 rate limit(只在个别核心服务使用)
LogRateLimitIntervalSec=0
LogRateLimitBurst=0

# 避免进程太多 fd 打爆
LimitNOFILE=1048576

# 发生崩溃自动拉起,减少“窗口期”
Restart=always
RestartSec=0.5s

Nginx/OpenResty(edge-ngx.service)我不再让它写 access.log 文件,而是改配置把访问日志写 syslog(由 systemd 接管)。在 nginx.conf:

error_log  syslog:server=unix:/dev/log,tag=edge-ngx,severity=error;
access_log syslog:server=unix:/dev/log,facility=local7,tag=ngx_access;

再在 edge-ngx.service 同样设置 StandardOutput/StandardError,确保一切归一到 journal。

容器场景:把 Docker 日志驱动也改 journald

/etc/docker/daemon.json:

{
  "log-driver": "journald",
  "log-opts": {
    "tag": "{{.Name}}.{{.ID}}"
  }
}

改完 sudo systemctl restart docker,容器 stdout/stderr 也统一走 journal 了。

5)集中式收集:journal-upload → journal-remote

汇聚机(集中节点)

sudo apt-get update
sudo apt-get install -y systemd-journal-remote
# 使用 socket 激活,监听 19532/TCP
sudo systemctl enable --now systemd-journal-remote.socket
# 也可直接启服务(需要配置 TLS 证书)
# sudo systemctl enable --now systemd-journal-remote.service

/etc/systemd/journal-remote.conf(启用 TLS,生产必须):

[Remote]
Seal=yes
SplitMode=host
ServerKeyFile=/etc/systemd/journal-remote/journal-remote.key
ServerCertificateFile=/etc/systemd/journal-remote/journal-remote.crt
TrustedCertificateFile=/etc/systemd/journal-remote/ca.crt

生成/下发证书略。目标目录默认在 /var/log/journal/remote。

业务机(每台)

sudo apt-get install -y systemd-journal-remote  # 里面包含 upload

/etc/systemd/journal-upload.conf:

[Upload]
URL=https://<汇聚机IP或域名>:19532
# 双向 TLS(推荐)
ServerKeyFile=/etc/systemd/journal-upload/upload.key
ServerCertificateFile=/etc/systemd/journal-upload/upload.crt
TrustedCertificateFile=/etc/systemd/journal-upload/ca.crt

启用:

sudo systemctl enable --now systemd-journal-upload.service
sudo systemctl status systemd-journal-upload -l

验证汇聚机能看到来自各主机的日志:

# 在汇聚机上
sudo journalctl -D /var/log/journal/remote -o short-iso --since "5 min ago" | head

journal-upload 内建断点续传(基于 cursor),短线/重启不会重复泛滥或丢失。

6)监控与可见性

常用指令:

# 看磁盘占用
journalctl --disk-usage

# 校验日志完整性与损坏
sudo journalctl --verify

# 看 journald 自身是否发生抑制(RateLimit)
journalctl -u systemd-journald -b -p warning

# 过滤某服务(含元数据)
journalctl -u api.service -o json | head -n 5

我还加了两个告警:

当 journalctl -u systemd-journald | grep Suppressed 命中次数 > 0,触发告警

当 /var/log/journal 使用率 > 85% 且增长速率持续 10 分钟,提示扩容或调低日志等级

压测与对比数据

压测方法:3 台 wrk 压住边缘层,业务链路写结构化 JSON 到 stdout;每条请求 2 条日志(入/出),可控洪峰。

调优前(默认 Debian 11)

指标 数值
RateLimit 参数 30s / 1000(默认)
丢日志(Suppress) 1.9% ~ 4.3%(峰值)
journald CPU 60% ~ 120%(总核)
/run 占用 峰到 850MB(易顶满)
P99 延迟抖动 +6.8%

调优后(本文参数)

指标 数值
RateLimit 参数 5s / 20000(关键服务 0/0)
丢日志(Suppress) 0%(连续 3 次压测)
journald CPU 18% ~ 35%
/var/log/journal 占用 稳定 2.1GB(配额 8GB)
P99 延迟抖动 +1.2%
汇聚落盘速率 75–110 MB/s(峰)

现场踩过的坑 & 修复手记

双写环路

装了 rsyslog 又让 journald ForwardToSyslog=yes,等于同时写两套,还可能 syslog 再写回 journald。表现就是 I/O 抖动 + 重复日志。

解法:关 ForwardToSyslog,保留一条通路;需要转发再让汇聚机做。

只开了 Runtime 存储

/var/log/journal 不存在,重启后“断档”。

解法:创建目录、设好权限(上文步骤 1)。

关键服务被全局 RateLimit 连坐

高峰期一些 metrics agent、健康检查刷屏,把全局突发额度吃掉,关键服务也被“连坐”。

解法:用 per-service 的 LogRateLimitIntervalSec/LogRateLimitBurst=0 单独放开核心服务;反而给 agent 类服务单独设置较低的 burst。

日志过长被截断

某些错误堆栈一次输出几十 KB,阅读困难。

策略:应用侧改成多行结构化(每行有相同 TraceID),而不是硬怼超长一行;检索时用 _PID + TRACE_ID 组合。

汇聚机时间偏移

引用时间范围查询不准。

解法:统一 NTP(systemd-timesyncd 或 chrony),并观察 _BOOT_ID 与 __REALTIME_TIMESTAMP 的一致性。

权限问题

观察/排障账号看不到完整日志。

解法:加入 systemd-journal 组:sudo usermod -aG systemd-journal <user>。

可复制的“最终配方”

journald(统一版)

# /etc/systemd/journald.conf
[Journal]
Storage=persistent
Compress=yes
Seal=yes
SplitMode=uid
SyncIntervalSec=5m
RateLimitIntervalSec=5s
RateLimitBurst=20000
SystemMaxUse=8G
SystemKeepFree=5G
SystemMaxFileSize=256M
RuntimeMaxUse=1G
RuntimeKeepFree=256M
RuntimeMaxFileSize=64M
ForwardToSyslog=no
ForwardToKMsg=no
ForwardToConsole=no
ForwardToWall=no
MaxLevelStore=info

关键服务 drop-in(示例)

# systemctl edit api.service
[Service]
StandardOutput=journal
StandardError=journal
SyslogIdentifier=api
LogRateLimitIntervalSec=0
LogRateLimitBurst=0
LimitNOFILE=1048576
Restart=always
RestartSec=0.5s

Docker 日志驱动
// /etc/docker/daemon.json
{
  "log-driver": "journald",
  "log-opts": {
    "tag": "{{.Name}}.{{.ID}}"
  }
}

汇聚机

# /etc/systemd/journal-remote.conf
[Remote]
Seal=yes
SplitMode=host
ServerKeyFile=/etc/systemd/journal-remote/journal-remote.key
ServerCertificateFile=/etc/systemd/journal-remote/journal-remote.crt
TrustedCertificateFile=/etc/systemd/journal-remote/ca.crt

业务机上传

# /etc/systemd/journal-upload.conf
[Upload]
URL=https://log-collector.internal:19532
ServerKeyFile=/etc/systemd/journal-upload/upload.key
ServerCertificateFile=/etc/systemd/journal-upload/upload.crt
TrustedCertificateFile=/etc/systemd/journal-upload/ca.crt

运维“复盘清单”(上线前逐条核对)

  •  /var/log/journal 存在,权限正确(2755 & systemd-journal)
  •  journald Storage=persistent,ForwardToSyslog=no
  •  全局 RateLimitIntervalSec/RateLimitBurst 合理,关键服务 drop-in 放开
  •  NVMe 专盘挂载参数合理(noatime 等)
  •  Docker 日志驱动 journald 生效
  •  journal-upload / journal-remote TLS 正常,跨机可查
  •  告警:检测 Suppressed 日志 & 空间水位
  •  预案:磁盘满时 journalctl --vacuum-size=... / --vacuum-time=...

尾声:再回到那个周五

活动结束后,我们把全链路重放了一次。那串最先抖动的 5xx,在汇聚机上被我一条条拉了出来:从边缘层的入站头、到 API 的超时重试、再到内部服务的一处锁竞争,路径清清楚楚。第二周,新的业务线在同样的压力测试下,零丢失、延迟平稳,上线像换了辆底盘扎实的新车。

我一直觉得,日志系统对运维像黑匣子之于航空。systemd + journald 并不花哨,但只要把入口统一、限流与配额想清楚、再加一台稳当的汇聚点,在香港这种高流量、波动大的环境里,也一样能稳稳落地。如果你也在为“Suppressed N messages”头疼,不妨按这篇把螺丝拧一遍。多数时候,问题就会安静下来。

目录结构
全文