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

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

·1096 字·6 分钟·
目录
SRE 实战手册 - 这篇文章属于一个选集。
§ : 本文

三年前我在推 SLO 体系时,被开发团队问了一个让我一时语塞的问题:“我们已经有 uptime 监控了,为什么还要搞这么复杂的东西?” 这篇文章是我对这个问题的完整回答,以及这三年实践下来的经验总结。

为什么需要 SLO 而不是 uptime
#

传统的 uptime 监控只告诉你服务"是否在线",但用户体验远比这复杂:服务在线但响应需要 30 秒,算不算正常?成功率 95% 但某个核心接口失败率 50%,算不算正常?

SLO(Service Level Objective)体系的核心价值是把可靠性量化,让工程决策有依据

  • 没有 Error Budget:每次故障都是"严重事故",团队陷入焦虑循环
  • 有 Error Budget:可以理性讨论"我们还有多少容忍度",发布决策有数据支撑

SLI 指标选取
#

SLI(Service Level Indicator)是用来衡量服务质量的具体指标。选取原则:站在用户视角,衡量用户实际感受到的服务质量。

三类核心 SLI
#

1. 可用性 SLI(Availability)

最直接的衡量方式:成功请求占总请求的比例。

availability = successful_requests / total_requests

什么算"成功"需要明确定义:

  • HTTP 2xx/3xx 算成功
  • 4xx 通常不算服务失败(是客户端错误),但要视业务而定
  • 5xx 算服务失败
  • 超时算失败

2. 延迟 SLI(Latency)

不要用平均延迟,用分位数:

latency_p99 = 99th percentile of request duration

选 P99 还是 P999 取决于业务场景。支付类接口对尾延迟敏感,可以用 P999;普通查询接口用 P99 足够。

3. 错误率 SLI(Error Rate)

error_rate = error_requests / total_requests

有时候可用性 SLI 和错误率 SLI 是等价的(availability = 1 - error_rate),但某些场景需要分开:比如可以接受 5xx 但不接受超时,就需要分别计算。

SLI 选取的常见误区
#

  • 用基础设施指标代替用户体验指标:CPU 使用率高不一定导致用户受影响,不适合作为 SLI
  • SLI 太多:一个服务超过 5 个 SLI 就难以管理,聚焦在最影响用户的 2-3 个
  • 忽略长尾用户:平均延迟良好不代表没有用户在受苦

Prometheus Recording Rules 计算 SLI
#

Recording Rules 把复杂的 PromQL 预计算成新的时序,显著提升 Dashboard 查询性能,也让告警规则更简洁。

首先,需要确保有原始指标。 以 HTTP 服务为例,Prometheus 通常会有:

# HTTP 请求总数(含状态码标签)
http_requests_total{service="my-service", status="200"}
http_requests_total{service="my-service", status="500"}

# HTTP 请求延迟直方图
http_request_duration_seconds_bucket{service="my-service", le="0.1"}
http_request_duration_seconds_bucket{service="my-service", le="0.5"}
http_request_duration_seconds_bucket{service="my-service", le="1.0"}

定义 Recording Rules:

# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: slo-recording-rules
  namespace: monitoring
  labels:
    prometheus: kube-prometheus
    role: alert-rules
spec:
  groups:
    # ===== 原始 SLI 计算(5m 窗口)=====
    - name: slo.sli.raw
      interval: 30s
      rules:
        # 可用性:过去5分钟的成功请求比例
        - record: job:http_requests:success_rate5m
          expr: |
            sum(rate(http_requests_total{status=~"2..|3.."}[5m])) by (job)
            /
            sum(rate(http_requests_total[5m])) by (job)

        # 错误率:过去5分钟的5xx比例
        - record: job:http_requests:error_rate5m
          expr: |
            sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
            /
            sum(rate(http_requests_total[5m])) by (job)

        # P99 延迟
        - record: job:http_request_duration_seconds:p99_5m
          expr: |
            histogram_quantile(
              0.99,
              sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le)
            )

    # ===== SLO 窗口计算(用于 Error Budget)=====
    - name: slo.windows
      interval: 30s
      rules:
        # 过去1小时的错误预算消耗
        - record: job:http_requests:error_budget_burn_rate1h
          expr: |
            (
              1 - sum(rate(http_requests_total{status=~"2..|3.."}[1h])) by (job)
              / sum(rate(http_requests_total[1h])) by (job)
            )
            / (1 - 0.999)  # 除以 (1 - SLO目标),SLO=99.9%

        # 过去6小时的消耗速率
        - record: job:http_requests:error_budget_burn_rate6h
          expr: |
            (
              1 - sum(rate(http_requests_total{status=~"2..|3.."}[6h])) by (job)
              / sum(rate(http_requests_total[6h])) by (job)
            )
            / (1 - 0.999)

        # 过去3天的消耗速率
        - record: job:http_requests:error_budget_burn_rate3d
          expr: |
            (
              1 - sum(rate(http_requests_total{status=~"2..|3.."}[3d])) by (job)
              / sum(rate(http_requests_total[3d])) by (job)
            )
            / (1 - 0.999)

        # 30天窗口剩余 Error Budget 百分比
        - record: job:http_requests:error_budget_remaining30d
          expr: |
            1 - (
              (
                1 - sum(rate(http_requests_total{status=~"2..|3.."}[30d])) by (job)
                / sum(rate(http_requests_total[30d])) by (job)
              )
              / (1 - 0.999)
            )

