跳转至

SOP —— 监控 P2:EFK 日志聚合 + 黑盒探测 + 保留期容量规划

变更日期:2026-10-01 | 工程:monitoring-p2-20261001 | 蓝图:docs/MONITORING-ROADMAP.md §4 P2(缺口 G7 + G8 的一部分) 运行与日常使用见同目录 RUNBOOK.md

目录

  1. 背景 2. 目标 3. 前置条件 4. 影响范围
  2. 分步操作 6. 验证 7. 回滚 8. 风险说明 附录

1. 背景

监控蓝图的三步走 P0(主机层)、P1(四领域语义)已完成,P2 未开始。P2 覆盖两个缺口:

  • G7:无黑盒与日志 —— 入口/域名/端口/证书全无探测;无日志聚合能力。故障时只能看指标,看不到日志上下文。
  • G8 的一部分:保留期与容量规划未做(Prometheus 接入 14 台主机后未复核序列与容量)。

本轮把这两块补齐。日志方案在蓝图原文中定为 Loki;实施前经用户重新选型,改为 ELK 系(用户对该技术栈更熟悉)。经两轮调整后最终形态为:

EFK(Elasticsearch + Filebeat + Kibana,不含 Logstash)—— Filebeat 采集成端直投 ES,省掉一个 JVM 与一跳管道。

2. 目标

变更完成后应达到的可验证状态:

项 目标状态 验证方式
日志采集 K8s 4 节点(DaemonSet)+ 集群外 14 台主机(systemd)均有 Filebeat 在跑 第 6 章 V1/V2
日志覆盖面 七类:系统(messages) / 安全(secure) / 审计(audit.log) / 定时(cron) / 领域(slurm·lsf) / 许可(lmgrd) / 图形会话(vnc);K8s 侧另有容器 stdout 第 6 章 V3
采集自监控 主机侧 filebeat_scrape_up 等 6 项进 Prometheus;K8s 侧进 ES .monitoring-beats-* 第 6 章 V8
日志可查 Kibana https://kibana.rainchen.top 可登录,能看到 filebeat-* 索引有数据 V3
保留期 ILM 策略 efk-14d:索引 1 天或 20 GB 滚动,14 天后删除 V4
黑盒探测 9 个目标(apiserver / 5 个 HTTPS 域名 / LDAP 389+636 / MariaDB 3306)probe_success=1 V5
证书监控 任一 HTTPS 目标证书余期 < 30 天告警、< 14 天严重告警 V6
容量基线 ES 磁盘占用与 Prometheus 序列数有实测数字,作为扩容判据 第 6 章 V7

3. 前置条件

全部由 main.py precheck 可执行校验(只读,落盘 reports/precheck-*.md):

# 条件 说明 不满足时
1 kubectl 可访问集群、/readyz 正常 本机 kubeconfig 已就绪 BLOCK
2 Harbor 已接入 docker.elastic.co(registry + proxy cache project) 节点 ghcr.io/上游不可达,镜像必须经 Harbor BLOCK(拉不到镜像)
3 集群外 14 台主机 SSH 可达且 OS 主版本 = 9 Rocky Linux 9.8 BLOCK
4 4 个 K8s 节点 vm.max_map_count >= 262144 ES 启动硬要求,低于此值容器直接退出;由工程幂等设置 工程自动补齐
5 目标主机可访问 artifacts.elastic.co(或已备离线 RPM) 实测可达(HTTP 302),Filebeat 走在线 RPM 需改用离线包
6 k8s-hdd 导出可用空间 ≥ 60 GB 实测 482 GB 可用 WARN
7 Traefik release 现场 values 基线可读 ../traefik_deploy/values.yaml BLOCK

权限:本机对 4 个 K8s 节点与 14 台目标主机均以 root 免密 SSH 操作;kubeconfig 为集群管理员。

4. 影响范围

对象 动作 是否停服 窗口
monitoring 命名空间 新增 ES(StatefulSet) / Kibana(Deployment) / Filebeat(DaemonSet) / blackbox-exporter(Deployment) 否 —
4 个 K8s 节点 写 /etc/sysctl.d/99-elasticsearch.conf 并 sysctl -w vm.max_map_count=262144 否(热生效) —
Traefik(k8s-master-01) 新增 elasticsearch entryPoint(宿主 9200)→ helm upgrade 是,入口中断数秒 低峰期
集群外 14 台主机 安装 Filebeat RPM + 下发配置 + systemctl enable --now 否(新增服务) 分批 5/5/4
存储 ES 新增 PVC 50 Gi(nfs-csi-hdd,宿主 .249) 否 —
既有监控 不改动 kube-prometheus-stack 的 values;仅新增 Probe / PrometheusRule 资源 否 —

⚠ 同源故障域:ES 数据与 Prometheus TSDB 同在宿主 .249 的 NFS 导出上。宿主故障会同时影响两者。

5. 分步操作

实现体:main.py → deployer.py → modules/k8s_efk.py(K8s 侧)与 modules/host_filebeat.py(主机侧)。以下为等价 shell,供阅读与手工复现;幂等、门禁与错误处理由实现体保证。

步骤 1:预检查(只读门禁)

cd /home/rain/Documents/k8s_ins/monitoring-p2-20261001
.venv/bin/python main.py precheck        # 报告落 reports/precheck-<ts>.md

预期:无 BLOCK 项。失败判断:出现 BLOCK → 按报告逐项修复,不要跳过门禁。

步骤 2:节点内核参数(ES 启动前置)

# 对 4 个节点各执行一次(幂等:已达标的节点跳过)
cur=$(sysctl -n vm.max_map_count); [ "$cur" -lt 262144 ] && {
  echo 'vm.max_map_count=262144' > /etc/sysctl.d/99-elasticsearch.conf
  sysctl -q -w vm.max_map_count=262144
}
sysctl -n vm.max_map_count     # 预期 262144

坑:ES 容器在 vm.max_map_count < 262144 时启动即退出,错误信息是 max virtual memory areas vm.max_map_count [65530] is too low,容易被误判为镜像问题。

步骤 3:凭据与 Secret

口令由工程生成(24 位随机),写入 /home/rain/.secrets/efk.env(600)后经 stdin 下发 Secret:

kubectl -n monitoring get secret elasticsearch-credentials \
  -o jsonpath='{.data}' | head -c 80      # 预期三个 key 存在

