跳转至

SOP — 日志解析增强:多行合并(#3)/ 日志内时间戳(#4)

工程:monitoring-p2-20261001(EFK)|实施日期:2026-10-02 交付目录:log-parse-20261002/|对应差距清单:docs/LOGGING-GAP.md #3、#4

1. 背景

EFK 采集面此前有两处语义缺口:

  • #3 多行合并缺失:Filebeat 模板无 multiline 配置,Java/Python 堆栈被拆成 N 条独立事件,检索与计数失真。
  • #4 日志内时间戳未解析:@timestamp 一律取采集时间,历史补采、跨机排序与延迟分析不准。

2026-10-02 只读取证(probe_samples.py / probe_es.py)结论,决定了本轮的实施范围:

日志类型 真实格式 处置
Slurm slurmctld/slurmdbd [2026-09-18T05:43:25.897] ✅ 解析(含年份)
Slurm prolog/epilog_slurmctld 2026-09-18 06:35:46 ✅ 解析(含年份)
OpenWrt 转发日志 2026-10-02T02:10:00+08:00 ✅ 解析(含时区)
audit / LSF logdir epoch 秒 ⏸ 本轮不做(需额外字段提取)
FlexLM lmgrd.log 18:45:17(只有时刻) ❌ 无日期,不可解析
VNC 会话日志 无时间戳 ❌ 不可解析
journald __REALTIME_TIMESTAMP ❌ 本就是真实时间,无需改
K8s 容器日志 CRI 前缀时间 ❌ container parser 已用于 @timestamp(实测确认)

多行堆栈的实际需求极低:ES 中 Traceback (most recent call last) 仅 2 条、at org./at java. 0 条;节点上含堆栈的容器日志文件 0 个。故 #3 采用保守 pattern、只挂在容器 input,属"补齐能力"而非"解决高频问题"。

2. 目标

可验证的完成状态:

  1. 主机侧 log_source=domain(Slurm)与 log_source=network(OpenWrt)事件的 @timestamp 等于日志行内时间(以 Asia/Shanghai 归一)。
  2. K8s 容器日志中,以空白开头的堆栈续行与首行合并为一条事件(log.flags 含 multiline)。
  3. 解析失败不影响采集成败:任何解析不出的事件保留采集时间,不丢事件。
  4. 幂等:复跑第二次零变更、全跳过。

3. 前置条件

  • 工程 venv:monitoring-p2-20261001/.venv(经 .pth 挂载共享库 ~/py_public_modules)。
  • 凭据:EFK 口令在 /home/rain/.secrets/efk.env(600),不得读取内容,只在实现体内取用。
  • 权限:集群外主机的 root SSH(由共享库连接池处理);kubectl 可用 ~/.kube/config。
  • 门禁:main.py precheck 通过(OS 版本、磁盘、时钟偏移)。

4. 影响范围

项 内容
主机 集群外 16 台(harbor 1 + Slurm 5 + Lustre 2 + LSF 5 + hpc-submit 1 + flexlm 1 + dell-R620 1)
集群 K8s 4 节点上的 filebeat DaemonSet(monitoring 命名空间)
停服 不需要。Filebeat 重启仅为采集器自身重启,不影响被采业务
采集中断 每台/每 Pod 秒级(重启窗口),期间该机新日志不受影响(文件仍在,重启后从 registry 断点续采)
窗口期 无硬性窗口;建议避开大规模作业启动高峰
在跑作业 有作业时不影响:本变更不触碰 Slurm/LSF 服务,仅重启采集器

5. 分步操作

步骤 1:主机侧模板增加时间戳 processor

在 artifacts/filebeat-host.yml.tpl 的 host-domain(Slurm/LSF 领域日志)与 host-network-log(OpenWrt 转发日志)两个 input 内,各追加同一段 processors:script(正则提取行内时间到临时字段 log_ts)→ timestamp(覆盖 @timestamp)→ drop_fields(清理临时字段)。

注意事项与坑

  • Filebeat 没有 grok processor(那是 Logstash 的)。用 grok 会让 Filebeat 直接起不来(the processor action grok does not exist)——本轮离线实测踩到,改用 script。
  • 必须 ignore_failure: true:timestamp 默认在解析失败时丢弃事件;不设会因个别畸形行丢日志。
  • 必须显式 timezone: Asia/Shanghai:日志按本地时间写,Filebeat 进程在 UTC 下运行;不设会整体偏移 8 小时。
  • 注释必须写在角色门控标记内(host-network-log 段):标记外的注释对所有主机可见,会改变既有渲染结果、触发无谓的配置重下发与重启。
  • log_ts 必须在 timestamp 之后 drop_fields 清掉,避免污染索引字段。
# 模板位置与改动点(人工核对用;实际下发由实现体渲染后写入目标机)
sed -n '/id: host-domain/,/check_interval: 30s/p' artifacts/filebeat-host.yml.tpl
sed -n '/id: host-network-log/,/host-network-log <<</p' artifacts/filebeat-host.yml.tpl

预期:两个 input 内均含 script + timestamp + drop_fields 三个 processor。 失败判断:模板 YAML 解析失败(python -c "import yaml,sys;yaml.safe_load(...)")→ 修正后重来。

实现体:modules/host_filebeat.py(render_config → probe_config(sha256 比对)→ put_text → systemctl restart)。

步骤 2:K8s 侧容器 input 增加 multiline parser

把 manifests/12-filebeat-k8s.yaml 中容器 input 的 parsers 由 [container] 扩为 [container, multiline]。

注意事项与坑

  • 顺序必须 container 在前、multiline 在后:容器每行都带 CRI 前缀(以时间戳开头),若 multiline 在前,^[[:space:]] 永不匹配,等于没配。
  • pattern 取 ^[[:space:]](以空白开头即为续行),是 Java/Python 堆栈的通用特征;保守,不会把正常单行日志合并。
  • 代价:一条多行事件的末行后需等默认 timeout(5s)才 flush,尾延迟增加。
  • 改 ConfigMap 内容不会重建 Pod,必须显式 rollout restart;本工程的 apply_with_configmap_restart() 已按 resourceVersion 指纹判断并自动重启。
# 等价底层操作(实现体已封装指纹判断)
kubectl --kubeconfig ~/.kube/config apply -f manifests/12-filebeat-k8s.yaml
kubectl --kubeconfig ~/.kube/config -n monitoring rollout restart ds/filebeat   # 仅当 ConfigMap 内容变化
kubectl --kubeconfig ~/.kube/config -n monitoring rollout status ds/filebeat --timeout=120s

预期:configmap/filebeat-config configured;rollout status 返回 successfully rolled out。 失败判断:DaemonSet 就绪数 < 4 或 Pod 报 the processor action ... does not exist → 第 7 章回滚。

实现体:modules/k8s_efk.py(apply_with_configmap_restart)。

步骤 3:下发与分批

主机侧由实现体分批(ops.batch_size=5,批内并发 5,失败阈值 1)。

# 全量下发(等价底层:逐台 scp 渲染后的 filebeat.yml + systemctl restart filebeat)
cd /home/rain/Documents/k8s_ins/monitoring-p2-20261001 && .venv/bin/python main.py deploy
# 或仅主机侧(复用本主题脚本,灰度用)
.venv/bin/python ../log-parse-20261002/apply_hosts.py <ip>[,<ip>...]

预期:每台 write-config,start-service;已下发过的主机输出空 actions(幂等跳过)。

6. 验证

6.1 主机侧时间戳(硬证据)

离线预验证(临时配置 + output.console,不触碰线上采集器):

# 在灰度机上以独立 path.data 跑一次,观察 @timestamp
filebeat -c /tmp/fbtest/t.yml --path.data /tmp/fbtest/data -e

实测结果:[2026-09-29T20:37:54.001] → 2026-09-29T12:37:54.001Z;2026-10-02T20:25:00+08:00 → 2026-10-02T12:25:00.000Z(时区正确)。

线上确认(ES):

# 取 log_source=domain / network 的最近事件,比对 @timestamp 与 message 行首时间
curl -s -u "elastic:$ELASTIC_PASSWORD" -H 'Content-Type: application/json' \
  'http://localhost:9200/filebeat-host-*/_search' -d '{"size":3,"sort":[{"@timestamp":"desc"}],
  "_source":["@timestamp","message","log_source"],"query":{"term":{"log_source":"network"}}}'

