跳过正文
基于 Error Budget 的 Prometheus 告警设计——燃烧率告警实战

基于 Error Budget 的 Prometheus 告警设计——燃烧率告警实战

·981 字·5 分钟·
目录
SRE 可靠性工程师路径 - 这篇文章属于一个选集。
§ : 本文

我们有一条 Prometheus 告警规则运行了两年:http_error_rate > 0.01(错误率大于 1%)。它每周平均触发 30 次,其中大约 20 次是短暂抖动,5 分钟内自愈,工程师什么都不用做。

这 20 次"无效告警"造成的损失不只是噪音:它训练了工程师的条件反射——看到这个告警先观察 5 分钟,因为"可能自愈"。于是真正严重的那几次,响应也慢了 5 分钟。

燃烧率告警(Burn Rate Alerting)解决的就是这个问题。

为什么简单阈值告警不够
#

先看两个场景:

场景 A:错误率突然飙到 10%,持续 15 分钟后恢复正常。

场景 B:错误率维持在 0.5%,持续了整整一天。

如果你的告警规则是 error_rate > 1%,场景 A 会触发告警(正确),场景 B 不会触发告警(但它会让你的月度 SLO 从 99.9% 跌到 99.4%,损失巨大)。

问题根源:简单阈值告警度量的是瞬时状态,不度量影响积累速度。SLO 是一个月维度的约束,但告警是瞬时的,两者语义对不上。

燃烧率告警从另一个角度切入:你的 Error Budget 正在以多快的速度被消耗?

Error Budget 计算基础
#

以 30 天 SLO 99.9% 为例:

Error Budget(月度)= (1 - 0.999) × 30天 × 24小时 × 60分钟
                   = 0.001 × 43,200 分钟
                   = 43.2 分钟

也就是说,整个月内,服务最多允许 43.2 分钟的"错误时间"(以 100% 错误率计算)。

换算为每小时的允许消耗:

每小时允许消耗 = 43.2 分钟 / (30 × 24 小时)
              = 43.2 / 720 分钟/小时
              = 0.06 分钟/小时 ≈ 每小时 3.6 秒的错误时间

关键概念:燃烧率(Burn Rate)

燃烧率 = 当前错误率 / (1 - SLO)
       = 当前错误率 / error_budget_ratio

以 SLO 99.9%(error_budget_ratio = 0.001)为例:

当前错误率燃烧率含义
0.1%1x正好以"预算速度"消耗,30 天刚好耗尽
1%10x10 倍速消耗,3 天耗尽月度预算
10%100x100 倍速,7.2 小时耗尽月度预算
14.4%144x2 小时耗尽月度预算 → 需要立即响应

现在告警的意义变清晰了:不是"错误率高了",而是"按这个速度,月度预算将在 X 小时内耗尽"

多窗口燃烧率告警规则
#

Google SRE Workbook 推荐的多窗口方案:使用长短窗口配对,短窗口提高召回率(不漏警),长窗口提高精确率(减少误报)。

同时触发短窗口 AND 长窗口告警时,才认为是真实故障。

完整 Prometheus 告警规则 YAML
#