坑:口令不得出现在命令行参数里(ps 可见)。实现体用 kubectl apply -f - + stdin 传递整个 Secret 清单。

步骤 4:Elasticsearch

kubectl apply -f manifests/10-elasticsearch.yaml
kubectl -n monitoring rollout status statefulset/elasticsearch --timeout=600s

坑(本次实测踩到,两处):

  1. readinessProbe 不能用 httpGet:security 开启后所有端点都要认证,GET /_cluster/health 返回 401 → Pod 长期停在 0/1,而 ES 自身早已 GREEN。必须用 exec + curl -u elastic:$ELASTIC_PASSWORD。
  2. StatefulSet 的 template 更新后 Pod 未自动重建(本次实测):kubectl apply 已改 sts spec,但 Pod 仍是旧探针。需显式重建:kubectl -n monitoring delete pod elasticsearch-0。

步骤 5:ES 业务配置(幂等,可重复执行)

设置 kibana_system 口令、建 filebeat_writer_role 与 filebeat_writer 用户、建 ILM 策略 efk-14d:

kubectl -n monitoring exec -i elasticsearch-0 -- bash -lc \
 'curl -s -o /dev/null -w "%{http_code}" -XPUT \
   -u "elastic:$ELASTIC_PASSWORD" -H "Content-Type: application/json" \
   "http://localhost:9200/_ilm/policy/efk-14d" -d @-' <<'JSON'
{"policy":{"phases":{
  "hot":{"actions":{"rollover":{"max_size":"20gb","max_age":"1d"},"set_priority":{"priority":100}}},
  "delete":{"min_age":"14d","actions":{"delete":{}}}}}}
JSON

坑(本次实测踩到,两处):

  1. body 必须经 stdin 传入(-d @- + kubectl exec -i)。早期实现把 body 内联进 shell 并依赖目标端展开 $KIBANA_SYSTEM_PASSWORD,但 ES 容器只注入了 ELASTIC_PASSWORD,该变量为空 → {"password":""} → HTTP 400。
  2. kubectl exec 必须显式带 -n monitoring:清单自带 namespace,exec 不会继承;漏掉会落到 default 命名空间并报 pod not found,表现为「一直等到超时」。
  3. filebeat_writer 用户必须显式创建(PUT /_security/user/filebeat_writer);只调 /_password 对不存在的用户返回 400。

步骤 6:Kibana / Filebeat(K8s) / 路由

kubectl apply -f manifests/11-kibana.yaml
kubectl apply -f manifests/12-filebeat-k8s.yaml
kubectl apply -f manifests/13-traefik-routes.yaml

步骤 7:Traefik 追加 ES TCP 入口(会导致入口中断数秒)

helm --kubeconfig ~/.kube/config upgrade --install traefik \
  ../traefik_deploy/chart/traefik -n server \
  -f ../traefik_deploy/values.yaml \
  -f manifests/15-traefik-es-port.yaml \
  --no-hooks --timeout 10m --wait

坑:必须带现场 values 基线(两个 -f)。只传 overlay 会让其余配置回落到 chart 默认值并触发全栈重建 —— 本环境此前已因此发生过一次事故。工程在变更前先实机推导端口是否已存在,已存在则跳过,避免无谓重建入口。

步骤 8:黑盒探测

kubectl apply -f manifests/20-blackbox-exporter.yaml
kubectl apply -f manifests/21-probes.yaml
kubectl apply -f manifests/22-blackbox-alerts.yaml
kubectl -n monitoring rollout restart deploy/blackbox-exporter   # ConfigMap 变更需重启才生效

坑(本次实测踩到):

  1. 探测目标必须用 IP + Host 头:集群内 Pod 无法解析 rainchen.top(CoreDNS 只管集群域名),target 一律写 VIP/IP,域名语义由 module 的 headers.Host 与 tls_config.server_name 承担。
  2. ldapadmin.rainchen.top 挂在 Traefik 的 web(80) entryPoint,走 443 返回 404 → 该目标用 http://。
  3. kas / registry 两域无对外入口:它们绑定在 Envoy Gateway(ClusterIP,PROGRAMMED=False),Traefik 侧无路由,从 443 访问恒为 404。故未纳入探测清单,作为环境缺口登记(见第 8 章)。
  4. kubectl apply 不会删除已从清单中移除的对象,改为删除 Probe 时需显式 kubectl delete probe <name>。

步骤 9:集群外 14 台主机 Filebeat(分批 5/5/4)

# 单台等价操作(工程按批并发执行,失败达阈值即停批)
dnf install -y https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.19.0-x86_64.rpm
install -m 0600 -o root -g root <渲染后的 filebeat.yml> /etc/filebeat/filebeat.yml
systemctl enable --now filebeat.service

坑:目标主机的 filebeat.yml 含 output.elasticsearch.password,故权限固定 0600 root;配置以 sha256 比对决定是否重写,避免无谓重启。

步骤 9.5:采集面补齐(审计 / 定时 / 许可 / 图形会话 / 自监控)

首轮上线只采了 messages+secure+调度器日志。按主机日志采集基线(等保 2.0 + Elastic 主机集成) 对照后补齐,并做了一处实现选择:

# 补齐后的采集路径(主机侧,模板 artifacts/filebeat-host.yml.tpl)
#   /var/log/messages /var/log/secure          → log_source=syslog
#   /var/log/audit/audit.log                   → log_source=audit      ← 新增
#   /var/log/cron                              → log_source=cron       ← 新增
#   /var/log/slurm/*.log /var/log/lsf/*.log    → log_source=domain
#   /opt/FlexLM/*/lmgrd.log                    → log_source=flexlm     ← 新增
#   /home/*/.vnc/*.log                         → log_source=vnc        ← 新增
chmod 0600 /etc/filebeat/filebeat.yml && systemctl restart filebeat

关键取舍:不引 journald(两处都去掉) —— 原计划用 journald input 覆盖 systemd 单元日志, 实测证伪:

  1. 内容重复:节点单元日志经 rsyslog 已并入 /var/log/messages(.10 实测含 kubelet 2795 / containerd 2660 / haproxy 39 / keepalived 60 行;.16 含 slurmctld 445 / slurmd 2713 行;.28 含 vnc 字样 1615 行),再采 journal 只是重复入库;
  2. K8s 侧根本起不来:官方 filebeat 镜像内没有 journalctl 二进制,journald input 启动即失败(exec: "journalctl": executable file not found in $PATH)。