预期:@timestamp(UTC)= message 行首时间 − 8h,秒级一致。 失败判断:@timestamp 与行内时间差为"采集延迟"量级(数秒~数分)→ 说明 processor 未生效,查该机 Filebeat 日志。

6.2 K8s 侧多行合并

kubectl --kubeconfig ~/.kube/config -n monitoring get ds filebeat \
  -o jsonpath='{.status.numberReady}/{.status.desiredNumberScheduled}'      # 预期 4/4
kubectl --kubeconfig ~/.kube/config -n monitoring logs <filebeat-pod> --tail=120 | grep -i error

本环境几乎无堆栈,数据侧计数可能为 0(属预期);行为正确性由离线 fixtures 证明:在 filebeat Pod 内以真实 Filebeat 二进制 + 真实 parser 配置跑 4 行样本,at ... 三行合并为一条且 log.flags=["multiline"]。

6.3 幂等复跑

再次执行步骤 3:预期 16 台主机全部无 actions、K8s 清单全部 unchanged、DaemonSet 不重启。

本轮实测:主机侧 16/16 零变更;K8s configmap/filebeat-config unchanged、daemonset/filebeat unchanged。

7. 回滚

备份位于 log-parse-20261002/backup/,台账见 backup/README.md。

cd /home/rain/Documents/k8s_ins
B=log-parse-20261002/backup/monitoring-p2-20261001
# 1) 还原两个文件
cp -p "$B/artifacts/filebeat-host.yml.tpl.bak.20261002-205140" monitoring-p2-20261001/artifacts/filebeat-host.yml.tpl
cp -p "$B/manifests/12-filebeat-k8s.yaml.bak.20261002-205140"  monitoring-p2-20261001/manifests/12-filebeat-k8s.yaml
# 2) 主机侧重新下发(sha256 变化即触发重新写入与重启)
cd monitoring-p2-20261001 && .venv/bin/python ../log-parse-20261002/apply_hosts.py <全部 16 台 ip>
# 3) K8s 侧 apply(指纹变化触发 DaemonSet 重启)
kubectl --kubeconfig ~/.kube/config apply -f manifests/12-filebeat-k8s.yaml