burn rate 的含义:

  • burn rate = 1:Error Budget 消耗速率恰好等于 SLO 允许的速率(30天刚好耗尽)
  • burn rate = 2:消耗速度是正常的2倍(15天就耗尽)
  • burn rate = 14.4:在1小时内消耗了约5%的月度 Error Budget(需要立即响应)

Grafana SLO Dashboard
#

一个好的 SLO Dashboard 需要展示三层信息:当前 SLI 状态、Error Budget 剩余量、历史趋势。

关键 Panel 配置:

// Panel 1: 当前可用性(Stat Panel)
{
  "title": "服务可用性(过去5分钟)",
  "type": "stat",
  "targets": [
    {
      "expr": "job:http_requests:success_rate5m{job='my-service'} * 100",
      "legendFormat": "可用性 %"
    }
  ],
  "fieldConfig": {
    "defaults": {
      "unit": "percent",
      "thresholds": {
        "steps": [
          {"color": "red", "value": 0},
          {"color": "yellow", "value": 99},
          {"color": "green", "value": 99.9}
        ]
      }
    }
  }
}
// Panel 2: Error Budget 剩余(Gauge Panel)
{
  "title": "Error Budget 剩余(本月)",
  "type": "gauge",
  "targets": [
    {
      "expr": "job:http_requests:error_budget_remaining30d{job='my-service'} * 100",
      "legendFormat": "剩余 %"
    }
  ],
  "fieldConfig": {
    "defaults": {
      "unit": "percent",
      "min": 0,
      "max": 100,
      "thresholds": {
        "steps": [
          {"color": "red", "value": 0},
          {"color": "yellow", "value": 25},
          {"color": "green", "value": 50}
        ]
      }
    }
  }
}

Grafana Dashboard JSON 关键配置(Time Series Panel):

// Panel 3: Burn Rate 趋势
{
  "title": "Error Budget 消耗速率",
  "type": "timeseries",
  "targets": [
    {
      "expr": "job:http_requests:error_budget_burn_rate1h{job='my-service'}",
      "legendFormat": "1h burn rate"
    },
    {
      "expr": "job:http_requests:error_budget_burn_rate6h{job='my-service'}",
      "legendFormat": "6h burn rate"
    }
  ],
  "options": {
    "tooltip": {"mode": "multi"}
  },
  "fieldConfig": {
    "overrides": [
      {
        "matcher": {"id": "byName", "options": "1h burn rate"},
        "properties": [
          {"id": "color", "value": {"mode": "fixed", "fixedColor": "orange"}}
        ]
      }
    ]
  }
}

Error Budget Burn Rate 告警
#

Google SRE 书中推荐的多窗口 burn rate 告警是目前最实用的告警策略,核心思路是:用多个时间窗口交叉确认,避免短暂毛刺触发告警,也避免低速消耗长期不告警。

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: slo-alerts
  namespace: monitoring