vnc 单独说明:.28 的 vncserver@:N 是 systemd 模板单元,其单元日志已随 messages 入库; 但 TigerVNC 的会话输出(Xlib/X 报错)只写 /home/<user>/.vnc/hpc-submit:<N>.log, 故单独采该路径。⚠ 属用户私有数据。

2026-10-01 修正(vnc 重复采集收敛):首版把该段无过滤地下发到 14 台,而 /home 为 共享(NFS)挂载,各主机都能读到 .28 家目录下的同一批文件 → 同一批数据被 11 台各采一遍 (入库 123.3 万条、冗余 90.9%;域分布 lsf 56.0 万 / slurm 56.0 万 / hpc 11.2 万)。 现改为模板内以 # >>> host-vnc >>> / # <<< host-vnc <<< 标记包裹,由 host_filebeat.render_config() → _strip_vnc_block() 按角色剥离:仅 submit 角色 (hpc-submit/.28)保留,其余主机连段删除;标记行对所有主机都去掉——保证 .28 的 渲染结果与旧版逐字节一致(sha256 不变 → 收敛时该台不重启、不断流)。收敛后 V1–V5 全通过。 详见 vnc-dedup-20261001/SOP.md。

2026-10-01 同族排查(NFS 共享挂载导致的跨主机重复采集):vnc 的根因是 /home 为 NFS 共享 (11 台挂到 192.168.31.249:/data/nfs/home)。对模板全部采集路径逐条核实: ① journald(本机);② /var/log/audit/audit.log(本机 xfs,14 台);③ /var/log/slurm/*.log (本机 xfs,实测仅 slurm 5 台有数据);④ /var/log/lsf/*.log(14 台均不存在); ⑤ /opt/FlexLM/*/lmgrd.log(本机,实测仅 .29);⑥ /data/nfs/lsf/*/logdir/*.log。

⚠ 上版对 ⑥ 的判断有误,2026-10-01 已更正:/data/nfs 确为本地挂载点,但其下的 /data/nfs/lsf 是 nfs4 子挂载(192.168.31.249:/data/nfs/lsf),被 6 台 (lsf .23–.27 + hpc-submit .28)同时挂载、内容字节级相同。findmnt --target /data/nfs 只看到父挂载点,会漏判子挂载——这是本次踩到的取证陷阱。当时该路径 0 条只是双重巧合: 通配层级少一层(实际为 /data/nfs/lsf/lsf/work/lsfce/logdir)且文件名不以 .log 结尾 (lsb.acct / lsb.events)。若按原意配对该路径,会造成 6 倍重复。 结论(更正版):ES 中当前确实只有 vnc 一处实际重复;但 ⑥ 的承载路径 /data/nfs/lsf 本就是共享挂载,只是因为路径写错才没采到——属"侥幸未中招",不是"没有风险"。 ⚠ 纳入 ⑥ 时必须单点采集(仅 lsf-master-01),否则会造成 6 倍重复。 附带发现:LSF 集群日志目前完全未进 EFK(/var/log/lsf 与 /data/nfs/lsf/*/logdir 均不存在), 需另行定位其真实 logdir。

2026-10-01 LSF / Lustre 真实日志源定位(承接上文)