回滚触发条件(任一达标即回滚):① 主机 Filebeat systemctl is-active 非 active;② Pod 报 processor 不存在等配置错误;③ @timestamp 与行内时间偏移量级不符(非秒级采集延迟);④ 出现事件丢弃(type=domain 计数明显下降)。

回滚后验证:目标主机 systemctl is-active filebeat = active;DaemonSet 4/4;ES 中 log_source 各类型计数恢复。

8. 风险说明

风险 缓解
timestamp 解析失败丢事件 全部 processor 设 ignore_failure: true,解析不出即保留采集时间
时区错误致整体偏移 8h 显式 timezone: Asia/Shanghai;已用离线 fixtures 验证
事件时间早于当前致写入过期索引 仅对"实时 tail"的日志启用;ILM 14 天,过期索引会自动清理
multiline 误合并正常日志 pattern 保守(仅以空白开头的行),且仅挂容器 input
multiline 增加尾延迟 知悉并接受(末行后最多 5s flush);如需可调 multiline.timeout
ConfigMap 改动未生效 rollout restart 由实现体按指纹自动触发
无年份时间戳(FlexLM/VNC)被误解析 不为其配置 timestamp;pattern 要求完整 YYYY-MM-DD

附录:交付物清单

文件 说明
probe_samples.py 只读取证:各日志类型的真实格式样本
probe_es.py 只读取证:ES 中堆栈/时间戳现状
probe_ts.py 只读离线验证:script+timestamp 行为(含踩坑记录)
apply_hosts.py 主机侧下发(灰度/全量共用)
apply_k8s.py K8s 侧 ConfigMap 下发与验证
verify_stage.py 灰度验证(服务状态 + ES 时间戳比对)
backup/ 两个被改文件的备份与台账

被改文件:monitoring-p2-20261001/artifacts/filebeat-host.yml.tpl、monitoring-p2-20261001/manifests/12-filebeat-k8s.yaml。


来源:/home/rain/Documents/k8s_ins/log-parse-20261002/SOP.md | 最后更新:2026-10-02 21:00(由 build/sync.py 同步,请勿在站点侧修改)