spec:
  groups:
    - name: slo.alerts
      rules:
        # 告警级别 P1:快速消耗(2% Error Budget 在1小时内)
        # burn rate 14.4 = 消耗速率是正常的14.4倍
        # 在1小时内消耗了5%的月度 Error Budget 就需要立即处理
        - alert: SLOBurnRateCritical
          expr: |
            (
              job:http_requests:error_budget_burn_rate1h{job="my-service"} > 14.4
              AND
              job:http_requests:error_budget_burn_rate5m{job="my-service"} > 14.4
            )
          for: 2m  # 持续2分钟才告警,过滤毛刺
          labels:
            severity: critical
            team: backend
          annotations:
            summary: "{{ $labels.job }} SLO Error Budget 快速消耗"
            description: |
              服务 {{ $labels.job }} 的 Error Budget 消耗速率为 {{ $value | humanize }}x(正常值1x)。
              当前速率下,月度 Error Budget 将在 {{ div 730 $value | humanizeDuration }} 内耗尽。
              当前消耗速率:{{ $value }}
            runbook_url: "https://wiki.example.com/runbooks/slo-burnrate"

        # 告警级别 P2:中速消耗(5% Error Budget 在6小时内)
        - alert: SLOBurnRateHigh
          expr: |
            (
              job:http_requests:error_budget_burn_rate6h{job="my-service"} > 6
              AND
              job:http_requests:error_budget_burn_rate30m{job="my-service"} > 6
            )
          for: 15m
          labels:
            severity: warning
            team: backend
          annotations:
            summary: "{{ $labels.job }} SLO Error Budget 消耗偏高"
            description: |
              服务 {{ $labels.job }} 的 Error Budget 消耗速率偏高({{ $value | humanize }}x)。
              请检查服务状态,评估是否需要暂停发布。

        # 告警级别 P3:慢速消耗(10% Error Budget 在3天内)
        - alert: SLOBurnRateMedium
          expr: |
            (
              job:http_requests:error_budget_burn_rate3d{job="my-service"} > 1
              AND
              job:http_requests:error_budget_burn_rate6h{job="my-service"} > 1
            )
          for: 1h
          labels:
            severity: info
            team: backend
          annotations:
            summary: "{{ $labels.job }} SLO Error Budget 消耗持续"
            description: "Error Budget 消耗速率大于1x,本月 Error Budget 有超支风险。"

        # Error Budget 耗尽告警
        - alert: SLOErrorBudgetExhausted
          expr: |
            job:http_requests:error_budget_remaining30d{job="my-service"} < 0.1
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "{{ $labels.job }} Error Budget 剩余不足10%"
            description: "本月 Error Budget 剩余 {{ $value | humanizePercentage }},建议冻结非紧急发布。"

多窗口告警的逻辑说明:

使用两个窗口 AND 的原因:

  • 长窗口(1h、6h、3d)检测持续性问题,过滤短暂抖动
  • 短窗口(5m、30m)确认问题正在发生,过滤历史遗留的"尾巴"

SLO 违规复盘流程
#

SLO 违规(Error Budget 耗尽或接近耗尽)后,需要有结构化的复盘流程。我用的模板:

复盘文档结构:

# SLO 违规复盘:my-service 2026-04-10

## 事件概述
- 违规时间窗口:2026-04-10 14:30 ~ 2026-04-10 16:45
- 受影响 SLI:HTTP 可用性(目标 99.9%,实际 97.2%)
- Error Budget 消耗:本月预算的 68%(本次事件消耗)

## 时间线
| 时间 | 事件 |
|------|------|
| 14:25 | 发布 v2.3.4 |
| 14:30 | P1 告警触发,burn rate 超过14.4 |
| 14:35 | 值班工程师响应 |
| 14:42 | 确认是新版本问题,开始回滚 |
| 14:58 | 回滚完成,服务恢复 |
| 16:45 | 延迟影响彻底消除 |

## 根因分析
新版本引入了一个 N+1 查询问题,在高流量下数据库连接池耗尽,导致大量请求超时。

## 改进措施
| 措施 | 负责人 | 截止日期 |
|------|--------|----------|
| 添加数据库连接数监控告警 | 张三 | 2026-04-17 |
| 代码审查加入查询性能检查 | 李四 | 2026-04-20 |
| 引入 SQL 慢查询自动检测 | 王五 | 2026-04-24 |

Error Budget 冻结策略: 当 Error Budget 消耗超过 50% 时,我们的策略是:

  1. 所有非紧急功能发布暂停
  2. 只允许修复性发布
  3. 下次发布前必须经过 SRE review

与开发团队的沟通策略
#

这是 SLO 体系落地最难的部分,技术实现反而简单。