LSF:真实 logdir = /data/nfs/lsf/lsf/work/lsfce/logdir/(由 mbatchd 的打开文件实证, 非靠猜),内容 lsb.acct / lsb.events / lsb.jobinfo.events / lsb.ncb.events + info/, 总量仅 104 KB(增长极慢)。⚠ 该路径在 6 台上是同一个 NFS 导出(249:/data/nfs/lsf), 故纳入时必须单点采集(仅 lsf-master-01),否则 6 倍重复。 原模板 /data/nfs/lsf/*/logdir/*.log 双重不匹配(层级少一层 + 文件名无 .log 后缀)→ 0 条。

Lustre:服务端(MGS/MDS .21、OSS .22)没有独立运行日志文件——其运行与错误信息经 printk 进内核日志,已由 journald 采集;量天然极少(近 7 天内核 lustre 消息 18 + 1 行, 全库仅 67 条 LustreError)。唯一的 /var/log/lustre-pkgs.log 是 9/22 的安装日志 (静态 398 行,无运维价值)。结论:Lustre 不是漏采,而是本来就没多少日志; 若要更细,须另配内核 debug mask(lctl dk)或启用 Lustre changelog。

2026-10-01 已纳管(LSF):LSF logdir 已按单点采集纳入 EFK —— 模板新增 host-lsf-logdir 段 (/data/nfs/lsf/lsf/work/lsfce/logdir/lsb.acct + lsb.*events,实测均为 ASCII text), 同时删除 host-domain 中那条无效的 /data/nfs/lsf/*/logdir/*.log;渲染剥离机制由 vnc 单段 通用化为 ROLE_GATED_BLOCKS(多段多角色)。实测:log_source=lsf_logdir 381 条只来自 lsf-master-01;14 台收敛、V1–V5 全通过。详见 lsf-logdir-20261001/SOP.md。 Lustre 无需新增采集路径(其日志即内核日志,已在采)。

2026-10-01 日志覆盖面盘点(回答"日志是否已覆盖环境几乎所有内容")。方法:ES 关键词聚合 + 抽样核对 ——⚠ syslog 行首含主机名,会让 lustre/MGS 这类词命中「主机名」而非正文,只靠关键词会严重误判: 实测 LSF 命中 14 万条、lustre 命中 6.9 万条,抽样后几乎全是我们自己部署的 env-*-metrics 采集器的 systemd 日志,不是集群进程日志。

层 状态 实测依据
主机系统层 syslog / auth / audit / cron ✅ 14 台全覆盖 每台均有四类来源
K8s 容器 stdout ✅ 4 节点 DaemonSet —
Slurm 调度器 ✅ 专属 domain 源(5 台,8 种文件) /var/log/slurm/*.log
LSF 调度器 ❌ 未采集 mbatchd/sbatchd/egos/bjobs 关键词全为 0;约定路径 14 台均不存在
Lustre 存储 ❌ 基本未采集 服务端对象日志 ll_ost/osc_/mdt_ 全为 0,仅 67 条 LustreError
FlexLM 许可 ✅ 专属源(.29) Synopsys / Cadence 两个 lmgrd.log
HPC 图形会话 ✅ vnc(仅 .28) —
EDA 工具作业日志 ❌ 未采集 dc_shell/pt_shell/icc2_shell 全为 0,且无对应采集路径

结论:日志面覆盖「主机 + K8s + Slurm + 许可 + 图形会话」,不含 LSF 集群日志、Lustre 运行日志、 EDA 作业日志。三者为独立缺口,需分别立项;EDA 作业日志位于用户作业目录、量大且属用户数据, 须先定采集范围与脱敏策略再动。

采集自监控(两端实现不同,原因是 Filebeat 的能力边界):

端 做法 为什么
集群外 14 台 helper 脚本把 Filebeat /stats(JSON)转成 node_exporter textfile 复用主机侧既有 textfile collector:不新增端口、不改防火墙
K8s 4 节点 monitoring.elasticsearch 上报到 ES 的 .monitoring-beats-* Filebeat 的 HTTP 端点只有 /stats(JSON),没有 Prometheus 格式的 /metrics(那是 Metricbeat 的特性),ServiceMonitor 抓不到任何序列

坑(本轮实测踩到,三条):

  1. Filebeat 无 /metrics:按 Prometheus 惯例去抓 /metrics 会永远拿到空数据 (filebeat_scrape_up=0),须改解析 /stats 的 JSON。
  2. 改 ConfigMap 内容不会重建 DaemonSet:Pod template 未变 → Filebeat 不会重载配置, 表现为「配置改了、行为没变」。工程按 ConfigMap 的 resourceVersion 指纹判断, 变化才 kubectl rollout restart ds/filebeat(幂等:内容未变不重启)。
  3. 主机侧改了配置必须重启服务:只判 systemctl is-active 会漏掉「进程在跑、但仍是旧配置」 这一态。工程在配置写入后强制 systemctl restart filebeat。

步骤 9.6:服务维度 service.name(2026-10-01 新增)

差距来源:docs/LOGGING-GAP.md #1 —— 日志能按主机 / 集群域 / 类别 / 级别筛,但不能按服务筛 (实测 service.name 命中 0 条)。本步在两侧 Filebeat 的顶层 processors 各加一段 逐字节相同的 script,按优先级统一推断服务名,取不到就留空:

序 判定依据 取值示例
1 kubernetes.container.name sidekiq / openldap / calico-node
2 systemd.unit(仅 .service) sshd / crond / systemd-logind
3 log.syslog.appname slurmctld / sshd-session
4 message 行首 syslog 标识 Oct 1 17:10:03 host sshd[123]: → sshd
5 log_source 兜底 audit→auditd、flexlm→lmgrd、vnc→vncserver、lsf_logdir→lsf
6 systemd.transport = kernel kernel
7 log.file.path 基名 /var/log/slurm/slurmctld.log → slurmctld

注意事项与坑(实现体已处理,此处留痕):

  • /var/log/containers/ 必须排除:该目录文件名形如 <pod>_<ns>_<container>-<sha>.log(80+ 字符), 直接取基名会把容器全 ID 当成服务名;该前缀显式跳过(缺 k8s metadata 的历史数据保持空值)。
  • 统一噪音名单:user@N(systemd 用户管理器)、messages/secure/cron/maillog/dmesg (系统通用日志名)不写入;名字长度 > 32 一律丢弃。名单收在内部 out() 里,所有来源共用。
  • 识别不出就留空:与 log.level 同一原则,宁缺勿猜。
  • script 必须排在 drop_fields 之前,否则判断所需的字段已被丢弃。
  • YAML > 折叠块把多行压成一行,JS 内不能写 // 注释(会把后续代码整行注释掉)。
  • 主机侧不建议用 log_source=auth|syslog 兜底成服务名:auth facility 含 sudo/login/munge 等, 映射成单一名字会误导。
# ① 语法门禁(任意一台已装 filebeat 的主机;零状态变更,临时文件用后即删)
filebeat test config -c /tmp/fb-syntaxcheck.yml          # 预期输出 Config OK

# ② 集群外 14 台:下发渲染后的配置并重启(高危:覆盖 /etc/filebeat/filebeat.yml,原件见 backup/)
install -m 0600 /tmp/rendered-filebeat.yml /etc/filebeat/filebeat.yml
systemctl restart filebeat && systemctl is-active filebeat.service   # 预期 active

# ③ K8s 4 节点:更新 ConfigMap 并滚动重启(只改内容不会重建 Pod,必须显式 restart)
kubectl --kubeconfig /home/rain/.kube/config -n monitoring apply -f manifests/12-filebeat-k8s.yaml
kubectl --kubeconfig /home/rain/.kube/config -n monitoring rollout restart daemonset/filebeat
kubectl --kubeconfig /home/rain/.kube/config -n monitoring rollout status daemonset/filebeat

预期:14 台主机产生 write-config + start-service;K8s 侧日志出现 ConfigMap filebeat-config 内容已变化 → 重启 DaemonSet filebeat;末尾 失败 0 台。 失败判断:test config 未返回 Config OK → 不要下发,先修脚本; 某台报 服务启动失败 即达阈值(failure_threshold: 1)→ 该批停止,按第 7 章回滚该台配置。

实现体:artifacts/filebeat-host.yml.tpl、manifests/12-filebeat-k8s.yaml(本次无 Python 改动)。 编排入口 main.py deploy(渲染含 ROLE_GATED_BLOCKS 按角色裁剪,手工复现时须同步处理)。

实测效果(2026-10-01 23:32–23:36,稳态近 60 秒窗口):

范围 改造前 改造后
集群外 14 台主机 0 100%(110/110)
K8s 4 节点 0 100%(265/265)

Kibana 侧同步新增第 6 个控件「服务」(kibana/saved_objects/06-dashboard-domain-overview.json), main.py kibana 下发并通过复检。备份与回滚见 backup/service-name-20261001/README.md。

步骤 9.7:作业维度 job.* 字段抽取(2026-10-01 新增)

差距来源:docs/LOGGING-GAP.md #2 —— 除整行 message 外无结构化解析,「某作业的日志」只能全文检索。 本步在主机侧模板的同一个 script processor 内抽出 4 个字段,只对调度器日志生效:

字段 来源(全部取自实机样本,不做无依据的位置猜测)
job.id Slurm JobId=N / job=N / [N.batch];LSF "JOB_*" 行空白分隔第 4 列
job.user Slurm user=U;LSF JOB_FINISH($12) / JOB_NEW($14) / JOB_EXECUTE($9)
job.node Slurm node=H
job.exit_code Slurm WEXITSTATUS n / (exit=n)(数值型)

注意事项与坑:

  • ⚠ 一个 processor、两个函数;禁止拆成两个 script 段:Filebeat 的 javascript processor 入口 函数名固定为 process,多个 script 段各自声明 function process 会互相覆盖(只有最后一个生效)。 正确写法是一个 process 内依次调用 putServiceName(event) 与 putJob(event)(见步骤 9.6 模板)。
  • ⚠ JS 内不得写 // 注释:YAML > 折叠块会把多行压成一行,// 之后的全部代码都会被注释掉。
  • 限定 log_source:仅 domain(Slurm)与 lsf_logdir(LSF)生效,避免误伤其它日志。
  • 无效值过滤:job=NA/user=NA(实测 EPILOGCTLD job=NA user=NA 共 14 条)属占位符,不写入; job.user 另要求匹配 ^[A-Za-z0-9._-]+$。
  • LSF 的 user 列随事件类型漂移(JOB_FINISH/JOB_NEW/JOB_EXECUTE 各不相同),仅这三类事件抽取 user; 其余 "JOB_*" 事件只抽 job.id(五类事件样本第 4 列一致)。
# 语法门禁(改 Filebeat 配置后必做;零状态变更,临时文件用后即删)
filebeat test config -c /tmp/fb-syntaxcheck.yml        # 预期输出 Config OK
# 下发到 14 台主机(配置 sha256 变化 → 重启 filebeat)
install -m 0600 /tmp/rendered-filebeat.yml /etc/filebeat/filebeat.yml
systemctl restart filebeat && systemctl is-active filebeat.service
# Kibana 明细表新增 job.id / job.user 列
cd /home/rain/Documents/k8s_ins/monitoring-p2-20261001 && \
  PYTHONPATH="$HOME/py_public_modules" .venv/bin/python main.py kibana

预期:13 台产生 write-config+start-service(另一台已在单台验证时下发,配置一致即跳过), 末尾 失败 0 台;main.py kibana 写入 efk-search-recent-logs 并复检通过;main.py drift rc=0。 失败判断:test config 未返回 Config OK → 不下发、先改脚本;某台报 服务启动失败 → 该批停止, 按第 7 章回滚该台配置。

实现体:artifacts/filebeat-host.yml.tpl(顶层 processors)、kibana/saved_objects/05-search-recent-logs.json。

验证方式与实测结果(2026-10-01 23:50–00:10)

集群当前无作业在跑,存量 domain 日志已采集到底、不会重放,故用受控样本文件做端到端验证 (写入 /var/log/slurm/selftest-job.log,用高位作业号区分,验证后删文件并 delete_by_query 清文档):

用例 输入 抽取结果
1 EPILOG job=9001 user=rain01 node=slurm-work-01 ... (exit=768) id/user/node/exit_code 四项全对
2 _job_complete: JobId=9003 WEXITSTATUS 3 id=9003、exit_code=3
3 [9004.batch] task/cgroup: ... id=9004
4 EPILOGCTLD job=NA user=NA 不写任何字段(占位符被过滤)

先后在 slurm-master-01、slurm-work-01 两台各验证一次(证明下发面生效,非只验首台)。

【未验证】LSF 侧:lsf_logdir 分支的列位置由五类事件的实机样本自证,但未做端到端验证—— 集群无作业、lsb.events/lsb.acct 无新增,且刻意不向 LSF 记账目录写入测试文件(避免干扰 LSF)。 待下次有真实作业时复核,判据:log_source: lsf_logdir and job.id: * 有值。

步骤 9.8:投递失败可观测(2026-10-01 新增)

差距来源:docs/LOGGING-GAP.md #5 —— 采集自监控只报 filebeat_scrape_up,日志悄悄丢时不可见 (与「指标停更与集群正常在盘上长得一样」同类)。本步做两件事:

  1. 扩自监控指标(artifacts/collectors/filebeat_metrics.py):新增 9 个投递丢失类 counter (dropped / failed / dead_letter / toomany / duplicates / pipeline dropped·failed / write·read errors)+ 2 个 gauge(libbeat_pipeline_queue_filled_pct,以及派生的 filebeat_delivery_backlog_events = filebeat.events.added − libbeat.output.events.acked)。
  2. 新增告警规则(manifests/23-filebeat-alerts.yaml),随 main.py deploy apply:
规则 表达式 severity / for
FilebeatDeliveryLoss Σ increase(丢失类[15m]) > 0 warning / 5m
FilebeatDeliveryBacklogged filebeat_delivery_backlog_events > 1000 warning / 10m
FilebeatQueueNearlyFull libbeat_pipeline_queue_filled_pct > 0.8 warning / 10m
FilebeatScrapeDown filebeat_scrape_up == 0(这段时间日志完全没采) critical / 5m

注意事项与坑:

  • 丢失类指标声明为 counter,告警一律用 increase(...[15m]):这类值随 Filebeat 重启归零, 用绝对值比较会误判(重启后从大值变 0 会被当成「恢复正常」)。既有 6 个指标(含 acked/added) 本次不改类型(保持 gauge),避免 Prometheus 摄入期的类型冲突。
  • write/read errors 只暴露、不告警:正常运行时也会因连接复用等原因非零 (实测 read.errors=24 / events=892),据此告警必然误报。
  • ⚠ deploy 不会自动 apply efk.manifests 全列表:该列表此前只用于 drift 的服务端校验, deploy 流程里是逐个硬编码 apply 的。本次为此在 deployer.py 新增第 6.5 步显式 apply 新清单—— 今后凡「只在配置里列、不在流程里 apply」的清单,必须同步改 deployer,否则对象不会被创建 (本轮实测踩到:deploy 跑完规则对象仍 NotFound)。
  • K8s 侧 Filebeat 不在本规则覆盖内:它自监控走 ES Stack Monitoring(.monitoring-beats-*), 未接入 Prometheus(原因见 manifests/12-filebeat-k8s.yaml 注释:Filebeat 无 Prometheus 格式端点)。
# ① 自监控脚本由 systemd timer 每 60s 执行;内容 sha256 变化才写,且更新脚本不重启 filebeat(不断流)
systemctl list-timers env-filebeat-metrics.timer
# ② 立即刷新一次并核对指标
/usr/bin/python3 /usr/local/bin/env-filebeat-metrics
grep -E '^(libbeat|filebeat)' /var/lib/node_exporter/textfile/filebeat.prom
# ③ 应用告警规则
kubectl --kubeconfig /home/rain/.kube/config apply -f manifests/23-filebeat-alerts.yaml
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get prometheusrule filebeat-delivery-alerts

预期:textfile 出现 18 条 filebeat/libbeat 指标(含 libbeat_output_events_dropped_total 0、 filebeat_delivery_backlog_events 为个位数);PrometheusRule 建立且带 release=kube-prometheus-stack。 失败判断:指标缺失 → 查 systemctl status env-filebeat-metrics;规则未加载 → 查 Prometheus rules API 的 lastError。

实现体:artifacts/collectors/filebeat_metrics.py、manifests/23-filebeat-alerts.yaml、 deployer.py(第 6.5 步)、config/default.yaml(efk.manifests 登记)。

实测结果(2026-10-01 23:50–00:20)

项 结果
指标落地 14 台全部到达 Prometheus(count(filebeat_scrape_up) = 14、count(libbeat_output_events_dropped_total) = 14、count(filebeat_delivery_backlog_events) = 14)
规则加载 规则组 filebeat-delivery 已加载,4 条规则 state=inactive、lastError 为空、evaluationTime 0.003s
当前状态 无主机触发丢失或失联(观测面新建、故障为零,符合预期)
幂等 复跑 write-config / start-service / selfmon-updated 均为 0

【未做】故障注入:规则的触发路径(Prometheus → Alertmanager → 既有分级路由)与 blackbox-alerts 完全同源,本次只验证到「规则已加载 + 表达式可求值 + 数据到位」,未人为制造丢事件以观察 firing (制造需停 Filebeat 或伪造 textfile,代价大于收益)。

步骤 9.9:日志内容告警(2026-10-01 新增)

差距来源:docs/LOGGING-GAP.md #15 —— 既有告警全是指标告警,告不了「认证失败激增」 「出现 OOM」这类日志内容事件。本步在集群外一台主机上部署内容探针,把「日志里出现了什么」 变成指标,从而复用既有告警分级路由。

选型与取舍(先勘察后决策):

候选 结论
ES Watcher ❌ 本集群为 basic license,Watcher 属付费能力
Kibana Alerting ❌ 需新建 connector 并改 Alertmanager 路由才能接入现有邮件链路,配置出错会让告警全断
探针 + textfile + PrometheusRule(本次采用) ✅ 与步骤 9.8 同一套链路,零 Alertmanager 改动,告警直接走现有 category × severity 路由

实现:

  1. artifacts/collectors/logwatch.py → 部署为 /usr/local/bin/env-logwatch + systemd timer(60s), 查询 ES 的 filebeat-*/_count,按 4 个内容模式统计近 5 分钟命中条数,写 /var/lib/node_exporter/textfile/logwatch.prom(原子替换)。
  2. manifests/24-log-content-alerts.yaml:5 条规则(4 条内容 + 1 条探针失效兜底)。
  3. ES 侧新增专属只读角色 logwatch_reader_role(仅 filebeat-* 的 read + view_index_metadata) 与账号 logwatch_reader;口令入工程 secrets 文件,探针凭据以 0600 下发到 /etc/env-logwatch.env。

