# Alertmanager · 从告警到通知

> Prometheus 只管产生告警，把它变成「一条恰当的通知」是 Alertmanager 的活。这一章讲清它的四个核心机制——分组、抑制、静默、去重，以及为什么「分组」是防止通知风暴的关键。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-07-15
- 原文：https://daiw.org/manual/observability/alertmanager
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# Alertmanager · 从告警到通知

一个容易被忽略的分工：**[Prometheus 只负责判断“告警该不该 firing”](https://daiw.org/manual/observability/alerting-rules)，它不发通知**。把告警变成“**一条恰当的、发给对的人的通知**”是 **Alertmanager**（一个独立进程）的活。

**为什么要拆成两个进程？** 因为这是两件性质完全不同的事：前者是**数据计算**（[周期性求值 PromQL](https://daiw.org/manual/observability/promql-engine)），后者是**通知路由与降噪**（分组、去重、限流、对接十几种通知渠道）。[拆开让 Prometheus 保持简单](https://daiw.org/manual/observability/prometheus-architecture)，也让**多个 Prometheus 可以共用一套 Alertmanager**——这在[分片部署](https://daiw.org/manual/observability/relabeling)时是必需的。

## 四个核心机制

<Callout type="info">
  **Alertmanager 干四件事，每件都是为了**让人少收到通知，但不漏掉信息**：**

  | 机制 | 解决什么 |
  | --- | --- |
  | **分组（grouping）** | **一次故障 = 一条通知**，而不是 500 条 |
  | **抑制（inhibition）** | 根因响了，就别再报衍生问题 |
  | **静默（silencing）** | 维护窗口、已知问题，临时闭嘴 |
  | **去重（dedup）** | [多个冗余 Prometheus](https://daiw.org/manual/observability/prometheus-architecture) 报同一件事 → 只发一次 |
</Callout>

## 分组：最重要的那个

<Callout type="info">
  **场景：一个机架断电，**500 个实例**同时不可达。**

  - **没有分组**：500 条 `InstanceDown` 通知，**500 条短信**。你的手机会爆炸，而且**真正重要的信息（整个机架挂了）淹没在里面**。
  - **有分组**：**1 条通知**——“`InstanceDown`：500 个实例受影响，cluster=rack-7”。

  ```yaml
  route:
    group_by: ['alertname', 'cluster']    # 按这些标签分组
    group_wait: 30s        # 第一条到了先等 30s,看还有没有同组的
    group_interval: 5m     # 同组有新成员时,最多每 5m 再通知一次
    repeat_interval: 4h    # 同一组一直没解决,每 4h 提醒一次
  ```

  **三个时间参数的分工值得说清：**

  - **`group_wait`(30s)** —— **第一条告警到达后，先憋一会儿**，等同组的其他告警。这就是“500 条变 1 条”的关键：断电后 500 个实例的告警会在几十秒内陆续到达，**憋 30 秒就能把它们攒成一组**。代价是**通知延迟 30 秒**——值得。
  - **`group_interval`(5m)** —— 这一组**已经通知过了**，但又来了**新成员**（比如又挂了 20 台），多久通知一次更新。
  - **`repeat_interval`(4h)** —— 这一组**啥也没变、一直没解决**，多久**再提醒**一次。**别设太短**（会变成骚扰），也别太长（会被遗忘）。

  **`group_by` 选什么标签是个设计决策：**
  - 太粗（`group_by: ['alertname']`）—— 不同集群的同名告警会被合并，你看不出是哪个集群。
  - 太细（`group_by: ['alertname', 'instance']`）—— **等于没分组**（每个实例一组），500 条通知照旧。
  - **经验：按“**同一个根因会同时触发的维度**”分组**——机架断电的共同点是 `cluster`，那就 `['alertname', 'cluster']`。

  **`group_by: [...]` 里那个特殊值 `'...'`** 表示“**按所有标签分组**”（即不分组），**几乎总是错的**——除非你真的想要每条告警一个通知。
</Callout>

## 抑制：别报衍生问题

<Callout type="info">
  **场景：整个集群网络断了。于是：**
  ```
  ClusterDown          ← 根因
  InstanceDown × 500   ← 衍生
  HighErrorRate × 50   ← 衍生
  LatencyHigh × 50     ← 衍生
  ```
  **人只需要知道第一条。** 后面那 600 条是**噪音**，而且会**掩盖根因**。

  ```yaml
  inhibit_rules:
  - source_matchers: [severity="critical", alertname="ClusterDown"]
    target_matchers: [severity="warning"]
    equal: ['cluster']       # 只抑制同一个 cluster 的
  ```

  **`equal` 是关键**：它说“**只有当两条告警的这些标签相同时，才抑制**”。没有它，north 集群的 `ClusterDown` 会把 south 集群的告警也抑制掉——**那就漏报了**。

  **抑制是“告警拓扑知识”的编码**：它要求你知道“**什么会导致什么**”。这也是它的局限——[和“对原因告警”一样，你不可能列全所有因果链](https://daiw.org/manual/observability/alert-thresholds)。**所以抑制规则要少而精，只覆盖那些最明显、最常见的因果关系**（集群 → 实例、实例 → 服务）。
</Callout>

## 静默与去重

**静默（silencing）** —— 临时闭嘴。典型用途：计划内维护、已知问题正在修、[已经开了工单的告警](https://daiw.org/manual/observability/alert-thresholds)。**注意静默是有过期时间的**——这是个好设计，**防止“临时静默”变成永久失明**（那是很多团队真实发生过的事故：某条告警被静默了两年，没人记得）。

**去重（dedup）** —— [生产环境通常跑多个冗余 Prometheus](https://daiw.org/manual/observability/prometheus-architecture)（它们抓同样的目标），于是**同一条告警会被报好几次**。Alertmanager 会**按标签集去重**，只发一次。这正是[上一章说的“变化的值要放 annotations 不放 labels”](https://daiw.org/manual/observability/alerting-rules)的原因——**放 labels 会破坏去重**（每个 Prometheus 算出的 `$value` 略有不同 → 标签不同 → 被当成不同告警 → 你收到 N 条）。

Alertmanager 自己也支持**集群模式**（多个实例用 gossip 协议同步状态），保证[它自己不是单点](https://daiw.org/manual/observability/prometheus-architecture)——**监控链路上的每一环都不该是单点**。

## 路由树：发给谁

```yaml
route:
  receiver: 'default'
  group_by: ['alertname', 'cluster']
  routes:
  - matchers: [team="api"]        # API 团队的告警
    receiver: 'api-team'
    routes:                        # 嵌套:更细的规则
    - matchers: [severity="page"]
      receiver: 'api-oncall-phone'
  - matchers: [team="data"]
    receiver: 'data-team'
```

<Callout type="info">
  **路由是一棵**树**，告警从根往下走，**匹配到就停**（除非配了 `continue: true`）。**

  **这就是[告警规则里那些 `labels`](https://daiw.org/manual/observability/alerting-rules) 的最终用途**：`team: api`、`severity: page` 这些标签，在这里决定了**这条告警去哪、通过什么渠道**。

  **实战建议：让告警规则打上 `team` 标签，而不是在 Alertmanager 里维护一个巨大的“哪个 alertname 属于哪个团队”的映射。** 前者是[代码即配置](https://daiw.org/manual/observability/recording-rules)（告警和归属写在一起，改服务时一起改），后者会**迅速腐烂**（没人记得更新那张表，新告警默认发给全世界）。
</Callout>

## 小结与下一章

**[Prometheus 判断告警，Alertmanager 送通知](https://daiw.org/manual/observability/alerting-rules)**——拆开是因为这是[两件性质不同的事](https://daiw.org/manual/observability/prometheus-architecture)，且能让多个 Prometheus 共用一套。**四个机制**：**分组**（[最重要：机架断电 500 条 → 1 条；`group_wait` 憋一会儿攒同组、`group_interval` 有新成员时更新、`repeat_interval` 未解决时提醒](https://daiw.org/manual/observability/alertmanager)；**`group_by` 要按“同一根因会同时触发的维度”选**，太细等于没分组）、**抑制**（[根因响了就别报衍生的 600 条；**`equal` 是关键**，否则会跨集群误抑制 → 漏报；规则要少而精，因为你不可能列全因果链](https://daiw.org/manual/observability/alert-thresholds)）、**静默**（[有过期时间是好设计，防止“临时静默”变成永久失明](https://daiw.org/manual/observability/alertmanager)）、**去重**（[多个冗余 Prometheus 报同一件事；这正是“变化的值不能放 labels”的原因](https://daiw.org/manual/observability/alerting-rules)）。**路由是树**，匹配到就停——[让告警规则自带 `team` 标签，别在 Alertmanager 里维护会腐烂的映射表](https://daiw.org/manual/observability/recording-rules)。

机制都齐了。但还有个更根本的问题：**告警多了，人就不看了。** 下一章收尾第 5 部分。👉 [告警哲学](https://daiw.org/manual/observability/alerting-philosophy)。

## 参考资料

- [1] Alertmanager 官方文档：Configuration（route/group_by/inhibit_rules/silences）、HA 集群模式。
- [2] 关联[告警规则的 labels](https://daiw.org/manual/observability/alerting-rules)、[阈值与分级](https://daiw.org/manual/observability/alert-thresholds)。
- [3] Prometheus 官方文档：Alerting overview。