常见阻力和应对:

阻力1:“99.9% 太严格了,我们根本达不到”

解法:把 SLO 目标和 Error Budget 一起讲。“我们允许每月有 43 分钟不可用,目前我们还有 30 分钟的余量,这次发布需要评估风险。” 数字比百分比更有说服力。

阻力2:“告警太多了,都是误报”

解法:让开发参与调整告警阈值和 for 时间。告警质量是需要持续迭代的,第一版一定不完美。记录每次告警是否有效,3个月后回顾一次。

阻力3:“我的功能很重要,必须按时发布”

解法:把 Error Budget 展示在公共 Dashboard 上,让发布决策透明化。“当前还有 X% 的 Error Budget,这次发布风险评估是 Y,是否继续发布由团队共同决定。”

推进 SLO 文化的实用建议:

第一阶段(1-2个月):
  - 只观察,不告警
  - 建立 Dashboard,让团队熟悉指标含义
  - 找出数据异常点,修正 SLI 计算逻辑

第二阶段(3-4个月):
  - 只有 P1 告警(快速消耗)
  - 每月做一次 Error Budget 回顾会议
  - 建立 SLO 违规复盘流程

第三阶段(5个月+):
  - 引入 Error Budget 冻结策略
  - SLO 指标影响发布决策
  - 定期调整 SLO 目标(每季度回顾)

Sloth:SLO 配置自动化
#

手写 Recording Rules 和告警规则容易出错,Sloth 可以从简单的 SLO 定义自动生成 Prometheus 规则:

# sloth-slo.yaml
apiVersion: sloth.slok.dev/v1
kind: PrometheusServiceLevel
metadata:
  name: my-service-slo
  namespace: monitoring
spec:
  service: "my-service"
  labels:
    team: "backend"
  slos:
    - name: "requests-availability"
      objective: 99.9
      description: "HTTP 请求可用性 SLO"
      sli:
        events:
          error_query: sum(rate(http_requests_total{job="my-service", status=~"5.."}[{{.window}}]))
          total_query: sum(rate(http_requests_total{job="my-service"}[{{.window}}]))
      alerting:
        name: MyServiceHighErrorRate
        labels:
          team: backend
        annotations:
          summary: "my-service 错误率过高"
        page_alert:
          labels:
            severity: critical
        ticket_alert:
          labels:
            severity: warning
# 生成 Prometheus 规则
sloth generate -i sloth-slo.yaml -o generated-rules.yaml

# 应用
kubectl apply -f generated-rules.yaml

总结
#

SLO 落地这几年让我最受益的不是那些指标数字,是它给了研发和 SRE 一套共同语言。几个关键:

  1. SLI 必须是用户视角,不是 CPU/内存这种系统视角
  2. Recording Rules 先行,否则告警和 Dashboard 都会慢死
  3. 多窗口 burn rate 是目前最实用的 SLO 告警,能降噪
  4. Error Budget 是决策工具,发版能不能扛就看它
  5. 先观察再告警,不然团队直接被淹掉

最后补一刀:SLO 不是越高越好。一味追 99.99% 会把团队耗死。合适的目标是"比用户期望略高一点",留出 budget 让工程师能折腾新东西。

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

相关文章

可观测性建设:从 Prometheus 采集到 Grafana 告警联动

·861 字·5 分钟
可观测性不是装几个监控工具,而是让系统在出问题时能快速定位根因。这篇文章从采集架构到 PromQL 到告警路由,覆盖我们在生产环境中实际遇到的 cardinality 爆炸、告警噪音等问题。

可观测性三支柱实战:Metrics/Logs/Traces 联动

·1110 字·6 分钟
监控告诉你系统挂了,可观测性告诉你为什么挂。本文从三支柱的核心差异出发,讲透 Prometheus+Loki+Tempo 的联动排障流程,覆盖 OpenTelemetry 采集标准、Exemplar 原理与配置,以及可观测性建设的优先级策略。

多集群 Kubernetes 运维:跨集群管理与统一可观测

·1202 字·6 分钟
从单集群到多集群,运维复杂度不是线性增加,而是指数级。这篇文章总结了我们管理跨地域、跨环境多套 K8s 集群的实际经验:如何用 ArgoCD ApplicationSet 统一部署、如何用 Thanos 聚合多集群指标、以及一次真实的跨集群迁移过程。