在真实的监控场景里,告警是系统求生的喊声。可当多个团队、多个客户同时使用一套监控平台时,喊声往往会混在一起:A团队的数据库报错,B团队的手机响个不停;租户甲的业务故障,租户乙的告警群里出现了原始日志。这种混乱的根源,往往就是告警路由设计没跟上多租户的脚步。今天咱们不绕弯子,直接聊 Alertmanager 如何通过标签来把这条路修清楚。
一、应用场景:多租户告警隔离为什么难
很多公司都会遇到这种局面:监控平台是统一的,但使用它的人却是分组织的。比如一个 SaaS 服务商,底层的 Kubernetes 集群、数据库、消息队列是共享的,但上面的租户各自的业务指标和核心接口却需要单独维护。再比如一个互联网公司,内部有订单组、支付组、用户增长组,大家共用一套 Prometheus,但告警必须各组自己负责。
如果没有路由隔离,会发生几件事。第一,告警“串门”——订单组的告警发给了支付组,大家半夜起来处理不是自己的故障,很快同事们就会把告警通知屏蔽掉。第二,安全风险——租户 A 的故障详情、IP地址、错误日志,如果直接出现在租户 B 的监控面板或通知里,这属于数据泄漏,严重时可能引发信任危机。第三,效率变低——所有告警都堆到一个接收人手里,没人分得清楚优先级,真正严重的告警被淹没在无关的噪声中。
因此,多租户场景下的监控系统必须做到“谁的告警进谁的耳朵”,同时“谁的数据不被别人看见”。这就需要在告警路由层面进行设计。
二、Alertmanager 路由基础:标签是灵魂
Alertmanager 是 Prometheus 生态里的告警分发组件。它接收 Prometheus 推来的告警,然后根据一组叫做“路由树”的配置,决定这些告警应该发送给哪个接收人。路由树本质上是一个层层匹配的规则列表,匹配的依据就是告警携带的标签。
标签在 Alertmanager 里就是键值对,比如 tenant="acme"、severity="critical"。一个告警可以带很多标签,路由规则就是靠这些标签来决定“这个告警归谁管”。理解这一点是后面所有设计的基础。
来看一个最简单的路由配置,技术栈统一为 Alertmanager 路由配置(YAML):
# Alertmanager 路由配置(YAML)
# 最简单的路由:所有告警都发给同一个接收人
route:
receiver: ops # 默认接收人,所有没被子路由匹配到的告警都发给他
group_by: ['alertname'] # 将相同告警名的告警合并成一组,避免重复轰炸
这个配置没有任何租户概念,所以它适合单团队使用。一旦进入多租户场景,这个配置就会成为混乱的源头。
三、标签隔离的设计方案
要隔离租户,第一步是让每个告警都明确地“报出自己的租户”。我们约定一个标签 tenant,所有告警都必须带这个标签。至于这个标签从哪来,可以在 Prometheus 的采集或者规则配置里加上,这一步不属于 Alertmanager 的范畴,但那是基础。
有了 tenant 标签,就可以在 Alertmanager 的路由树里按租户分叉了。具体做法是:在根路由下挂多个子路由,每个子路由用 matchers 匹配特定的 tenant 值,并指定不同的接收人。这样,租户 A 的告警只会进入租户 A 的分支,发给租户 A 的团队;租户 B 的告警同理。
下面是一个典型的多租户路由配置示例:
# Alertmanager 路由配置(YAML)
# 多租户路由:把不同租户的告警路由到不同接收人
route:
receiver: default-ops
group_by: ['tenant', 'alertname'] # 分组时带上租户,避免跨租户合并
matchers:
- tenant=~".+" # 只处理带 tenant 标签的告警
routes:
- matchers:
- tenant="acme" # 匹配 acme 租户
receiver: acme-alerts # acme 团队接收
group_by: ['alertname'] # 在租户内进一步分组
- matchers:
- tenant="globex" # 匹配 globex 租户
receiver: globex-oncall # globex 值班接收人
group_by: ['alertname']
这段配置的含义是:所有告警必须带有 tenant 标签才能进入路由树。tenant 是 acme 的告警交给 acme-alerts,tenant 是 globex 的交给 globex-oncall。如果某些告警没有 tenant 标签,由于根路由的 matchers 不匹配,它们会走默认接收人 default-ops。这样做的好处是:即使告警来源混乱,也不会被错误地分配到某个租户,而是留在默认接收人里,便于人工排查。
这里需要注意的是,group_by 中加了 tenant。如果不加,Alertmanager 会把不同租户的同类告警合并成一条通知,这恰恰是我们要避免的。分组是路由中很容易忽略的细节,也是隔离的关键。
3.1 用 continue 实现告警的叠加分发
有时候,一个告警既需要发给租户自己的团队,也需要发给一个集中的严重告警处理组。Alertmanager 的路由默认在匹配到一个子路由后就会停下来,但如果我们设置 continue: true,告警在匹配完当前路由后,还会继续尝试匹配后面的路由。利用这个特性,可以实现“一次告警,多处通知”。
示例配置如下:
# Alertmanager 路由配置(YAML)
# 使用 continue 让 acme 的告警同时发给汇总接收人和严重告警处理组
route:
receiver: default
routes:
- matchers:
- tenant="acme" # 匹配 acme 租户
receiver: acme-summary # acme 租户的汇总接收人
continue: true # 匹配后不要停,继续往下走
- matchers:
- severity="critical" # 匹配严重级别
receiver: critical-oncall # 严重告警由专人负责
在这个配置里,如果收到一条 tenant="acme", severity="critical" 的告警,它会先匹配第一个路由,发给 acme-summary;由于 continue: true,它继续匹配第二个路由,同时发给 critical-oncall。这样就实现了告警的叠加分发。要注意的是,如果继续匹配的路由太多,会产生重复通知,所以 continue 要谨慎使用。
3.2 租户内部的进一步细分
一个租户内部也可能有不同环境、不同服务,需要细分路由。做法是在租户的子路由下面再挂一级路由,形成树形结构。
# Alertmanager 路由配置(YAML)
# 租户 acme 内部按环境划分接收人
route:
receiver: default
routes:
- matchers:
- tenant="acme" # 进入 acme 分支
receiver: acme-default # acme 的默认接收人
routes: # acme 内部再按环境分流
- matchers:
- env="production"
receiver: acme-prod # 生产环境告警发给 acme-prod
- matchers:
- env="staging"
receiver: acme-staging # 预发环境告警发给 acme-staging
看到没有,路由树可以一层层往下长,只要标签够明确,匹配规则就可以非常细致。注意,子路由没有匹配到 env 时,会使用父路由的接收人 acme-default,这相当于兜底,保证不会漏。
四、权限控制:从路由到管控
Alertmanager 本身并没有“用户登录”“角色权限”这些概念。它只做两件事:根据标签匹配路由,然后发送通知。所以,如果我们说的“权限控制”是指“谁能收到哪些告警”,那路由配置本身就是在做权限控制——通过限制接收人,确保租户 A 的告警不会进入租户 B 的通知渠道。
但如果我们说的是“谁能查看 Alertmanager 的页面、谁能在网页上看到所有告警”,那 Alertmanager 原生并不支持。实际生产环境中,通常有两种解决思路。
第一种是部署多套 Alertmanager。每个租户一套,互不联通。这种方法隔离性最好,但是运维成本高,告警源也要做适配。
第二种是在 Alertmanager 前面加一个反向代理层,用外部身份认证系统(比如 OAuth、部门 SSO)来控制访问。这一步其实已经超出了 Alertmanager 配置的范畴,但它在多租户权限控制中是必不可少的。我们在今天的配置示例中不涉及外部代理,因为我们的技术栈只聊 Alertmanager 的 YAML 配置。
需要注意的是,在路由配置层面,租户之间的隔离并不是自动安全的。如果两条规则不小心用了同一个 receiver,或者匹配条件写得太宽,比如 tenant=~".*",就会导致所有告警被合在一起,租户隔离瞬间失效。因此,权限控制的第一条原则是:每个租户至少要有独立的 receiver,且匹配条件要写具体,不能图方便写通配符。
在配置中,我们可以利用根路由的 matchers 来做一个总闸。比如只允许带 tenant 标签的告警进入租户路由,否则全部丢给默认接收人。这能在一定程度上防止租户信息泄露到错误渠道。下面的示例展示了这个“总闸”思路:
# Alertmanager 路由配置(YAML)
# 根路由强制要求 tenant 标签存在,否则走默认接收人
route:
receiver: unclassified # 没有租户标签的告警都到这里
group_by: ['alertname']
routes:
- matchers:
- tenant=~".+" # 必须有 tenant 标签
receiver: internal-error # 有租户信息的告警先进这里做中转
continue: false # 中转后不再匹配其它兄弟路由
routes: # 然后在这个父路由下面按租户分支
- matchers:
- tenant="acme"
receiver: acme-alerts
- matchers:
- tenant="globex"
receiver: globex-oncall
这个配置的核心逻辑是:先把“有 tenant 标签”和“没有 tenant 标签”的告警区分开。没有租户标签的告警被直接放到 unclassified 接收人,由平台管理员处理;有租户标签的告警进入 internal-error 这个中转接收人,再按租户分流。这里 continue: false 是因为我们不想让这些告警再跑到其他根路由分支里去。
注意,internal-error 这个接收人实际上会收到所有带租户标签的告警,因为中转路由没有匹配具体的租户值。如果你不想让中转接收人收到所有告警,可以把路由结构改回上一节的示例,直接在根路由下按租户匹配,但那样没有 tenant 标签的告警就会进入根路由默认的 unclassified。两种方式各有取舍,关键看你的管理需求。
五、技术优缺点
Alertmanager 的多租户路由设计有明显的好处,也有一些绕不过去的坑。
5.1 优点
- 配置原生:不需要额外开发,Alertmanager 本身就支持复杂的路由树。
- 灵活度高:匹配条件可以用等值,也可以在 matchers 中支持正则表达式,适用于各种场景。
- 分组能力强:结合
group_by参数,可以把同一租户的相同告警合并,减少通知次数。 - 与 Prometheus 天然配合:只要在采集端加上
tenant标签,整个链路无需大改。
5.2 缺点
- 权限控制薄弱:Alertmanager 不是身份认证系统,无法管理用户的查看权限。
- 配置膨胀:租户越多,路由规则越庞大,维护成本随之上升。
- 缺少动态管理:新增一个租户需要修改配置文件并 reload,无法通过 UI 快速添加。
- 隔离依赖规则自觉:如果规则写得不严谨,很容易出现租户告警误投。
六、注意事项
在实际落地过程中,有几个地方需要特别留心。
第一,强制规范标签。所有告警必须携带 tenant 标签,这要作为团队规范固定下来。可以在 Prometheus 的 alerting rules 或者 relabel 阶段统一添加标签。如果不规范,路由设计再复杂也没用。
第二,谨慎使用正则匹配。正则虽然强大,但建议只在根路由做总闸,具体租户分支最好用明确的等值匹配。比如 tenant="acme" 远清晰于 tenant=~"acm.*",后者可能匹配到不该匹配的租户。
第三,控制 continue 的使用。continue 会扩大通知范围,用多了容易造成告警轰炸。在默认路由分支上尽量设置为 false,只在需要叠加通知的特定分支上开启。
第四,关注分组和抑制的隔离。Alertmanager 的分组是按照标签值进行的,如果你的分组标签里没有 tenant,不同租户的相同告警就会被合并。抑制规则也一样,不要在抑制条件里跨租户匹配,否则一个租户的静默可能影响另一个租户的告警。
第五,定期进行路由审计。因为配置是静态的,租户变动、人员调整都会导致路由过期。建议每个迭代都检查一次路由树,确认没有失效的 receiver 和错误的匹配项。
第六,测试你的路由。虽然我们不在这里展示命令,但你可以借助 Alertmanager 团队提供的工具来模拟一条告警,看看它最终落到哪个接收人。这个步骤能有效避免线上路由捅娄子。
七、文章总结
多租户场景下的 Alertmanager 路由设计,核心思路就是围绕标签做文章。我们给每个告警打上租户标签,然后在路由树里按租户分流,再配合分组、继续匹配等机制,就能实现告警的精准投递和基本隔离。同时我们也要清醒地认识到,Alertmanager 本身不是权限控制平台,它只在“通知”层面提供隔离;如果要做完整的租户隔离,还需要在外部部署认证和授权层,或者采用多实例部署模式。
回到最初的问题:为什么多租户监控不能让告警串门?因为串门不仅会造成噪音,还会带来数据泄漏风险。而解决这个问题,不需要复杂的框架,只需要在设计路由时,多问自己一句:这条告警,到底该进谁的耳朵?带着这个问题去写配置文件,路由设计就不会跑偏。
好,这就是本文的全部内容,希望能帮助你更好地驾驭 Alertmanager 的多租户路由。
评论
围绕“多租户场景下Alertmanager路由设计:标签隔离与权限控制”参与讨论