跳到内容

元数据卡

  • 前置知识:第8章(可观测性)、基本的 K8s 知识
  • 预计时间:40 分钟
  • 阅读模式:高度专注
  • 完成标志:理解 SLI/SLO/错误预算的定义和意义,能设计一个合理的 SLO 并推导错误预算,理解 toil 和自动化的分类方法

你的进度

你在远征军的岗位上已经站稳了脚跟。前线指挥系统跑在 K8s 集群上,Prometheus 收集着 Metrics,Jaeger 追踪着每次侦查请求的调用链。系统稳定运行了两个月——但问题出现在意想不到的地方。

情报分析服务每两周出一次问题,每次你都要通宵排查。最麻烦的是,问题每次都不一样:一次是磁盘满了,一次是某个下游哨塔超时,一次是配置错误的滚动更新引发级联故障。每次你都要从零开始定位,没有流程,没有清单。

林将军在晨会上问:"我们这个系统到底有多可靠?下个月的新战区侦查计划,这个系统能不能扛住三倍流量?"

你答不上来。你不知道系统的可靠性上限在哪,也不知道当前还剩多少"安全余地"。更糟糕的是,开发和运维之间出现了裂痕——开发想频繁发布新功能,你怕发布导致故障,两边吵得不可开交。

你的任务

理解 SRE(Site Reliability Engineering)的核心机制:用 SLI(服务等级指标)量化系统状态,用 SLO(服务等级目标)定义可靠性目标,用错误预算在可靠性和开发速度之间建立科学平衡。你将学会如何把"系统是否可靠"这个模糊问题变成一组可量化、可协商、可执行的工程决策。


破局 · 溯源


为什么需要 SRE?Dev 和 Ops 的矛盾

在大型系统中,开发团队和运维团队天生有冲突:

  • 开发团队的目标是推出新功能。每多一个新功能,就多一份市场竞争力。
  • 运维团队的目标是系统稳定。每次变更都伴随风险。

这是一种零和博弈的感觉——开发团队快起来,运维团队就紧张;运维团队安全了,开发团队觉得太慢。

SRE 源自 Google。它将运维工作"软件工程化"——不靠人肉运维,而是用代码自动化解决重复问题。SRE 团队本身就是一个软件开发团队,只是"产品"是系统的可靠性。

SRE 的核心思路:不追求 100% 可靠性

直觉上这听起来奇怪——难道不是越可靠越好吗?但 100% 可靠性的代价是无限的成本和零功能迭代。SRE 讲的"足够可靠"是:系统的可靠性达到用户能接受的水平,余下的产能投入到功能开发和自动化上。


SLI:服务等级指标

你要回答"系统是否可靠",必须先定义"可靠"是什么。SLI(Service Level Indicator)就是对服务质量的一个具体量化指标。

一场战役中,SLI 就是不同的战场观测指标:

SLI 类型在战场中的含义技术指标
可用性哨塔能否正常收发信号成功请求 / 总请求
延迟传令兵送信要从出发到送达的时间请求响应时间 P50/P95/P99
吞吐量每天能处理多少份战报QPS / RPS
错误率传令路上被拦截的比例HTTP 5xx / 总请求
饱和度补给线还能承受多少压力CPU/内存/连接池使用率

定义 SLI 时,你需要确定三个要素:

  1. 测量对象:什么请求算一次"操作"?是所有的 HTTP 请求,还是只有写入操作?
  2. 测量位置:在客户端测还是服务器端测?不同位置的延迟数据可能差很大。
  3. 聚合方式:按时间窗口平均?按百分位?按时间段?

定义 SLI 的常见方法:

SLI: 可用性
  定义: 所有的 HTTP GET 请求中,返回非 5xx 状态码的比例
  测量: Prometheus counter `http_requests_total{status!~"5.."}`
        除以 `http_requests_total`
  聚合: 滚动 5 分钟窗口,按 1 分钟粒度

SLI: 延迟
  定义: HTTP GET /api/reports 请求的 P99 响应时间
  测量: Prometheus histogram `http_request_duration_seconds`
  聚合: 1 分钟窗口,P99 百分位

用 PromQL 表达可用性 SLI:

promql
# 5 分钟内请求成功率
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))

SLO:服务等级目标

SLO 是你给 SLI 设定的目标值。不是一个"尽力而为"的承诺,而是一个精确定义的阈值。

SLO: 可用性
  目标: 99.9%(三个九)
  窗口: 30 天滚动窗口

SLO: 延迟
  目标: P99 < 500ms
  窗口: 30 天滚动窗口

SLO 选择有原则。你先要回答:用户能感知什么?

对前端用户来说,P50 延迟 200ms 和 P50 延迟 500ms 可能差别不大——但 P99 延迟 2s 和 10s 差别巨大。因此 P99 比 P50 更值得作为 SLO。

99.9% 听起来很高,但算一下它的含义:

SLO每周允许不可用时间每月允许不可用时间每年允许不可用时间
99% (两个九)1.68 小时7.32 小时3.65 天
99.9%(三个九)10.1 分钟43.8 分钟8.77 小时
99.99%(四个九)1.01 分钟4.38 分钟52.6 分钟
99.999%(五个九)6 秒26.3 秒5.26 分钟

同样目标,组件越多越难。如果你的系统由 10 个服务组成,每个服务都要达到 99.99%,整体可用性是:

0.9999 ^ 10 = 0.9990 ≈ 99.9%

十个四个九的服务串起来,整体只剩三个九。这就是为什么你需要先定义"我们到底对用户承诺多少",然后向下分解到每个服务。


错误预算:协调可靠性与发布速度

这是 SRE 最精妙的设计——错误预算。

错误预算 = 1 - SLO

如果 SLO 是 99.9%,那么错误预算是 0.1%。在 30 天的窗口内,系统可以"不达标"的总时间是:

30 天 × 24 小时 × 60 分钟 × 0.001 ≈ 43.2 分钟

这 43.2 分钟就是你的"败仗预算"——在 30 天内,系统可以有总共 43.2 分钟处于不可接受的状态,这仍然算你达成了 SLO。

错误预算的运作规则很简单:

+-------------------------------+-------------------------------+
| 错误预算还有剩余              | 错误预算已耗尽                |
+-------------------------------+-------------------------------+
| 开发可以放心发布新功能        | 停止所有非关键变更            |
| 正常上线                      | 只允许紧急修复(paging 级别) |
| 运维无法用"系统可能不稳定"    | SRE 有否决权,可以冻结发布   |
| 来阻拦发布                    |                                |
+-------------------------------+-------------------------------+

这个机制把"系统有多可靠"这个模糊问题,变成了"我们还剩多少错误预算"这个清晰数字。你不需要争论"能不能发版",只需要看错误预算还剩多少。

在 Grafana 上创建一个错误预算面板:

promql
# 错误预算消耗率(过去 30 天)
1 - (
  sum(rate(http_requests_total{status!~"5.."}[30d]))
  /
  sum(rate(http_requests_total[30d]))
) / (1 - 0.999)  * 100

# 结果 > 100% -> 错误预算已耗尽

错误预算还有一个隐含好处:它迫使你在系统还不完美时就有清晰的数据依据来做决策,而不是凭感觉互相指责。


Toil 和自动化

SRE 把工作分为两类:工程工作和 Toil。

Toil(苦工)的特征:

  • 手动操作(SSH 进去改配置、手动重启服务)
  • 重复性(每周做一次同样的操作)
  • 可自动化(规则明确,可以写成脚本)
  • 无长期价值(这次修好了,下次还是同样的操作流程)

林将军所说的"不要让你的哨兵每天去擦同一把枪"就是这个意思——每天擦枪没有积累,没有长期收益。

SRE 的原则:每个 SRE 团队的 toil 占比不超过 50%。超过意味着你需要自动化。

自动化不是一上来就写一个完整的运维平台。三步走:

  1. 记录:把操作步骤写成文档,放在一个所有人都能访问的地方。至少你不在的时候别人能接手。
  2. 脚本化:把步骤写成 Shell 脚本或一个 CLI 工具。参数化,可重复执行。
  3. 平台化:把脚本集成到 CI/CD 或运维平台中,触发自动执行。

