概述

告警是监控系统的"最后一公里",也是最难做好的一环。一个常见的困境是:服务器上跑着几十个告警规则,每天产生上百条告警通知,值班工程师在微信/钉钉/邮件的轮番轰炸下逐渐麻木——真正紧急的告警被淹没在噪声中,直到客户投诉才发现系统早已出问题。

SRE 的黄金法则是:每一条告警都应该有明确的处理动作。如果一个告警收到后既不需要立即处理,也不需要记录跟踪,那它就不应该存在。从告警疲劳问题出发,详细梳理告警分级、SLO-based 告警设计、抑制与聚合策略、告警度量指标和治理方法,帮助你从"告警噪声"中提取出真正的"信号"。

参考来源:Google SRE Book《Monitoring Distributed Systems》、Prometheus 告警好的实践

一、告警疲劳:问题的根源

1.1 告警泛滥的典型表现

某团队告警统计(一周):
┌──────────────────────────┬────────┬──────────┐
│ 告警类型                  │ 数量   │ 实际处理 │
├──────────────────────────┼────────┼──────────┤
│ CPU 使用率 > 80%          │ 156    │ 3        │
│ 磁盘使用率 > 70%          │ 89     │ 2        │
│ Pod 重启                  │ 34     │ 5        │
│ HTTP 5xx 错误率 > 1%      │ 12     │ 4        │
│ 数据库连接数 > 80%        │ 8      │ 1        │
│ 证书即将过期              │ 3      │ 1        │
│ 服务不可达                │ 2      │ 2        │
├──────────────────────────┼────────┼──────────┤
│ 总计                      │ 304    │ 18       │
└──────────────────────────┴────────┴──────────┘
有效告警率:18/304 = 5.9%

只有不到 6% 的告警需要实际处理,其余 94% 都是噪声。这种状态下,值班工程师的行为模式会变成:

  • 看到告警通知 → 粗略扫一眼 → 判断"又是老问题" → 忽略
  • 真正紧急告警到来时 → 也被忽略 → 事故扩大
  • 事后复盘:“告警太多了,没注意到那条关键的”

1.2 告警泛滥的常见原因

原因表现根因
阈值不合理CPU > 80% 频繁告警阈值太低,正常负载就触发
告警无分级所有告警都发到同一个群缺乏 severity 区分
缺少抑制上游故障引发下游连锁告警未配置 inhibit_rules
重复告警同一问题每小时通知一次repeat_interval 配置不当
告警无文档收到告警不知道怎么处理缺少 runbook 链接
自动恢复告警Pod 重启后立即恢复恢复通知也发到群里

1.3 信号 vs 噪声

告警质量 = 信号量 / 总告警量

信号(Signal):需要人工干预或关注的问题
噪声(Noise):自动恢复、重复、误报、无意义的告警

目标:信号占比 > 80%,噪声占比 < 20%

SRE 原则:如果一条告警不能指向一个具体的处理动作(修复、扩容、记录、排查),它就不应该存在。宁可少告警,也不要告警疲劳。

二、告警分级策略

2.1 四级告警体系

级别标签通知方式响应时间示例
P0 - 紧急severity=critical电话 + 短信 + IM< 5 分钟核心服务宕机、数据丢失
P1 - 严重severity=warningIM + 邮件< 30 分钟部分功能降级、错误率升高
P2 - 提醒severity=info邮件 / 日报工作时间内磁盘使用率 70%、证书 30 天过期
P3 - 记录severity=debug仅记录不通知测试环境告警、非核心组件

2.2 分级路由配置

# Alertmanager 路由配置
route:
  receiver: default
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    # P0 - 紧急告警:立即电话通知
    - matchers:
        - severity = critical
      receiver: critical-phone
      group_wait: 0s
      repeat_interval: 30m  # 每 30 分钟重复提醒

    # P1 - 严重告警:IM 群通知
    - matchers:
        - severity = warning
      receiver: warning-im
      group_wait: 30s
      repeat_interval: 2h

    # P2 - 提醒类:邮件通知
    - matchers:
        - severity = info
      receiver: info-email
      group_wait: 5m
      repeat_interval: 12h

    # P3 - 记录类:不通知
    - matchers:
        - severity = debug
      receiver: null

receivers:
  - name: critical-phone
    webhook_configs:
      - url: 'http://phone-gateway/alert'    # 电话通知网关
        send_resolved: true
  - name: warning-im
    webhook_configs:
      - url: 'http://dingtalk-webhook/alert' # 钉钉/企业微信
  - name: info-email
    email_configs:
      - to: 'ops-team@example.com'
  - name: null
    # 不配置任何通知方式