注意事项与坑:

  • ⚠ 不能复用 filebeat_writer:它只有写权限(缺 read/search)——首轮实测直接 403。 正确做法是新建只读账号,而不是给它加 read(否则被投毒的采集端可读取全部日志)。
  • ⚠ 探针是单点:部署在 config 的 logwatch.host(当前 slurm-master-02)。为此加了 LogwatchProbeStale(time() - logwatch_last_success_timestamp > 900);脚本失败时会 沿用上一次成功的时间戳,否则该序列会在 Prometheus 侧直接消失、「多久没成功」无从判断。
  • 阈值口径:logwatch_matches 是近 5 分钟的滚动计数(gauge,非累计),规则直接比较数值, 不用 increase()。
  • 用 match_phrase 而非 match:message 是分词字段,match 会把正文里偶发出现的单个词计进来; 短语要求相邻,显著降低误报。
  • ⚠ SearchReplace 的 old_str 若含文件中不存在的行,工具会做模糊匹配并产生错误拼接 (本轮把 config 的两行注释粘成一行);长 old_str 必须照抄原文,改完立即用解析器校验结构。
# ① 在 ES 上创建只读角色与账号(口令取自工程 secrets 文件,命令内取值、不回显)
E="elastic:$ELASTIC_PASSWORD"
curl -s -u "$E" -H 'Content-Type: application/json' -X PUT \
  "http://localhost:9200/_security/role/logwatch_reader_role" \
  -d '{"cluster":[],"indices":[{"names":["filebeat-*"],"privileges":["read","view_index_metadata"]}]}'