groups:
  - name: slo_burn_rate_alerts
    rules:
      # P1:极速燃烧 - 预计 2 小时内耗尽月度预算
      - alert: HighErrorBudgetBurnRate
        expr: |
          (
            job:slo_errors_per_request:ratio_rate1h{job="payment-service"} > (14.4 * 0.001)
          )
          and
          (
            job:slo_errors_per_request:ratio_rate5m{job="payment-service"} > (14.4 * 0.001)
          )
        for: 2m
        labels:
          severity: critical
          team: payment
        annotations:
          summary: '{{ $labels.job }} 极高错误燃烧率:预计 2 小时内耗尽月度 Error Budget'
          description: |
            服务 {{ $labels.job }} 当前 1h 燃烧率为 {{ $value | humanizePercentage }}(阈值 14.4x)。
            按此速度,月度 Error Budget 将在约 2 小时内耗尽。
            Runbook: https://wiki.example.com/runbook/high-burn-rate
            Grafana: https://grafana.example.com/d/slo-dashboard?var-job={{ $labels.job }}

      # P2:快速燃烧 - 预计 6 小时内耗尽月度预算
      - alert: MediumErrorBudgetBurnRate
        expr: |
          (
            job:slo_errors_per_request:ratio_rate6h{job="payment-service"} > (6 * 0.001)
          )
          and
          (
            job:slo_errors_per_request:ratio_rate30m{job="payment-service"} > (6 * 0.001)
          )
        for: 15m
        labels:
          severity: warning
          team: payment
        annotations:
          summary: '{{ $labels.job }} 较高错误燃烧率:预计 6 小时内消耗 5% 月度 Error Budget'
          description: |
            服务 {{ $labels.job }} 当前 6h 燃烧率为 {{ $value | humanizePercentage }}(阈值 6x)。
            Runbook: https://wiki.example.com/runbook/medium-burn-rate

      # P3:趋势告警 - 3 天窗口燃烧率超标
      - alert: SlowErrorBudgetBurnRate
        expr: |
          job:slo_errors_per_request:ratio_rate3d{job="payment-service"} > (1 * 0.001)
        for: 1h
        labels:
          severity: info
          team: payment
        annotations:
          summary: '{{ $labels.job }} Error Budget 消耗趋势告警:按当前速度月底将超出预算'
          description: |
            服务 {{ $labels.job }} 3 天燃烧率超过 1x(SLO 基准线),月底有超出 Error Budget 风险。
            当前剩余 Error Budget: {{ $value }}%

各窗口的计算逻辑
#

告警级别短窗口长窗口燃烧率阈值预计耗尽时间
P1 Critical5m1h> 14.4x~2 小时
P2 Warning30m6h> 6x~5 小时内消耗 5%
P3 Info3d> 1x月底超出

为什么 14.4 这个数字?

月度允许消耗比例 = 1 - SLO = 0.001
2 小时耗尽 = 2h / (30d × 24h) = 2 / 720 ≈ 0.00278
燃烧率 = 0.00278 / 0.001 × 100% ≈ 2.78%
但这里说的是"2小时内耗尽月度预算的 5%"
实际阈值:如果要在 1 小时窗口内消耗 2% 的月预算
2% × 0.001 / (1/720) ≈ 14.4

直接记住结论即可:P1 = 14.4x,P2 = 6x,这是 Google SRE Workbook 的推荐值。

Recording Rules:性能优化的关键
#

燃烧率计算涉及多个时间窗口的比率运算,实时计算会让 Prometheus 查询超时,而且同样的表达式会被多条规则重复计算。

Recording Rules 把高频计算的结果预先存储为新指标:

groups:
  - name: slo_recording_rules
    interval: 30s
    rules:
      # 基础错误率:HTTP 5xx / 总请求数
      - record: job:http_requests_total:rate5m
        expr: |
          sum(rate(http_requests_total[5m])) by (job, status_code)

      - record: job:http_errors_total:rate5m
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[5m])) by (job)

      # SLI:各时间窗口的错误率
      - record: job:slo_errors_per_request:ratio_rate5m
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[5m])) by (job)
          /
          sum(rate(http_requests_total[5m])) by (job)

      - record: job:slo_errors_per_request:ratio_rate30m
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[30m])) by (job)
          /
          sum(rate(http_requests_total[30m])) by (job)

      - record: job:slo_errors_per_request:ratio_rate1h
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[1h])) by (job)
          /
          sum(rate(http_requests_total[1h])) by (job)

      - record: job:slo_errors_per_request:ratio_rate6h
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[6h])) by (job)
          /
          sum(rate(http_requests_total[6h])) by (job)

      - record: job:slo_errors_per_request:ratio_rate3d
        expr: |
          sum(rate(http_requests_total{status_code=~"5.."}[3d])) by (job)
          /
          sum(rate(http_requests_total[3d])) by (job)

      # Error Budget 剩余量(百分比)
      - record: job:slo_error_budget_remaining:ratio
        expr: |
          1 - (
            sum_over_time(job:slo_errors_per_request:ratio_rate5m[30d])
            /
            count_over_time(job:slo_errors_per_request:ratio_rate5m[30d])
          ) / 0.001

