SOP — 环境监控扩面(P0:14 台非 K8s 主机主机层接入)¶
本文件是变更档案:这次改了什么、怎么改、怎么验、怎么退。 日常使用与排障见同目录
RUNBOOK.md。 变更主题:把 Slurm / Lustre / LSF / HPC / FlexLM 五套集群共 14 台主机的主机层 指标接入本环境monitoring命名空间的 kube-prometheus-stack。
目录¶
1. 背景¶
触发条件:现有监控只有「K8s 集群监控」,不是「环境监控」。
实测基线(2026-09-29,命令见附录 B):
| 事实 | 数值 |
|---|---|
| 受管主机 | 18 台 VM + 1 台物理宿主(Dell R620) |
| 有指标的主机 | 5 台(4 台 K8s 节点 + 宿主 .250)→ 覆盖率 26% |
| 零采集的主机 | 14 台(.16–.29,Slurm/Lustre/LSF/HPC/FlexLM) |
| 现有大盘 | 25 个 chart 自带 mixin + 1 个 R620 硬件盘,纯 K8s 视角,无 HPC 语义 |
| 告警 | Alertmanager 默认 route 为 null(只存不发);仅 R620 硬件走 163 邮件 |
即:这套环境真正的生产力(作业调度、并行文件系统、许可服务)完全不可见, 且出了问题没有任何通知路径。
2. 目标¶
变更完成后应达到可验证状态:
- 14 台主机全部产生
node_*指标,Prometheus 中count(up{job=~"external-node-exporters.*"} == 1) == 14。 - 每台主机带
cluster/role标签,instance为主机名形式(如slurm-work-01:9100)。 - 新增 16 条主机基础告警规则(磁盘 / inode / 内存 / CPU 饱和度 / 系统单元 / 时间同步 / 网卡错误 / 重启 / 失联)。
- 新增「环境总览」大盘(19 台主机统一视图)与社区大盘 Node Exporter Full (1860)。
- 主机层告警按 severity 分级投递邮件:
critical立即发、warning降频发、info不发(仅大盘可见);主机整体失联时抑制其衍生告警。 - 幂等:
deploy连续执行两次,第二次零变更、全跳过。
3. 前置条件¶
以下条件全部已落为可执行检查(utils/precheck.py + utils/config_validate.py),
由所有变更类子命令的第一步自动执行;写在这里供人工复核。
| # | 条件 | 检查方式 | 级别 |
|---|---|---|---|
| 1 | 操作机 Python ≥ 3.9 且运行在工程 .venv 内 |
check_local |
WARN |
| 2 | 共享库可导入(.pth 已挂载) |
check_local |
BLOCK |
| 3 | 配置文件存在且合法(必填/取值域/唯一性/跨字段) | utils/config_validate.py |
BLOCK |
| 4 | kubectl、helm 可用且集群可达 |
check_k8s |
BLOCK |
| 5 | monitoring 命名空间与 helm release 存在 |
check_k8s |
BLOCK |
| 6 | 抓取源网段可由 Calico IPPool 推导(失败即拒绝,不做全开) | check_source_cidr |
BLOCK |
| 7 | 14 台目标主机 SSH 免密可达 | check_ssh |
BLOCK |
| 8 | 目标机为 Rocky/RHEL 系、主版本 9、x86_64 | check_os |
BLOCK |
| 9 | 包可安装:已装 或 EPEL 可用且 dnf 能解析到包 |
check_package |
BLOCK |
| 10 | /etc/dnf/dnf.conf 的 gpgcheck=1(包来源可信) |
check_package |
BLOCK |
| 11 | 9100 端口未被其它进程占用 | check_port |
WARN |
| 12 | 目标机 / 与 /var 可用空间 ≥ 5 GiB |
check_free_space |
BLOCK |
| 13 | 目标机与操作机时钟偏差 ≤ 5s | check_clock |
WARN |
| 14 | SELinux / firewalld 现状记录(只预警,不阻断) | check_selinux_port、check_firewalld |
WARN/INFO |
权限:主机侧需 root(装包、改 systemd、改防火墙),集群侧用
config/env-monitor.yaml 中 kubernetes.kubeconfig 指定的凭证。
凭据:SMTP 授权码不入库,运行时从 ~/.secrets/dell-r620-monitor.env
的 DELL_R620_SMTP_AUTH_PASSWORD 读取(600 权限,值不落日志、不落工程目录)。
4. 影响范围¶
| 维度 | 内容 |
|---|---|
| 变更主机 | 14 台:.16–.29(Slurm 5 / Lustre 2 / LSF 5 / HPC 1 / FlexLM 1) |
| 是否停服 | 否。仅新增采集进程与端口放行,不改动任何既有服务 |
| 集群侧对象 | monitoring 命名空间:Service / Endpoints / ServiceMonitor / PrometheusRule / ConfigMap×2 |
| 既有配置变更 | 仅 Alertmanager route(null → 两级邮件路由),改前导出 live values 备份 |
| 窗口期 | 无特殊要求。dnf install 为单次事务,对 Slurm/LSF 计算节点影响可忽略;建议避开作业高峰 |
| 在跑作业 | 装包不重启任何调度器/计算服务,不影响正在运行的作业;yum 锁冲突会直接失败并跳过该台 |
| 目标机新开端口 | 9100/tcp,仅对 10.244.0.0/16(Calico Pod 网段)放行,不对全网开放 |
| 新增文件(目标机) | /etc/systemd/system/prometheus-node-exporter.service.d/10-env-monitor.conf、/var/lib/node_exporter/textfile/ |
5. 分步操作¶
实现体:
main.py+deployer.py+modules/host_agent.py+modules/k8s_ingest.py。 以下每步给出等价 shell 命令,供阅读、复核与手工复现; 幂等、重试、批次门禁、备份编排由实现体保证,不在代码块里展开。 示例只以.16(slurm-master-01)演示,其余 13 台同理。
步骤 1:准备操作机运行环境¶
工程用独立 venv,共享库通过 .pth 挂载(不 pip install,避免与工程内同名模块冲突)。
坑:必须用本机正式 python3 建 venv;编辑器内置解释器常缺 venv 模块。
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
python3 -m venv .venv
.venv/bin/python -c "import sysconfig,pathlib; p=pathlib.Path(sysconfig.get_paths()['purelib'])/'py_public_modules.pth'; p.write_text('/home/rain/py_public_modules',encoding='utf-8')"
.venv/bin/python -m pip install -r requirements.txt
# 验证挂载:应输出 True 与共享库内模块真实路径
.venv/bin/python -c "import sys,importlib.util as u; t='/home/rain/py_public_modules'; print(t in sys.path, u.find_spec('config_module').origin)"
预期:True /home/rain/py_public_modules/config_module.py。
失败判断:输出 False 或 None → 挂载未生效,不要在工程内重建公共模块,
先排查 .pth 路径。
步骤 2:拉取社区大盘制品(固定 revision)¶
1860 大盘是 468 KB 的 JSON,从 grafana.com 拉取后以 ConfigMap 交给 Grafana sidecar 导入 (集群侧不通外网,不能由 Grafana 自行下载)。
坑:必须固定 revision(本工程用 45),用 latest 会让制品随时间漂移,
sha256 校验也就失去意义。
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
curl -fsSL -o artifacts/dashboards/node-exporter-full-1860-r45.json \
https://grafana.com/api/dashboards/1860/revisions/45/download
python3 -c "import json;d=json.load(open('artifacts/dashboards/node-exporter-full-1860-r45.json'));print(d['title'],d['uid'],len(d['panels']))"
sha256sum artifacts/dashboards/node-exporter-full-1860-r45.json | tee artifacts/dashboards/SHA256SUMS
预期:Node Exporter Full rYdddlPWk 31;sha256 为
184c6b7409f306da75525d7772f71945b10cea23ad16b5d78c4698ea0ea51986。
失败判断:HTTP 非 200 或 uid 不是 rYdddlPWk → 制品来源存疑,停止后续步骤。
步骤 3:变更前预检查(只读)¶
目标机侧逐台确认:SSH、OS 版本、包与包源、gpgcheck、端口占用、SELinux、firewalld、
时钟、磁盘;集群侧确认 kubectl/helm/命名空间/release,并推导防火墙放行源网段。
坑:源网段必须由 Calico IPPool 推导(本环境实测 10.244.0.0/16),
不能用 node.spec.podCIDR(它写着每节点 /24,与真实 Pod 分布不符);
推导失败时实现体拒绝执行,不会退化成全开。
# 逐台基础事实(示例 .16)
timeout 20 ssh -o BatchMode=yes -o ConnectTimeout=4 root@192.168.31.16 \
"cat /etc/os-release | grep -E '^(ID|VERSION_ID)='; uname -m; getenforce; firewall-cmd --state"
# 包与签名校验
timeout 200 ssh root@192.168.31.16 \
"rpm -q node-exporter; rpm -q epel-release; grep -i ^gpgcheck /etc/dnf/dnf.conf; dnf -q list --available node-exporter"
# 端口占用
timeout 20 ssh root@192.168.31.16 "ss -lntH | grep -c ':9100' || true"
# 抓取源网段(推导依据)
kubectl --kubeconfig /home/rain/.kube/config get ippools.crd.projectcalico.org \
-o jsonpath='{.items[*].spec.cidr}'
预期:BLOCK=0,报告落 reports/precheck-<时间戳>.md。
失败判断:出现任何 BLOCK → 先按报告的修复建议处理,再重跑本步;不要带病进入步骤 5。
步骤 4:预演(dry-run,零改动)¶
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
.venv/bin/python main.py deploy --dry-run
预期:日志逐台打印「将执行」的动作清单,系统状态零变化 (可用步骤 3 的探测命令对照确认)。 失败判断:dry-run 期间发现任何实际写入(远端文件、端口、systemd 状态变化)→ 实现体缺陷,停止并修复。
步骤 5:单台试点(关键:实测 SELinux 行为)¶
这一步不可跳过。 本环境 8 台为 SELinux=Enforcing,且实测 9100 已被标签为
hplip_port_t(不是 http_port_t)—— 与 16443 事故同源的风险点。
9100 到底能否被 node-exporter 绑定,只有实测能下结论。
在 .16 上执行步骤 5.1–5.5:
5.1 补仓与装包(.21 / .22 / .23 / .24 / .29 需先补 epel-release)¶
ssh root@192.168.31.16 "dnf install -y node-exporter"
预期:Complete!,退出码 0;已装时报 Nothing to do(幂等跳过)。
失败判断:Error: Unable to find a match → 该机缺 EPEL,先 dnf install -y epel-release。
5.2 写入 systemd drop-in(高危:覆盖既有配置)¶
用 drop-in 而非改包自带的文件,避免依赖包的文件布局,ExecStart= 先清空再重设。
坑(实测踩到,两处):
1. 单元名必须是 prometheus-node-exporter.service。EPEL 包同时提供
node_exporter.service(只是 alias)与真实单元 prometheus-node-exporter.service,
用错名字会报 Unit file … does not exist;
2. node_exporter 1.12 已无 cmdline collector,写 --no-collector.cmdline
会让进程启动即失败(该开关不在 --help 选项表里)。
ssh root@192.168.31.16 "install -d -m 0755 /var/lib/node_exporter/textfile /etc/systemd/system/prometheus-node-exporter.service.d"
ssh root@192.168.31.16 "cat > /etc/systemd/system/prometheus-node-exporter.service.d/10-env-monitor.conf <<'EOF'
# 由 env-monitor-deploy 生成,请勿手工修改
[Unit]
Description=Prometheus Node Exporter (managed by env-monitor-deploy)
[Service]
ExecStart=
ExecStart=/usr/bin/node_exporter --collector.systemd --collector.processes --collector.textfile --no-collector.wifi --no-collector.nvme --no-collector.rapl --collector.textfile.directory=/var/lib/node_exporter/textfile --web.listen-address=:9100
Restart=on-failure
RestartSec=5s
EOF"
预期:命令退出码 0。
失败判断:目录不可写(SELinux/权限)→ 检查 getenforce 与目标目录属主。
5.3 启用并启动(高危:启停服务)¶
ssh root@192.168.31.16 "systemctl daemon-reload && systemctl enable --now prometheus-node-exporter"
ssh root@192.168.31.16 "systemctl is-active prometheus-node-exporter; ss -lntH | grep ':9100'"
预期:active 且 LISTEN 0 4096 *:9100。
失败判断(本步是本变更最大的未知):若服务反复重启且端口不监听,
优先查 SELinux 是否拒绝绑定:
ssh root@192.168.31.16 "journalctl -u prometheus-node-exporter -n 30 --no-pager | grep -iE 'denied|bind|permission'; grep 'type=AVC' /var/log/audit/audit.log | tail -5"
若确为端口标签所拒,按实测结论处理(semanage port -a/-m -t http_port_t -p tcp 9100,
需先装 policycoreutils-python-utils),并把该操作回写进本 SOP 第 5.2 步
与本工程 agent 配置,不要只留在临时命令里。
5.4 防火墙放行(高危:改防火墙规则)¶
只放行抓取来源,不放宽其它来源。
坑(本次变更最大的设计错误,实测教训):放行来源不是 Pod 网段
10.244.0.0/16,而是K8s 各节点的 InternalIP。原因:Calico 的 IPPool 开启了
natOutgoing,Pod 访问集群外地址时会被 SNAT 成所在节点的 IP,目标机防火墙
看到的源是节点 IP。首次按 Pod 网段放行后,11 台启用 firewalld 的主机抓取
全部失败,只有未启用 firewalld 的 3 台成功。
故实现体按 kubectl get nodes 的 InternalIP 逐台生成 rich rule。
# 取的来源(实测:192.168.31.10 / .13 / .14 / .15,逐条放行)
kubectl --kubeconfig /home/rain/.kube/config get nodes \
-o jsonpath='{range .items[*]}{.status.addresses[?(@.type=="InternalIP")].address}{"\n"}{end}'
# 逐条放行(以 .10 为例;实现体对 4 个来源各生成一条)
ssh root@192.168.31.16 "firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.31.10\" port port=\"9100\" protocol=\"tcp\" accept'"
ssh root@192.168.31.16 "firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.31.13\" port port=\"9100\" protocol=\"tcp\" accept'"
ssh root@192.168.31.16 "firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.31.14\" port port=\"9100\" protocol=\"tcp\" accept'"
ssh root@192.168.31.16 "firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.31.15\" port port=\"9100\" protocol=\"tcp\" accept'"
ssh root@192.168.31.16 "firewall-cmd --reload && firewall-cmd --list-rich-rules | grep 9100"
预期:输出 4 条针对 9100/tcp 的 rich rule。
说明:.23/.24 的 firewalld 未运行、.25 为 failed(既有异常),
这三台无包过滤,实现体会跳过本步并留日志。
5.5 试点验证¶
kubectl --kubeconfig /home/rain/.kube/config apply -f logs/rendered-manifests.yaml # 先接入抓取
kubectl --kubeconfig /home/rain/.kube/config exec -n monitoring \
$(kubectl --kubeconfig /home/rain/.kube/config get pods -n monitoring -l app.kubernetes.io/name=prometheus -o jsonpath='{.items[0].metadata.name}') \
-c prometheus -- wget -qO- --post-data='query=up{instance="slurm-master-01:9100"}' \
http://localhost:9090/api/v1/query
预期:返回 "value":["<ts>","1"]。
失败判断:value 为 0 → 抓取不通,回到 5.3/5.4 查端口与防火墙。
试点通过后才进入步骤 6。
步骤 6:分批全量主机侧¶
按 ops.batch_size(默认 5 台)分批推进;批内并发 batch_max_workers;
单批失败台数达到 failure_threshold(默认 1)即停止后续批次并回报失败主机。
每台的动作与步骤 5.1–5.4 完全一致(同一实现,不分叉)。
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
.venv/bin/python main.py deploy
预期:日志按批打印,每台出现「收敛完成」;结束状态 success。
失败判断:出现 FAILED → 按日志定位该台(常见:yum 锁冲突、EPEL 不可达、SELinux 拒绝绑定),
修复后直接重跑(幂等,已达标主机会被跳过)。
步骤 7:集群侧接入¶
清单由配置渲染(14 台端点 + 每台三条 relabel 规则把 instance 规整为主机名、
并补 cluster / role 标签),随后下发;Alertmanager 用渲染后的 overlay values 更新。
大盘由 dashboards/ 精简源经 main.py build-dashboards 生成(唯一真源是精简源,
详见附录 G.9);manifests/ 里的大盘 YAML 是生成物,不得手改——
pre_check 会校验二者一致性,不一致即 BLOCK。
# 7.1 渲染并下发抓取/告警/大盘清单(渲染产物见 logs/rendered-manifests.yaml)
kubectl --kubeconfig /home/rain/.kube/config apply -f logs/rendered-manifests.yaml
# 7.2 下发社区大盘 1860 的 ConfigMap(label 驱动 sidecar 导入)
kubectl --kubeconfig /home/rain/.kube/config create configmap node-exporter-full-1860 \
-n monitoring --from-file=node-exporter-full-1860.json=artifacts/dashboards/node-exporter-full-1860-r45.json \
--dry-run=client -o yaml | kubectl --kubeconfig /home/rain/.kube/config apply -f -
# 7.3 更新 Alertmanager 邮件路由(授权码由实现体注入临时 600 文件,不落明文)
# 先导出 live values 作为回滚基线:
helm --kubeconfig /home/rain/.kube/config get values kube-prometheus-stack -n monitoring -o yaml \
> backup/cluster/kube-prometheus-stack-values.live.yaml.bak.$(date +%Y%m%d-%H%M%S)
helm --kubeconfig /home/rain/.kube/config upgrade kube-prometheus-stack \
../prometheus_deploy/charts/kube-prometheus-stack-91.4.1.tgz \
-n monitoring -f backup/cluster/kube-prometheus-stack-values.live.yaml.bak.<时间戳> \
-f <渲染后的 Alertmanager values> --no-hooks --timeout 15m
坑:--no-hooks 不可省。chart 的 pre-upgrade hook 需拉
ghcr.io/jkroepke/kube-webhook-certgen,而集群侧 ghcr.io 不可达,会一直卡到 --timeout 失败(实测)。
预期:STATUS: deployed;Prometheus Operator 随后重建 Alertmanager 配置
(实测 reload 需 1~2 分钟,实现体用轮询等待而非固定 sleep)。
步骤 8:幂等复跑(验收硬条件)¶
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
.venv/bin/python main.py deploy
预期:第二次执行零变更——14 台全部「已就绪,跳过」,集群侧全部「已存在,跳过」, Alertmanager 判定为「已是最新」。 失败判断:出现任何变更动作 → 幂等判据不完整,先定位再继续。
6. 验证¶
| # | 验证项 | 命令 | 预期 |
|---|---|---|---|
| 1 | 抓取目标数 | kubectl ... exec -n monitoring <prom-pod> -c prometheus -- wget -qO- --post-data='query=count(up{job=~"external-node-exporters.*"} == 1)' http://localhost:9090/api/v1/query |
14 |
| 2 | 标签规整 | 同上,query 换成 count(up{job=~"external-node-exporters.*", cluster="slurm"}) |
5 |
| 3 | 告警规则已加载 | kubectl ... get prometheusrule env-host-baseline-alerts -n monitoring |
存在 |
| 4 | 大盘已导入 | Grafana → Dashboards → 搜「环境总览」与「Node Exporter Full」 | 均可打开且变量有值 |
| 5 | Alertmanager 路由 | kubectl ... get secret alertmanager-kube-prometheus-stack-alertmanager-generated -n monitoring -o jsonpath='{.data.alertmanager\.yaml}' \| base64 -d \| grep env-monitor-email |
两个 receiver 名均出现 |
| 6 | 邮件可投递 | 临时构造一条 category="host" 的测试告警,或等待真实告警 |
收到 163 邮件 |
| 7 | 主机侧五要素 | .venv/bin/python main.py verify |
全部通过 |
| 8 | 漂移检测 | .venv/bin/python main.py drift,退出码应为 0 |
无漂移 |
| 9 | 幂等 | 步骤 8 | 零变更 |
7. 回滚¶
备份位置与台账¶
- 目录:
env-monitor-deploy/backup/ - 台账:
backup/README.md(逐条登记:备份文件、原路径 / 主机、产生时间、因何产生、回滚什么、何时可删) - 集群侧:
backup/cluster/kube-prometheus-stack-values.live.yaml.bak.<时间戳>(helm live values) 与alertmanager-config.yaml.bak.<时间戳>(改前 Alertmanager 配置快照) - 主机侧:drop-in 若原本已存在,会在目标机原位留
<原文件>.bak.<时间戳>(就地回滚,不跨机搬运), 其位置已在backup/README.md中登记
回滚步骤¶
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
# 1) 逆序回滚:先撤集群侧抓取,再撤主机侧采集(避免抓取一个已停采集的主机)
.venv/bin/python main.py rollback
等价的手工步骤(集群侧):
kubectl --kubeconfig /home/rain/.kube/config delete -f logs/rendered-manifests.yaml --ignore-not-found
kubectl --kubeconfig /home/rain/.kube/config delete configmap node-exporter-full-1860 env-monitor-overview -n monitoring --ignore-not-found
helm --kubeconfig /home/rain/.kube/config upgrade kube-prometheus-stack \
../prometheus_deploy/charts/kube-prometheus-stack-91.4.1.tgz -n monitoring \
-f backup/cluster/kube-prometheus-stack-values.live.yaml.bak.<时间戳> --no-hooks --timeout 15m
等价的手工步骤(主机侧,以 .16 为例;高危,会卸载软件包):
ssh root@192.168.31.16 "systemctl disable --now prometheus-node-exporter"
ssh root@192.168.31.16 "rm -f /etc/systemd/system/prometheus-node-exporter.service.d/10-env-monitor.conf && systemctl daemon-reload"
ssh root@192.168.31.16 "firewall-cmd --permanent --remove-rich-rule='rule family=\"ipv4\" source address=\"10.244.0.0/16\" port port=\"9100\" protocol=\"tcp\" accept' && firewall-cmd --reload"
ssh root@192.168.31.16 "rm -rf /var/lib/node_exporter/textfile"
ssh root@192.168.31.16 "dnf remove -y node-exporter"
回滚后验证¶
count(up{job=~"external-node-exporters.*"}) == 0(若已删除 ServiceMonitor 则该 job 直接消失)。kubectl get servicemonitor,configmap -n monitoring | grep -E 'external-node-exporters|env-monitor'无输出。- 目标机
ss -lntH | grep ':9100'无监听。 - Alertmanager 配置回到改动前内容(与
alertmanager-config.yaml.bak.<时间戳>比对)。
注意:dnf remove node-exporter 这一步不可逆到「未安装」之前的状态
(重装会重新拉包);如需完全复原请重跑 deploy。
8. 风险说明¶
| # | 风险 | 影响 | 缓解 | 残余风险 |
|---|---|---|---|---|
| 1 | SELinux 拒绝绑定 9100 | 8 台 Enforcing 主机上服务起不来 | 单台试点实测(步骤 5.3);备选方案 semanage port -t http_port_t -p tcp 9100 |
9100 现属 hplip_port_t,改标签会影响 hplip(本环境未使用打印机服务),改动前需确认 |
| 2 | 防火墙误放行 | 端口暴露面扩大 | 源网段由 Calico IPPool 推导,只放 9100/tcp,不给 any |
推导出的 10.244.0.0/16 若与实际不同会漏抓(预检查已交叉核对 Prometheus Pod IP) |
| 3 | 5 台缺 epel-release |
装包失败 | 实现体先补 epel-release;失败即停该批 |
依赖公网可达(实测可) |
| 4 | 包来源被替换 | 装上非预期软件 | 预检查强制 gpgcheck=1;大盘制品固定 revision + sha256 校验 |
无 |
| 5 | .25 firewalld failed(既有) |
该机无包过滤 | 判 WARN 并记录,不在本变更修复 | 安全基线问题遗留,已登记 |
| 6 | 批次中途失败 | 半程状态 | 分批 + 失败阈值 + 立即停止;已成功主机重跑幂等跳过 | 需人工介入失败主机 |
| 7 | helm upgrade 卡死 |
变更超时 | 固定使用 --no-hooks(ghcr.io 不可达);用本地 tgz 免拉取 |
若日后恢复 ghcr.io,可去掉该参数 |
| 8 | TSDB 容量增长 | 写满 PVC | 实测当前 head series 76,135 / 采样 2,680 samples/s / TSDB 4.02 GiB(PVC 100 GiB);预计 +15~22%,余量充足 | 若后续 P1 再叠加多个 exporter,需重做容量预算 |
| 9 | Alertmanager 路由误配 | 告警噪声或漏报 | 改前备份 live values;info 级不发信;主机失联时抑制衍生告警 |
Alertmanager 原生不支持「工作时段/日报」,P3 以「不发信 + 大盘可见」实现 |
| 10 | helm upgrade 漏传现场 values 基线 | 其余配置回落 chart 默认值,触发全栈重建(本次已实际发生) | 实现体强制要求基线:-f <现场 values> -f <overlay>,缺基线直接拒绝执行;改前导出基线并脱敏 |
见 INCIDENT-20260930-monitoring-fullstack-rollout.md |
| 11 | Grafana PVC 被删 | 面板与 Grafana 内部状态丢失 | 基线里保留 persistence.enabled: true;改前备份 NFS 数据目录 |
本次已发生并回填恢复 |
| 12 | k8s-node-02 上新 Pod 挂 NFS 卷卡在 Init:0/1 |
Pod 长时间不可用 | 用 manifests/06-grafana-node-pin.yaml 把 Grafana 钉到 k8s-node-03 规避 |
已调查未复现(复现实验 11 秒通过、无脏状态残留,判为当时临时状态);排除清单与预防建议见 node02-init-stuck-20260930/20260930-k8s-node-02-Pod卡Init故障报告.md |
| 13 | v1 Endpoints 在 K8s v1.33+ 已弃用 |
未来版本移除后抓取失效 | 当前可用;升级到移除该 API 的版本前,需改用 EndpointSlice | kubectl 会打印 deprecation 警告 |
9. 附录¶
A. 交付物清单¶
| 路径 | 说明 |
|---|---|
main.py、deployer.py |
入口与编排层 |
modules/host_agent.py |
主机侧收敛模块 |
modules/k8s_ingest.py |
集群侧接入模块 |
utils/ |
命令层、配置校验、预检查、异常、大盘生成器(dashboard_builder.py) |
utils/probe/ |
变更前的前置只读实测脚本(留档 + 可复跑) |
dashboards/ |
大盘精简源(5 个环境大盘的唯一真源,容器见附录 G.9) |
config/env-monitor.yaml |
配置唯一真源(受管主机、包、防火墙、集群侧、告警、批次) |
manifests/ |
抓取清单模板、告警规则、Alertmanager values 模板;大盘 YAML 为生成物 |
artifacts/dashboards/ |
社区大盘制品(固定 revision)与 SHA256SUMS |
reports/ |
预检查报告、漂移报告、前置实测原始输出 |
backup/ |
备份与 README.md 台账 |
docs/SOP.md、docs/RUNBOOK.md |
本文件与运行手册 |
B. 基线数据来源¶
- 覆盖率、包与包源、firewalld、SELinux、9100 标签:
reports/probe-*.md、reports/probe-ne-service-*.txt、reports/probe-pkg-source-*.txt(2026-09-29) - Pod 网段:
kubectl get ippools.crd.projectcalico.org -o jsonpath='{.items[*].spec.cidr}'→10.244.0.0/16 - 抓取来源:
kubectl get nodes -o jsonpath=…InternalIP→192.168.31.10/.13/.14/.15(Calico SNAT 后的真实源) - Prometheus 容量:
prometheus_tsdb_head_series、rate(prometheus_tsdb_head_samples_appended_total[5m])、prometheus_tsdb_storage_blocks_bytes
C. 实施期实测结论(均已验证,非推断)¶
| 结论 | 证据 |
|---|---|
| SELinux 不阻止 node-exporter 绑定 9100 | 8 台 Enforcing 主机上服务均 active、9100 正常监听;EPEL 包以 User=prometheus 运行,未落入受限域。无需补 semanage 端口标签 |
9100 上原有的 hplip_port_t 标签未造成影响 |
同上;预检查里的该项预警按「只预警不下结论」设计,实测确认为无害 |
| 单台试点耗时约 9 秒(已装包时) | .16 试点:success | 耗时: 8.60s |
| 全量 13 台耗时约 100 秒 | 分 3 批、批内并发 5 |
| 幂等复跑零变更 | 主机侧 14 | 跳过 14;集群侧「渲染清单与实机一致」「Alertmanager 邮件路由已是最新」 |
D. 实施期事故¶
本次实施过程中发生一次 P1 级事故(监控栈全栈重建、Grafana PVC 被删),
已恢复并复盘:见 INCIDENT-20260930-monitoring-fullstack-rollout.md。
E. 执行性能记录(2026-09-30 提速改造)¶
先测量再优化:改造前的基线取自实测(precheck 计时 + 日志里每次运行的时间戳与 SSH 连接计数)。
| 指标 | 改造前 | 改造后 | 变化 |
|---|---|---|---|
precheck 耗时 |
136.8 s | 10.9 s | 12.6× |
precheck 的 SSH 连接次数 |
126(14 台 × 9 次逐项探测) | 14(每台 1 次合并采集) | 9× |
| 预检结论复用 | 无(每次 deploy 全量重跑) |
命中缓存约 0.3 s | 单轮省 ~11 s |
幂等复跑 deploy 总耗时 |
~26 s(含每次 11 s 预检) | 16.2 s(预检复用) | — |
| 批次间隔等待 | 20 s × 2 = 40 s/轮 | 0(批内完成即继续) | -40 s |
| 校验等待上限 | 180 s + 240 s | 45 s(纳管)+ 90 s(全 up)+ 120 s(AM) | 最坏 -5 min |
改动清单
- 预检每台只发一次 SSH:把 hostname/发行版/包/端口/SELinux/firewalld/时钟/磁盘
合并成一段脚本一次取回,判定改为本地纯函数(
_collect_facts+_judge_*)。 - 预检主机间并发:线程池,度数与
ops.batch_max_workers同源。 - 昂贵探测按需:
dnf list --available只在「包未装且 EPEL 已配」时才查 (原实现无条件查,曾与dnf makecache争锁阻塞 9 分钟)。 - 预检结论复用(T2):缓存指纹 = 配置 + 全部实现体源码的 sha256,
TTL 默认 300 s(
ops.precheck_cache_ttl_seconds),--refresh-precheck强制重跑。 - 批次间不再固定 sleep:
batch_pause_seconds默认 0,批内全部完成即进入下一批。 - 变更阶段复用探测结果:
_converge_host改为只对缺失项发命令, 不再重查 RPM/SHA/服务状态/防火墙规则;已就绪的机器一条命令都不发。 - 校验按状态量收敛即退出:拆成「纳管」与「全部 up」两段,超时按实测收紧。
- 判定纠偏:
port/9100被本工程的 node-exporter 占用时降为 INFO (否则变更后每轮都刷 14 条噪声 WARN)。
仍未解决的性能点(下一轮):实测 每次远端命令会产生 2 次 SSH 握手
(一次复跑 14 台探测 = 28 次连接,约 13 s 中绝大部分是握手)。共享库
paramiko_module.connection_pool.get_connection 是「每次新建连接」的实现,
要彻底解决需引入连接复用;它被多个工程共用,改动须单独回归,故本轮未动。
F. 规范取舍说明(豁免登记)¶
| 规范项 | 本工程做法 | 理由 |
|---|---|---|
| 离线 bundle / 本地仓库 | 未做 | 目标环境 14 台均可访问 EPEL 与公网(实测),RPM 由 EPEL 官方仓库 + GPG 签名保证来源可信;离线仓库收益低于维护成本。按规范的「建议项」登记豁免 |
| K8s 侧与主机侧同一工程 | 合并为一个工程 | 主机侧是主变更单元(主机型收敛);集群侧是本工程的幂等制品下发,非控制器式声明收敛。拆成两个工程会让门禁、幂等、回滚各自一套 |
| 分批执行 | 已做(默认 5 台/批,失败阈值 1) | 本变更动作可重入,属于建议项;仍按建议做了分批与逐台可用性复核 |
| Alertmanager 三级分级 | 落为两级 + P3 不发信 | Alertmanager 原生不支持「工作时段」与「定时日报」,如需日报须另加定时组件,不在本变更范围 |
G. 增量变更记录¶
G.1 2026-09-30:补集群侧「工作负载长时间起不来」告警¶
背景:当天变更期间 Grafana 等 Pod 在 k8s-node-02 卡在 Init:0/1 超过 20 分钟,
当时没有任何告警,是靠人工在另一次变更里偶然发现。这类盲区必须用告警兜住,
否则下次仍要靠运气。故障本身结论为「已调查·未复现」,见
node02-init-stuck-20260930/20260930-k8s-node-02-Pod卡Init故障报告.md。
改了什么
| 文件 | 改动 |
|---|---|
manifests/07-env-cluster-baseline-alerts.yaml |
新增 PrometheusRule env-cluster-baseline-alerts,2 条规则:EnvPodStuckPending(phase=Pending 持续 10 分钟)、EnvPodContainerCreateFailed(容器处于 ImagePullBackOff/CreateContainerError 等持续 10 分钟),标签 category=cluster、severity=warning |
manifests/05-alertmanager-routing-values.yaml |
路由新增 category = "cluster" 的 critical / warning 两条分支——原先只认 category = "host",不加则新告警只会「只存不发」;邮件主题前缀由「主机P1/P2」改为「P1/P2」 |
config/env-monitor.yaml |
resources.prometheus_rule_names(列表)取代单数写法;manifests 列表加入 07 |
modules/k8s_ingest.py |
validate 遍历全部规则名逐个检查;新增 expected_am_matchers() |
utils/config_validate.py |
kubernetes.resources 的值允许为资源名列表 |
两个必须留痕的坑
- 只查 receiver 名会把「新增路由」误判为「已是最新」——receiver 名没变,但
matcher 变了。故
_alertmanager_needs_update()增加matcher 文本比对: 从模板文件自动提取全部 matcher(形如- 'category = "host"'),逐个确认出现在 Alertmanager 运行配置里(实测 operator 写入 Secret 的格式为category = "host")。 - 配置校验会拦住列表写法——
kubernetes.resources原先只接受字符串,改列表后 门禁报 BLOCK 并终止(门禁工作正常)。已放开为「字符串或字符串列表」。
验证
# 两条规则都在
kubectl --kubeconfig /home/rain/.kube/config get prometheusrule -n monitoring -o name | grep env-
# 预期:env-cluster-baseline-alerts、env-host-baseline-alerts
# Prometheus 已加载新规则组
kubectl --kubeconfig /home/rain/.kube/config exec -n monitoring \
$(kubectl --kubeconfig /home/rain/.kube/config get pods -n monitoring -l app.kubernetes.io/name=prometheus -o jsonpath='{.items[0].metadata.name}') \
-c prometheus -- wget -qO- 'http://localhost:9090/api/v1/rules?type=alert'
# 预期:出现 env-cluster-pod-stuck 组,含 EnvPodStuckPending / EnvPodContainerCreateFailed
实测结果:两条规则均已下发;Prometheus 已加载 env-cluster-pod-stuck 组;
Alertmanager 运行配置出现两条 category = "cluster" 路由;幂等复跑退出码 0
(复用预检结论、渲染清单一致、AM 已是最新、主机侧 14 台全跳过)。
回滚
# 1) 删除新增的规则对象
kubectl --kubeconfig /home/rain/.kube/config delete prometheusrule env-cluster-baseline-alerts -n monitoring
# 2) 用备份覆盖被改的四个文件(见 backup/README.md 的 2026-09-30 条目)
cp backup/manifests/05-alertmanager-routing-values.yaml.bak.<TS> manifests/05-alertmanager-routing-values.yaml
cp backup/config/env-monitor.yaml.bak.<TS> config/env-monitor.yaml
cp backup/modules/k8s_ingest.py.bak.<TS> modules/k8s_ingest.py
# 3) 重跑 deploy 以恢复 Alertmanager 路由(删掉 cluster 分支)
.venv/bin/python main.py deploy --refresh-precheck
G.3 2026-09-30:P1 深化(EDA 运营视角与 FlexLM 数据修复)¶
变更结论:本轮在既有 P1 基础上深化 Slurm 与 FlexLM,不改变调度器、许可服务或作业状态。
- Slurm 采集器新增低基数聚合:Pending 原因、用户/Account 作业数;不输出 JobID,避免 Prometheus 高基数。
- Slurm 大盘新增 Pending 原因、用户/Account 分布面板;新增 Pending 原因持续升高告警。
- FlexLM 对计数型 feature 输出
license_type="counted"的 issued/in_use;Uncounted、node-locked只输出 feature 存在性,不伪造容量。 - FlexLM 占用率、明细表和耗尽告警统一限定
license_type="counted",避免将不可计量 feature 当作可用许可。 - 实机核验:
.29的 Synopsys 2700 与 Cadence 5280 均 server/vendor daemon UP;Prometheus 查询flexlm_up=1;Slurm 两个控制节点slurm_up=1;外部抓取目标 14/14 UP。 - 幂等复跑:第二次
deploy集群侧清单状态一致并跳过,主机侧无非预期变更。
限制与后续:本轮已补充许可拒绝累计计数与 Slurm 当前作业等待时延;统计均依赖当前日志/队列可见窗口,不能替代完整历史作业审计。FairShare/QOS/TRES、License 与作业关联需在取得稳定历史基线后继续补充。
G.4 2026-09-30:P1 深化补充(FlexLM denied 与 Slurm 调度等待)¶
- FlexLM 采集器新增
--log VENDOR=PATH,读取.29两个lmgrd.log,按拒绝事件关键字累计输出flexlm_license_denied_total{vendor};当前实测 Synopsys/Cadence 均为 0。 - 新增
EnvFlexlmLicenseDenied告警:15 分钟窗口出现新增拒绝事件并持续 5 分钟时告警。 - Slurm
squeue增加%V提交时间和%S启动时间,输出slurm_pending_wait_seconds(当前 Pending 作业等待)与slurm_dispatch_wait_seconds(运行作业提交到启动) 的 avg/max。 - 新增 Slurm 等待时延看板与 30 分钟 Pending 最大等待、10 分钟平均调度等待告警。
- 变更不触碰作业提交/取消、调度器、license 服务或日志文件;第二次
deploy集群侧清单状态一致并跳过。
G.2 2026-09-30:P1 四领域指标接入(Slurm / Lustre / LSF / FlexLM)¶
背景与目标:监控体系蓝图(docs/MONITORING-ROADMAP.md)的 P1 要求「加 Slurm、Lustre、
FlexLM exporter(LSF 生态封闭,用 textfile collector 解析 bhosts/bjobs 兜底);分配角色
看板 + 对应 PrometheusRule」。P0 只交付了主机层,领域指标全缺(缺口 G4)。本次把四个领域的
指标、角色看板与告警一次补齐。
选型决策:走 textfile collector,不部署专用 exporter(依据为实测,非推断)
| 依据 | 实测结论 |
|---|---|
| 制品可达性 | 集群侧 ghcr.io 不可达,专用 exporter 需额外离线制品;textfile 路线无需新制品 |
| 端口与防火墙 | textfile 复用 node-exporter 的 9100,不新增端口、不改防火墙、不改集群侧抓取配置 |
| 原生查询命令 | 四领域均可用(路径见下表);14 台均有 python3 |
| 已知限制 | Lustre 容量只能在挂载了客户端的节点取:.21/.22 是 MGS/OSS,无客户端视图,lfs df 无输出;.28 已挂载 /mnt/lustre |
目标分布与命令
| 领域 | 主机(role) | 主命令(绝对路径,均不在默认 PATH) | 主要指标 |
|---|---|---|---|
| slurm | .16/.17(controller-primary/backup) |
/usr/local/slurm/bin/{sinfo,squeue,scontrol} |
slurm_up、slurm_partition_{nodes,cpus,avail}、slurm_jobs、slurm_ctld_up |
| lustre | .21(mgs-mds)、.22(oss)、.28(submit) |
/usr/sbin/lctl、/usr/bin/lfs |
lustre_{mdt,ost}_exports、lustre_ost_ops_samples、lustre_fs_bytes |
| lsf | .23/.24(master-primary/backup) |
/data/nfs/lsf/lsf/10.1/linux2.6-glibc2.3-x86_64/bin/{bhosts,bqueues,bjobs} |
lsf_host_{status,jobs}、lsf_queue_jobs、lsf_jobs_unfinished |
| flexlm | .29(license-server) |
/opt/FlexLM/Synopsys/lmutil(通用工具;Cadence 目录无此文件) |
flexlm_server_up、flexlm_vendor_daemon_up、flexlm_license_{total,in_use} |
变更点(工程内)
- 新增
modules/domain_collectors.py:主机型变更单元,与host_agent并列;生产物为 「采集脚本 + systemd service + timer」,写agent.textfile_dir下的.prom。 幂等判据四要素:脚本 sha256 / service sha256 / timer sha256 / timer active+enabled, 外加「指标文件已生成」。 - 新增顶层配置段
collectors.<领域>(enabled / clusters / roles / script / remote_path / bin_dir / probe_cmd / interval_seconds,可选extra_args)。 - 新增制品
artifacts/collectors/{slurm,lustre,lsf,flexlm}_metrics.py。 - 预检查新增判定:按
collectors.<领域>的作用域判定「本节点是否应采该领域」,命令不可执行即 BLOCK(与变更阶段共用同一套作用域逻辑,避免两处漂移)。 - 集群侧新增
manifests/08-env-slurm-dashboard.yaml、09-env-slurm-alerts.yaml、10-env-lustre-dashboard.yaml、11-env-lsf-dashboard.yaml、12-env-flexlm-dashboard.yaml、13-env-domain-alerts.yaml;config 的manifests与resources.prometheus_rule_names同步。
分步操作(与实现体等价;实现体:modules/domain_collectors.py)
# 1) 门禁:配置校验 + 环境预检查(含四领域命令可用性)
.venv/bin/python main.py precheck
# 预期:BLOCK=0;.16/.17 出现 collector/slurm「采集命令可用」,
# .21/.22/.28 出现 collector/lustre,.23/.24 出现 collector/lsf,.29 出现 collector/flexlm
# 2) 预演(零改动)
.venv/bin/python main.py deploy --dry-run
# 预期:列出待变更目标与动作(确保 textfile 目录 → 传脚本 → chmod → 写 unit/timer
# → daemon-reload → enable --now timer → 立即执行一次)
# 3) 真实变更(分批 + 批内并发;failure_threshold=1)
.venv/bin/python main.py deploy
# 4) 目标机核对产物(以 Lustre MDS 为例)
ssh root@192.168.31.21 'systemctl is-active env-lustre-metrics.timer'
ssh root@192.168.31.21 'cat /var/lib/node_exporter/textfile/lustre.prom'
# 预期:active;文件含 lustre_up 1 与 lustre_mdt_exports
# 5) 集群侧核对对象
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get prometheusrule,cm | grep -E 'slurm|lustre|lsf|flexlm'
# 预期:4 个 PrometheusRule(env-slurm-alerts/env-domain-alerts 及既有)+ 4 个看板 ConfigMap
坑与注意事项(实现体已处理,此处留痕,便于复现与排障)
- 命令必须写绝对路径:Slurm 与 LSF 的二进制不在默认 PATH(由
/etc/profile.d注入), 非交互 shell 取不到 —— 实测command -v sinfo在bash -s下为空。 lctl get_param不要加-n:-n只输出裸值(如3),丢掉参数名后无法区分多个 target;不带-n时输出形如mdt.edafs-MDT0000.num_exports=3,才能解析出 target 名。- 三个采集脚本的参数接口必须统一(
--bin-dir / --output / --cluster,FlexLM 另有--server):首次上线时 Lustre/FlexLM 脚本因缺--bin-dir/--cluster而systemctl start失败(unrecognized arguments),修正后通过。 - 同一集群多采集点须去重:Slurm 与 LSF 都在主备两台控制器上采同一集群视图,告警表达式
一律用
max by (...)聚合,否则一次故障会因两个采样点而重复发信。 - 零作业时
squeue无输出:脚本对PENDING/RUNNING补零值,避免大盘显示「无数据」而非 0。 closed_Full不是异常:.28是提交节点,在 LSF 里正常显示closed_Full;EnvLsfHostAbnormal只匹配unreach|unavail|unusable,避免恒定噪声。
验证(实测结论)
| 检查 | 结果 |
|---|---|
| 四领域指标经 9100 暴露 | 22 个指标名齐备(slurm 6 / lustre 4 / lsf 5 / flexlm 5,另含各自 _up) |
| Prometheus 抓取 | up 14/14;slurm_up=1(2 实例)、lustre_up=1(3)、lsf_up=1(2)、flexlm_up=1(1) |
| 告警规则加载 | 6 组共 20 条:env-host-(ExternalHost)/ env-cluster- / env-slurm- / env-lustre / env-lsf / env-flexlm |
| 角色看板 | 4 个 ConfigMap:env-slurm / env-lustre / env-lsf / env-flexlm |
verify |
14 台主机「五要素」+ 8 个采集目标「四要素」全部满足 |
| 幂等复跑 | 14 + 8 + 3 全部「状态一致,无需变更」,零变更、退出码 0 |
顺带修复的两处 P0 遗留(同一轮变更内完成)
Watchdog告警此前落到默认 receivernull(等于放弃自监控心跳)——新增env-monitor-email-p3接收器与alertname = "Watchdog"路由,每日一报(repeat_interval: 24h)。inhibit_rules的source_matchers写的是并不存在的EnvHostDown,导致 「主机失联时抑制其衍生告警」从未生效 —— 已更正为实际告警名ExternalHostNodeExporterDown(见manifests/02-env-host-baseline-alerts.yaml)。
回滚
# 1) 主机侧:停用并删除四类采集器(timer / service / 脚本 / prom)
.venv/bin/python main.py rollback
# 2) 集群侧:删除本次新增的看板与告警对象
kubectl --kubeconfig /home/rain/.kube/config -n monitoring \
delete prometheusrule env-slurm-alerts env-domain-alerts
kubectl --kubeconfig /home/rain/.kube/config -n monitoring \
delete configmap env-slurm-dashboard env-lustre-dashboard env-lsf-dashboard env-flexlm-dashboard
# 3) 工程侧:用备份覆盖(见 backup/README.md 的 2026-09-30 P1 条目)
cp backup/docs/SOP.md.bak.<TS> docs/SOP.md
cp backup/config/env-monitor.yaml.bak.<TS> config/env-monitor.yaml
回滚触发条件(动手前量化):① verify 或幂等复跑不通过且修正 2 次仍不过;② 目标机
node-exporter 因 textfile 内容异常而抓取失败;③ 新告警规则造成邮件风暴(P1/P2 分级误配)。
G.3 2026-09-30:修正预检并发度配置失效(ops.batch_max_workers)¶
背景:attribution-split-20260930 的归因测量中,把配置副本的 ops.batch_max_workers
改成 1 后预检并发度毫无变化,据此查出该配置项从未生效——EnvMonitorPreCheck.__init__
传入 section="precheck",基类 _cfg() 只读该段,而键定义在 ops: 段,取值处
self._cfg().get("batch_max_workers", 5) 恒回落默认值 5。使用侧与校验侧不同源:
utils/config_validate.py 校验的正是 ops: 段的同名键。
改了什么
| 文件 | 改动 |
|---|---|
utils/precheck.py |
host_findings() 的并发度改从 config_data["ops"] 读取,与校验同源;其余逻辑与阈值不变 |
配置项位置不变(仍只在 ops: 段定义)。第 476 行「度数与 ops.batch_max_workers 同源」
的表述自本版本起才与代码相符,该行文字本身无需修改。
验证
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
# 1) 造一份 workers=1 的配置副本:副本必须留在 config/ 下——project_root 由 config
# 路径上推两级得出,放 /tmp 会把 reports/ 指到根目录
sed 's/^ batch_max_workers: 5 / batch_max_workers: 1 /' \
config/env-monitor.yaml > config/env-monitor-w1.yaml
/usr/bin/time -f "workers=1: %e s" .venv/bin/python main.py precheck \
--refresh-precheck --config config/env-monitor-w1.yaml
rm config/env-monitor-w1.yaml
# 2) 默认配置(workers=5)
/usr/bin/time -f "workers=5: %e s" .venv/bin/python main.py precheck \
--refresh-precheck --config config/env-monitor.yaml
预期:workers=1 约 46 s(14 台逐台串行),workers=5 约 11 s(批内 5 并发);
两轮结论一致、BLOCK=0。
实测(2026-09-30 14:02–14:03):workers=1 → 47.62 s,workers=5 → 10.87 s,
与附录 E 的 B/C 组基线(46.20 s / 11.06 s)吻合;两轮均 BLOCK=0 WARN=9 INFO=149。
修复前该配置项取任何值耗时都不变(恒 5 并发)。
回滚:见第 7 章「备份位置与台账」,对应条目为 backup/README.md 的
2026-09-30 修复条目(两个 .bak.20260930-140113 文件)。
影响面:只读子命令 precheck 的线程池尺寸,不改变探测内容与判定阈值,
不涉及任何目标机与集群侧状态;deploy 的批次并发走 ops 段,不受本次影响。
G.5 2026-09-30:Slurm 大盘按 EDA 运营视角扩展(节点 / 调度器 / 会计 / 公平性)¶
背景:P1 的 Slurm 大盘只有「分区级快照 + 作业状态计数」,用户反馈过于单调。EDA 行业看
Slurm 的四个核心视角——排队体验、资源有效性(CPU 效率)、项目公平性(Account/FairShare)、
License 与作业的耦合——在旧盘中全部缺失。根因不在面板数量,而在采集面只调用了
sinfo / squeue / scontrol ping 三条命令,粒度只到分区聚合。
目标:不新增端口、不改抓取配置、不触碰调度器与作业状态,把采集面扩到节点级、调度器内部、 公平性与会计历史,并按 EDA 视角重排大盘。
数据源(均为只读查询命令,均已实测可用;Slurm 26.05.2、slurmdbd active)
| 命令 | 提供什么 | 新增指标(cluster 标签之外) |
|---|---|---|
sinfo -N -h -o '%N\|%t\|%C\|%m\|%e\|%G' |
逐节点状态 / CPU 四元组 / 内存 / GRES | slurm_node_state、slurm_node_cpus、slurm_node_memory_mb、slurm_node_gres |
squeue -h -o '…%M\|%l\|%Q\|%C\|%m' |
运行时长、时限、优先级、所需 CPU/内存 | slurm_running_job_{runtime,timelimit}_seconds、slurm_running_job_{cpus,memory_mb,priority}、slurm_running_jobs_by_runtime、slurm_pending_job_{cpus,memory_mb} |
sdiag |
调度器主循环、队列深度、backfill、License 阻塞、RPC 时延 | slurm_sched_cycle_seconds、slurm_sched_queue_length、slurm_sched_cycles_per_minute、slurm_sched_blocked_on_licenses、slurm_sched_backfilled_jobs、slurm_sched_rpc_job_info_seconds |
sshare -P |
Account 份额与用量 | slurm_account_{norm_shares,raw_usage,fairshare} |
sacct -P -X -a -S now-1hour |
窗口内完成作业 | slurm_finished_jobs_1h、slurm_failed_jobs_1h、slurm_finished_job_elapsed_seconds_1h、slurm_job_cpu_efficiency_1h |
sreport cluster utilization -P -t hours |
分状态 CPU 小时 | slurm_cluster_cpu_hours_1h |
改了什么
| 文件 | 改动 |
|---|---|
artifacts/collectors/slurm_metrics.py |
新增 5 个采集段(节点 / 调度器 / 公平性 / 会计 / 利用率)与 --acct-window 参数;解析一律走 -P 结构化输出或逐键提取;原有 6 条指标的指标名、标签与语义保持不变 |
manifests/08-env-slurm-dashboard.yaml |
由 8 个面板扩为 27 个,分 7 组:集群与调度器健康 / 队列压力与等待 / 调度器内部与 License 阻塞 / 作业运行与资源效率 / 节点与分区资源 / 公平性与账户 / 跨域联动(FlexLM 许可、Lustre 容量、集群利用率) |
配置、systemd 单元、抓取链路与告警规则均未改动;slurm_up 的判定口径不变(仍是 sinfo + squeue)。
分步操作(与实现体等价;实现体:modules/domain_collectors.py)
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
# 1) 门禁
.venv/bin/python main.py precheck
# 预期:BLOCK=0 WARN=9 INFO=149(与既有基线一致)
# 2) 预演(零改动)
.venv/bin/python main.py deploy --dry-run
# 预期:主机侧只列 .16/.17(采集脚本内容不符),其余 6 个采集目标「已就绪,跳过」;
# 集群侧 1 处待下发(08 大盘 ConfigMap)
# 3) 真实变更(主机侧分批 + 批内并发;集群侧 kubectl apply)
.venv/bin/python main.py deploy
# 4) 目标机核对(新指标是否落盘并经 9100 暴露)
ssh root@192.168.31.16 'ls -l /var/lib/node_exporter/textfile/slurm.prom'
ssh root@192.168.31.16 'curl -s http://127.0.0.1:9100/metrics | grep -oE "^slurm_sched_[a-z_]+" | sort -u'
# 5) 集群侧核对:Prometheus 收录 + 大盘对象
kubectl -n monitoring get --raw "/api/v1/namespaces/monitoring/services/kube-prometheus-stack-prometheus:9090/proxy/api/v1/label/__name__/values"
kubectl -n monitoring get cm env-slurm-dashboard -o jsonpath='{.data.env-slurm-dashboard\.json}' | head -c 200
坑与注意事项(实现体已处理,此处留痕)
- 同一节点在多个分区重复出现:
sinfo -N按「节点 × 分区」各输出一行(本环境 3 节点 × 4 分区 = 12 行), 不去重会写出重复 series,Prometheus 会拒收整段指标 → 按节点名去重后只保留一条。 sdiag没有结构化输出:只能逐键提取,且Last cycle/Max cycle在 「Main schedule statistics」与「Backfilling stats」两段中各出现一次 → 必须先按段切分, 否则会把 backfill 的 0 误当成主循环耗时。sdiag的 cycle 单位是微秒:四舍五入到 4 位小数会把 17 µs 变成 0 → 数值统一保留 6 位小数, 大盘侧再乘1e6以 µs 显示。sshare -P首行是表头,Account 行与 User 行混排:只取 User 列为空的行, 否则会把每个用户的用量写进 Account 维度(高基数)。sacct/sreport依赖 slurmdbd(本环境 active):两段均带 20 s 超时, 任一段失败只跳过该段,不影响其余指标,也不改变slurm_up的既有语义。- 零作业时
squeue无输出:作业类指标统一走「无数据输出 0」分支,大盘显示 0 而非「无数据」。
验证(实测结论)
| 检查 | 结果 |
|---|---|
| Prometheus 收录 slurm_ 指标 | 35 个(新增 24 个);采样 slurm_node_cpus{state="idle"} = 8 / 8 / 8 |
| node-exporter 暴露 | .16 的 slurm.prom 7944 B,env-slurm-metrics.timer active |
| 集群侧大盘 | ConfigMap 已更新为 27 面板 / 7 分组;Grafana sidecar 日志 Writing /tmp/dashboards/env-slurm-dashboard.json → dashboards/reload 200 OK |
| 幂等复跑 | 14 主机 + 8 采集目标 + 3 集群侧对象全部「状态一致,无需变更」,退出码 0 |
回滚:见第 7 章「备份位置与台账」,对应条目见 backup/README.md 的
2026-09-30 EDA 大盘节(工程内制品 backup/artifacts/collectors/slurm_metrics.py.bak.20260930-221849、
backup/manifests/08-env-slurm-dashboard.yaml.bak.20260930-221849、
backup/docs/{SOP,RUNBOOK}.md.bak.20260930-222358);远端脚本备份由实现体自动登记
(.16 / .17 的 env-slurm-metrics.bak.*)。步骤:用备份覆盖上述三个工程内文件 → 重跑 deploy。
风险与残留
- 作业类指标(
slurm_running_job_*、slurm_pending_job_*)在空队列下只验证了 0 值分支: 字段解析(%M/%l/%Q/%C/%m)需在集群实际有作业时再复核一次; 字段本身已用squeue --helpformat确认存在。 slurm_account_fairshare当前无输出:本环境各 Account 的 RawUsage 均为 0,sshare的 FairShare 列为空,产生用量后才会出现。sacct/sreport每次采集都会查一次 slurmdbd。当前规模下实测无压力; 作业量大时可加大--acct-window或调大collectors.slurm.interval_seconds。
G.6 2026-10-01:LSF 大盘按 EDA 运营视角扩展,并修复采集环境缺失缺陷¶
背景:本次有两条独立动因,第二条是扩展前实机取证才发现的。
- 用户反馈 LSF 大盘「过于单调」:P1 交付时只做了「主机状态 + 队列积压 + 未完成作业数」 共 7 个面板,与同期按 EDA 视角扩展过的 Slurm 大盘(27 面板 / 7 分组,见 G.5)明显不对等。
- LSF 领域指标此前从未真正采集成功。
.23上的lsf.prom内容是:
lsf_host_status{cluster="lsf",host="Oct",status="1"} 1
lsf_host_jobs{cluster="lsf",host="Oct",type="njobs"} 312346
lsf_host_status{cluster="lsf",host="lsb_init:",status="Failed"} 1
即 LSF 命令的初始化报错文本被当成主机表格解析了 —— host="Oct" 来自日期前缀
Oct 1 00:17:54 2026 ...,host="lsb_init:" 来自报错行本身。
根因(实测证据)
- LSF 二进制依赖
LSF_ENVDIR/LSF_SERVERDIR才能找到lsf.conf;本环境这些变量由/etc/profile.d/lsf.sh注入,systemd 非登录环境取不到; - 缺失时
env -i <bin>/bhosts -w实测rc=255,并把Oct 1 00:17:54 2026 312728 3 3.4.0 lsb_init: initenv_:fopen(/etc/lsf.conf) failed, No such file or directory.打到 stdout;补上两个变量后rc=0且输出正常表格; - 旧采集器对
bhosts/bqueues两段不检查返回码,只按「列数不小于 N」判断, 于是把报错行的 token 当成host/status/njobs; lsf_up旧口径是「任一类指标可用」,被误置为 1 —— 指标停更与集群正常在盘上长得一样,正是 G.1 里强调过的那类盲区。
目标:把 LSF 采集面扩到与 Slurm 对等的 EDA 七视角,并让「采集失败」的判定真实可信。
数据源(均为只读查询命令,均已实机验证;LSF 10.1.0.12 Community Edition,集群 lsfce)
| 命令 | 提供什么 | 新增指标(cluster 标签之外) |
|---|---|---|
lsclusters |
集群状态与主备控制端 | lsf_cluster_status、lsf_master_up、lsf_cluster_hosts |
badmin showstatus |
服务器 / 作业 / 用户 / CPU / 槽位计数、mbatchd | lsf_sched_servers、lsf_sched_jobs、lsf_sched_users、lsf_sched_capacity、lsf_sched_estimation_{pending,done}、lsf_sched_jobinfo_cache_mb、lsf_sched_server_total、lsf_mbatchd_up |
lsload |
逐主机负载 | lsf_host_load、lsf_host_util_percent、lsf_host_capacity_mb |
bqueues -l |
队列 FAIRSHARE 份额表 | lsf_queue_share |
busers all |
用户 / 组作业数与限额 | lsf_buser_jobs、lsf_buser_limit |
bjobs -u all -o … |
作业聚合(结构化,分隔符 |) |
lsf_jobs、lsf_pending_reason、lsf_jobs_{user,by_queue,by_project}、lsf_{running,pending}_job_*、lsf_{pending,dispatch}_wait_seconds、lsf_running_jobs_by_runtime |
bacct -u all -C <窗口> |
窗口内完成作业会计 | lsf_finished_jobs_window、lsf_finished_jobs_throughput_window、lsf_job_window_{seconds,factor}、lsf_scheduler_utilization_window |
改了什么
| 文件 | 改动 |
|---|---|
artifacts/collectors/lsf_metrics.py |
新增 7 个采集段;_run 增加「初始化失败特征串」判定(命中即整体按失败处理);bhosts / bqueues / lsload 段增加状态列取值集合 + 数值列可解析双重行校验,从源头挡住报错文本;lsf_up 判定收紧为「主机表与队列表均解析成功」(与 slurm_up 同口径)。原有 5 个指标名与语义保持不变 |
manifests/11-env-lsf-dashboard.yaml |
由 7 个面板重做为 30 个 / 7 分组,分组与 Slurm 大盘一一对应 |
manifests/14-env-lsf-alerts.yaml |
新增:12 条 LSF 告警,分「可用性(6)」与「容量与等待(6)」两组 |
manifests/13-env-domain-alerts.yaml |
删除原 env-lsf-alerts 组(3 条规则迁入 14 并扩充),只保留 Lustre / FlexLM,避免同名规则重复发信 |
modules/domain_collectors.py |
systemd 单元生成支持 extra_env,输出 Environment= 行 |
utils/config_validate.py |
校验 collectors.<领域>.extra_env(键须为合法环境变量名、值为非空字符串) |
config/env-monitor.yaml |
collectors.lsf.extra_env 注入 4 个 LSF 环境变量;manifests 加 14;resources.prometheus_rule_names 加 env-lsf-alerts |
分步操作(与实现体等价;实现体:modules/domain_collectors.py)
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
# 1) 门禁
.venv/bin/python main.py precheck
# 预期:BLOCK=0 WARN=9 INFO=149(与既有基线一致)
# 2) 预演(零改动)
.venv/bin/python main.py deploy --dry-run
# 预期:主机侧只列 .23/.24(脚本与单元内容不符),其余 6 个采集目标「已就绪,跳过」;
# 集群侧 1 处待下发
# 3) 真实变更(主机侧分批 + 批内并发;集群侧 kubectl apply)
.venv/bin/python main.py deploy
# 4) 目标机核对:单元已注入环境变量、产物不再是垃圾
ssh root@192.168.31.23 'systemctl cat env-lsf-metrics.service | grep -E "Environment=|ExecStart"'
ssh root@192.168.31.23 'grep -cE "host=\"(Oct|lsb_init)" /var/lib/node_exporter/textfile/lsf.prom'
# 预期:4 条 Environment=;第二条输出 0
# 5) 集群侧核对
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get prometheusrule env-lsf-alerts
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get cm env-lsf-dashboard -o jsonpath='{.data.env-lsf-dashboard\.json}' | head -c 200
坑与注意事项(实现体已处理,此处留痕)
- LSF 命令必须注入环境变量:
LSF_ENVDIR/LSF_SERVERDIR(另加LSF_LIBDIR/LSF_BINDIR)——只给--bin-dir不够,这是本次缺陷的根因。 - 只看返回码也不够:故同时按行结构过滤(
bhosts/lsload的状态列必须命中ok|closed|unavail|unreach|unusable|busy|full前缀),双保险。 bjobs -o必须显式指定分隔符:pend_reason的值含空格(如Waiting for a host), 按空白切列会错位;用delimiter="|"后按|解析。字段名已实测合法(非法字段会立即报错)。bacct的时间格式是MM/DD/HH:MM:now-1h一类相对式会被Bad time specification拒绝(实测);窗口内无完成作业时输出No job was found,脚本按 0 输出。bqueues -l的 FAIRSHARE 表按队列分布:SHARE_INFO_FOR: <queue>/后跟一张用户表, 本环境只有启用 FAIRSHARE 的队列才有该表;用户数会放大基数,脚本设 500 行上限。badmin showstatus无结构化输出:按缩进分段解析(顶格 / 4 空格为段头,8 空格为子项), 否则会把Number of users与Number of user groups混入作业段。- 同一集群被两台控制端各采一份:大盘与告警一律
max by(...)去重。 closed_Full不是异常:.28(hpc-submit)是提交节点,EnvLsfHostAbnormal只匹配unreach|unavail|unusable。
验证(实测结论)
| 检查 | 结果 |
|---|---|
| 目标机单元 | .23 / .24 的 env-lsf-metrics.service 含 4 条 Environment= |
| 产物变化 | lsf.prom 1624 B → 17433 B;垃圾序列(host="Oct" / host="lsb_init:")计数 0 |
| Prometheus 收录 | lsf_ 指标 36 个(原 6 个);lsf_up{instance} 两实例均为 1 |
| 数值抽查 | lsf_sched_servers{state="ok"}=5、closed=1;lsf_host_status{host="hpc-submit",status="closed_Full"}=1;lsf_queue_share{queue="normal",user_group="rain01",metric="priority"}=0.333;lsf_sched_jobinfo_cache_mb=64.26 —— 均与命令行实测一致 |
| 集群侧大盘 | ConfigMap 已更新为 37 个面板(含 7 个分组行)/ 7 分组 |
| 告警规则 | env-lsf-alerts 已下发,2 组 12 条;13-env-domain-alerts 仅剩 Lustre / FlexLM 共 6 条;无重名 |
| 幂等复跑 | 14 主机 + 8 采集目标 + 3 集群侧对象全部「状态一致,无需变更」,退出码 0 |
回滚:见第 7 章「备份位置与台账」,对应条目见 backup/README.md 的
2026-10-01 LSF EDA 大盘节(工程内制品 backup/artifacts/collectors/lsf_metrics.py.bak.20261001-001541、
backup/manifests/11-env-lsf-dashboard.yaml.bak.20261001-001541、
backup/config/env-monitor.yaml.bak.20261001-001541、
backup/docs/SOP.md.bak.20261001-001541、backup/docs/RUNBOOK.md.bak.20261001-001541、
backup/docs/HANDOFF.md.bak.20261001-002642(根级 docs/HANDOFF.md 的备份,原位在此目录));
远端脚本备份由实现体自动登记
(.23 / .24 的 env-lsf-metrics.bak.*)。步骤:用备份覆盖上述工程内文件 →
kubectl delete prometheusrule env-lsf-alerts → 从备份恢复 13 中的 LSF 组 → 重跑 deploy。
风险与残留
- 作业类指标(
lsf_running_job_*、lsf_pending_job_*、lsf_pending_reason、等待时延)在 空队列下只验证了 0 值分支:bjobs -o的字段名已实测合法,但字段值的解析 (时长[D-]HH:MM:SS、时间MMM DD HH:MM、内存14.8G)需在集群实际有作业时复核一次; lsf_queue_share只在启用 FAIRSHARE 的队列有数据;bacct依赖 mbatchd 事件日志,历史被清理后窗口内可能无数据(按 0 输出,不报错);- 采集脚本单次执行增加 5 条只读命令(
lsclusters/badmin/lsload/busers/bacct), 实测单次仍在秒级,interval_seconds未变(60s),对目标机压力可忽略。
G.7 2026-10-01:Lustre 大盘按 EDA 运营视角扩展(存储七视角)¶
背景:用户反馈 Lustre 大盘「过于单调」:P1 交付时只有 7 个面板 / 2 分组(采集状态、
MDT/OST 连接数、客户端容量、OST 操作速率),与同期已按 EDA 视角扩展的 Slurm(27 面板 / 7 分组,
见 G.5)和 LSF(30 面板 / 7 分组,见 G.6)明显不对等。根因不在面板数量,而在采集面:
旧脚本只调用 3 条命令(mdt.*.num_exports、obdfilter.*.num_exports/stats、lfs df -h),
既无元数据侧性能、无 grant/驱逐等写入压力面,也无磁盘层 IO 与客户端体验。
目标:不新增端口、不改抓取配置、不触碰 Lustre 服务与文件系统,把采集面扩到与 Slurm / LSF 对等的 EDA 七视角,并按同一七分组结构重排大盘。
数据源(均为只读查询命令,均实机验证;Lustre 2.17.0,文件系统 edafs)
| 命令 | 提供什么 | 新增指标(cluster 之外的标签) |
|---|---|---|
lctl get_param mdt.*.md_stats |
元数据操作次数与累计时延 | lustre_mdt_md_ops_total、lustre_mdt_md_time_us_total |
lctl get_param mdt.*.eviction_count |
客户端驱逐累计 | lustre_mdt_evictions |
lctl get_param osd-ldiskfs.*.{kbytes,files}{free,total} |
MDT 容量与 inode(注意不在 mdt.* 层) |
lustre_mdt_capacity_kbytes、lustre_mdt_inodes |
lctl get_param obdfilter.*.stats |
OST 操作次数与累计时延 | lustre_ost_op_time_us_total |
lctl get_param obdfilter.*.eviction_count |
客户端驱逐累计 | lustre_ost_evictions |
lctl get_param obdfilter.*.tot_{granted,dirty,pending} |
写入授权 / 脏页 / 受阻字节 | lustre_ost_grant_bytes |
lctl get_param obdfilter.*.{kbytes,files}{free,total} |
OST 容量与 inode(服务端自查,不依赖客户端) | lustre_ost_capacity_kbytes、lustre_ost_inodes |
lctl get_param osd-ldiskfs.*.brw_stats |
磁盘层 IO 分桶分布(大小 / 延迟 / 队列 / 块映射) | lustre_ost_disk_io_ios |
lfs df -h / lfs df -i |
客户端视角容量与 inode | lustre_fs_bytes(保留)、lustre_fs_inodes |
lctl get_param llite.*.stats |
客户端文件系统操作次数与延迟 | lustre_client_ops_total、lustre_client_op_time_us_total |
lctl get_param mdc.*.md_stats |
客户端元数据操作次数 | lustre_mdc_ops_total、lustre_mdc_op_time_us_total |
lctl get_param osc.*.{resend_count,cur_grant_bytes,cur_dirty_bytes} |
RPC 重传与客户端 grant 使用 | lustre_client_rpc_resend、lustre_client_grant_bytes |
lctl dl |
客户端设备状态(mgc/lov/lmv/mdc/osc) | lustre_client_device_up |
改了什么
| 文件 | 改动 |
|---|---|
artifacts/collectors/lustre_metrics.py |
由 4 个指标扩为 22 个;新增 10 个采集段;新增 _split_blocks(按 <path>.md_stats= 切块)、_scalars、_stats_rows、_emit_brw 等解析器;lustre_up 判定收紧为「识别出角色并取到该角色核心数据」。原有 4 个指标名与语义保持不变(含 lustre_ost_ops_samples 对全部 op 计数、不区单位的旧口径) |
manifests/10-env-lustre-dashboard.yaml |
由 7 面板 / 2 分组重做为 35 面板(含 7 分组行)/ 7 分组:服务与采集健康 / 容量与水位 / 元数据性能(MDS) / 数据 IO 性能(OSS) / 磁盘层 IO(OSD brw_stats) / 客户端体验 / 跨域联动 |
manifests/13-env-domain-alerts.yaml |
Lustre 告警由 2 条扩为 11 条,拆为两组:env-lustre-availability(采集失败 / 客户端设备未就绪 / 驱逐 / RPC 重传)与 env-lustre-capacity(OST / MDT / 客户端容量与 inode / grant 受阻 / 元数据与 OST 延迟)。FlexLM 组不变 |
配置、systemd 单元模板、抓取链路与采集频率(60s)均未改动。
分步操作(与实现体等价;实现体:modules/domain_collectors.py)
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
# 1) 门禁
.venv/bin/python main.py precheck
# 预期:BLOCK=0 WARN=9 INFO=149(与既有基线一致)
# 2) 预演(零改动)
.venv/bin/python main.py deploy --dry-run
# 预期:主机侧只列 .21/.22/.28(脚本内容不符),其余 5 个采集目标「已就绪,跳过」;
# 集群侧 1 处待下发(10 大盘 + 13 告警)
# 3) 真实变更
.venv/bin/python main.py deploy
# 4) 目标机核对:新指标是否落盘并经 9100 暴露
ssh root@192.168.31.21 'wc -c /var/lib/node_exporter/textfile/lustre.prom'
ssh root@192.168.31.22 'curl -s http://127.0.0.1:9100/metrics | grep -oE "^lustre_[a-z_]+" | sort -u | tr "\n" " "'
ssh root@192.168.31.28 'systemctl is-active env-lustre-metrics.timer'
# 5) 集群侧核对:Prometheus 收录 + 大盘对象 + 告警分组
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get --raw \
"/api/v1/namespaces/monitoring/services/kube-prometheus-stack-prometheus:9090/proxy/api/v1/label/__name__/values" \
| tr ',' '\n' | grep -c '"lustre_'
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get cm env-lustre-dashboard -o jsonpath='{.data.env-lustre-dashboard\.json}' | head -c 200
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get prometheusrule env-domain-alerts -o jsonpath='{range .spec.groups[*]}{.name}{" "}{end}'
坑与注意事项(实现体已处理,此处留痕)
- 服务端
brw_stats在osd-ldiskfs.*层:mdt.*.brw_stats与obdfilter.*.brw_stats在本环境实测均报No such file or directory,磁盘层 IO 分布只能从 osd 层取。 - MDS 没有容量参数:
mdt.*.kbytesfree等不存在(实测报错),MDT 容量与 inode 必须走osd-ldiskfs.*。这也是旧大盘只有客户端容量视图的原因。 stats行的单位有[usecs]与[reqs]两种:操作次数对所有 op 都计数(否则会改变lustre_ost_ops_samples的既有语义与旧图形的历史可比性),只有[usecs]的 op 才输出累计 时延;ioctl一行只有 samples 没有 min/max/sum,正则须容忍末四列缺失。- 客户端 osc/mdc 的参数名带后缀:形如
edafs-OST0000-osc-ffff98778250d800,需截断 为edafs-OST0000才能与服务端ost标签对齐(否则跨面板无法联看)。 brw_stats空载时只有表头无数据行:解析器按「表头 → 列名行 → 分桶数据行」状态机走, 无数据行即无输出,不产生垃圾序列(实测空载下.22未出现该指标)。lctl dl的状态列必须严格为UP才记 1,其余(DOWN/RECOVERING/NEW)记 0, 否则设备掉线会显示成正常。cluster标签由 ServiceMonitor relabel 按节点所属集群生成,会覆盖指标文件里的同名标签:.21/.22为lustre、.28为hpc。因此大盘把客户端与文件系统容量类面板的cluster过滤去掉(它们跨集群),只保留服务端面板的过滤;变量补allValue: ".*"。 —— 曾尝试让采集器统一输出cluster=lustre,实测无效(被 relabel 覆盖),已回退。
验证(实测结论)
| 检查 | 结果 |
|---|---|
| Prometheus 收录 | lustre_ 指标 4 → 22 个;lustre_up=1(3 实例:lustre-mgs-01 / lustre-oss-01 / hpc-submit) |
| 目标机产物 | .21 915 B(7 个指标族)/ .22 1315 B(8 个)/ .28 2429 B(9 个);三个 env-lustre-metrics.timer 均 active |
| 数值抽查 | lustre_mdt_exports=3、lustre_ost_capacity_kbytes{type="free"}=308693824、lustre_fs_inodes{target="edafs-MDT0000",type="total"}=8388608、lustre_client_device_up 5 个设备全为 1、lustre_ost_grant_bytes{type="granted"}=279104 —— 均与命令行实测一致 |
| 本地解析自测 | 用实机取样驱动 stub lctl/lfs 跑 collect(),26/26 断言通过(含命令全失败时 lustre_up=0 且零垃圾序列) |
| 集群侧大盘 | ConfigMap 更新为 35 面板 / 7 分组;Grafana sidecar 日志 Writing /tmp/dashboards/env-lustre-dashboard.json |
| 告警规则 | env-domain-alerts 含 env-lustre-availability(4) + env-lustre-capacity(7) + env-flexlm-alerts(4),无重名 |
| 幂等复跑 | 14 主机 + 8 采集目标 + 3 集群侧对象全部「状态一致,无需变更」,退出码 0 |
回滚:见第 7 章「备份位置与台账」,对应条目见 backup/README.md 的
2026-10-01 Lustre EDA 大盘节(工程内制品 backup/artifacts/collectors/lustre_metrics.py.bak.20261001-003617、
backup/manifests/10-env-lustre-dashboard.yaml.bak.20261001-003617、
backup/manifests/13-env-domain-alerts.yaml.bak.20261001-003617、
backup/modules/domain_collectors.py.bak.20261001-004226(本次尝试后回退,内容与改前一致)、
backup/utils/config_validate.py.bak.20261001-004226、
backup/config/env-monitor.yaml.bak.20261001-004226、
backup/docs/{SOP,RUNBOOK}.md.bak.20261001-003617、
backup/docs/HANDOFF.md.bak.20261001-*(根级 docs/HANDOFF.md 的备份,原位在此目录));
远端脚本备份由实现体自动登记(.21/.22/.28 的 env-lustre-metrics.bak.*)。
步骤:用备份覆盖上述工程内文件 → 重跑 deploy(告警为同一 PrometheusRule 内的分组,
随 env-domain-alerts 一并回退,无需单独 delete)。
风险与残留
- 磁盘层 IO 分布(
lustre_ost_disk_io_ios)未经实测验证:本环境空载,brw_stats无数据行, 该指标尚未在 Prometheus 出现。解析器用的是通用「段名 + 分桶标签原样透传」策略,且有 IO 时 才产出;集群有作业负载后需复核一次桶标签形式与数值。 - 作业级 IO(
*.job_stats)本期未采集:空队列下mdt.*.job_stats与obdfilter.*.job_stats均为空,输出格式无法实测验证。EDA 场景的「按作业/用户归属 IO」是重要视角,待有作业时补采。 - 延迟类告警阈值(20 ms)取自保守估计:实测元数据操作在几十至几百微秒、OST 操作在毫秒级,
但样本量小(无作业负载)。有真实负载后需按分位数重新校准,见
13-env-domain-alerts.yaml头部。 - 采集命令由 3 条增至 13 条(均为
lctl get_param/lctl dl/lfs df只读查询), 单次实测仍在秒级,interval_seconds未变(60s)。 cluster变量在两个值之间(lustre/hpc):大盘已按「服务端过滤、客户端不过滤」处理, 但若日后新增 Lustre 客户端挂载到其它集群的主机,需复核各面板的过滤口径是否仍然合适。
G.8 2026-10-01:FlexLM 大盘按 EDA 运营视角扩展(许可七视角)¶
背景:P1 交付的 FlexLM 大盘只有 6 个面板 / 2 分组(采集状态、服务器 / daemon UP、 许可占用率、各 Feature 明细、denied),是唯一未做 EDA 化改造的领域 —— 同期 Slurm 27 面板、 LSF 30 面板、Lustre 35 面板(见 G.5–G.7)。用户反馈「又拥挤又简单」,两者同源:
- 拥挤:
lmstat -a实测输出 5698 行 / 2843 个 counted feature(另有 Cadence 604 个 uncounted),旧盘的「各 Feature 许可明细」把上千行一次性渲染; - 简单:根因在采集面只解析了 lmstat 的三类字段(服务器 UP / daemon UP / feature 总数与在用), 用户行(谁在用、占了多久)、服务器角色与版本、许可到期日、许可文件与运行服务是否对应, 全部没有采集。
目标:不新增端口、不改抓取配置、不触碰许可服务与许可文件,把采集面扩到与 Slurm / LSF / Lustre 对等的 EDA 七视角,并按同一七分组结构重排大盘。
数据源(均为只读查询,2026-10-01 在 .29 实机验证;FlexNet v11.14.1 / v11.16.4)
| 来源 | 提供什么 | 新增指标(cluster="license" 之外) |
|---|---|---|
lmstat -a 服务器行 |
UP 状态、角色 (MASTER)、lmgrd 版本 |
flexlm_server_role{role}、flexlm_license_server_version{version} |
lmstat -a daemon 行 |
daemon 状态与版本 | flexlm_vendor_daemon_version{daemon,version} |
lmstat -a 用户行【未验证】 |
持有者、主机、checkout 时间 | flexlm_license_user_checkouts{user}、flexlm_license_oldest_checkout_seconds{feature}、flexlm_checkout_parse_failed |
lmstat -a Feature 段 |
有占用的 feature 数、空闲量、利用率、许可类型分布 | flexlm_license_features_used、flexlm_license_available_sum、flexlm_license_utilization、flexlm_license_type_features{license_type} |
许可文件 FEATURE/INCREMENT 行 |
到期日、permanent 占比 | flexlm_license_expiry_timestamp_seconds、flexlm_license_expiring_features{window}、flexlm_license_permanent_features、flexlm_license_feature_expiry_timestamp_seconds{feature} |
| 许可文件存在性 / 服务对应关系 | 文件与运行服务是否脱节 | flexlm_license_file_present、flexlm_license_file_served、flexlm_license_file_readable |
| lmgrd 日志可读性 | 区分「没有拒绝」与「统计不到」 | flexlm_license_denied_log_available |
| 采集自省 | 返回码、耗时、解析健康 | flexlm_lmstat_rc、flexlm_collect_success、flexlm_parse_ok、flexlm_collect_duration_seconds |
改了什么
| 文件 | 改动 |
|---|---|
artifacts/collectors/flexlm_metrics.py |
指标名 10 → 29 个(新增 19 个有样本指标,另有 3 个仅在出现 checkout 或临近到期 feature 时产出);新增 _parse_usage(含用户行状态机)、_license_file_stats、_parse_start;原有 10 个指标名与语义保持不变 |
manifests/12-env-flexlm-dashboard.yaml |
由 6 面板 / 2 分组重做为 30 面板(含 7 分组行)/ 7 分组:服务与采集健康 / 许可池容量与水位 / 占用与持有者 / 拒绝与争抢 / 到期与版本 / 许可结构 / 跨域联动 |
manifests/13-env-domain-alerts.yaml |
FlexLM 由 1 组 4 条扩为 2 组 11 条:env-flexlm-availability(7) + env-flexlm-capacity(4) |
config/env-monitor.yaml |
collectors.flexlm.extra_args 新增 --license-file(Synopsys / Cadence / Calibre 三个许可文件) |
端口、systemd 单元模板、抓取链路与采集频率(60s)均未改动;Lustre 告警与大盘未受影响。
分步操作(与实现体等价;实现体:modules/domain_collectors.py)
cd /home/rain/Documents/k8s_ins/env-monitor-deploy
# 1) 门禁
.venv/bin/python main.py precheck
# 预期:BLOCK=0 WARN=9 INFO=149(与既有基线一致)
# 2) 预演(零改动)
.venv/bin/python main.py deploy --dry-run
# 预期:主机侧只列 .29(采集脚本内容不符),其余 7 个采集目标「已就绪,跳过」;
# 集群侧「渲染清单与实机存在差异,待下发」(12 大盘 + 13 告警),Alertmanager 跳过
# 3) 真实变更
.venv/bin/python main.py deploy
# 4) 目标机核对:脚本下发、指标落盘、9100 暴露
ssh root@192.168.31.29 'systemctl show -p ExecMainStatus --value env-flexlm-metrics.service'
# 预期:0
ssh root@192.168.31.29 'grep -oE "^[a-z_]+" /var/lib/node_exporter/textfile/flexlm.prom | sort -u | wc -l'
# 预期:29(改造前为 10)
ssh root@192.168.31.29 'curl -s http://127.0.0.1:9100/metrics | grep -c "^flexlm_"'
# 预期:6352
# 5) 集群侧核对:Prometheus 收录 + 大盘对象 + 告警分组
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get cm env-flexlm-dashboard \
-o jsonpath='{.data.env-flexlm-dashboard\.json}' | python3 -c \
"import json,sys; d=json.load(sys.stdin); print(len(d['panels']))"
# 预期:37(30 面板 + 7 分组行)
kubectl --kubeconfig /home/rain/.kube/config -n monitoring get prometheusrule env-domain-alerts \
-o jsonpath='{range .spec.groups[*]}{.name}{" "}{end}'
# 预期:env-lustre-availability env-lustre-capacity env-flexlm-availability env-flexlm-capacity
# 6) 幂等复核
.venv/bin/python main.py deploy
# 预期:14 主机 + 8 采集目标 + 3 集群侧对象全部「状态一致,无需变更」,退出码 0
坑与注意事项(实现体已处理,此处留痕)
- Cadence 侧没有容量语义:其实测 604 个 feature 全部是
(Uncounted, node-locked), 只有存在性、没有 issued/in_use;容量类面板与告警对本厂商必然为空,这不是采集缺陷。 「许可类型结构」面板专门用来呈现这一点。 - 用户行必须含
, start <时间>才认作用户行:feature 段里还有"3D" v…, vendor: …与floating license一类说明行,宽松匹配会把floating license误判成「用户 floating、 主机 license」。解析器要求行内含, start,且匹配失败只计入flexlm_checkout_parse_failed,不伪造数据。 - 用户行格式本环境无法实测:两台服务器长时间空闲,
lmstat -a无任何 checkout, lmgrd 日志中OUT/IN/denied均为 0 条。解析器按 FlexNet 11.x 标准格式实现, 按 G.7 对job_stats的先例标注【未验证】,并配EnvFlexlmCheckoutParseFailed告警兜底。 - 许可文件有续行符:
INCREMENT行以\续行,关键字段(feature / 版本 / 到期 / 数量) 都在首行,解析只取首行即可,不必拼接续行。 - 序列体量是硬约束:2843 个 counted feature × (total + in_use) 已占 5686 条序列,
许可文件本身 620 KB。新增指标一律固定基数(合计仅 +49 行),
不得再对既有 per-feature 指标乘上用户 / 主机等新维度;feature 级明细在大盘侧用
topk(10, …)收敛,到期明细只输出 365 天内到期的前 50 个。 file_served=0不等于「配置脱节」——先看许可文件的类型:本环境/opt/FlexLM/Cadence/calibre.license.dat经 2026-10-01 复核,0 条SERVER行、0 条VENDOR行, 88 条FEATURE全部为HOSTID=ANY(Mentor Graphics,2036-09-26 到期),主机上也没有mgcld二进制 —— 它是不需要 license server 的 uncounted / node-locked 型许可文件, 由工具客户端直接读取,file_served=0属正常。对照cds.license.dat含SERVER 192.168.31.29 525400310e02 5280,才会由flexlm-cadence.service提供。 这类不一致不定为告警(属环境既有事实、非故障),只在大盘「许可文件明细」面板呈现。- denied 口径未改动:仍是 lmgrd 日志全文累计,日志被轮转时计数会回落。
面板改用
increase(...[15m])看增量以规避该问题;口径原样保留以免破坏历史可比性。
验证(实测结论)
| 检查 | 结果 |
|---|---|
| Prometheus 收录 | flexlm_ 序列 6352 条;新指标 flexlm_license_total_sum / flexlm_license_expiring_features / flexlm_license_file_served 均可查(job="external-node-exporters") |
| 目标机产物 | flexlm.prom 677 KB → 697 KB;9100 暴露 6352 条;单元退出码 0;单次采集实测 3.44 s(周期 60 s) |
| 数值抽查 | Synopsys total_sum=281359(2843 个 counted feature)、in_use_sum=0;Cadence 604 个 uncounted、total_sum=0;Calibre file_present=1 / file_served=0;最早到期 Synopsys 2030-12-30、Calibre 2036-09-26 |
| 本地解析自测 | 实机取样驱动 stub lmutil 跑 collect(),15/15 断言通过;另以 stdin 方式在 .29 上实跑(零落盘)得 6354 行,逐项核对取值 |
| 集群侧大盘 | ConfigMap 更新为 30 面板 / 7 分组(JSON 共 37 对象);Grafana sidecar 日志 Writing /tmp/dashboards/env-flexlm-dashboard.json → reload 200 OK |
| 告警规则 | env-domain-alerts 含 env-lustre-*(11) + env-flexlm-availability(7) + env-flexlm-capacity(4),共 22 条,无重名;原 env-flexlm-alerts 组名已取消 |
| 幂等复跑 | 14 主机 + 8 采集目标 + 3 集群侧对象全部「状态一致,无需变更」,退出码 0 |
回滚:见第 7 章「备份位置与台账」,对应条目见 backup/README.md 的
2026-10-01 FlexLM EDA 大盘节(backup/artifacts/collectors/flexlm_metrics.py.bak.20261001-011858、
backup/manifests/12-env-flexlm-dashboard.yaml.bak.20261001-011858、
backup/manifests/13-env-domain-alerts.yaml.bak.20261001-011858、
backup/config/env-monitor.yaml.bak.20261001-011858、
backup/docs/{SOP,RUNBOOK}.md.bak.20261001-011858)。
步骤:用备份覆盖上述工程内文件 → 重跑 deploy(告警与 Lustre 同处 env-domain-alerts
这一个 PrometheusRule,随它一并回退,无需单独 delete);远端脚本备份由实现体自动登记
(.29 的 env-flexlm-metrics.bak.*)。
风险与残留
- 「占用与持有者」组的数据未经验证:本环境无 checkout,
flexlm_license_user_checkouts/flexlm_license_oldest_checkout_seconds在 Prometheus 中不存在,面板为空属正常。 有真实作业后必须复核一次用户行格式;若flexlm_checkout_parse_failed > 0会触发告警, 届时按实机样本修正解析器(改动只涉及_RE_USER与_START_FORMATS)。 - denied 的按 feature / 按用户拆分未做:日志当前无 denied 记录,格式无从验证, 因此只保留原有的厂商级累计计数,不硬编码不可验证的日志格式。
- 到期告警阈值(30 天)为经验值:首批数据为 2030 / 2036 年到期,暂无临近样本, 待有真实临期再校准窗口。
- 大盘对 2843 个 feature 的渲染已收敛(
topk/ 窗口过滤),但 per-feature 序列 仍占用较多 TSDB 空间;若日后继续扩展,需先复核 Prometheus 容量预算 (docs/MONITORING-ROADMAP.md的 G2/G8 缺口)。
G.9 2026-10-01:大盘改为「精简源 + 生成器」产出(消除手写 JSON 样板)¶
背景:5 个环境大盘(总览 / Slurm / Lustre / LSF / FlexLM)此前是手写 YAML ——
ConfigMap 内嵌整段 Grafana JSON,合计 143 KB,约九成是重复样板(datasource 常量、
overrides: []、refId、gridPos、各面板类型的 options/fieldConfig 骨架)。
扩展一个面板要重新输出上万 token 的 JSON,手写 gridPos / id 也容易出错。
改动(大盘自此成为生成物,唯一真源是精简源)
| 文件 | 改动 |
|---|---|
utils/dashboard_builder.py |
新增生成器:extract(现网反推精简源)/ render(精简源 → manifests)/ verify(往返等价)/ check(一致性) |
dashboards/{overview,slurm,lustre,lsf,flexlm}.yaml |
新增精简源(由 extract 从现网自动产出,非手写) |
manifests/{03,08,10,11,12}-*.yaml |
由生成器重新产出覆盖(语义等价、字节不同:JSON 缩进展开 + 键序规范化) |
main.py |
新增 build-dashboards 子命令(--check 只校验,不写文件) |
deployer.py |
预检链 _collect_precheck() 增加「精简源 ↔ manifests 一致」校验,手改 manifests 即 BLOCK |
设计要点
- 面板
id/gridPos由生成器计算:id 按出现顺序(row 独立从row_id_base递增), gridPos 按「面板顺序 + 宽度」自动布局 —— 已实测可 100% 复现现网 5 盘的坐标(0 不匹配); datasource常量、targetrefId由生成器补齐;- 各面板类型的样板收进
PANEL_SKELETON,只收录「该类型全部面板都有、且值相同」的键 —— 这是往返无损的前提,verify是这条约束的验收方(改骨架后必跑)。
改大盘的正确姿势
# 1) 改精简源(只写差异行,不写 id / gridPos / datasource / refId)
# 面板类型骨架见 utils/dashboard_builder.py 的 PANEL_SKELETON
$EDITOR dashboards/slurm.yaml
# 2) 生成 manifests(幂等:内容未变则报「未变」,不写文件)
.venv/bin/python main.py build-dashboards
# 3) 下发
.venv/bin/python main.py deploy
验证(实测结论)
| 检查 | 结果 |
|---|---|
| 往返等价 | 5 盘 extract → render 与原 JSON 逐键比对 0 差异(verify 全 OK) |
| 迁移等价 | 迁移后新旧 manifests 的 JSON 逐键等价(5/5) |
| 精简率 | 5 盘合计 143 KB → 80 KB(55%);扩展单面板只需在精简源写约 5~8 行 |
| 一致性门禁 | 正向:一致时 exit 0;反向:模拟手改 title 后被拦截(exit 1) |
| 下发 | deploy 后 14/14 抓取目标 up、5 组告警规则加载、幂等复跑全跳过、drift exit 0 |
回滚:用备份覆盖 5 个 manifests 与 main.py / modules/k8s_ingest.py / docs/SOP.md,
删除 dashboards/ 与 utils/dashboard_builder.py → 重跑 deploy。
备份清单见 backup/README.md 的 2026-10-01 大盘模板化节。
风险:生成器与手写 YAML 双轨漂移 —— 已由「pre_check 一致性门禁 + 本节约定」双重约束:
大盘一律由生成器产出,不再手改 manifests。
来源:
/home/rain/Documents/k8s_ins/env-monitor-deploy/docs/SOP.md| 最后更新:2026-10-01 01:58(由build/sync.py同步,请勿在站点侧修改)