curl -s -u "$E" -H 'Content-Type: application/json' -X PUT \
  "http://localhost:9200/_security/user/logwatch_reader" \
  -d '{"password":"<LOGWATCH_PASSWORD>","roles":["logwatch_reader_role"]}'
# ② 探针侧(目标主机):脚本 0755 + 凭据 0600
install -m 0755 env-logwatch /usr/local/bin/env-logwatch
install -m 0600 /dev/stdin /etc/env-logwatch.env     # 内容:ES_HOST / ES_USER / ES_PASSWORD
systemctl enable --now env-logwatch.timer
# ③ 自检(14 天窗口,只读、不写指标)与正常执行
/usr/bin/python3 /usr/local/bin/env-logwatch --selftest
/usr/bin/python3 /usr/local/bin/env-logwatch && cat /var/lib/node_exporter/textfile/logwatch.prom
# ④ 应用规则
kubectl --kubeconfig /home/rain/.kube/config apply -f manifests/24-log-content-alerts.yaml

预期:selftest 打印 4 个模式的 14 天计数;textfile 出现 logwatch_scrape_up 1、 logwatch_window_seconds 300、4 个 logwatch_matches{pattern=...} 与 logwatch_last_success_timestamp。 失败判断:scrape_up 0 → 查 systemctl status env-logwatch.timer;HTTP 403 → 检查 logwatch_reader_role 是否含 read 权限。

