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. 目标¶
可验证的完成状态:
- 主机侧
log_source=domain(Slurm)与log_source=network(OpenWrt)事件的@timestamp等于日志行内时间(以 Asia/Shanghai 归一)。 - K8s 容器日志中,以空白开头的堆栈续行与首行合并为一条事件(
log.flags含multiline)。 - 解析失败不影响采集成败:任何解析不出的事件保留采集时间,不丢事件。
- 幂等:复跑第二次零变更、全跳过。
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 没有
grokprocessor(那是 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同步,请勿在站点侧修改)