Alertmanager 根据告警标签遍历路由树,选择通知接收器。路由树表达责任归属与升级策略,接收器则定义实际通知集成。配置能够解析,并不代表每一类告警都被分配给正确目的地。因此,不能把重载成功当作路由有效的完成证据;应该先核对进入系统的标签契约,再验证整棵树针对典型告警的选择结果。

既要看匹配,也要看何时停止

每条告警都会进入必须匹配所有告警的根路由。子路由继承没有主动覆盖的配置;没有子节点匹配时,由当前节点处理。否则,遍历通常在第一个匹配的子节点后停止。匹配节点配置 continue: true,才允许继续检查后续同级节点。这不表示配置中的所有接收器都会获得同一条告警,未匹配的节点仍然不会参与。

一个路由节点只能指定一个接收器。接收器内部可以包含多个通知集成,多条匹配路由也可以选择不同接收器,两者是向多个目的地通知的不同方式。不能把路由的 receiver 字段直接换成列表,就期待具有相同行为。选择方案时应核对是否会重复通知,以及不同目的地是否需要独立的分组方式、升级策略和发送节奏。

责任归属依赖稳定的标签契约

匹配 team="payments" 的路由,需要进入 Alertmanager 的告警真正带有这个标签值。名为 team 的注解不能满足该条件。Prometheus 表达式中的聚合可能提前移除一个维度,所以应该检查最终告警标签,包括告警重新标记的影响,不能假定原始指标上的标签会原样保留到通知阶段。原始指标存在团队字段,不足以证明路由可以看到它。

根接收器应当对没有匹配具体子节点的告警仍然有用。这类告警也需要可观察的去向,以及能够修正分类的负责人。测试时除了已知团队,还应覆盖标签缺失:缺少团队的警告可能留在根节点,但缺少同一标签的严重告警仍可能命中独立的严重程度路由。是否缺失与是否完全没有匹配路径,是不同问题。

路由排列顺序本身就是行为。较宽泛的前置路由,可能让后面的具体路由根本没有机会被检查,除非启用了继续匹配。因此,即使匹配器文字没有变化,移动一段配置也可能改变收件方。顺序调整应按策略变化审阅,并把代表性标签输入与配置放在一起,以便持续核对预期选择。

按时间静默不会让匹配节点从遍历中消失。一个被静默的子节点在关闭继续匹配时,仍可能阻止后面的同级节点。因此,办公时间路由在不活跃时,不会自动变成值班路由的回退条件。升级策略需要同时审阅时间条件和树的停止规则,不能只看两个时段是否互补,就认为收件方一定连续覆盖。

坏掉的样子

拼写错误或标签改名可能使告警落到根接收器,新添加的宽泛子路由可能截走后面团队的告警,而不必要的继续匹配则可能让一个事件产生多份通知。诊断这些问题,需要实际标签和完整匹配路径,单独阅读某个节点通常不够。还应核对新增规则与既有顺序共同作用后的结果,而不只是新增部分自身是否正确。

接收器选择正确仍不等于送达。分组等待、静默、抑制、模板错误、传输故障和目的地行为,都可能在之后介入。先验证路由,再沿所选接收器追踪通知链路。增加规则等待时间不会修复团队匹配条件,缩短分组等待也不能修复接收器凭据缺失。每个阶段都需要对应证据,不能用前一阶段通过代替后一阶段结果。

快速验证

把程序保存为 routing.py,执行 python3 routing.py。需要 Docker 与公开的 Alertmanager 0.28.1 镜像,镜像缺失时可能先下载。真实 amtool 检查配置并验证七组标签输入。实验接收器只有名称,没有实际发送集成,因此可以检查路由选择而不会联系任何人员或外部服务,也不会影响现有告警系统。

import subprocess
import uuid

# Requires Docker and Alertmanager 0.28.1. No receiver has a delivery integration.
script = r"""set -eu
cat > /tmp/routes.yml <<'ROUTES'
route:
  receiver: default
  group_by: [alertname, service]
  routes:
    - receiver: payments
      matchers: ['team="payments"']
      continue: true
    - receiver: pager
      matchers: ['severity="critical"']
receivers:
  - name: default
  - name: payments
  - name: pager

ROUTES
/bin/amtool check-config /tmp/routes.yml
/bin/amtool config routes test --config.file=/tmp/routes.yml --verify.receivers=payments,pager team=payments severity=critical
/bin/amtool config routes test --config.file=/tmp/routes.yml --verify.receivers=payments team=payments severity=warning
/bin/amtool config routes test --config.file=/tmp/routes.yml --verify.receivers=pager team=data severity=critical
/bin/amtool config routes test --config.file=/tmp/routes.yml --verify.receivers=default team=data severity=warning
/bin/amtool config routes test --config.file=/tmp/routes.yml --verify.receivers=default severity=warning
/bin/amtool config routes test --config.file=/tmp/routes.yml --verify.receivers=default team=payments-preview severity=warning
sed 's/continue: true/continue: false/' /tmp/routes.yml > /tmp/stop.yml
/bin/amtool config routes test --config.file=/tmp/stop.yml --verify.receivers=payments team=payments severity=critical
"""
name = "xbcatm-route-test-" + uuid.uuid4().hex[:12]
try:
    result = subprocess.run(
        ["docker", "run", "--rm", "--name", name, "--network", "none", "-i",
         "--entrypoint", "/bin/sh", "prom/alertmanager:v0.28.1", "-s"],
        input=script, text=True, capture_output=True, timeout=30,
    )
finally:
    subprocess.run(["docker", "rm", "-f", name],
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print(result.stdout, end="")
if result.returncode:
    raise RuntimeError(result.stderr or result.stdout)
print("Verified seven route cases, including continuation and unmatched labels.")

第一组结果为 payments,pager:支付团队路由先匹配并继续,严重程度路由随后也匹配。支付团队警告只选择支付接收器,数据团队严重告警选择呼叫接收器;没有匹配或缺少团队值的相应输入进入根接收器。payments-preview 不会匹配精确的支付团队值,避免把名称相近误认为条件成立。

最后一项把继续匹配改为关闭,确认支付团队严重告警现在只选择支付接收器。每组结果都由 --verify.receivers 强制核对,出现差异会直接使执行失败。容器没有外部网络,不挂载宿主机目录,退出时自动移除。真实接收器的通知送达仍需要另行安排受控验证,这份结果只证明路由选择。

本轮没有修改此运行实验,也没有重新执行。文中的预期输出和此前记录的观察,与本轮执行证据分别保留,不能据此声称真实集成已经再次通过。

来源