实现体:artifacts/collectors/logwatch.py、manifests/24-log-content-alerts.yaml、 modules/host_filebeat.py(ensure_logwatch / _logwatch_env)、modules/k8s_efk.py(角色与账号)、 deployer.py(第 6.5 步)、config/default.yaml(logwatch 段)。

实测结果(2026-10-01 23:55–00:25)

项 结果
自检(14 天,只读) auth_fail 31、crash 1、oom 157、storage 16 —— 查询有效
指标落地 logwatch_scrape_up{instance="slurm-master-02:9100"} = 1,4 个 pattern 计数均 0(近 5 分钟无匹配)
规则加载 规则组 log-content 已加载,5 条 state=inactive、lastError 空、evaluationTime 0.002s
幂等 复跑 write-config / start-service / selfmon-updated / logwatch-updated 均为 0

【待办】oom 157 条 / 14 天 值得排查:探针首次自检即发现历史 OOM 类日志 (Out of memory / oom-kill / Killed process 等短语),需确认是否真实内核 OOM、 发生时间与主机;已记入 HANDOFF 待办。

步骤 9.10:纳管扩面(1b 步骤 2/3)—— deb 系主机 + 网络设备日志(2026-10-02 新增)

把 dell-R620(Ubuntu 24.04,KVM 宿主,platform=physical) 与 OpenWrt(.2) 的转发日志 (platform=network) 纳入采集;同时把包管理与预检查从「只认 RPM」改为按节点 os_family 分派。

  • 完整八章(含前置条件、分步、验证命令、回滚、风险与实测记录): docs/SOP-onboard-deb-network.md;
  • 工程侧要点:config/default.yaml 的 nodes.dell_r620 / platforms / filebeat.deb_url / filebeat.textfile_dir / precheck.expected_os_major_deb / network_logs; modules/host_filebeat.py(probe_pkg/_install_pkg 按族分派)、 modules/network_logs.py(新增,宿主 rsyslog + OpenWrt 两端幂等)、 utils/precheck.py(按族校验 OS 主版本)、 artifacts/collectors/filebeat_metrics.py(textfile 目录可配置);
  • 一句话结论(2026-10-02 实测):16 台处理 0 失败、platform:physical 191 条、 platform:network 5 条、幂等复跑零变更。

步骤 9.11:IK 中文分词插件(2026-10-13 新增)

给 monitoring/elasticsearch(8.19.0)装 analysis-ik,供知识库(「百晓生」)做中文检索。 需求规格见 docs/IK-INSTALL-HANDOFF.md,执行结果与逐条验收证据见 docs/IK-INSTALL-RESULT.md。

  • 施加的改动只有一条:容器 volumeMounts 增加 name: data / mountPath: /usr/share/elasticsearch/plugins / subPath: plugins,让插件落进 data PVC(plugins 目录原本未持久化,只 exec 安装会随重建丢失)。
  • 关键坑:elasticsearch-plugin install <url> 不会把包内 config/ 目录落盘, 于是 IK 缺 main.dic/surname.dic 等词典,Dictionary.initial() 抛异常; 该异常发生在 AnalysisRegistry.build 阶段,会让索引创建失败、集群整体 red(实测约 14 分钟)。 必须从同一 URL 的包中解出 config/ 并拷入 plugins/analysis-ik/config/(详见结果文档第 4 节)。
  • 新增主机/重装插件时的顺序不可颠倒:预建 data/plugins 目录 → apply 挂载 → 安装插件 → 补 config/ → 重启。
  • 备份:backup/ik-install-20261013/10-elasticsearch.yaml.bak.20261013-190528;回滚见该目录 README.md。
  • 一句话结论(2026-10-13 实测):A1~A6 全部通过;重启后插件与分词保持;回滚实测可退(挂载 0 条、ik_smart 400); 末态集群 yellow、156/156 primary,Kibana 200、4 个 Filebeat 正常。

步骤 10:验证与幂等复跑

.venv/bin/python main.py verify
.venv/bin/python main.py deploy      # 再跑一次:应零变更、全跳过

6. 验证

# 检查 命令 预期
V1 K8s 侧就绪 kubectl -n monitoring get sts,deploy,ds \| grep -E "elastic\|kibana\|filebeat\|blackbox" ES 1/1、Kibana 1/1、Filebeat 4、blackbox 1/1
V2 主机侧服务 ssh root@192.168.31.16 systemctl is-active filebeat active(14 台同)
V3 日志已入库 Kibana → Stack Management → Index Management,或 curl -u elastic:$PW 'http://127.0.0.1:9200/_cat/indices/filebeat-*?v'(在 ES Pod 内) 出现 filebeat-8.19.0-* 与 filebeat-host-8.19.0-*
V4 ILM 生效 curl -u elastic:$PW 'http://127.0.0.1:9200/_ilm/policy/efk-14d' delete.min_age = 14d
V5 黑盒探测 Prometheus 查询 probe_success 9 个目标全为 1(target_name 见 manifests/21-probes.yaml)
V6 证书余期 Prometheus 查询 probe_ssl_earliest_cert_expiry - time() > 30 天(否则触发告警)
V7 容量基线 kubectl -n monitoring exec elasticsearch-0 -- df -h /usr/share/elasticsearch/data;Prometheus 查询 prometheus_tsdb_head_series 记录为扩容判据
V8 采集自监控 主机:ssh root@192.168.31.28 'grep filebeat_scrape_up /var/lib/node_exporter/textfile/filebeat.prom' 期望 1;K8s:curl -u elastic:$PW 'http://127.0.0.1:9200/_cat/indices/.monitoring-beats*'(在 ES Pod 内) 主机 1;K8s 出现 .monitoring-beats-7-*
V9 采集面齐全 ES 聚合:log_source 应含 syslog/audit/cron/domain/flexlm/vnc 六类 六类均有文档数

7. 回滚

先定义失败判据(任一达标即回滚,不靠临场判断):ES/Kibana 在 15 分钟内未 Ready;probe_success 目标数 < 9 且非目标本身故障;Traefik upgrade 后 443 入口不可用超过 1 分钟。

