告警/MQ 链路体检(check-pipeline.sh)¶
仓库路径:
deploy/nfos/check-pipeline.sh| 线上路径:/opt/Cy/services/system.d/nfos/check-pipeline.sh语言:bash(调curl+python3解析)| 定时器:无(手动体检脚本)
用途¶
一条命令判断「告警/MQ 链路现在能不能信任」。
源码头部记下的动机(2026-09-24 实战暴露的问题不是「没有监控」,而是没人能一眼判断链路是否可用):
- NATS 服务器活着(
NatsDown=inactive)≠ 有人在消费(实测in_msgs=35862/out_msgs=38); - 平台
messages表pending=0≠ 端到端送达(可能是「没人订阅、消息被丢弃」); - 告警自检
success=1≠ 刚刚送达到人(要看last_run新鲜度与 adapter 投递计数是否在增长)。
本脚本按「六道关」逐项给 PASS / FAIL / WARN,最后给一句结论:
| 关 | 检查内容 | 判据要点 |
|---|---|---|
| 1 | NATS 服务器是否可用 | GET $NATS/varz 里有没有 "server_id" |
| 2 | 投递率:有没有人在消费(最关键) | ① 订阅级 msgs 是否随时间增长(/subsz?subs=1,前后各取一次,sleep 6);② JetStream 是否积压(/jsz?streams=1,messages>0 但 consumer_count=0 → !! 有消息但无消费者(积压风险))。明确警告不要用 /varz 的 out_msgs 当投递计数(启用 JetStream 时会得出「投递率 0.1%、没人消费」的错误 FAIL) |
| 3 | 平台侧消息队列积压(messages 表) |
sudo -n test -r "$DB" 可读时按 status 分组计数,最老未投递时间;VERDICT=BACKLOG(pending+queued+failed > 100)或 OK。不可读时 warn "本机没有 nfarmsd 库(备节点为常态)" |
| 4 | 告警链路自检的新鲜度与结果 | Prometheus 查 nfos_alert_selftest_success 与 nfos_alert_selftest_last_run_timestamp_seconds;success=1 则 ok,否则 bad;age > 129600(36h)→ warn "自检超过 36h 未跑" |
| 5 | 投递计数是否在增长(adapter 有没有真的推出去) | 前后各查一次 nfos_alert_adapter_webhooks_total(sleep 3);并给出「要主动测用 sec-notify.py --selftest」的提示 |
| 6 | Alertmanager 活动告警的「可判读性」 | GET $AM/api/v2/alerts?active=true;统计缺 alertname 的条数,VERDICT=UNREADABLE 或 OK。在主节点按设计 SKIP(见下) |
输出结论(三选一):
第 6 关的重要前提(源码注释)¶
Alertmanager 只在备节点 Cy.TencentCOS-SG 上,而且绑的是 127.0.0.1:9093(podman 端口映射 127.0.0.1:9093->9093)。所以在主节点执行本脚本时,第 6 关按设计就取不到——这不是链路故障。脚本据此直接打印 SKIP。
何时用¶
- 察觉到「告警没来」时:这是第一现场工具,比逐个
systemctl status快。 - 汇总通报正文末尾:
digest.py会把sudo bash /opt/Cy/services/system.d/nfos/check-pipeline.sh作为自证链路命令附在消息末尾(整条命令豁免路径脱敏,见sanitize.PRESERVE_PATHS),值班照着敲即可。 - 改过监控/告警链路之后:作为回归手段。
- 不要当监控用:它是手动体检,没有 timer。
命令与参数¶
用法(脚本头部逐字):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--json |
开关(仅出现在用法注释里) | — | 源码里没有实现:脚本体中不存在任何 argv 解析(无 case "$1"、无 getopts),$1 只作为 ok() / bad() / warn() 函数的第一个入参使用。(源码未给出 --json 的实现;传它不会报错,但也不会改变任何输出) |
已验证的 argv 参数:0 个。
不带参数时的默认行为¶
完整跑完六道关,输出彩色 PASS / FAIL / WARN 行与结论。 全程只读(curl 查询 + sudo -n 只读打开 SQLite),不修改任何系统状态。
可用的环境变量(脚本头部三个变量,形式为 VAR=${VAR:-默认值})¶
| 变量 | 默认值 | 说明 |
|---|---|---|
PROM |
http://127.0.0.1:9090 |
Prometheus 基址(第 4/5 关) |
NATS |
http://127.0.0.1:8222 |
NATS 监控端口基址(第 1/2 关) |
AM |
http://127.0.0.1:9093 |
Alertmanager 基址(第 6 关) |
DB |
/opt/Cy/services/system.d/nfarmsd/data/nfarmsd.db |
硬编码,不可用环境变量覆盖(第 3 关) |
示例¶
C=/opt/Cy/services/system.d/nfos/check-pipeline.sh
# 1) 标准用法(脚本头部给出的形式)
sudo bash "$C"
# 2) 从汇总通报正文里那条自证链路命令(digest.py 附在消息末尾)
sudo bash /opt/Cy/services/system.d/nfos/check-pipeline.sh
# 3) 只想看结论(过滤掉六道关的明细)
sudo bash "$C" | tail -5
# 4) 只在主节点看:第 6 关会 SKIP(AM 只在备节点且绑回环)
sudo bash "$C" | grep -A2 '第 6 关'
# 5) 在备节点 / 指定 AM 地址上真跑第 6 关
AM=http://<备节点内网IP>:9093 sudo -E bash "$C"
# 6) 指向非默认的 Prometheus / NATS(例如本机端口被改过)
PROM=http://127.0.0.1:9090 NATS=http://127.0.0.1:8222 sudo -E bash "$C"
# 7) 只看第 5 关的提示(要主动测链路时照它做)
sudo bash "$C" | grep -A2 '第 5 关'
sudo python3 /opt/Cy/services/system.d/nfos/monitoring/sec-notify.py --selftest # 应 ok:true, feishu:true
依赖与权限¶
| 项 | 内容 |
|---|---|
| 解释器 | bash(set -uo pipefail,注意没有 -e) |
| 外部命令 | curl(带 -m 超时:varz 6s、其它 8s)、python3(内联脚本解析 JSON)、date、grep、hostname、sleep、sudo(sudo -n 只读打开库)、awk 未被使用 |
| 第三方模块 | 无(python3 只用标准库 sys / json / sqlite3) |
| root | 建议用 root(脚本头部的用法就是 sudo bash …);第 3 关通过 sudo -n test -r "$DB" 与 sudo -n python3 - 读 nfarmsd 库。非 root 且 sudo -n 不可用时该关降级为 warn "本机没有 nfarmsd 库(备节点为常态)" |
sudo -n |
需要(第 3 关)。必须是免密——-n 在需要口令时会直接失败,脚本就退化到 warn 分支 |
| 读取 | $PROM/api/v1/query、$NATS/varz、$NATS/subsz?subs=1、$NATS/jsz?streams=1、$AM/api/v2/alerts?active=true、$DB(SQLite messages 表) |
| 写入 | 不写任何文件(输出全在 stdout,带 ANSI 颜色) |
| 网络 | 仅本机回环 HTTP(Prometheus 9090 / NATS 8222 / Alertmanager 9093) |
幂等与失败语义¶
- 只读、幂等:无状态文件、无写入、重复执行结果一致(第 2/5 关有
sleep,会让脚本跑 9 秒以上)。 - 每关独立降级:某一关取不到数据只会
warn(例如warn "NATS varz 不可达"之外还有warn "取不到自检指标"、warn "取不到 adapter 计数"),不会中断后续关卡。 - 一半状态的风险:没有(不写任何东西)。
- 退出码:恒为
0。脚本没有exit语句,也没有set -e;最后一条执行的是printf ' PASS=%d FAIL=%d WARN=%d\n' …(成功)。因此不能用退出码判断链路是否可信任—— 必须解析输出里的结论行与PASS=/FAIL=/WARN=计数。
常见错误与排查¶
| 现象 | 代码里的对应分支 | 排查步骤 |
|---|---|---|
FAIL NATS varz 不可达 <NATS> —— 总线整体不可用,所有依赖它的功能都在降级 |
第 1 关 bad 分支 |
curl -s -m 6 http://127.0.0.1:8222/varz;sudo podman ps \| grep nats;sudo systemctl status nfos-nats.service --no-pager |
FAIL 取不到订阅级投递计数 subsz?subs=1 不可读,无法判断有没有消费者 |
第 2 关的 bad 分支 |
直接 curl -s "http://127.0.0.1:8222/subsz?subs=1" \| head -c 400;被防火墙/绑定地址限制时改 NATS= |
WARN 6 秒内投递计数未增长 累计投递 N(说明这段时间没有新消息,不等于链路坏;可发一条探针再测) |
第 2 关的 warn 分支 |
这不是坏。按提示主动造一条:sudo python3 …/monitoring/sec-notify.py --selftest,再跑一次 check-pipeline.sh |
!! 有消息但无消费者(积压风险) |
第 2 关 JetStream 段 | 说明有流但 consumer_count=0;检查消费方进程(nfarmsd worker 等)是否在跑 |
WARN 本机没有 nfarmsd 库(备节点为常态) |
第 3 关 sudo -n test -r "$DB" 为假 |
备节点上这是预期;在主节点上出现则说明没有 sudo -n 免密或库路径不对。手工验证:sudo -n test -r /opt/Cy/services/system.d/nfarmsd/data/nfarmsd.db && echo ok |
VERDICT=BACKLOG |
第 3 关判据 pending+queued+failed > 100 |
看它打印的「状态分布」与「最老未投递」时间;结合第 2 关判断是「没人消费」还是「消费慢」 |
FAIL 自检失败 success=<值> —— 链路有问题,须立刻查 nfos-alert-selftest 日志 |
第 4 关 bad 分支 |
sudo systemctl status nfos-alert-selftest.service --no-pager、journalctl -u nfos-alert-selftest.service -n 50。注意:timers.md 第 8.1 节记录该单元在线上未安装——此时本关会落到「取不到自检指标」的 warn |
WARN 自检超过 36h 未跑 <age>s 前(timer 是否停摆?) |
第 4 关的 case "$age" 分支 |
systemctl list-timers 'nfos-alert-selftest*' --all |
WARN 取不到自检指标 Prometheus 未采集 nfos_alert_selftest_*(自检可能从未运行) |
第 4 关的 else warn 分支 |
curl -s 'http://127.0.0.1:9090/api/v1/query?query=nfos_alert_selftest_success';对照 timers.md 第 8.1 节 |
WARN 取不到 adapter 计数 nfos_alert_adapter_webhooks_total 无数据 |
第 5 关的 else warn 分支 |
adapter 可能没在跑(curl -s http://127.0.0.1:9095/metrics),见 alert-adapter.md |
| 第 5 关「adapter 计数不变」被当成故障 | 脚本自带提示:adapter 计数不变只说明"这段时间没有新告警",不代表链路坏;要主动测用… |
按提示跑 sec-notify.py --selftest |
第 6 关打印 SKIP |
if [ "$AM" = "http://127.0.0.1:9093" ] && ! hostname \| grep -q "Tencent" |
在主节点这是设计行为;要真跑:登备节点执行,或 AM=http://<备节点内网IP>:9093 sudo -E bash check-pipeline.sh(若备节点也只绑回环,就只能登备节点本机跑) |
第 6 关 VERDICT=UNREADABLE |
活动告警里有条目缺 labels.alertname |
输出会列出前 3 条的 since= 与 labels=;说明有告警规则没写 alertname,需要修规则 |
--json 传了但输出还是彩色文本 |
源码未实现(见「命令与参数」) | 需要机器可读结果时自行解析 PASS=/FAIL=/WARN= 行与结论行 |
回滚方式¶
本脚本完全只读——不发告警、不写文件、不改库、不动 unit。因此:
| 项 | 结论 |
|---|---|
| 系统状态 | 无需回滚(脚本无副作用) |
| 脚本本体 | 若改坏了,用版本化备份覆盖回去即可 |
| 唯一的「间接副作用」 | 第 5 关的提示会让值班去跑 sec-notify.py --selftest,那个会真发一条通知(属 sec-notify.md 的范畴,不是本脚本的副作用) |
D=/opt/Cy/services/system.d/nfos
BK=$D/state/unit-backups
# 1) 改之前先备份
TS=$(date +%Y%m%d-%H%M%S)
sudo install -d -m 755 "$BK"
sudo cp -a "$D/check-pipeline.sh" "$BK/check-pipeline.sh.bak-$TS"
# 2) 回滚
sudo cp -a "$BK/check-pipeline.sh.bak-<时间戳>" "$D/check-pipeline.sh"
sudo chown root:root "$D/check-pipeline.sh"
sudo chmod 755 "$D/check-pipeline.sh"
# 3) 验收(只读,跑一遍即可)
sudo bash "$D/check-pipeline.sh" | tail -5
ls -l /etc/systemd/system/ | grep '\.bak' # 必须为空(否则触发 PS-002)
备份落点铁律:脚本/单元的任何备份放
/opt/Cy/services/system.d/nfos/state/unit-backups/(命名<文件名>.bak-YYYYMMDD-HHMMSS)或仓库侧/opt/Cy/nfarms/.backup/。 绝不能放在/etc/systemd/system/—— 会触发 PS-002 完整性告警。见 timers.md 第 11 节。⚠️ 诚实标注:脚本头部的用法行写了
[--json],但代码里没有实现任何参数解析。 若你依赖--json做自动化,请先按上面的「常见错误与排查」最后一行自行解析,或补实现 (补实现属于改脚本,需走 lib-ops.md 之外的常规改名/备份流程)。
相关 CHANGELOG¶
| CHANGELOG | 章节标题 |
|---|---|
docs/CHANGELOG-2026-09-26.md |
§14.2 补部署 check-pipeline.sh(页面引用了它,但它当时不在节点上) |
docs/CHANGELOG-2026-09-26.md |
§13.6 最后一公里:日志审计的告警改「近期口径」(SL-004 不再复报)(同期的链路可判读性治理) |
docs/CHANGELOG-2026-09-26.md |
§6.1 真实链路实测(生产主节点) |
docs/CHANGELOG-2026-09-26.md |
§9.7 真实链路联合验收(主节点,合成数据已清理) |
本脚本被 digest.md 作为汇总通报正文末尾的自证链路命令引用; 定时器与单元全景见 timers.md;adapter 计数见 alert-adapter.md。