Recording Rules 的命名约定遵循 level:metric:operations 格式:

  • job = 聚合维度
  • slo_errors_per_request = 指标含义
  • ratio_rate5m = 计算方式(比率 + 窗口)

延迟 SLI 的 PromQL 示例
#

除了错误率,P99 延迟是另一个常见 SLI。

# P99 延迟(使用 histogram_quantile,需要应用上报 histogram 类型指标)
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket{job="payment-service"}[5m])) by (le, job)
)

# 延迟 SLI:超过 1 秒的请求比例(另一种方式,更精确)
(
  sum(rate(http_request_duration_seconds_bucket{job="payment-service", le="1.0"}[5m]))
  /
  sum(rate(http_request_duration_seconds_count{job="payment-service"}[5m]))
)

# 综合 SLI:同时满足错误率和延迟的请求占比(复合 SLO)
(
  sum(rate(http_requests_total{job="payment-service", status_code!~"5..", duration_le="1.0"}[5m]))
  /
  sum(rate(http_requests_total{job="payment-service"}[5m]))
)

延迟的燃烧率配置方式和错误率完全相同,只是把 ratio_rate* 指标换成延迟 SLI 的 Recording Rule。

Grafana Dashboard 设计
#

Error Budget Dashboard 需要回答三个问题:

  1. 当前状态:现在的错误率是多少,燃烧率是多少?
  2. 历史趋势:本月 Error Budget 消耗曲线
  3. 剩余预算:还剩多少 Error Budget?

推荐面板布局
#