2.3 告警分级原则

告警分级决策树:

服务是否对外提供核心功能?
  ├── 否 → severity=info/debug(不打扰值班)
  └── 是 → 是否影响大量用户(> 1% 用户感知)?
              ├── 否 → severity=warning
              └── 是 → 是否导致功能完全不可用?
                          ├── 否 → severity=warning
                          └── 是 → severity=critical

关键原则:

  • 以用户影响为标准:不是"CPU 高了"就 critical,而是"用户无法下单"才 critical
  • P0 告警必须少:每周不超过 2-3 条 P0,否则说明分级有问题
  • 通知渠道匹配级别:电话用于 P0,IM 用于 P1,邮件用于 P2
  • 夜间降级:非工作时间降低 P1/P2 通知频率,P0 不降级

三、阈值告警的问题

3.1 传统阈值告警的陷阱

# 典型的阈值告警规则
- alert: HighCPU
  expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
  for: 5m
  labels:
    severity: warning

这个规则看起来没问题,但实际运行中会遇到:

问题场景后果
阈值难定CPU 80% 对某些服务正常,对另一些已危险误报或漏报
瞬时尖峰定时任务导致 CPU 短暂飙升大量无效告警
容量差异大实例 CPU 50% 的负载远超小实例 80%阈值不适用所有实例
无用户视角CPU 80% 但用户无感知告警无实际意义
上下文缺失不知道这是否影响 SLO无法判断严重性

3.2 多级阈值方案

改善方法之一是配置多级阈值,减少误报:

groups:
  - name: cpu-alerts
    rules:
      # P2 - 提醒:CPU > 85%,持续 30 分钟
      - alert: HighCPUWarning
        expr: |
          100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85          
        for: 30m
        labels:
          severity: info
        annotations:
          summary: "CPU 使用率偏高: {{ $labels.instance }}"
          description: "CPU 使用率 {{ $value }}% 超过 85%,持续 30 分钟"

      # P1 - 严重:CPU > 95%,持续 5 分钟
      - alert: HighCPUCritical
        expr: |
          100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95          
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CPU 使用率极高: {{ $labels.instance }}"
          description: "CPU 使用率 {{ $value }}% 超过 95%,可能影响服务"
          runbook: "https://wiki.internal/runbooks/high-cpu"

这比单一阈值更好,但仍然是"猜测阈值"——你不知道 85% 或 95% 是否真的对应用户感知到的问题。

四、SLO-based 告警

4.1 什么是 SLO-based 告警

SLO(Service Level Objective)是基于服务质量目标的告警方式。核心理念是:不要告警"某指标超过阈值",而是告警"即将消耗完错误预算"

错误预算(Error Budget)= 1 - SLO 目标

示例:SLO = 99.9% 可用性
错误预算 = 1 - 0.999 = 0.1%

30 天窗口内允许的不可用时间 = 30 × 24 × 60 × 0.1% = 43.2 分钟

SLO-based 告警不是问"CPU 是否高",而是问"我们是否在过快地消耗错误预算"。

4.2 多窗口多燃烧率告警

Google SRE 推荐使用多窗口多燃烧率(Multi-Window Multi-Burn-Rate)策略:

groups:
  - name: slo-alerts
    rules:
      # 快速燃烧:1h 窗口消耗 2% 错误预算(14.4x 燃烧率)
      # → 在 1 小时内就能发现严重问题
      - alert: SLOBurnRateFast
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h])) /
            sum(rate(http_requests_total[1h]))
          ) > (1 - 0.999) * 14.4
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[5m])) /
            sum(rate(http_requests_total[5m]))
          ) > (1 - 0.999) * 14.4          
        for: 2m
        labels:
          severity: critical
          slo: availability-999
        annotations:
          summary: "SLO 错误率快速燃烧"
          description: "近 1 小时错误率超 SLO 14.4 倍,预计 2 小时内耗尽错误预算"
          runbook: "https://wiki.internal/runbooks/slo-burn"

      # 慢速燃烧:6h 窗口消耗 5% 错误预算(6x 燃烧率)
      # → 发现持续性、非突发的服务质量下降
      - alert: SLOBurnRateSlow
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h])) /
            sum(rate(http_requests_total[6h]))
          ) > (1 - 0.999) * 6
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[30m])) /
            sum(rate(http_requests_total[30m]))
          ) > (1 - 0.999) * 6          
        for: 15m
        labels:
          severity: warning
          slo: availability-999
        annotations:
          summary: "SLO 错误率慢速燃烧"
          description: "近 6 小时错误率超 SLO 6 倍,预计 5 天内耗尽错误预算"