# 1) 集群外主机:停用并可选卸载
for h in 16 17 18 19 20 21 22 23 24 25 26 27 28 29; do
  ssh root@192.168.31.$h 'systemctl disable --now filebeat.service'
done

# 2) K8s 资源:按依赖逆序删除(工程 --rollback 会做同样的事,需 --force)
kubectl delete -f manifests/22-blackbox-alerts.yaml
kubectl delete -f manifests/21-probes.yaml
kubectl delete -f manifests/20-blackbox-exporter.yaml
kubectl delete -f manifests/13-traefik-routes.yaml
kubectl delete -f manifests/12-filebeat-k8s.yaml
kubectl delete -f manifests/11-kibana.yaml
kubectl delete -f manifests/10-elasticsearch.yaml     # 【高危】连 PVC 一并删除

# 3) Traefik 入口:重跑 helm(不带 overlay)即回到变更前状态
helm --kubeconfig ~/.kube/config upgrade --install traefik \
  ../traefik_deploy/chart/traefik -n server -f ../traefik_deploy/values.yaml \
  --no-hooks --timeout 10m --wait

⚠ 不可逆点:nfs-csi-hdd 的回收策略是 Delete,删 PVC 即删除全部已采集日志;vm.max_map_count 写入 /etc/sysctl.d/99-elasticsearch.conf 属系统级参数,回滚需手工删该文件(不影响其它服务,故未自动撤销)。

备份位置:本主题 backup/(见 backup/README.md)。本次唯一被改动的既有文件是 Harbor 的 config/default.yaml(新增 docker.elastic.co 源)。

8. 风险说明

# 风险 缓解
1 Traefik 重启导致入口中断数秒 变更前实机推导端口是否已存在;已存在则跳过;置于低峰期执行
2 ES 吃内存/磁盘(单节点 JVM 2G + 索引膨胀) 锚定 k8s-node-02(实时内存占用最低);ILM 14 天 + 20 GB rollover 双阈值;容量见 V7
3 同源故障域:ES 数据与 Prometheus TSDB 同在宿主 .249 已知并接受;宿主故障时两者同时不可用
4 主机 Filebeat 写入 ES 的凭据落在 14 台机器上 专用 filebeat_writer 角色,仅能写 filebeat* 索引;配置文件 0600 root
5 内网明文 HTTP 连 ES(xpack.security.http.ssl=false) 内网环境折中:保留认证、放弃传输加密;如需加密需为 Filebeat/Kibana 分发 CA
6 两域无对外入口(kas / registry 绑定在 Envoy Gateway,无外部路由) 已登记为环境缺口(附录 A),待补入口后再加探测
7 Filebeat 首次安装需访问外网 RPM 源 实测可达;不可达时改用离线包(工程支持 artifacts/ 内预置 RPM)

附录

A. 本次新发现的环境缺口(待办)

  1. kas.rainchen.top / registry.rainchen.top 无对外入口 —— Gateway gitlab-gw 的 PROGRAMMED=False,Traefik 无对应 IngressRoute。
  2. Grafana 的 Loki 数据源与大盘未做(选型改为 EFK,Grafana 侧不接 ES;日志可视化统一走 Kibana)。
  3. Prometheus 扩容后容量预算复核已完成(见 §6 V7 实测值),但降采样(recording rules)未做 —— 属 P2 原文「保留期与降采样容量规划」的后半段,是否落地待定。

B. 交付物清单

monitoring-p2-20261001/
├── main.py / deployer.py        工程入口与编排
├── modules/k8s_efk.py           K8s 侧:ES/Kibana/Filebeat/blackbox 部署与配置
├── modules/host_filebeat.py     集群外 14 台主机 Filebeat(分批并发)
├── utils/{cmd,precheck}.py      命令封装与只读门禁
├── config/default.yaml          唯一真源配置(不含凭据)
├── manifests/                   10 ES / 11 Kibana / 12 Filebeat / 13 路由 /
│                                15 Traefik overlay / 20 blackbox / 21 Probe / 22 告警
├── artifacts/filebeat-host.yml.tpl   主机侧配置模板
├── docs/{SOP,RUNBOOK}.md        本文与运行手册
├── reports/                     预检查报告
└── backup/                      备份与台账(README.md)

C. 变更日志

时间 事项
2026-10-01 16:06 建工程目录;备份 Harbor config/default.yaml
2026-10-01 16:08 Harbor 接入 docker.elastic.co(registry id=7 + proxy cache project)
2026-10-01 16:15 三镜像预热完成(ES/Kibana/Filebeat 8.19.0)
2026-10-01 16:31–16:36 部署:内核参数 → ES → 业务配置 → Kibana/Filebeat → Traefik → 黑盒 → 14 台主机
2026-10-01 16:39 修正探测清单(ldapadmin 走 80;移除 kas/registry)并复验
2026-10-01 23:25–23:36 服务维度 service.name(差距 #1):两侧 Filebeat 顶层 processors 加同款 script,稳态覆盖率 0 → 100%;Kibana 新增第 6 个控件「服务」
2026-10-01 23:45–00:10 作业维度 job.*(差距 #2):主机侧抽出 job.id/user/node/exit_code(仅 Slurm、LSF),明细表加两列;两台主机受控样本验证通过,幂等零变更,LSF 侧待真实作业复核
2026-10-01 23:50–00:20 投递失败可观测(差距 #5):自监控 6 → 18 条指标,新增 4 条投递告警规则(含 FilebeatScrapeDown);deployer.py 增第 6.5 步 apply 新清单(踩到「配置登记≠流程 apply」)
2026-10-01 23:55–00:25 日志内容告警(差距 #15):集群外内容探针(ES _count → textfile)+ 5 条规则;ES 新建只读角色 logwatch_reader(首轮因复用采集写账号而 403);自检发现历史 oom 类 157 条待排查
2026-10-13 19:05–19:53 IK 中文分词插件(需求见 docs/IK-INSTALL-HANDOFF.md):plugins 子路径挂载落 data PVC;踩「安装器不落 config/ 目录」致集群 red 约 14 分钟,手工补词典后恢复;A1~A6 全通过、回滚实测可退(证据见 docs/IK-INSTALL-RESULT.md)

来源:/home/rain/Documents/k8s_ins/monitoring-p2-20261001/docs/SOP.md | 最后更新:2026-10-13 19:50(由 build/sync.py 同步,请勿在站点侧修改)