Row 1:当前状态(Stat 面板)

  • 当前错误率(job:slo_errors_per_request:ratio_rate5m
  • 当前燃烧率(错误率 / 0.001)
  • Error Budget 剩余百分比

Row 2:燃烧曲线(Time Series)

# 各时间窗口燃烧率对比
job:slo_errors_per_request:ratio_rate1h{job="payment-service"} / 0.001
job:slo_errors_per_request:ratio_rate6h{job="payment-service"} / 0.001

# 告警阈值参考线
vector(14.4)  # P1 阈值
vector(6)     # P2 阈值

Row 3:Error Budget 剩余量(Gauge + Time Series)

# 剩余 Error Budget 百分比(Gauge,0-100%)
(1 - (
  sum_over_time(job:slo_errors_per_request:ratio_rate5m{job="payment-service"}[30d:5m])
  / count_over_time(job:slo_errors_per_request:ratio_rate5m{job="payment-service"}[30d:5m])
) / 0.001) * 100

Row 4:请求量和错误分布(Bar Chart)

# 按错误码分组的请求量
sum(rate(http_requests_total{job="payment-service"}[5m])) by (status_code)

颜色编码建议
#

Error Budget 剩余量 Gauge 使用阈值着色:

  • 绿色:> 50%(健康)
  • 黄色:20%-50%(关注)
  • 橙色:5%-20%(告警)
  • 红色:< 5%(危险)

告警文本模板:让 On-Call 一眼看懂
#

好的告警通知应该包含:发生了什么、有多严重、该去哪里处理

# Alertmanager 消息模板(Go template)
annotations:
  summary: |
    [{{ .Labels.severity | toUpper }}] {{ .Labels.job }} Error Budget 燃烧告警
  description: |
    🚨 服务:{{ .Labels.job }}
    📊 燃烧率:{{ $value | printf "%.1f" }}x(正常基准 1x)
    ⏱ 按此速度月度 Error Budget 将在 {{ if gt $value 14.4 }}2 小时{{ else if gt $value 6.0 }}6 小时{{ else }}本月底{{ end }}耗尽
    📉 当前 1h 错误率:{{ with query "job:slo_errors_per_request:ratio_rate1h" }}{{ . | first | value | humanizePercentage }}{{ end }}

    ➡️  Runbook:https://wiki.example.com/runbook/slo-burn-rate
    📈 Grafana:https://grafana.example.com/d/slo?var-job={{ .Labels.job }}
    🔍 日志:https://loki.example.com/?query={job="{{ .Labels.job }}"}

钉钉效果示意:

[CRITICAL] payment-service Error Budget 燃烧告警

服务:payment-service
燃烧率:18.3x(正常基准 1x)
⏱ 按此速度月度 Error Budget 将在 2 小时耗尽
当前 1h 错误率:1.83%

➡️ Runbook:...
📈 Grafana:...

收到这条告警,工程师不需要去查面板就知道:问题很严重(18x),很紧急(2 小时),要去哪里处理。

常见陷阱
#

陷阱 1:忘记设置 for 参数

- alert: HighBurnRate
  expr: ...
  # 没有 for,瞬间触发

没有 for 的告警会在条件刚满足时立即触发,非常容易产生抖动误报。建议 P1 设 for: 2m,P2 设 for: 15m

陷阱 2:Recording Rules 的 interval 设置过长

如果 Recording Rule 的 interval: 5m,而你的告警 for: 2m,告警可能因为数据刷新不及时而产生奇怪的行为。Recording Rule interval 应该 ≤ 告警的 for 时间的一半。

陷阱 3:SLO 基准值写死在告警表达式里

# 糟糕的做法
expr: job:slo_errors_per_request:ratio_rate1h > 0.0144  # 14.4 × 0.001

当 SLO 从 99.9% 调整为 99.95% 时,你需要找到所有告警规则并更新。更好的做法是用 Recording Rule 存储 SLO 配置,或者在 Helm values 中管理。

# 更好的做法(Helm values 注入)
expr: |
  job:slo_errors_per_request:ratio_rate1h{job="{{ .Values.service.name }}"}
  > (14.4 * {{ .Values.slo.errorBudget }})

陷阱 4:多窗口条件写 OR 而不是 AND

# 错误:OR 条件太宽松,误报率高
expr: |
  job:slo_errors_per_request:ratio_rate1h > 0.0144
  or
  job:slo_errors_per_request:ratio_rate5m > 0.0144

# 正确:AND 条件,两个窗口都超标才告警
expr: |
  job:slo_errors_per_request:ratio_rate1h > 0.0144
  and
  job:slo_errors_per_request:ratio_rate5m > 0.0144

OR 条件会因为短窗口抖动频繁触发。多窗口方案的精髓就是 AND:短窗口提高灵敏度,长窗口过滤噪音。

陷阱 5:只做错误率 SLO,忽略延迟 SLO

用户感受到的"慢"和"错"同样影响体验。P99 延迟超 2 秒的比例,和错误率一样需要 Error Budget 管理。两个维度的 SLO 可以用不同的 recording rule 系列分别管理。


切到燃烧率告警前期要写一堆 Recording Rules 和 Dashboard,但换来的是:告警真的可以被信任,不再需要"先观察 5 分钟"。我们团队切完之后 P1 告警的 MTTA 从平均 12 分钟降到 6 分钟——这 6 分钟全是真刀实枪,不是"看是不是误报"。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。
SRE 可靠性工程师路径 - 这篇文章属于一个选集。
§ : 本文

相关文章

SLO/SLI/Error Budget 从理论到落地:SRE 可靠性工程实战

·1096 字·6 分钟
从 SLI 指标选取到 Error Budget 消耗速率告警,系统讲解 SRE 可靠性工程体系的落地实践,包括 Prometheus recording rules 计算 SLI、多窗口 burn rate 告警规则配置、SLO 违规复盘流程,以及与开发团队的协作策略。

On-Call 工程实践:从告警响应到 Runbook 设计

·849 字·4 分钟
好的 On-Call 体系不是让人 24 小时盯着屏幕,而是让每一次叫醒都有价值。从告警质量到 Runbook 设计,从轮班制度到数据驱动改进,这篇文章是我们团队在生产环境打磨 3 年的实践总结。