4.3 多窗口多燃烧率参数

窗口组合燃烧率消耗预算告警级别目的
1h + 5m14.4x2% (1h)critical快速发现严重问题
6h + 30m6x5% (6h)warning发现持续性问题
1d + 2h3x10% (1d)warning长期趋势监控
3d + 6h1x10% (3d)info预算消耗跟踪
多窗口多燃烧率逻辑:

短窗口 + 长窗口 都超阈值 → 告警
(避免短窗口的瞬时尖峰误报,同时保证响应速度)

4.4 SLO 告警 vs 阈值告警对比

维度阈值告警SLO 告警
关注点某指标是否超阈值用户体验是否受影响
误报率高(阈值难精确设定)低(基于实际错误率)
上下文缺失有(错误预算消耗进度)
用户相关性间接直接
运维负担高(需频繁调阈值)低(SLO 目标稳定)
告警数量少(只有影响 SLO 的才告警)

核心理念:SLO 告警回答的是"是否需要立即行动以保护用户体验",而不是"某个数字是否好看"。这是从"基础设施监控"到"用户体验监控"的思维转变。

五、告警抑制与聚合

5.1 抑制规则(Inhibition)

当上游故障发生时,下游会产生大量连锁告警。抑制规则可以在高级别告警触发时,自动静默低级别相关告警。

# Alertmanager inhibit_rules
inhibit_rules:
  # 当服务不可达时,抑制该服务的 CPU/内存告警
  - source_matchers:
      - alertname = ServiceDown
    target_matchers:
      - alertname =~ 'HighCPU|HighMemory|DiskSpaceWarning'
    equal: ['service', 'instance']

  # 当集群级别告警触发时,抑制该集群下所有实例告警
  - source_matchers:
      - alertname = ClusterDown
      - severity = critical
    target_matchers:
      - severity =~ 'warning|info'
    equal: ['cluster']

  # 当数据库主节点宕机时,抑制从节点同步延迟告警
  - source_matchers:
      - alertname = MySQLMasterDown
    target_matchers:
      - alertname = MySQLReplicationLag
    equal: ['cluster']

5.2 告警聚合(Grouping)

通过 group_by 将相关告警合并为一条通知,减少通知数量:

route:
  group_by: ['alertname', 'cluster', 'service']  # 按告警名+集群+服务分组
  group_wait: 30s      # 首次告警等待 30s,收集同组告警
  group_interval: 5m   # 同组后续告警间隔 5 分钟
  repeat_interval: 4h  # 重复通知间隔 4 小时

聚合效果示例

聚合前(无 group_by):
  [10:00:01] CPU 高 - web-01
  [10:00:02] CPU 高 - web-02
  [10:00:03] CPU 高 - web-03
  [10:00:05] 内存高 - web-01
  [10:00:06] 磁盘高 - web-01
  → 5 条独立通知

聚合后(group_by: ['alertname', 'cluster']):
  [10:00:30] CPU 高 (web-01, web-02, web-03)
  [10:05:00] 内存高 (web-01) + 磁盘高 (web-01)
  → 2 条聚合通知

5.3 告警去重

在双副本 Prometheus 场景下,两个实例都会产生相同的告警。Alertmanager 通过 Gossip 协议去重:

# Alertmanager 启动参数(集群模式)
alertmanager \
  --cluster.listen-address=0.0.0.0:9094 \
  --cluster.peer=alertmanager-2:9094 \
  --cluster.peer=alertmanager-3:9094 \
  --cluster.gossip-interval=200ms \
  --cluster.pushpull-interval=1m

六、告警度量与治理

6.1 关键告警度量指标

指标定义目标值计算方式
告警准确率有效告警 / 总告警> 80%人工标注 + 定期审计
MTTR平均故障恢复时间< 30min事故记录
误报率误报告警 / 总告警< 10%告警 + 工单匹配
告警数量每日告警总数趋势下降Prometheus 指标
告警静默率被静默的告警 / 总告警< 20%Alertmanager API
P0 告警数每周 P0 告警数< 5告警记录
告警恢复率自动恢复告警 / 总告警< 30%Alertmanager metrics

