错误预算用完了怎么办?——SLO 红线的应急处置 原创
本文是 SRE 实战系列第 10 篇,聚焦错误预算耗尽后的应急处置流程和决策机制。全文约 7500 字,包含可直接执行的决策流程图、冻结检查清单、降级操作命令和复盘模板。
一、错误预算是什么,为什么它会用完
错误预算(Error Budget)是 SRE 的核心概念:如果某服务的 SLO 目标是 99.9%,那么年化错误预算就是 0.1% × 365 天 × 24 小时 = 8.76 小时。这 8.76 小时是全年允许的服务不可用时间。
错误预算不是「配额」,而是可靠性投资的信用额度。它回答了一个关键问题:为了发布新功能,你愿意承担多大的稳定性风险?
错误预算的三种消耗模式
| 消耗模式 | 典型场景 | 特征 |
|---|---|---|
| 突发消耗 | 线上故障、发布事故 | 几小时内烧掉数周预算 |
| 慢性消耗 | 小 bug 累积、性能退化 | 每天烧一点,不易察觉 |
| 计划消耗 | 计划内变更、数据中心迁移 | 可控,可提前规划 |
突发消耗最危险——它往往在团队还没反应过来时就把预算烧光了。本文重点讲的就是:预算已经用完了,接下来怎么办。
二、错误预算耗尽的三级响应机制
错误预算不是一用完就拉闸。根据剩余预算比例,设置三级响应:
三级响应阈值
| 级别 | 剩余预算比例 | 状态 | 行动 |
|---|---|---|---|
| 🟡 黄色预警 | < 50% | 关注 | 周会通报,评估近期变更计划 |
| 🟠 橙色告警 | < 25% | 警惕 | 暂停非紧急发布,启动每日站会跟踪 |
| 🔴 红色熔断 | < 0%(耗尽) | 紧急 | 立即冻结发布,启动应急流程 |
关键原则
错误预算耗尽 ≠ 服务不可用。预算耗尽意味着「本周期(通常 30 天)的可靠性承诺已经兑现完毕」,接下来任何额外的风险都可能违反 SLO。
所以响应的核心是:停止引入新的风险,同时保障现有服务的稳定性。
三、红色熔断:错误预算耗尽后的 5 步应急流程
当错误预算进入红色熔断状态,按以下 5 步执行。每一步都有明确的决策点和验收标准。
Step 1:确认预算状态(5 分钟)
目标:排除误报,确认预算确实耗尽。
操作清单:
- 检查 Prometheus 中错误预算指标:
slo:period_error_budget_remaining:ratio - 确认时间窗口:是 30 天滚动窗口还是自然月窗口?
- 核对 SLI 计算逻辑:events 类型的分子分母是否正确?
- 检查是否有重复计算:同一故障是否被多个 SLI 重复计入?
PromQL 查询:
# 查看所有服务的剩余错误预算
slo:period_error_budget_remaining:ratio < 0
# 查看具体服务的预算消耗速率
slo:period_burn_rate:ratio{sloth_service="payment-api"}
# 查看历史消耗趋势(过去 7 天)
slo:period_error_budget_remaining:ratio{sloth_service="payment-api"}[7d]决策点:如果确认是计算错误(比如 SLI 查询写错了),修复后重新评估。如果是真实的预算耗尽,进入 Step 2。
Step 2:立即冻结发布(15 分钟)
目标:停止引入新的变更风险。
冻结范围:
| 冻结项 | 说明 | 例外情况 |
|---|---|---|
| 功能发布 | 暂停所有非紧急的功能上线 | 安全补丁、合规要求 |
| 配置变更 | 暂停 ConfigMap、环境变量等变更 | 故障修复配置 |
| 基础设施变更 | 暂停节点扩缩容、集群升级 | 容量告警触发的扩容 |
| 数据变更 | 暂停 schema 变更、大规模数据迁移 | — |
CI/CD 冻结实现:
在 GitLab CI 或 GitHub Actions 中加入错误预算检查门禁:
# .gitlab-ci.yml 中的错误预算门禁
error_budget_check:
stage: pre_deploy
script:
- |
BUDGET=$(curl -s "http://prometheus:9090/api/v1/query?query=slo:period_error_budget_remaining:ratio{sloth_service=\"$SERVICE\"}" | jq -r '.data.result[0].value[1]')
if (( $(echo "$BUDGET < 0" | bc -l) )); then
echo "❌ 错误预算已耗尽 ($BUDGET),禁止发布"
exit 1
fi
echo "✅ 剩余错误预算: $BUDGET"
only:
- main通知模板:
【发布冻结通知】
服务:payment-api
SLO:99.9%(30 天滚动窗口)
错误预算状态:已耗尽(剩余 -12.3%)
冻结范围:功能发布、配置变更、基础设施变更
冻结期限:直至预算恢复至 25% 以上
审批通道:紧急发布需 CTO 审批Step 3:止血与恢复(1-4 小时)
目标:处理正在进行的故障,恢复服务稳定性。
优先级排序:
- P0 - 正在发生的故障:先止血(回滚、降级、扩容),再排查根因
- P1 - 已知隐患:检查是否有未修复的已知 bug 可能在近期触发
- P2 - 监控盲区:检查是否有未被 SLI 覆盖的故障模式
止血操作命令速查:
# 快速回滚 Deployment
kubectl rollout undo deployment/payment-api
# 紧急扩容(HPA 未触发时手动扩容)
kubectl scale deployment payment-api --replicas=10
# 开启熔断(如果服务有熔断配置)
kubectl patch configmap payment-config --type merge -p '{"data":{"circuit_breaker":"true"}}'
# 切换流量到备用集群(多集群场景)
kubectl apply -f failover-to-backup-cluster.yaml关键原则:止血阶段不求根因,先求恢复。根因分析放在 Step 5 复盘阶段。
Step 4:评估紧急发布需求(30 分钟)
冻结不是绝对的。有些发布必须走,但需要升级审批。
紧急发布审批矩阵:
| 发布类型 | 默认决策 | 审批层级 | 需要提供的证据 |
|---|---|---|---|
| 安全补丁 | ✅ 允许 | 安全负责人 | CVE 编号、影响范围评估 |
| 合规要求 | ✅ 允许 | 法务/合规负责人 | 法规依据、截止日期 |
| 故障修复 | ✅ 允许 | SRE 负责人 | 故障单号、修复验证报告 |
| 收入影响 | ⚠️ 评估 | 业务负责人 + SRE 负责人 | 收入损失预估、回滚方案 |
| 功能上线 | ❌ 禁止 | CTO | 业务不可替代性论证 |
审批检查清单:
- 该发布是否解决当前或即将发生的故障?
- 是否有经过充分测试的回滚方案?
- 发布窗口是否避开业务高峰?
- 是否有专人值守(on-call)?
- 发布后是否有自动化的健康检查?
Step 5:复盘与预算恢复计划(1-2 天)
目标:理解预算为什么耗尽,制定恢复计划。
复盘会议模板
会议信息:
- 时间:错误预算耗尽后 48 小时内
- 参与者:SRE、开发、测试、产品代表
- 时长:90 分钟
议程:
时间线重建(20 分钟)
- 精确到分钟的故障发生、发现、响应、恢复时间线
- 使用 PagerDuty/OpsGenie 的 incident timeline 作为基础
预算消耗分析(20 分钟)
- 哪些故障消耗了预算?各消耗多少?
- 是否有未被 SLI 覆盖的故障?
- 慢性消耗 vs 突发消耗的比例?
根因分析(30 分钟)
- 使用 5 Whys 或鱼骨图
- 区分触发因素、促成因素、根本原因
改进措施(20 分钟)
- 每个措施必须有负责人和截止日期
- 措施必须能防止同类故障再次发生,或缩短检测/恢复时间
复盘文档模板:
# Incident Review: [故障标题]
## 基本信息
- 服务: payment-api
- SLO: 99.9% (30 天滚动窗口)
- 预算消耗: 45.2% (单次故障)
- 故障时间: 2024-01-15 14:32 - 15:47
- 检测时间: 14:35 (3 分钟后)
- 恢复时间: 15:47 (1 小时 15 分钟)
## 时间线
| 时间 | 事件 | 备注 |
|------|------|------|
| 14:32 | 发布 v2.3.1 | 包含订单查询优化 |
| 14:35 | 告警触发 | 5xx 率 > 1% |
| 14:38 | on-call 响应 | 开始排查 |
| 14:50 | 定位到数据库连接池耗尽 | — |
| 15:10 | 尝试扩容,无效 | 问题在连接池配置 |
| 15:30 | 回滚到 v2.3.0 | 5xx 率开始下降 |
| 15:47 | 完全恢复 | — |
## 根因分析
**触发因素**: 新版本的连接池配置从 100 改为 200,但未考虑数据库端 max_connections 限制 (150)
**促成因素**: 连接池配置未纳入代码审查清单;缺乏连接池监控
**根本原因**: 配置变更缺乏影响评估流程
## 改进措施
| 措施 | 负责人 | 截止日期 | 验收标准 |
|------|--------|---------|---------|
| 将连接池配置纳入发布检查清单 | 张三 | 2024-01-22 | 清单在 wiki 发布,下次发布前检查 |
| 增加数据库连接数监控和告警 | 李四 | 2024-01-29 | Grafana dashboard + PagerDuty 告警 |
| 在预发环境模拟连接池耗尽场景 | 王五 | 2024-02-05 | 混沌测试用例通过 |预算恢复计划
错误预算耗尽后,需要等待下一个时间窗口才能自然恢复。但团队可以主动加速恢复:
加速恢复的措施:
| 措施 | 效果 | 实施难度 |
|---|---|---|
| 缩短 SLO 测量窗口 | 从 30 天改为 7 天,更快恢复 | 低(改配置) |
| 临时收紧 SLO | 本周期目标提到 99.95%,减少预算 | 中(需业务同意) |
| 增加自动化测试覆盖 | 减少发布导致的故障 | 高(长期工程) |
| 改进监控和告警 | 缩短 MTTD(平均检测时间) | 中 |
| 混沌工程演练 | 提前发现系统脆弱点 | 高 |
重要提醒:缩短窗口或收紧 SLO 是临时措施,不能替代真正的可靠性改进。如果团队反复耗尽预算,说明 SLO 定得太松或系统可靠性有结构性问题。
四、预防:让错误预算耗尽不再发生
应急处置是「治已病」,更重要的是「治未病」。
1. 预算消耗预警
在预算消耗到 50% 和 25% 时自动触发预警,给团队留出反应时间:
# Prometheus 告警规则:错误预算预警
- alert: ErrorBudget50PercentConsumed
expr: slo:period_error_budget_remaining:ratio < 0.5 and slo:period_error_budget_remaining:ratio >= 0.25
for: 1h
labels:
severity: warning
team: sre
annotations:
summary: "错误预算已消耗 50%"
description: "服务 {{ $labels.sloth_service }} 的 30 天错误预算已消耗超过 50%,剩余 {{ $value | humanizePercentage }}"
- alert: ErrorBudget75PercentConsumed
expr: slo:period_error_budget_remaining:ratio < 0.25 and slo:period_error_budget_remaining:ratio >= 0
for: 30m
labels:
severity: critical
team: sre
annotations:
summary: "错误预算已消耗 75%"
description: "服务 {{ $labels.sloth_service }} 的 30 天错误预算已消耗超过 75%,剩余 {{ $value | humanizePercentage }}。建议暂停非紧急发布。"
- alert: ErrorBudgetExhausted
expr: slo:period_error_budget_remaining:ratio < 0
for: 5m
labels:
severity: page
team: sre
annotations:
summary: "错误预算已耗尽"
description: "服务 {{ $labels.sloth_service }} 的 30 天错误预算已耗尽(剩余 {{ $value | humanizePercentage }})。立即冻结发布,启动应急流程。"2. 发布节奏与预算对齐
将发布计划与错误预算状态挂钩:
每周一晨会流程:
1. 查看各服务错误预算状态
2. 预算 < 50%:本周减少 50% 发布计划
3. 预算 < 25%:本周只保留安全/合规发布
4. 预算 < 0%:冻结所有发布,直至恢复3. 慢性消耗的治理
慢性消耗最难发现。建议每月做一次「预算消耗审计」:
# 查看过去 30 天每天的预算消耗
sum_over_time(
(1 - slo:period_error_budget_remaining:ratio{sloth_service="payment-api"})[30d:1d]
)
# 找出消耗最大的几天
topk(5, increase(slo:sli_error:ratio_rate1h{sloth_service="payment-api"}[30d]))4. 多服务预算的联动管理
如果一个服务耗尽预算,检查依赖它的上游服务是否需要同步冻结:
| 场景 | 上游服务 | 决策 |
|---|---|---|
| A 服务预算耗尽 | B 服务强依赖 A | B 同步冻结发布 |
| A 服务预算耗尽 | C 服务弱依赖 A | C 评估后决定 |
| A 服务预算耗尽 | D 服务不依赖 A | D 不受影响 |
五、常见误区与正确做法
| 误区 | 正确做法 |
|---|---|
| "预算用完了,服务就要宕机" | 预算用完是「风险额度用完」,不是「服务必须宕机」。服务可能仍然稳定,只是不能再承担新风险。 |
| "冻结发布就万事大吉" | 冻结发布只是停止引入新风险,还要处理已知隐患和监控盲区。 |
| "等预算自然恢复就行" | 被动等待恢复是放任问题。应该利用冻结期做可靠性改进。 |
| "提高 SLO 目标就能解决问题" | 提高目标(如 99.9% → 99.99%)会减少预算,让问题更频繁触发。根本解决是减少故障。 |
| "错误预算是 SRE 的事,开发不用管" | 错误预算是「共享责任」。开发负责代码质量,SRE 负责监控和响应,共同对预算负责。 |
六、实战:一个完整的错误预算耗尽处置案例
背景
- 服务:user-service(用户服务)
- SLO:99.9% 可用性(30 天滚动窗口)
- 年化预算:8.76 小时
- 月化预算:约 43 分钟
事件经过
1 月 15 日 14:32:发布 v2.5.0,包含用户画像新功能 1 月 15 日 14:35:5xx 率飙升至 15%,触发 PagerDuty 1 月 15 日 14:38:on-call 工程师响应 1 月 15 日 15:47:回滚到 v2.4.9,服务恢复 故障持续时间:1 小时 15 分钟
预算影响:单次故障消耗了 75 天的预算(按 30 天窗口年化折算)。错误预算从 65% 直接变为 -40%(耗尽且透支)。
处置过程
Step 1 - 确认(14:50):
- 检查 Prometheus:
slo:period_error_budget_remaining:ratio{sloth_service="user-service"} = -0.40 - 确认 SLI 计算正确:events 类型,分子是 5xx 请求数,分母是总请求数
- 确认不是重复计算:该故障只影响 user-service,未波及其他服务
Step 2 - 冻结(15:00):
- 在发布群中发送冻结通知
- CI/CD 门禁自动拦截 user-service 的所有发布(已配置错误预算检查)
- 同步冻结依赖 user-service 的 order-service 和 payment-service 的非紧急发布
Step 3 - 止血(15:10-15:47):
- 15:10:尝试扩容,无效(问题在代码逻辑,不是容量)
- 15:30:决定回滚
- 15:35:执行
kubectl rollout undo deployment/user-service - 15:47:5xx 率降至 0.01%,服务恢复
Step 4 - 评估紧急发布(16:00):
- 当天有一个安全补丁(log4j 漏洞修复)需要发布
- 审批:安全负责人确认 CVE 评分 9.8,必须当天修复
- 决策:允许发布,但要求:
- 在 staging 环境验证补丁不影响 user-service 核心功能
- 发布窗口选在晚上 22:00(低峰期)
- on-call 工程师全程值守
- 发布后立即运行 30 分钟自动化健康检查
Step 5 - 复盘(1 月 17 日):
- 根因:新功能的数据库查询未加索引,导致查询超时级联崩溃
- 改进措施:
- 数据库变更纳入发布检查清单(负责人:DBA 组长,截止:1 月 24 日)
- 增加慢查询监控和自动告警(负责人:SRE,截止:1 月 31 日)
- 预发环境数据量对齐生产(负责人:测试,截止:2 月 7 日)
- 发布流程增加性能基线检查(负责人:CI 负责人,截止:2 月 14 日)
预算恢复:
- 由于采用 30 天滚动窗口,故障发生在 1 月 15 日,到 2 月 15 日该故障会滚出窗口
- 团队决定临时将 SLO 测量窗口缩短为 7 天(仅用于监控,不改变 SLO 承诺),加速恢复感知
- 2 月 1 日预算恢复至 15%,解除冻结
七、工具配置速查
Prometheus 错误预算告警规则(完整版)
groups:
- name: error_budget_alerts
interval: 1m
rules:
# 错误预算 50% 预警
- alert: ErrorBudget50PercentConsumed
expr: |
(
slo:period_error_budget_remaining:ratio < 0.5
and
slo:period_error_budget_remaining:ratio >= 0.25
)
for: 1h
labels:
severity: warning
team: sre
annotations:
summary: "错误预算已消耗 50%"
description: "服务 {{ $labels.sloth_service }} 的 30 天错误预算已消耗超过 50%,剩余 {{ $value | humanizePercentage }}。建议评估近期发布计划。"
# 错误预算 75% 告警
- alert: ErrorBudget75PercentConsumed
expr: |
(
slo:period_error_budget_remaining:ratio < 0.25
and
slo:period_error_budget_remaining:ratio >= 0
)
for: 30m
labels:
severity: critical
team: sre
annotations:
summary: "错误预算已消耗 75%"
description: "服务 {{ $labels.sloth_service }} 的 30 天错误预算已消耗超过 75%,剩余 {{ $value | humanizePercentage }}。建议暂停非紧急发布。"
# 错误预算耗尽 - 熔断
- alert: ErrorBudgetExhausted
expr: slo:period_error_budget_remaining:ratio < 0
for: 5m
labels:
severity: page
team: sre
annotations:
summary: "🚨 错误预算已耗尽"
description: "服务 {{ $labels.sloth_service }} 的 30 天错误预算已耗尽(剩余 {{ $value | humanizePercentage }})。立即冻结发布,启动应急流程。"
# 错误预算恢复通知
- alert: ErrorBudgetRecovered
expr: |
(
slo:period_error_budget_remaining:ratio >= 0.25
and
slo:period_error_budget_remaining:ratio < 0.26
)
for: 1h
labels:
severity: info
team: sre
annotations:
summary: "错误预算已恢复至 25%"
description: "服务 {{ $labels.sloth_service }} 的错误预算已恢复至 25% 以上(当前 {{ $value | humanizePercentage }}),可以逐步恢复发布。"CI/CD 错误预算门禁脚本
#!/bin/bash
# error_budget_gate.sh
# 部署前检查错误预算状态
SERVICE=$1
PROMETHEUS_URL=${PROMETHEUS_URL:-"http://prometheus:9090"}
THRESHOLD=${THRESHOLD:-0} # 默认预算耗尽时阻止
echo "🔍 检查服务 $SERVICE 的错误预算状态..."
# 查询剩余错误预算
BUDGET=$(curl -s "$PROMETHEUS_URL/api/v1/query?query=slo:period_error_budget_remaining:ratio{sloth_service=\"$SERVICE\"}" | \
jq -r '.data.result[0].value[1]')
if [ "$BUDGET" == "null" ] || [ -z "$BUDGET" ]; then
echo "⚠️ 无法获取错误预算指标,请检查 Prometheus 配置"
exit 1
fi
echo "📊 剩余错误预算: $BUDGET"
# 比较(使用 bc 处理浮点数)
if (( $(echo "$BUDGET < $THRESHOLD" | bc -l) )); then
echo "❌ 错误预算已耗尽 ($BUDGET < $THRESHOLD),禁止发布"
echo "📋 如需紧急发布,请联系 SRE 负责人审批"
exit 1
fi
echo "✅ 错误预算充足 ($BUDGET >= $THRESHOLD),允许发布"
exit 0Grafana 错误预算看板关键查询
# 剩余错误预算(仪表盘核心指标)
slo:period_error_budget_remaining:ratio
# 预算消耗速率(燃烧率)
slo:period_burn_rate:ratio
# 预算消耗趋势(过去 7 天)
slo:period_error_budget_remaining:ratio[7d]
# 多服务预算对比(表格面板)
slo:period_error_budget_remaining:ratio * 100
# 预算耗尽倒计时(基于当前燃烧率)
(
slo:period_error_budget_remaining:ratio
/
avg_over_time(slo:period_burn_rate:ratio[1h])
) / 24 # 单位:天八、写在最后
错误预算耗尽不是终点,而是可靠性工程的起点。它迫使团队停下来,审视系统的脆弱点,修复根本问题,然后以更稳健的姿态继续前进。
记住三个原则:
- 预算耗尽时,先冻结再分析 —— 停止引入新风险是第一优先级
- 复盘不是为了追责,而是为了预防 —— 找出系统漏洞,而不是找人背锅
- 预算恢复后,不要回到老样子 —— 利用冻结期做的改进,要固化为流程
最好的错误预算管理,是让预算永远用不完。 这需要持续的工程投入:更好的测试、更完善的监控、更自动化的恢复、更谨慎的发布流程。错误预算只是把这些投入量化的工具。
本文是 SRE 实战系列第 10 篇。系列已覆盖 SLO 落地、Sloth/Pyrra 工具、告警治理、监控选型、K8s 可观测性、云成本优化、on-call 生存指南、思维转变方法论。本文聚焦错误预算耗尽后的应急处置——SLO 体系中最容易被忽视、但最关键的环节。