一个好 SRE 的判断标准:你在为一个问题创建的自动化工具,比这个问题本身花费了你更多时间,这个系统在往好的方向走。


On-call 基础

轮值

SRE 通常采用轮值 on-call 制度。一个典型的安排:

  • 主值班(Primary):负责处理告警,第一响应人
  • 副值班(Secondary):主值班忙不过来时协助,覆盖主值班的休息时间
  • 值班周期:一周或两周一轮换

值班不是让你 24 小时盯着屏幕。值班是在被 paged(告警)时 15 分钟内响应。安静的值班是好事——说明系统稳定。

事件升级(Escalation)

每级告警对应不同的响应时延:

P0(严重): 系统瘫痪或数据丢失 → 15 分钟内响应,7x24
P1(高)  : 核心功能严重受损 → 30 分钟内响应,工作时间
P2(中)  : 非核心功能受影响 → 按工作时间处理
P3(低)  : 轻微的异常或不美观 → 随工单处理
P4(提示): 信息性告警 → 日志存档,不需要立即响应

P0 和 P1 才应该生成告警电话或消息。如果 P3 也 page 你,你很快会对告警麻木——这是"告警疲劳"的根源。

Runbook

值班最重要的资产是 runbook(操作手册)。一个好的 runbook 像一份战舰上贴在操作台旁边的"紧急故障处理流程图":

Runbook: 侦查服务 Outpost-Alpha 连接失败如何处理

症状: Grafana 面板显示 Outpost-Alpha 错误率 > 10%
       Prometheus 告警: OutpostAlpha_HighErrorRate

第一步: 确认问题范围
  - 是否只有 Outpost-Alpha 受影响?
  - 检查: kubectl get pods -n front-line -l service=outpost-alpha
  - 检查日志: kubectl logs -l service=outpost-alpha --tail=200 | grep ERROR

第二步: 如果是 Pod 崩溃
  - kubectl describe pod <pod-name>  查看 last state
  - 如果 OOMKilled: 增加内存 limits
  - 如果 CrashLoopBackOff: 检查新版本配置

第三步: 如果是整个服务无响应
  - 检查下游依赖: curl -I http://intel-service:8080/health
  - 如果下游挂了: 先恢复下游服务
  - 如果网络分区: 检查 NetworkPolicy

第四步: 恢复后
  - 在 incident 文档记录时间线和 root cause
  - 自动创建 follow-up ticket 做 postmortem

Runbook 要"可执行"——一个从没值班过的人,按 runbook 操作,至少能做到故障止损。runbook 的质量直接影响事故的恢复时间(MTTR)。

Incident 管理流程

发现告警
   → 确认(是真的问题还是误报?)
   → 响应(按 runbook 操作)
   → 处理(修补 / 回滚 / 切换)
   → 恢复(确认指标恢复正常)
   → 复盘 postmortem(5 个 Why / 根因分析 / 行动项)

复盘最重要的原则:不追责,找根源。复盘不是"谁犯了错",而是"系统为什么没有拦住这个错误"。


常见陷阱

  1. 追求 100% 可靠性。 四个九(99.99%)的成本是三个九的 10 倍。你到底"够用就行"还是"不计成本"?先回答业务需求。大多数 SaaS 产品 99.9% 已经足够。错误预算存在的意义就是告诉你不需要追求完美。

  2. SLO 设置不合理,或不测量就承诺。 没有测量的 SLO 不是承诺,是谎言。先跑三个月的 SLI 数据,看清楚"我们的系统现在是什么水平",再设定一个有挑战但不离谱的 SLO。

  3. 过度告警。 每个很小的异常都发告警。结果是"狼来了"效应——值班人员对告警麻木,真正的 P0 被淹没在 P3 的噪音里。告警准则是:如果这个告警响了,没有人有必要行动,就不应该发出告警。

  4. 用 SLO 做奖惩。 SLO 的目标是帮助团队做决策,而不是用来扣绩效。如果 SLO 达不到是"扣奖金"的依据,团队会倾向于设置极低的 SLO 或者操纵数据。

  5. runbook 写了没人维护。 半年后系统架构变了,runbook 里的命令全都过时了。维护 runbook 要像维护代码一样纳入迭代——每次 incident 后更新 runbook,每次架构变更后审查。