6.2 用 Prometheus 度量告警

# 告警度量规则
groups:
  - name: alert-metrics
    rules:
      # 每日告警总数
      - record: alerts:fired:total_per_day
        expr: increase(ALERTS{alertstate="firing"}[24h])

      # 各级别告警数量
      - record: alerts:fired:by_severity
        expr: count by(severity) (ALERTS{alertstate="firing"})

      # 自动恢复率
      - record: alerts:auto_resolved:rate
        expr: |
          sum(rate(ALERTS_FOR_STATE{alertstate="resolved"}[1h])) /
          sum(rate(ALERTS_FOR_STATE[1h]))          

      # P0 告警数量(周)
      - record: alerts:critical:per_week
        expr: increase(ALERTS{alertstate="firing", severity="critical"}[7d])

6.3 告警治理流程

┌─────────────────────────────────────────────────────┐
│                  告警治理闭环                          │
│                                                     │
│  1. 告警审计(每月)                                  │
│     │                                                │
│     ▼                                                │
│  2. 分类标注:有效 / 误报 / 噪声 / 缺文档              │
│     │                                                │
│     ▼                                                │
│  3. 分析根因:阈值不当 / 缺少抑制 / 规则过时            │
│     │                                                │
│     ▼                                                │
│  4. 优化措施:调阈值 / 加抑制 / 删规则 / 补文档          │
│     │                                                │
│     ▼                                                │
│  5. 效果验证:下月告警数量和准确率对比                   │
│     │                                                │
│     ▼                                                │
│  6. 持续迭代 ←─────────────────────────────────────── │
└─────────────────────────────────────────────────────┘

6.4 告警审计脚本

#!/bin/bash
# alert-audit.sh — 月度告警审计报告

AM_URL="http://alertmanager:9093"
PROM_URL="http://prometheus:9090"

echo "========== 月度告警审计报告 =========="
echo "时间范围: $(date -d '1 month ago' '+%Y-%m-%d') ~ $(date '+%Y-%m-%d')"
echo ""

# 各级别告警数量
echo "## 告警数量统计"
curl -s "$PROM_URL/api/v1/query" \
  --data-urlencode 'query=sum by(severity)(increase(ALERTS{alertstate="firing"}[30d]))' | \
  jq -r '.data.result[] | "  \(.metric.severity): \(.value[1])"'

echo ""

# Top 10 高频告警
echo "## Top 10 高频告警"
curl -s "$PROM_URL/api/v1/query" \
  --data-urlencode 'query=topk(10, sum by(alertname)(increase(ALERTS{alertstate="firing"}[30d])))' | \
  jq -r '.data.result[] | "  \(.metric.alertname): \(.value[1])"'

echo ""

# 自动恢复率
echo "## 自动恢复率"
curl -s "$PROM_URL/api/v1/query" \
  --data-urlencode 'query=sum(rate(ALERTS{alertstate="resolved"}[30d])) / sum(rate(ALERTS[30d]))' | \
  jq -r '.data.result[0].value[1] | "  自动恢复率: \(.* 100 | floor)%"' 2>/dev/null

echo ""
echo "=========================================="

七、告警规则编写规范

7.1 告警规则模板

- alert: <AlertName>           # 驼峰命名,简洁明确
  expr: <PromQL>               # 查询表达式
  for: <duration>              # 持续时间,避免瞬时告警
  labels:
    severity: <level>          # critical/warning/info
    service: <service>         # 受影响的服务
    team: <team>               # 负责团队
  annotations:
    summary: "<一句话描述>"      # 简短摘要
    description: "<详细描述>"    # 包含当前值和影响
    runbook: "<URL>"           # 处理文档链接
    dashboard: "<URL>"         # Grafana 仪表盘链接

7.2 好告警 vs 坏告警

# ✗ 坏告警:信息不足,无法行动
- alert: HighCPU
  expr: cpu_usage > 80
  for: 5m
  labels:
    severity: warning

# ✓ 好告警:信息完整,可行动
- alert: APIServerHighCPU
  expr: |
    100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle", job="api-server"}[5m])) * 100) > 90    
  for: 10m
  labels:
    severity: warning
    service: api-server
    team: platform
  annotations:
    summary: "API Server CPU 使用率超过 90%"
    description: "实例 {{ $labels.instance }} CPU 使用率 {{ $value }}%,持续 10 分钟,可能影响 API 响应延迟"
    runbook: "https://wiki.internal/runbooks/api-server-high-cpu"
    dashboard: "https://grafana.internal/d/api-server?var-instance={{ $labels.instance }}"

