跳转至

告警桥接器 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。