SRE 实践真经:可观测性、SLOs、Runbooks 与事故报告
近期看到的最好的一篇 SRE 领域的文章,分享给大家,其中包含了 Runbook、Postmortem 模板很有参考价值,对于稳定性和绩效的关联也做了建议。作者 Fatih K. 原文:https://fatihkoc.net/posts/sre-observability-slo-runbooks/
你可以搭建完美的可观测性基础设施:部署统一的 OpenTelemetry 流水线、添加安全遥测数据、实现持续性能剖析、为每个服务植入观测代码、收集所有指标、日志和追踪数据,还能构建精美的 Grafana 仪表盘。
即便如此,在发生故障时,你仍可能陷入困境。
缺失的并非技术层面的东西,而是组织层面的体系。当事件触发告警时,团队需要立即回答四个问题:事件严重程度如何?应采取哪些行动?需要哪些人参与?何时能解决问题?
没有服务级别目标(SLO),事件严重程度的判定就会主观化——不同工程师对5%的错误率是可接受还是灾难性的,可能会有截然不同的看法。没有运行手册,事件响应就会变成临场发挥,每个工程师都按照自己的思路行动,最终导致结果不一致。没有结构化的事后分析,团队只能解决表面问题,却无法找到根本原因,从而反复遭遇同样的问题。
可观测性与可靠性之间的差距,不在于收集更多数据,而在于为团队提供系统性利用这些数据的框架。SLO 定义了“正常运行”的共同认知,运行手册将故障修复的集体知识整理成规范,事后分析则能从失败中总结经验,实现组织层面的学习。
本文聚焦于将可观测性转化为可靠性的人员体系,将介绍如何定义能指导决策的 SLO、构建可扩展团队知识的运行手册、设计能推动改进的结构化事后分析,以及如何将这些实践融入工程文化,同时避免增加冗余流程。
为何仅靠可观测性无法预防事件
此前关于 OpenTelemetry 流水线的文章介绍了如何统一指标、日志和追踪数据,关于安全可观测性的文章补充了审计日志和运行时检测的内容,关于剖析的文章则涵盖了性能优化方法。至此,你已能全面掌握系统的运行状况。
但“可见”并不等同于“可靠”。
以一个处理交易的支付服务为例,你的可观测性栈显示以下数据:
- 请求量:1200 次/秒
- 错误率:2.3%
- P99 延迟:450 毫秒
- CPU 使用率:65%
- 活跃数据库连接数:180
这些数据是好是坏?若没有预先定义的目标,你只能靠猜测判断。有些团队可能会因 2.3% 的错误率惊慌失措,而另一些团队则可能要等到错误率达到 15% 才会叫醒工程师处理。此时,决策不再基于系统逻辑,反而变成了“人情博弈”。
更糟糕的是,当告警触发时,工程师往往只能临场摸索解决方案。告警提示“延迟过高”,却没有说明应重启 Pod、横向扩容、检查数据库,还是回滚上一次部署——每一次事件都变成了一场“研究任务”。
而且,若没有结构化的回顾流程,你只能解决眼前的问题,却会忽略系统性根源。比如,数据库连接池配置过小、配置变更无需审批、部署回滚未自动化等问题。由于没有从中吸取教训,类似的问题会反复出现。
SLO、运行手册和事后分析(Postmortem)恰好能解决这些问题。它们将可观测性从被动的数据收集,转变为主动的可靠性提升手段。我曾见证多个团队在实施这些实践后的三个月内,将平均恢复时间(MTTR)缩短了 60%——这并非因为收集了更多数据,而是因为团队知道如何利用数据采取行动。
服务级别指标(SLI):定义真正重要的内容
服务级别指标(SLI)是衡量用户端可靠性的特定指标,而非 CPU、内存等内部指标,也不是 Pod 数量等基础设施指标,而是用户实际体验到的系统行为。
“四大黄金信号”为定义 SLI 提供了基础框架:延迟(速度快慢)、流量(请求规模)、错误(失败次数)和饱和度(关键资源的占用情况,如 CPU、内存、线程/连接池、队列深度、磁盘/网络 I/O)。这些信号几乎适用于所有服务,但你需要根据具体业务场景将其具体化。
以 REST API 为例,其 SLI 可能包括:
- 可用性:返回 2xx 或 3xx 状态码的请求占比
- 延迟:成功请求的 99 分位响应时间
- 吞吐量:服务每秒能处理的请求数
而对于数据流水线,SLI 则有所不同:
- 新鲜度:数据生成到在数据仓库中可用的时间间隔
- 准确性:无数据质量错误的记录处理占比
- 完整性:输出中包含的预期源记录占比
关键在于,要衡量用户的实际体验,而非基础设施的运行状态。用户并不关心你的 Pod CPU 使用率是否达到 80%,他们只关心结账是否成功以及耗时多久。
你可以利用 OpenTelemetry 流水线的数据实现可用性 SLI。如果按上文设置 namespace: traces.spanmetrics,那么 span 指标将在 Prometheus 中以 traces_spanmetrics_* 的形式呈现;若使用其他命名空间,则需相应调整指标名称。以下是示例查询语句:
# 可用性 SLI:成功请求的占比
sum(rate(traces_spanmetrics_calls_total{
service_name="checkout-service",
status_code=~"2..|3.."
}[5m]))
/
sum(rate(traces_spanmetrics_calls_total{
service_name="checkout-service"
}[5m]))
对于延迟 SLI,可使用埋点请求时长指标中的直方图分位数。当设置 namespace: traces.spanmetrics 时,时长直方图会以 traces_spanmetrics_duration_bucket 的形式暴露,同时还会附带 _sum(总和)和 _count(计数)指标:
# 延迟 SLI:99 分位响应时间
histogram_quantile(
0.99,
sum by (le) (
rate(traces_spanmetrics_duration_bucket{
service_name="checkout-service",
status_code=~"2.."
}[5m])
)
)
OpenTelemetry Collector 的 spanmetrics 连接器会自动从追踪数据中生成这些指标(旧版本中该功能以处理器形式提供)。只需植入一次观测代码,即可同时获得用于调试的详细追踪数据和用于 SLO 的聚合指标。指标名称取决于你为连接器设置的 namespace,且在 Prometheus 中会将点(.)转换为下划线(_),例如 traces.spanmetrics 会变为 traces_spanmetrics。
以下是与下文所用指标名称对齐的示例配置:
connectors:
spanmetrics:
namespace: traces.spanmetrics
service:
pipelines:
traces:
receivers: [otlp]
exporters: [spanmetrics]
metrics:
receivers: [spanmetrics]
exporters: [prometheusremotewrite]
无需为所有内容都创建 SLI,应从最关键的 2-3 个用户流程入手。以电商平台为例,关键流程可能是浏览商品、加入购物车和完成结账。每个流程只需设置可用性和延迟两项 SLI,总共仅 6 项,易于管理。
避免将“面子指标”伪装成 SLI。“平均响应时间”就是一个糟糕的 SLI,因为它会掩盖异常值——比如 99 个请求耗时 100 毫秒,1 个请求耗时 30 秒,平均值仅为 400 毫秒,看似正常,实际却代表着极差的用户体验。应改用分位数(如 P50、P95、P99)作为指标。
同时,避免使用与用户体验无关的内部指标。“Kafka 消费者延迟”本身并非 SLI,除非你能将其转化为用户可感知的影响。例如,若延迟导致用户看到过期数据,那么“数据新鲜度”才是应关注的 SLI。记住,要衡量用户可见的问题,而非内部系统的原因。
服务级别目标(SLO):将指标转化为可靠性目标
SLO 是为 SLI 设定的目标,例如“99.9% 的请求成功”或“99% 的请求在 500 毫秒内完成”。这些目标相当于服务与用户之间的“契约”。
合理的 SLO 需要在用户期望与工程成本之间找到平衡。设定 99.99% 的可用性目标听起来很棒,但这意味着每月允许的 downtime 仅为 4.38 分钟。要实现这一目标,需要冗余架构、自动化工具和大量运维投入——对于内部工具而言,这样的成本可能并不值得。
设定 SLO 的流程如下:
- 测量 2-4 周的当前性能数据
- 明确用户的实际需求(与用户沟通)
- 设定略高于当前水平但可实现的目标
- 根据错误预算消耗情况迭代调整
以下是设定 SLO 的示例思路: 若当前性能数据显示可用性为 99.7%、P99 延迟为 800 毫秒,且用户调研表明“偶尔的延迟可接受,但失败不可容忍”,则可设定如下 SLO:
- 可用性:99.5% 的请求成功(比当前水平更保守,从而预留错误预算)
- 延迟:99% 的请求在 1000 毫秒内完成
这些 SLO 可转化为可量化的预算:
- 0.5% 的错误预算 = 每月 14.4 小时的 downtime
- 1% 的请求可超出延迟目标
这为决策提供了明确的“护栏”:当错误预算消耗速度超出预期时,团队会放慢功能发布节奏,专注于提升可靠性;若仍有剩余错误预算,则可承担一定风险进行创新。
可将 SLO 实现为 Prometheus 记录规则,这样能预先计算 SLI 值,加快仪表盘的加载速度。以下延迟示例假设 spanmetrics 的时长单位为毫秒,且阈值设为 1 秒(le="1000"):
groups:
- name: checkout-service-slo
interval: 30s
rules:
# 可用性 SLI
- record: sli:availability:ratio_rate5m
expr: |
sum(rate(traces_spanmetrics_calls_total{
service_name="checkout-service",
status_code=~"2..|3.."
}[5m]))
/
sum(rate(traces_spanmetrics_calls_total{
service_name="checkout-service"
}[5m]))
# 延迟 SLI(低于阈值的请求占比)
- record: sli:latency:ratio_rate5m
expr: |
sum(rate(traces_spanmetrics_duration_bucket{
service_name="checkout-service",
le="1000"
}[5m]))
/
sum(rate(traces_spanmetrics_duration_count{
service_name="checkout-service"
}[5m]))
之后,可创建一个 Grafana 仪表盘,展示 SLO 随时间的合规情况,并添加一个显示剩余错误预算的仪表盘。当错误预算低于 20% 时,将其标为红色,为团队提供直观的风险提示。
错误预算:可靠性的“通用货币”
错误预算彻底改变了可靠性的讨论方式。它不再追求“100% 可用性”(这根本不可能实现),而是提出“我们有一定的故障预算,应将其用于创新而非应对恐慌”。
假设你的 SLO 是 30 天内可用性达到 99.5%,那么错误预算就是 0.5% 的请求允许失败。若每天有 100 万次请求,则每天允许 5000 次失败请求,每月允许 15 万次——每一次实际失败都会减少剩余预算。
通过计算错误预算消耗率,可在预算耗尽前发现问题:
# 当前错误率与错误预算率的比值
# 若该值大于 1.0,说明预算消耗速度超出预期
(1 - sli:availability:ratio_rate5m) / (1 - 0.995)
消耗率为 1.0 意味着预算消耗速度与 SLO 允许的速度完全一致;消耗率为 10 意味着按当前失败率,月度预算将在 3 天内耗尽;消耗率为 0.5 则意味着仍有预算余量。
“多窗口、多消耗率”告警机制可同时避免误报和检测延迟。《Google SRE 工作手册》建议基于以下两个条件触发告警:
- 快速消耗(1 小时内消耗 2% 预算):立即通知相关人员
- 缓慢消耗(6 小时内消耗 5% 预算):创建工单用于后续调查
以下是告警规则示例:
groups:
- name: checkout-service-slo-alerts
rules:
# 快速消耗:1 小时内消耗率达 14.4 倍,且持续 2 分钟
- alert: CheckoutServiceErrorBudgetFastBurn
expr: |
(1 - sli:availability:ratio_rate5m{service_name="checkout-service"}) / (1 - 0.995) > 14.4
for: 2m
labels:
severity: critical
team: payments
annotations:
summary: "结账服务错误预算消耗率达 14.4 倍"
description: "按当前错误率,月度错误预算将在 {{ $value | humanizeDuration }} 内耗尽。当前可用性:{{ $labels.availability }}%"
runbook_url: "https://runbooks.internal/payments/checkout-error-budget-burn"
# 缓慢消耗:6 小时内消耗率达 6 倍
- alert: CheckoutServiceErrorBudgetSlowBurn
expr: |
(1 - avg_over_time(sli:availability:ratio_rate5m{service_name="checkout-service"}[6h])) / (1 - 0.995) > 6
for: 15m
labels:
severity: warning
team: payments
annotations:
summary: "结账服务错误预算消耗率达 6 倍"
description: "错误预算消耗已升高,请检查近期变更。"
runbook_url: "https://runbooks.internal/payments/checkout-error-budget-burn"
这些阈值在误报率和检测速度之间取得了平衡:快速消耗告警能立即捕捉严重故障,缓慢消耗告警则能在预算耗尽前发现渐进式性能下降。
错误预算还能指导政策制定,许多团队会实施以下规则:
- 绿色(剩余预算 >75%):可自由发布功能,允许承担风险
- 黄色(剩余预算 25%-75%):审核变更请求,优先选择低风险改进
- 红色(剩余预算 <25%):冻结功能发布,专注于提升可靠性
该政策通过工程流程而非工具强制执行。当仪表盘显示剩余错误预算仅为 18% 时,团队负责人会知道应将高风险重构推迟到下个月。我曾见过这一框架彻底改变了产品与工程团队的协作模式——不再为“发布是否风险过高”争论不休,团队只需查看错误预算仪表盘,5 分钟内就能基于数据做出决策。
错误预算回答了“何时行动”,而运行手册则回答了“如何行动”。
运行手册:将告警转化为行动
运行手册将告警从“系统出问题了”转化为“具体该做什么”。每一条告警都应链接到对应的运行手册,无一例外。
所有服务的运行手册结构应保持一致,以下是通用模板:
# [服务名称] - [告警名称]
## 概述
用一句话描述该告警的含义及对用户的影响。
## 严重程度
严重(Critical)/ 警告(Warning)/ 信息(Info)
## 诊断步骤
1. 查看当前 SLO 状态:[Grafana 仪表盘链接]
2. 查看近期追踪数据:{service_name="checkout-service", status="error"}
3. 关联部署记录:[ArgoCD/Flux 仪表盘链接]
4. 查看指标数据:[Grafana 仪表盘链接]
## 缓解措施
1. 回滚:`kubectl rollout undo deployment/checkout-service -n production`
2. 扩容:`kubectl scale deployment/checkout-service --replicas=10 -n production`
3. 禁用功能:`kubectl set env deployment/checkout-service FEATURE_X=false`
## 升级流程
- 值班工程师(15 分钟内)→ 服务负责人 → 事件指挥官 → 管理层
- 联系方式:[PagerDuty 链接]
## 调查流程
缓解后操作:查看追踪数据、[剖析数据](/posts/ebpf-parca-observability/)、[审计日志](/posts/kubernetes-security-observability/) 和数据库日志。
该模板直接与可观测性栈关联:诊断部分利用统一的 OpenTelemetry 流水线关联各类信号,调查部分则在需要时引用剖析和安全可观测性数据。
运行手册应与服务代码一同存储在 Git 中,并将其视为代码进行管理:版本控制、同行评审、测试验证。发布新功能时,需更新运行手册;事件暴露问题后,需提交 PR(拉取请求)完善运行手册。
运行手册与告警的关联
还记得我们在 OpenTelemetry 流水线中添加的 runbook.url 注解吗?现在它终于派上用场了——告警会自动包含运行手册链接:
annotations:
summary: "{{ $labels.service_name }} 错误率超过阈值"
description: "当前错误率:{{ $value }}%。SLO 允许的错误率为 0.5%。"
runbook_url: '{{ $labels.runbook_url }}'
dashboard: 'https://grafana.internal/d/service-overview?var-service={{ $labels.service_name }}'
traces: 'https://grafana.internal/explore?queries=[{"datasource":"tempo","query":"{{ $labels.service_name }}","queryType":"traceql"}]'
当 PagerDuty 通知送达时,会包含以下信息:
- 故障内容(服务名称、错误率)
- 当前状态与预期状态(SLO 阈值)
- 查看位置(仪表盘链接、追踪查询链接)
- 操作指南(运行手册链接)
值班工程师只需点击运行手册链接,按步骤操作即可,无需猜测,也无需在 Slack 中翻找“上次是怎么解决的”。
事后分析:从失败中学习
事后分析(也称为事件回顾或行动后复盘)能将事件转化为系统性改进。其目标不是追究个人责任,而是找出导致事件发生的流程、工具或架构漏洞。
核心原则是“无指责文化”:假设每个人在当时掌握的信息下都做出了合理决策。若有人部署了有问题的代码,不应问“他们为什么这么做”,而应问“为什么我们的测试没有发现问题”;若有人做出了不佳的架构决策,不应指责个人,而应思考“需要哪些信息才能帮助他们做出更好的决策”。
事后分析模板
# 事件:[简要描述]
**日期**:[日期] | **持续时间**:[时长] | **严重程度**:[严重/高/中]
**指挥官**:[姓名] | **响应人员**:[姓名列表]
## 影响范围
- 受影响用户数:[数量/百分比]
- 收入影响:[金额(如适用)]
- SLO 影响:[消耗的错误预算]
## 时间线
关键事件:告警触发 → 开始调查 → 发现原因 → 实施缓解 → 恢复服务 → 问题解决
## 根本原因
说明发生了什么变更、为何会导致问题,以及为何现有防护措施未能阻止问题。
## 做得好/待改进的地方
**做得好**:检测迅速、协作高效、工具使用得当
**待改进**:告警缺失、职责不明确、测试不足、流程手动化
## 行动项
| 行动内容 | 负责人 | 优先级 | 截止日期 | 状态 |
|----------|--------|--------|----------|------|
| [具体改进措施] | [团队] | P0-P2 | [日期] | [状态] |
**优先级定义**:P0(防止类似事件再次发生)、P1(提升检测/缓解能力)、P2(优化项)
## 经验总结
系统层面的洞察、团队实践的变更、已识别的可观测性漏洞。
事后分析应在事件解决后的 48 小时内进行,此时细节仍清晰可忆。安排 60 分钟的会议,邀请所有响应人员及相关利益方参加,用模板引导讨论。
行动项部分至关重要:每个行动项都必须明确负责人、截止日期,并进行跟踪。在迭代规划中跟进这些行动项,确保优先处理。否则,事后分析会变成“走过场”——所有人都点头同意“我们应该加强监控”,但最终什么都没改变。
常见的事后分析反模式
- 以“流程”为名的指责:如“本该知道更好”是错误的,应追问“为什么系统允许这种情况发生”
- 模糊的行动项:如“改进监控”毫无意义,需明确具体日期和衡量指标
- 缺乏跟进:未将行动项纳入迭代待办清单优先处理
- 孤立学习:仅在团队内部分享事后分析结果,未在整个工程部门推广
- 形式主义:若不落实行动项,就不必做事后分析——这只是在浪费时间
一个常见的反模式是:团队将事后分析的行动项标记为“已完成”,但仅修复了表面问题,未解决系统性根源。这不是学习,只是完成文书工作。
外部资源与真实事件报告
以下权威链接可帮助你深入学习,并为读者提供实用参考,同时提升内容的 SEO 效果:
- 《Google SRE 工作手册》:关于无指责事后分析和错误预算的指南 — sre.google/workbook
- Atlassian 事件管理:事件处理手册与模板 — atlassian.com/incident-management
- PagerDuty 事件响应指南:实用的事后分析指导 — response.pagerduty.com
- GitLab 公开事件:可搜索的事件工单与回顾报告 — gitlab.com/gitlab-com/gl-infra/production/-/issues?label_name[]=incident
- Cloudflare 事件:技术博客中的故障分析 — blog.cloudflare.com/tag/outage
- GitHub 工程团队:关于可靠性和事件工程的文章 — github.blog/engineering
- GCP 事件历史:云服务商的公开事件报告 — status.cloud.google.com
- Azure 状态历史:历史事件记录 — azure.status.microsoft/en-us/status/history
- 事后分析库(ilert):精选的真实事件案例 — ilert.com/postmortems
如何查找其他公司的事件报告?
- 在技术博客中搜索“incident”“postmortem”或“outage”等标签,例如使用搜索语法
site:company.com/engineering incident - 在公开状态页面中查找“历史”或“事件后”板块
- 在产品/基础设施代码仓库中查找标注“incident”“postmortem”或“root-cause”的内容
构建可持续的可靠性文化
若仅靠自上而下的强制推行,而没有团队认同,SLO、运行手册和事后分析都会流于形式。你需要将这些实践融入日常工作,而非作为冗余流程叠加。
落地策略
从最关键的用户流程入手:定义 2-3 个 SLI、设定 SLO、搭建仪表盘。当下一次事件发生时,团队会立即看到这些实践的价值——明确的错误预算影响、有指导的响应流程(通过运行手册)、具体的改进方向(通过事后分析)。一个服务的成功落地会自然引发其他团队的需求:当看到其他团队借助清晰的运行手册和错误预算数据,在 15 分钟内解决事件时,原本抗拒的团队也会主动跟进。
不要设立专门的“可靠性团队”来负责所有 SLO 和运行手册——这种模式无法扩展。应提供模板(Prometheus 记录规则、Grafana 仪表盘、运行手册和事后分析模板),让服务团队根据自身需求定制。例如,支付团队关注交易成功率,搜索团队关注结果新鲜度,框架相同,但指标不同。
与激励机制挂钩
“可衡量的才会被管理”。若晋升标准仅包含“交付 5 个功能”,却未提及“维持 99.9% 的 SLO 合规率”,工程师自然会优先追求功能数量。
应将可靠性指标纳入团队目标:
- 维持 SLO 合规(95% 以上的时间段达到目标)
- 无“无运行手册”的事件
- 所有严重事件在 48 小时内完成事后分析
- 事后分析的行动项在 30 天内落地
像庆祝功能发布一样庆祝可靠性成果:若一个团队连续三个月未耗尽错误预算,就应给予认可。
与现有工作流程融合
将可靠性融入现有流程:
- 迭代规划:回顾错误预算消耗情况
- 每日站会:除功能进度外,同步 SLO 状态
- 代码评审:检查是否植入观测代码、是否更新运行手册
- 迭代回顾:对影响 SLO 的事件,使用事后分析模板进行复盘
为每个团队指派一名“可观测性负责人”:负责定义 SLI/SLO、维护运行手册、组织事后分析、分享最佳实践。负责人每月召开会议,交流共性问题,统一工具标准。
以心理安全为基础
没有心理安全,所有实践都无从谈起。“无指责文化”意味着将责任聚焦于系统和流程,而非个人。当有问题的代码被部署时,应问“为什么 CI 没有拦截?”,而非“为什么你没测试?”;当有人做出不佳的架构决策时,应问“需要哪些信息才能帮助决策?”。领导者要以身作则,多问“我们能学到什么?”,少问“是谁搞砸了?”。
总结
没有可靠性实践的可观测性,只是为了收集数据而收集数据;没有可观测性的可靠性实践,则只能靠猜测。二者结合,才能将被动的“救火”转变为主动的可靠性工程。
这些实践能构建超越工具的组织能力:对可靠性的共同认知(SLO 合规、错误预算)、可扩展的故障修复知识(运行手册)、来自统一可观测性的关联信号(指标、日志、追踪),以及从失败中学习的系统性方法(事后分析)。
从小处着手:一个服务、两个 SLI、三个运行手册。先证明价值,再逐步扩展。将平均恢复时间缩短 60%、5 分钟内完成风险决策、避免重复事件——这些都不是遥不可及的目标,而是实施这些实践后数月内就能实现的成果。基础设施已就绪,现在,通过人员体系和团队实践,将信号转化为可靠性吧。