7.3 告警规则 Review Checklist

检查项要求
告警名称简洁明确,能从名称判断问题
持续时间配置合理的 for 时间,避免瞬时尖峰
严重性标签正确的 severity 分级
摘要一句话说清楚发生了什么
描述包含当前值、影响范围、相关实例
Runbook有可操作的处理步骤文档
仪表盘有 Grafana 仪表盘链接
抑制已配置相关的 inhibit_rules
去重已加入合适的 group_by

八、告警降噪实战技巧

8.1 利用 for 消除瞬时尖峰

# 磁盘使用率告警:for 15m 避免日志写入尖峰
- alert: DiskSpaceWarning
  expr: |
    (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} /
     node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 < 20    
  for: 15m   # 持续 15 分钟才告警
  labels:
    severity: warning

8.2 利用预测函数提前告警

# 预测磁盘将在 24 小时内写满
- alert: DiskWillFillIn24h
  expr: |
    predict_linear(node_filesystem_avail_bytes[2h], 24 * 3600) < 0    
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "磁盘预计 24 小时内写满: {{ $labels.instance }}"
    description: "基于过去 2 小时趋势预测,{{ $labels.mountpoint }} 将在 24 小时内写满"

8.3 利用 absent 检测数据缺失

# Exporter 不可达导致数据缺失
- alert: ExporterDown
  expr: absent(up{job="node-exporter"})
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "所有 node-exporter 数据缺失"
    description: "可能整个监控网络故障,或所有 node-exporter 都已下线"

8.4 按时间范围智能告警

# 工作时间才告警非关键问题
- alert: LowReplicaCountBusinessHours
  expr: |
    kube_deployment_status_replicas < kube_deployment_spec_replicas
    and on() (hour() >= 8 and hour() < 22 and day_of_week() > 0 and day_of_week() < 6)    
  for: 10m
  labels:
    severity: warning

九、从告警到行动:Runbook

9.1 Runbook 的价值

告警的价值不在于通知本身,而在于通知后能被快速处理。Runbook(运维手册)是连接告警与处理动作的桥梁。

告警通知 → Runbook 链接 → 诊断步骤 → 修复操作 → 验证 → 关闭告警

9.2 Runbook 模板

# Runbook: API Server CPU 使用率过高

## 告警信息
- 告警名称: APIServerHighCPU
- 严重性: warning
- 影响范围: API 响应延迟可能升高

## 诊断步骤

### 1. 确认告警真实性
检查 Grafana 仪表盘确认 CPU 是否确实持续高位
- 仪表盘: https://grafana.internal/d/api-server

### 2. 检查异常进程
```bash
ssh {{ $labels.instance }}
top -c -b -n 1 | head -20

3. 检查请求量

确认是否有异常流量激增

curl -s http://localhost:9090/api/v1/query?query=rate(http_requests_total[5m])

修复操作

情况 A: 流量激增

  • 检查是否有异常请求源
  • 考虑临时扩容 Pod 副本数

情况 B: 资源泄漏

  • 检查 Goroutine 数量
  • 考虑滚动重启 Pod

升级路径

  • 如果 30 分钟内无法解决 → 升级为 P0 → 通知架构师团队

## 总结

构建高质量告警体系是一个持续优化的过程,核心要点:

- **告警即行动**:每条告警必须有明确的处理动作,不能行动的告警应该删除或降级为记录
- **分级是基础**:四级别体系让值班工程师知道该关注什么、忽略什么
- **SLO 是方向**:从阈值告警迁移到 SLO-based 告警,以用户体验为中心
- **抑制降噪**:利用 Alertmanager 的 inhibit 和 group_by 消除连锁告警和重复通知
- **度量驱动治理**:定期审计告警准确率、MTTR、误报率,用数据驱动持续优化
- **Runbook 闭环**:每条告警都应该有对应的处理文档,告警→诊断→修复→验证形成闭环

告警治理不是一次性工程,而是持续迭代的过程。每月审计、定期优化、持续迭代,才能让告警体系从"噪声制造机"变成"可靠的故障信号灯"。

## 参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

1. [Prometheus 告警好的实践](https://prometheus.io/docs/practices/alerting/) — Prometheus 官方,参考了Prometheus 告警好的实践相关内容