多租户指标平台上的告警分层设计

英文版:English

Why

Federation 拓扑顶端的 global Prometheus 每周 OOM 两到三次。它的 head block 维护着 1.2M series,占用 5 到 10 GB 内存;每次崩溃重启都会在数据上留下空洞,而这些空洞会以"不存在的故障"的形式把 oncall 从床上叫醒。这是可靠性的一半。另一半是延迟,而且是结构性的:Federation 叠加了两层 scrape 周期,集群本地 15s 加全局层 30s,全局视图最多滞后现实 45 秒。P0 发生时,被 page 的人看到的是过去。

规模让两个问题都无解。50 个集群的负载下 /federate 接口频繁 scrape timeout,Grafana 上出现数据断点;每个集群各自维护一套 federation rules,新集群接入要手工修改 global Prometheus 的 scrape 配置。同时平台没有可用的多租户维度:指标跨租户聚合,单个租户的局部故障被平均值掩盖。

这套问题的解法是把指标平台从 Federation 切换到 VictoriaMetrics。这是 platform 团队在我接手这个范围之前完成的迁移:引擎选型(VictoriaMetrics vs Thanos)、数据生命周期策略和迁移执行都不是我做的。我可以从 SLO 和架构角度讨论这个选择为什么合理,但答不上迁移过程里踩过的具体坑。

我一手 own、端到端负责的部分,是叠在这个新指标存储之上的告警层。对 30 天告警历史的审计显示,约 80% 的 page 是 resource-centric 的:CPU 或内存抖动,值班人看一眼就关掉;而且指标没有可用的租户维度,单个租户的局部故障会被整个集群的平均值掩盖。

How

这套平台是推送式的中心化架构:每个 workload 集群部署一个 vmagent 就地采集,在 relabel 阶段注入 cluster 和 cluster group 标签,然后 remote_write 到中心 VictoriaMetrics 集群(vminsert ×2 挂 LB,vmstorage ×3 副本因子 2,vmselect ×2)。vmalert 在同一存储上评估 recording rules 和 alert rules;Alertmanager 以 HA 双实例做通知路由;Grafana 加 Loki 构成读取与验证面。这套拓扑和它的容量规划(50 集群 × 约 12 节点 ≈ 600 节点,每节点约 2,000 series,合计约 1.2M active series、约 80,000 samples/s,15s 采集间隔;热存储 SSD 上 3 个月约 250 GB;冷存储走 S3,5 分钟降采样,180 天约 25 GB)在我开始在它上面工作之前就已经落地。

平台拓扑(既有背景知识,不是我建的)。

flowchart TB subgraph FLEET2["50 个集群"] A["vmagent<br/>remote_write + 持久化队列"] end A --> VI["vminsert ×2"] VI --> VS[("vmstorage ×3 · 双副本")] VS --> VQ["vmselect ×2"] VQ --> VA["vmalert<br/>recording + 告警规则"] VQ --> GF["Grafana / Loki"] VA --> AM["Alertmanager<br/>三层分级 + inhibition"]

在这个平台之上,我设计了告警层。recording rules 先物化一族 SLI:QPS、error ratio、P95/P99 latency、saturation、分租户 SLI,全部按 {tenant, cluster group} 键控;dashboard 和告警规则都消费预计算结果,不在现场跑重查询。告警分三层:PAGER 走 PagerDuty,只留给确认的用户影响;HIGH 进 Slack;MEDIUM 归工单。PAGER 必须满足 multi-window burn-rate:5m 和 30m 两个窗口同时超阈值才触发,既滤掉瞬时抖动,又不放过持续恶化。inhibition 只沿因果明确的链条做抑制(NodeNotReady 抑制同节点的 PodUnableToStart);跨服务的推测性抑制被刻意排除,因为配错方向的 inhibition 会掩盖真实故障。

我在这之上设计的:告警层。

flowchart TB VA["vmalert<br/>按 {tenant, cluster_group} 键控的 SLI recording rules"] --> SEV{"严重度准入控制"} SEV -->|"可观测用户影响,<br/>5m+30m burn-rate"| PAGER["PAGER → 寻呼通道"] SEV -->|"降级但未影响用户"| HIGH["HIGH → Slack"] SEV -->|"信息性"| MED["MEDIUM → 工单"] PAGER --> INH["Inhibition:只做因果链"]

租户身份有两条注入路径:应用在自己的 metrics 上打 tenant label,因为只有应用知道在处理谁的请求;vmagent 在 scrape 时通过 relabel 注入集群与 cluster group 上下文。项目之前各集群的 relabel 配置各自为政:cluster 命名不统一,部分团队用 client 而不是 tenant,这会悄无声息地破坏告警路由、drill-down 和 inhibition 的 label 相等匹配。我写了统一的 relabel 模板分发到所有集群,并推动应用侧收敛到 tenant

Hard parts

多租户 series 基数治理。 加租户维度会成倍放大 series,label 铺开后增长很快。治理手段:tenant 级 recording rules 只覆盖核心 SLI;基础设施指标一律不带 tenant label;retention 分层(tenant SLA 序列 90 天,排障序列 30 天,基础设施 15 天);定期 cardinality 审查清理意外的高基数 label。诚实的复盘结论:cardinality budget 应该在第一天就存在,而不是等增长曲线逼出来。

让 severity 有语义。 对 30 天告警历史做审计,约 80% 的 page 是 resource-centric 的:CPU 或内存抖动,值班人看一眼就关掉。解法是给 PAGER 层做准入控制:一条 page 必须指示可观测的用户影响(SLO burn rate、SLA latency 击穿、关键链路 QPS 断崖),其余全部降级到 HIGH 或 MEDIUM;静态阈值全部换成 multi-window burn-rate。上线后用两个反馈信号持续调参:每条 PAGER 的 action rate,以及用户报了问题但没有对应 page 的漏报。

Production

severity 重设计与 SLI recording rules 是在既有(已完成迁移)的指标平台之上按集群逐步上线的,所以告警层本身没有单独的切换风险。新规则在路由生效前,先对照历史 firing 行为验证过。inhibition 规则在 staging 环境人工触发上游故障做验证,确认下游告警被正确抑制,并保留 firing 历史供 postmortem 回溯。日常验证靠两个信号:PAGER action rate、漏报追踪(用户报了问题但没有对应 page)。租户 label 的一致性由共享 relabel 模板持续强制,不再靠逐集群 review。

Takeaways

  • Federation 的 45s 延迟和每周 OOM 是架构属性而不是配置属性。这是我能扛住追问的判断,即便我没有执行那次迁移。
  • 告警质量是准入控制问题:定义什么配得上一条 page,其余全部降级,用每条 page 的 action rate 做回归测试。
  • label 一致性本身就是平台基础设施:路由、drill-down、inhibition 全部依赖 label 相等匹配,必须在 relabel 层一次性强制,不能靠各团队各自约定。