告警桥接器 Alertmanager → nfarmsd(alert-adapter.py)¶
仓库路径:
deploy/nfos/monitoring/alert-adapter.py| 线上路径:/opt/Cy/services/system.d/nfos/monitoring/alert-adapter.py语言:python3 | 定时器:无(常驻 HTTP 服务,nfos-alert-adapter.service;该 unit 文件不在本仓库)
用途¶
把 Alertmanager 的 v4 webhook 载荷映射为 nfarmsd 统一告警接口。它是一个常驻 HTTP 服务(不是一次性任务脚本),也是 tools 站点与 Prometheus 侧「告警链路」的中间一环。
映射规则(源码 docstring):
firing → POST http://127.0.0.1:9990/alerts/report
{rule: alertname, level: severity, message: summary + description,
source: "prometheus", dedup_key: fingerprint}
resolved → POST http://127.0.0.1:9990/alerts/recover
{dedup_key: fingerprint, message: "prometheus 已恢复"}
身份:采用零信任身份链模式——桥接器作为受信组件注入 X-User-ID / X-User-Roles 头(等价于网关注入),无需长期令牌;nfarmsd 侧对该身份执行命令级/接口级权限校验。
零依赖:仅用标准库 http.server + urllib,便于审计与长期维护。
对外端点¶
| 方法 | 路径 | 行为 |
|---|---|---|
GET |
/metrics |
输出 Prometheus 文本(text/plain; version=0.0.4),含 nfos_alert_adapter_up、…_webhooks_total、…_reports_total、…_resolved_total、…_resolved_noop_total、…_retries_total、…_notified_total |
GET |
/healthz、/health |
{"status":"ok","target":…,"identity":"…","notify_available":bool};通知可用时附 notify_config(不含口令)、notify_stats、notify_last_error;不可用时附 notify_import_error |
POST |
/webhook |
Alertmanager 回调入口;载荷非法 → 400,处理异常 → 500,成功 → 200 {"code":0,"message":"forwarded","data":…} |
POST |
/selftest |
触发一次通知渠道自检(飞书 + 邮件);通知模块不可用 → 503 |
| 其它路径 | — | 404 {"code":404,"message":"not found"} |
何时用¶
- 常驻运行,不需要手工调用:由
nfos-alert-adapter.service拉起(nfos-alert-selftest.service的After=/Wants=引用了该单元名)。 - 告警「没进平台」时:先
curl它的/healthz与/metrics,判断是「桥接器没收到」还是「收到但上报失败」。 - 改通道之后:
curl -X POST http://127.0.0.1:9095/selftest做一次通道自检,不用真造一条告警。 - Prometheus 抓取配置:抓
/metrics。
命令与参数¶
本脚本没有 argparse,也不读任何位置参数。 main() 直接构造 ThreadingHTTPServer((LISTEN_HOST, LISTEN_PORT), Handler) 并 serve_forever();Ctrl-C(KeyboardInterrupt)时打印「收到中断,退出」并关闭 server。
因此「命令与参数」即环境变量(全部逐字取自源码常量):
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ADAPTER_LISTEN_HOST |
字符串 | 127.0.0.1 |
监听地址(默认只绑回环) |
ADAPTER_LISTEN_PORT |
int | 9095 |
监听端口 |
NFARMSD_API |
字符串 | http://127.0.0.1:9990 |
平台 API 目标 |
ADAPTER_IDENTITY_ID |
字符串 | alertmanager |
注入的 X-User-ID |
ADAPTER_IDENTITY_ROLES |
字符串 | service |
注入的 X-User-Roles |
ADAPTER_TIMEOUT_SECS |
float | 10 |
每次上报的超时 |
ADAPTER_ACCESS_LOG |
字符串 | 未设置 | 值为 1 时才输出 HTTP 访问日志(默认收敛,避免噪音) |
共 7 个配置项(0 个命令行参数)。
不带参数时的默认行为¶
启动即监听。 进程输出一行 INFO 日志:告警桥接器启动 http://<host>:<port>/webhook → <NFARMSD>(附 identity=<ID>/<ROLES>),随后阻塞在 serve_forever()。不写状态文件、不产生一次性产物。
示例¶
# 1) 启动(手工前台,排障用;线上由 unit 常驻)
sudo python3 /opt/Cy/services/system.d/nfos/monitoring/alert-adapter.py
# 2) 存活与身份自检(不产生副作用)
curl -s http://127.0.0.1:9095/healthz | python3 -m json.tool
# 3) 通道自检(会真发一条"通知渠道自检",无需处理)
curl -s -X POST http://127.0.0.1:9095/selftest | python3 -m json.tool
# 4) 指标(Prometheus 抓取的内容)
curl -s http://127.0.0.1:9095/metrics
# 5) 只看「adapter 有没有收到回调」这一条计数
curl -s http://127.0.0.1:9095/metrics | grep nfos_alert_adapter_webhooks_total
# 6) 造一条最小 webhook 载荷做端到端验证(firing)
curl -s -X POST http://127.0.0.1:9095/webhook -H 'Content-Type: application/json' \
-d '{"status":"firing","alerts":[{"status":"firing","fingerprint":"selftest-1",
"labels":{"alertname":"NfosAdapterSelftest","severity":"warning"},
"annotations":{"summary":"桥接器自检"}}]}'
# 7) 对应的 resolved(走 /alerts/recover;平台侧已无活动告警时为幂等 no-op)
curl -s -X POST http://127.0.0.1:9095/webhook -H 'Content-Type: application/json' \
-d '{"status":"resolved","alerts":[{"status":"resolved","fingerprint":"selftest-1",
"labels":{"alertname":"NfosAdapterSelftest","severity":"warning"},
"annotations":{"summary":"桥接器自检"}}]}'
# 8) 排障:临时打开访问日志(需要带上启动)
sudo env ADAPTER_ACCESS_LOG=1 python3 /opt/Cy/services/system.d/nfos/monitoring/alert-adapter.py
与
digest.py正文末尾那条自证链路命令配套使用:sudo bash /opt/Cy/services/system.d/nfos/check-pipeline.sh(见 check-pipeline.md)。
依赖与权限¶
| 项 | 内容 |
|---|---|
| 解释器 | python3(#!/usr/bin/env python3) |
| 同目录模块 | notify.py(可选:from notify import Notifier 失败时 NOTIFY_AVAILABLE=False,记 _NOTIFY_IMPORT_ERROR,告警主链路不受影响) |
| 第三方模块 | 无(标准库 json / os / sys / threading / time / urllib / http.server) |
| 外部命令 | 无 |
| root | 取决于部署形态:只绑回环 + 调本机 nfarmsd 时不必须 root;但读 secrets/notification.env(600)以启用通知必须要(unit 里通常 User=root) |
sudo -n |
不需要 |
| 读取 | 无文件读取(除 notify.py 间接读 secrets/notification.env) |
| 写入 | 不写文件。全部状态是进程内的 _STATS(threading.Lock 保护),只经 /metrics 暴露 |
| 网络 | 入站:HTTP 监听 ADAPTER_LISTEN_HOST:ADAPTER_LISTEN_PORT(默认回环)。出站:POST $NFARMSD_API/alerts/report、POST $NFARMSD_API/alerts/recover;通知启用时还有飞书 webhook + SMTP |
| 并发 | ThreadingHTTPServer(多线程),_STATS 有锁 |
| 关联 unit | nfos-alert-adapter.service 被 nfos-alert-selftest.service 的 After= / Wants= 引用。该 unit 文件不在本仓库(源码未给出) |
| 上报目标备注 | call_nfarmsd() 的 docstring 记录:告警上报走 10.89.0.1:9990,即 nfos-monitor-tunnel 的本地监听口 |
幂等与失败语义¶
- 上报重试:
call_nfarmsd(path, payload, retries=3)—— - 对连接级失败(HTTP 码 0,如隧道重启导致的
TimeoutError)与 5xx 做最多retries次重试,退避1s / 2s; - 4xx 属业务语义(如 404 无匹配活动告警),重试无意义,直接返回。
- 恢复上报的幂等 no-op:nfarmsd 侧已无活动告警时,恢复上报会被记为
resolved_noop(nfos_alert_adapter_resolved_noop_total),源码注释明确「幂等,非故障」。 resolved的条件:只有载荷里status为恢复态时走/alerts/recover(源码注释与 docstring 一致)。- 状态文件:没有。重启即清零所有计数器(
/metrics的 counter 会重置)。 - 一半状态的风险:有——「平台已收到、桥接器计数未更新」这类窗口极小;更现实的是隧道抖动期间在途连接超时,这正是重试机制存在的原因。重试仍失败则计
reports_failed/resolved_failed并写ERROR日志(含alertname/http/body前 200 字符)。 - 通知是 best-effort:通知分发异常只计
notify_failed并写WARN,不影响告警上报结果。 - 退出码:
| 退出码 | 含义 |
|---|---|
0 |
收到 Ctrl-C / KeyboardInterrupt 后正常关闭(srv.server_close()) |
| 非 0 | 端口被占用 / 绑定失败时 ThreadingHTTPServer(...) 直接抛异常,进程退出(源码没有捕获这个异常,属「启动失败」) |
常见错误与排查¶
| 现象 | 代码里的对应分支 | 排查步骤 |
|---|---|---|
进程起不来 / Address already in use |
main() 里 ThreadingHTTPServer(...) 未捕获异常 |
ss -lntp \| grep 9095、sudo systemctl status nfos-alert-adapter.service --no-pager、journalctl -u nfos-alert-adapter.service -n 50 |
curl /healthz 的 notify_available: false 且有 notify_import_error |
from notify import Notifier 的 except |
notify.py 不在同目录或不可读;告警上报仍能工作,只是不再发通知。ls -l /opt/Cy/services/system.d/nfos/monitoring/notify.py |
notify_config 里 feishu_channels 为空 |
NOTIFIER.cfg.describe() |
环境文件未读到或没配通道。取值见 secrets/notification.env(不得转录) |
| 告警「进不了平台」 | call_nfarmsd() 的重试与失败分支 |
① curl /metrics 看 nfos_alert_adapter_webhooks_total 是否增长(判断「收没收到」);② 看 reports_total{result="failed"} 与 retries_total;③ 日志里 告警上报失败 的 http / body 是最直接的线索 |
| 恢复上报返回 404 但看起来「没坏」 | call_nfarmsd() 的 if code < 500: return code, body |
4xx 不重试是刻意的;平台侧已无活动告警时读 resolved_noop 计数,源码注释写明这是幂等、非故障 |
| 隧道抖动期间丢恢复上报 | call_nfarmsd() docstring 记录的 http=0 TimeoutError |
已有 1s/2s 退避重试;若仍频繁出现,查 nfos-monitor-tunnel.service(注意:该单元在 docs/CHANGELOG 与 timers.md 第 8.2 节被记录为线上未安装) |
/webhook 返回 400 |
json.loads(raw.decode() or "{}") 的 except → 无效载荷: … |
检查载荷是不是合法 JSON、Content-Length 是否正确 |
/webhook 返回 500 |
handle_payload(payload) 的 except → 内部错误 |
日志有 处理异常: <类型>: <消息>;多为载荷字段缺失(例如 labels.alertname) |
| 通知发了但平台没有对应告警 | 通知与上报是两条独立路径 | handle_payload 先上报再通知;通知成功不等于上报成功。用 /metrics 的两个计数器分别判定 |
| 计数器突然从大变小 | 进程被重启(_STATS 是内存态) |
对比 nfos_alert_adapter_up 与进程启动时间;降级不等于故障,但会让「活动告警数」类元告警的判据短暂失真 |
回滚方式¶
本脚本不写文件、不改系统状态,唯一状态是进程内存里的计数。回滚路径:
D=/opt/Cy/services/system.d/nfos
BK=$D/state/unit-backups
# A) 停掉桥接器(告警会改为"不进平台",但这不影响 Prometheus/Alertmanager 自身告警)
sudo systemctl stop nfos-alert-adapter.service
sudo systemctl disable nfos-alert-adapter.service
# B) 换回脚本本体
TS=$(date +%Y%m%d-%H%M%S)
sudo install -d -m 755 "$BK"
sudo cp -a "$D/monitoring/alert-adapter.py" "$BK/alert-adapter.py.bak-$TS"
sudo cp -a "$BK/alert-adapter.py.bak-<时间戳>" "$D/monitoring/alert-adapter.py"
sudo chown root:root "$D/monitoring/alert-adapter.py"
sudo chmod 644 "$D/monitoring/alert-adapter.py"
sudo systemctl restart nfos-alert-adapter.service
# C) 验收(零副作用)
curl -s http://127.0.0.1:9095/healthz | python3 -m json.tool
curl -s http://127.0.0.1:9095/metrics | grep nfos_alert_adapter_up
# D) 回调端点是否复位(Alertmanager 侧不需要改动即会继续投递)
curl -s -X POST http://127.0.0.1:9095/selftest | python3 -m json.tool
备份落点铁律:备份放
/opt/Cy/services/system.d/nfos/state/unit-backups/(命名<文件名>.bak-YYYYMMDD-HHMMSS)或仓库侧/opt/Cy/nfarms/.backup/。 绝不能放在/etc/systemd/system/—— 会触发 PS-002 完整性告警。见 timers.md 第 11 节。⚠️
nfos-alert-adapter.service的 unit 文件不在本仓库。要回滚 unit 之前, 先把它从/etc/systemd/system/备份到state/unit-backups/(不要在原地留.bak), 否则会立刻触发 PS-002。
相关 CHANGELOG¶
| CHANGELOG | 章节标题 |
|---|---|
docs/CHANGELOG-2026-09-26.md |
§1.7 脱敏:出口统一收口在 notify.py 的 render()(alert-adapter.py 作为调用方不需要各自实现脱敏) |
docs/CHANGELOG-2026-09-26.md |
§0.2 行号校准记录(2026-09-26)(含 alert-adapter.py 的关键行号校准) |
docs/CHANGELOG-2026-09-26.md |
§9.7 真实链路联合验收(主节点,合成数据已清理) |
docs/CHANGELOG-2026-09-26.md |
§1.5 平台四档级别:high 已可落库(曾是治理层独有的级别)(severity → level 映射口径) |
定时器与单元全景见 timers.md;链路体检用 check-pipeline.md;通道与限速见 notify.md。