通关挑战

  • 实战:在你目前负责的系统中,找出三个核心 API 或服务,定义它们的 SLI(可用性、延迟、错误率),用 PromQL 查询过去 7 天的数据。

  • 挑战:根据查询结果设定一个合理的 SLO。计算 30 天窗口的错误预算。在 Grafana 上创建错误预算仪表盘面板,包含消耗进度条。

  • 观察:设置 Prometheus 告警规则,当错误预算消耗超过 50% 和 90% 时触发不同级别的告警。

用 Prometheus 配置错误预算告警。当败仗预算消耗过半时发警告,耗尽时触发紧急告警:

yaml
# prometheus-alert.yaml
groups:
- name: error-budget
  rules:
  - alert: ErrorBudgetBurningFast
    expr: |
      (
        1 - (
          sum(rate(http_requests_total{status!~"5.."}[1h]))
          /
          sum(rate(http_requests_total[1h]))
        )
      ) / (1 - 0.999) > 0.5
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "错误预算消耗速度过快"
      description: "过去1小时内错误预算消耗了50%以上"

  - alert: ErrorBudgetExhausted
    expr: |
      (
        1 - (
          sum(rate(http_requests_total{status!~"5.."}[30d]))
          /
          sum(rate(http_requests_total[30d]))
        )
      ) / (1 - 0.999) > 1.0
    labels:
      severity: critical
    annotations:
      summary: "错误预算已耗尽"
      description: "30天窗口内的错误预算已经用完"
  • 排障:你的 on-call 值班收到一个 P0 告警——核心服务的错误率超过 30%。你的 runbook 还没写。写下从接到告警到恢复的每一步操作,然后把这个过程写成 runbook 模板。

验收标准

  • 能说清楚 SLI、SLO、错误预算三者的关系
  • 能设计一个合理的 SLO 并用 PromQL 测量
  • 能计算错误预算并理解它的决策作用
  • 知道 toil 的四种特征和自动化三步走
  • 能设计一个 on-call 轮值方案和 runbook 模板

常见卡点

  1. SLO 设置多严格? 先看现状。如果当前可用性是 99.5%,设定 99.99% 的 SLO 完全没有意义。一个可行的路径:三个月达到 99.7%,六个月达到 99.9%。

  2. 错误预算耗尽了怎么办? 停止非关键变更,聚焦稳定性。这不是"罚你"——是数据告诉你,系统当前太脆弱,经不起更多变化了。

  3. runbook 写的多细? 写到"一个刚值班的新人,按着 runbook 的操作可以完成故障止损"的程度。太粗等于没写,太细没人维护。一个折中方案:每个 runbook 包含症状、诊断步骤、恢复步骤三个部分。

  4. on-call 频率怎么定? 团队有 6 个人,每人轮值一周,每 6 周轮一次。如果团队只有 3 个人,考虑增加 toil 自动化或引入轮值后备机制。


现在不需要理解

  • 服务等级协议(SLA)——这是商业合同层面的概念,和 SLO 不同。SLO 是技术团队自己的目标,SLA 是对外承诺。在你建立 SRE 文化的初期,先关注 SLI 和 SLO。
  • 多维度 SLO 复合计算——非常复杂的场景,多个 SLO 共存时如何做决策。大部分团队不需要这个复杂度的分析。

旅人笔记

SRE 的核心不是把系统做到 100% 可靠——那是幻想。SRE 的核心是:用一个可量化的指标(错误预算)来指导"什么时候该做稳定性投入,什么时候可以加速开发"。这不是技术问题,而是决策框架问题。SLI 给你数据,SLO 给你目标,错误预算给你策略。有了这三者,开发和运维不再是对立阵营——他们是同一支远征军中负责不同战线的协作单位。


下一站预告

你有了可靠性目标和量化工具,但每次环境搭建依然靠手动 SSH。部署一个新系统需要半天时间,而且每台机器的配置有细微差别——"雪花服务器"的问题迟早会出事。下一章,我们把这些环境配置代码化,让 Git 成为基础设施的唯一真相来源。

Built with VitePress | Software Systems Atlas