Google SRE 运维解密笔记

本书对于 SRE 的定义

我们认为如果软件工程师主要专注于设计和构建软件系统,那么应该有另外一种职业专注于整个软件系统的生命周期管理。从其设计一直到部署,历经不断改进,最后顺利退役。这样一种职业必须具备非常广泛的技能,但是和其他职业的专注点都不同。Google 将这个职业称为站点可靠性工程师(SRE: Site Reliability Engineering)。

“无论对一个软件系统运行原理掌握得多么彻底,也不能阻止人犯意外错误。”

只有靠着对细节的不懈关注,做好充足的灾难预案和准备工作,时刻警惕着,不放过一切机会去避免灾难发生。这就是 SRE 最重要的理念!

第一章:介绍

不能将碰运气当成战略。 - SRE 俗语

SRE 团队要承担以下几类职责

  • 可用性改进
  • 延迟优化
  • 性能优化
  • 效率优化
  • 变更管理
  • 监控
  • 紧急事务处理
  • 容量规划与管理

在保障服务 SLO 的前提下最大化迭代速度

在企业中,最主要的矛盾就是迭代创新的速度与产品稳定程度之间的矛盾。 在 SRE 模型中,我们选择正面面对这种矛盾,使用的工具是错误预算

“错误预算"起源于这样一个理念: 任何产品都不是,也不应该做到 100%可靠(显然这并不适用于心脏起博器和防抱死系统等)。因为对最终用户来说,99.999%和 100%的可用性是没有实质区别的。

多少才是?

  • 基于用户的使用习惯,服务可靠性要达到什么程度用户才会满意?
  • 如果这项服务的可靠程度不够,用户是否有其他的替代选择?
  • 服务的可靠程度是否会影响用户对这项服务的使用模式?

如果一个服务的可靠性目标是 99.99%,那么错误预算就是 0.01%。这意味着产品研发部门和 SRE 部门可以在这个范围内将预算用于新功能上线或产品创新等任何事情。

监控系统

监控系统是 SRE 团队监控服务质量和可用性的一个主要手段。

一个需要人工阅读邮件和分析警报来决定目前是否需要采取某种行动的系统本质上就是错误的。监控系统不应该依赖人来分析警报信息,而是应该由系统自动分析,仅当需要用户执行某种操作时,才需要通知用户。

一个监控系统应该只有三类输出

  • 紧急警报 - alert

    意味着收到警报的用户需要立即执行某种操作,目标是解决某种已经发生的问题,或者是避免即将发生的问题。

  • 工单 - ticket

    意味着接受工单的用户应该执行某种操作,但是并非立即执行。系统并不能自动解决目前的情况,但是如果一个用户在几天内执行这项操作,系统不会受到任何影响。

  • 日志 - logging

    平时没有人需要关注日志信息,但是日志信息依然被收集起来以备调试和事后分析时使用。正确的做法是平时没人会去主动阅读日志,除非有特殊需要。

变更管理

大概 70%的生产事故由某种部署的变更而触发。变更管理的最佳实践是使用自动化来完成以下几个项目

  • 采用渐进式发布机制
  • 迅速而准确地检测到问题的发生
  • 当出现问题时,安全迅速地回退改动

需求预测和容量规划

自然增长 - 随着用户使用量上升,资源用量也上升。 非自然增长 - 新功能的发布、商业推广以及其他商业因素在内。

容量规划有几个步骤是必需的

  • 必须有一个准确的自然增长需求预测模型,需求预测的时间应该超过资源获取的时间
  • 规划中必须有准确的非自然增长的需求来源的统计
  • 必须有周期性压力测试,以便准确地将系统原始资源信息与业务容量对应起来

第二章:Google 生产环境: SRE 视角

为了区分物理服务器和软件服务器的概念,本书采用以下两种说法

  • 物理服务器 - machine

    代表具体的硬件(有时候也代表一个 VM 虚拟机)

  • 软件服务器 - server

    代表一个对外提供服务的软件系统

一个典型的 Google 数据中心的拓扑结构

  • 约 10 台物理服务器组成一个机柜 - Rack
  • 数台机柜组成一个机柜排 - Row
  • 一排或多排机柜组成了一个集群 - Cluster
  • 一般来说,一个数据中心 - Datacenter包含多个集群
  • 多个相邻的数据中心组成了一个园区 - Campus

存储

  • Lustre
  • HDFS

网络

带宽控制器(Bandwidth Enforcer, BwE)负责管理所有可用带宽。优化带宽的使用的目的不仅仅是降低成本。利用中心化的路由计算,可以解决以前在分布式路由模式下难以解决的流量迁移问题。

研发环境

除了一些开源项目之外(Android 和 Chrome 等),其他 Google 软件工程师全部使用同一个共享软件仓库开发。这同时也对日常工作流带来一些挑战

  • 如果一个工程师遇到了他工作的项目之外的一个基础组件问题,他可以直接修改这个问题,向管理者提交一份改动申请(changelist, CL),等待代码评审,最后直接提交。
  • 任何对自己项目代码的改动也需要代码评审。

任务和数据的组织方式

假设压力测试的结果现实,我们的软件服务器可以每秒处理大概 100 个请求(100 QPS)。通过对用户进行的调查显示,我们预计峰值流量会达到 3470 QPS,为了处理这些流量,至少需要 35 个任务实例。但是,由于以下几点考量,我们最终决定至少采用 37 个实例,也就是N+2模式

  • 在更新过程中,有一个任务实例将会短暂不可用,只有 36 个实例可提供服务。
  • 如果另外一个物理服务器同时也出现问题,那么另外一个任务实例也受到影响,只剩 35 个实例可以对外服务,刚好可以满足峰值要求。

第三章:拥抱风险

过分追求稳定性限制了新功能的开发速度和将产品交付给用户的速度,并且很大程度地增加了成本,这反过来又减少了一个团队可以提供的新功能数量。 此外,用户通常不会注意到一项服务在高可靠性和极端可靠性直接的差异,因为用户体验主要是受较不可靠的组件主导,例如手机移动网络或他们正在使用的设备。用户在一个有着 99%可靠性的智能手机上是不能分辨出 99.99%和 99.999%的服务可靠性的区别的!

SRE 旨在寻求快速创新和高效的服务运营业务之间的风险的平衡,而不是简单地将服务在线时间最大化。这样一来,我们可以优化用户的整体幸福感,平衡系统的功能、服务和性能

管理风险

可靠性进一步提升的成本并不是线性增加的 - 可靠性的下一个改进可能比之前的改进成本增加 100 倍。 高昂的成本主要存在与

  • 冗余物理服务器/计算资源成本

    通过投入冗余设备,我们可以进行常规的系统离线或其他预料之外的维护性操作。又或者可以利用一些空间来存储奇偶校验码块,以此来提供一定程度的数据持久性保证。

  • 机会成本

    这类成本由某一个组织承担。当该组织分配工程资源来构建减少风险的系统或功能,而非那些用户直接可用的功能时需要承担这些成本。这些工程师不能再从事为终端用户设计新功能和新产品的工作。

我们会努力提高一项服务的可靠性,但不会超过该服务需要的可靠性。当设定了一个可用性目标为 99.99%时,我们即使要超过这个目标,也不会超过太多,否则会浪费为系统增加新功能、清理技术债务或降低运营成本的机会。

度量服务的风险

为了使问题在我们运行的各种类型的系统中易于处理,并且保持一致,我们选择主要关注计划外停机这个指标。

计划外停机时间是由服务预期的可用性水平所体现,通常我们愿意用提供的"9"系列的数字来体现,比如可用性为99.9%/99.99%或99.999%。每个额外的"9"都对应一个向100%可用性的数量级上提高。对于系统服务而言,这个指标通常是基于系统正常运行时间比例的计算得出的。

公式 3-1: 基于时间的可用性

可用性=系统正常运行时间/(系统正常运行时间+停机时间)

公式 3-2: 合计可用性

可用性=成功请求数/总的请求数

例如,一个每天可用性目标为 99.99%的系统,一天要接受 2.5M 个请求。它每天出现少于 250 个错误即可达到预计的可用性目标。

服务的风险容忍度

辨别消费者服务的风险容忍度

消费者服务通常会有一个对应的产品团队,是该服务的商业所有者。比如说,Search、Google Maps 和 Google Docs,它们每一个都有自己的产品经理。这产品经理负责了解用户和业务,在市场上塑造产品的定位。存在产品团队时,我们能够更好地通过这个团队来讨论服务的可靠性要求。在没有专门的产品团队情况下,建立系统的工程师们经常在知情或不知情的情况下扮演了这个角色。 评价服务的风险容忍度时,有许多需要考虑的因素。

  • 需要的可用性水平是什么?
  • 不同类型的失败对服务有不同的影响吗?
  • 我们如何使用服务成本来帮助风险曲线上定位这个服务?
  • 有哪些其他重要的服务指标需要考虑?
可用性目标

对于某个 Google 服务而言,服务的可用性目标通常取决于它提供的功能,以及这项服务在市场上是如何定位的。下面列出了要考虑的一些问题

  • 用户期望的服务水平是什么?
  • 这项服务是否直接关系收入(我们的收入或我们的客户的收入)?
  • 这是一个有偿服务,还是免费服务?
  • 如果市场上有竞争对手,那些竞争对手提供的服务水平如何?
  • 这项服务是针对消费者还是企业的?

例如Google Apps for Work,这个服务的主要用户是企业类用户,包括大型企业和中小型企业。服务的中断不仅会影响 Google 本身,也会影响到这些企业。对于这类服务,我们可能会设置季度性的外部可用性目标为 99.9%。同时,我们会设置一个更高的内部可用性目标,以及签署一份如果我们未能达到外部目标的处罚性协议。 Youtube 则需要截然不同的考虑。尽管当时 Youtube 已经有了一个很出色的产品,但它仍然在不断变化和快速发展着。因此,我们为 Youtube 设定了相比我们企业的产品更低的可用性目标,因为快速发展更加重要。

故障的类型

假设有一个联系人管理应用程序,一种情况是导致用户头像显示失败的间歇性故障,另一种情况是将 A 用户的私人联系人列表显示给 B 用户的故障。第一种情况显然是一个糟糕的用户体验,SRE 会努力去快速地解决这个问题。然而,在第二种情况下,暴露私人数据的风险可能会破坏基本的用户信任。因此,在第二种情况下,在进行调试和事后的数据清理时,完全停止该服务更加恰当。 有时候,我们可以接受计划内的常规服务中断。Ads 前端曾经就是这样的一种服务。因为这项工作大部分发生在正常工作时间内,我们认为维修窗口中发生的偶然的、正常的、计划之中的故障是可以接受的,并且我们把这些故障看作计划内停机(不影响 SLO?),而不是计划外停机时间。

成本

在为每一项服务确定可用性目标时,可以考虑如下问题

  • 构建和运维可用性再多一个"9"的系统,收益会增加多少?
  • 额外的收入是否能抵消为了达到这一可靠性水平所付出的成本?

可靠性目标: 99.9% -> 99.99%

增加的可用性: 0.09%

服务收入: 100 万美元

改进可用性后的价值: 100 万美元 x 0.0009 = 900 美元

在这种情况下,如果可用性提高一个"9"的成本不到 900 美元,那就是合理的投资。如果成本超过 900 美元,那么成本将超过预计增加的收入。

错误预算的构建过程

  • 产品管理层定义一个 SLO,确定一项服务在每个季度的正常运行时间。
  • 实际在线时间是通过一个中立的第三方来测算的: 我们的监控系统。
  • 这两个数字的差值就是这个季度中剩余的不可靠性预算。
  • 只要测算出的正常在线时间高于 SLO,也就是说,只要仍然有剩余的错误预算,就可以发布新版本。

本章关键点

  • 管理服务的可靠性主要在与管理风险,而且管理风险的成本可能很高。
  • 100%可能永远都不是一个正确的可靠性目标: 不仅是不可能实现的,而且它通常比一项服务的用户期望的可靠性大得多。我们要将服务风险和愿意承担的业务风险相匹配。
  • 错误预算在 SRE 和产品研发团队之间调整激励,同时强调共同责任。错误预算使得讨论发布速率更容易,同时可有效地减少任何关于事故的讨论。这样,多个团队可以毫无怨言地对生产环境风险度达成一致。

第四章: 服务质量目标

这个过程中,我们需要利用一些主观判断结合过去的经验对服务的理解来定义一些服务质量指标(SLI)服务质量目标(SLO),以及服务质量协议(SLA)。这三项分别是指该服务最重要的一些基础指标、这些指标的预期值,以及当指标不符合预期时的应对计划。

服务质量术语

指标(Indicator)

SLI 是指服务质量指标 - 该服务的某项服务质量的一个具体量化指标。 大部分服务都将请求延迟(处理请求所消耗的时间)作为一个关键 SLI。其他常见的 SLI 包括错误率(请求处理失败的百分比)、系统吞吐量(每秒请求数量)等。这些度量通常是汇总过的: 在某一个度量时间范围内将原始数据收集起来,计算速率、平均值、百分比等汇总数据。 可用性(availability)是另外一个重要的 SLI,代表服务可用时间的百分比。对于数据存储系统来说,持久性(durability) - 数据能够完整保存的时间也是一个重要指标。

目标(Objective)

SLO 是服务质量目标 - 服务的某个 SLI 的目标值,或者目标范围。 SLO 的定义是 SLI ≤ 目标值,或者范围下限 ≤ SLI ≤ 范围上限

协议(Agreement)

SLA 是服务质量协议 - 指服务与用户之间的一个明确的,或者不明确的协议,描述了在达到或者没有达到 SLO 之后的后果。 区别 SLO 和 SLA 的一个简单的方法就是问"如果 SLO 没有达到目标,有什么后果?” 如果没有定义明确的后果,那么我们就肯定是在讨论一个 SLO,而不是 SLA。

指标在实践中的应用

运维人员和最终用户各关心什么

我们不应该将监控系统中的所有指标都定义 SLI;只有理解用户对系统的真实需求才能真正决定哪些指标是否有用。一般来说四五个具有代表性的指标对系统健康程度的评估和关注就足够了。 常见的服务,根据它们的相关 SLI 通常会归类为以下几个大类。

  • 用户可见的服务系统,例如莎士比亚搜索服务的前端服务器通常关系可用性、延迟,以及吞吐量。换句话说: 是否能正常处理请求?每个请求花费的时间是多少?多少请求可以被处理?
  • 存储系统通常强调: 延迟、可用性和数据持久性。换句话说: 读写数据需要多少时间?我们是否可以随时访问数据?数据是否一段时间内还能被读取?
  • 大数据系统,例如数据处理流水线系统,一般来说关系吞吐量和端到端延迟。换句话说: 处理了多少数据?数据从输入到产出需要多少时间?(某些流水线任务还会关注某个单独处理阶段的延迟)
  • 所有的系统都应该关注: 正确性。是否返回了正确的回复,是否读取了正确的数据,或者进行了正确的数据分析操作。正确性是系统健康程度的一个重要指标,但是它更关注系统内部的数据,而不是系统本身,所以这通常不是 SRE 直接负责的。

目标在实践中的应用

我们应该从思考(或者调研)用户最关心的方面入手,而非从现在能度量什么入手。用户真正关心的部分经常是度量起来很困难,甚至是不可能的,所以我们需要以某种形式近似。如果我们只是从可以简单度量的数值入手,最终的 SLO 的作用就会很有限。 因此,与其选择指标,再想出对应的目标,不如从想要的目标反向推导出具体的指标。

目标的选择

  • 不要仅以目前的状态为基础选择目标

    了解系统的各项指标和限制非常重要,但是仅仅按照当前系统的标准制定目标,而不从全局出发,可能会导致团队被迫长期运维一个过时的系统,没有时间去推动架构重构等任务

  • 保持简单

    SLI 中过于复杂的汇总模式可能会掩盖某种系统性能的变化,同时也更难以理解。

  • 避免绝对值

    虽然要求系统可以在没有任何延迟增长的情况下无限扩张,或者"永远"可用是很诱人的,但是这样的要求是不切实际的。就算有一个系统能够做到这一点,它也需要花很长时间来设计和构建,同时运维也很复杂,最关键的是,这可能比用户可以接受的(甚至是很开心接受的)标准要高太多。

  • SLO 越少越好

    应该仅仅选择足够的 SLO 来覆盖系统属性,一定要确保每一个 SLO 都是必不可少的: 如果我们无法针对某个 SLO 达标问题说服开发团队,那么可能这个 SLO 就是不必要的(如果 SRE 团队无法说服研发团队接受任何一个 SLO,那么这个产品可能压根不需要 SRE 团队的支持)。然而,不是所有的产品属性都能用 SLO 表达,用户的"满意度"就很难。

  • 不追求完美

    我们可以随着时间流逝了解系统行为之后优化 SLO 的定义。刚开始可以以一个松散的目标开始,逐渐收紧。这比一开始制定一个困难的目标,在出现问题时放松要好的多。

控制手段

SLI 和 SLO 在决策系统运维时也非常有用

  1. 监控并且度量系统的 SLI
  2. 比较 SLI 和 SLO,以决定是否需要执行操作
  3. 如果需要执行操作,则要决定究竟什么操作需要被执行,以便满足目标
  4. 执行这些操作 例如,如果在第 2 步中,请求延迟正在上涨,无操作的话,会在几个小时内超出 SLO 范围。第 3 步则会测试服务器是否是 CPU 资源不够,同时增加一些 CPU 来分散负载。没有 SLO 的话,我们就不知道是否(或者何时)需要执行该操作。

第五章: 减少琐事

如果系统正常运转中需要人工干预,应该将此视为一种 BUG。“正常"的定义会随着系统的进步而不断改变。

SRE 要把更多的时间花费在长期项目研发上而非日常运维中。因为术语日常运维可能会被误解,我们在这里使用一个专门的词语 - 琐事(toil)

琐事的定义

  • 手动性
  • 重复性的
  • 可以被自动化的
  • 战术性的
  • 没有持久价值
  • 与服务同步线性增长

什么算做工程工作

典型的 SRE 活动分为如下几类

  • 软件工程
  • 系统工程
  • 琐事
  • 流程负担

琐事繁多是不是一定不好

琐事不太多的时候,已知的和重复性的工作有一种让人平静的功效。琐事可能是低风险低压力的活动,有些员工甚至喜欢做这种类型的工作。 如果琐事特别繁重,那就应该非常担忧。 琐事有害的因素

  • 职业停滞
  • 士气低落 另外,牺牲工程实践而做琐事会对 SRE 组织的整体发展造成损害
  • 造成误解
  • 进展缓慢
  • 开创先例
  • 促进摩擦产生
  • 违反承诺

本章小结

如果我们都致力于每一周通过工程工作消除一点琐事,就可以持续性地整顿服务。我们就可以将更多的力量投入到扩大服务规模的工程工作上去,或者是进行下一代的服务的架构设计,又或者是建立一套跨 SRE 使用的工具链。让我们多创新,少干琐事吧!

第六章: 分布式系统的监控

术语定义

  • 监控(monitoring)

    收集、处理、汇总,并且现实关于某个系统的实时量化数据,例如请求的数量和类型,错误的数量和类型,以及处理用时,应用服务器的存活时间等。

  • 白盒监控(white-box monitoring)

    依靠系统内部暴露的一些性能指标进行监控。包括日志分析、Java 虚拟机提供的监控接口,或者一个列出内部统计数据的 HTTP 接口的监控。

  • 黑盒监控(black-box monitoring)

    通过测试某种外部用户可见的系统行为进行监控。

  • 监控台页面(dashboard)

    提供某个服务核心指标一览服务的应用程序(一般是基于 Web 的)。该应用程序可能会提供过滤(filter)功能、选择(selector)功能等,但是主要的功能是用来显示系统最重要的指标。该程序同时可以显示相应团队的一些信息,包括目前工单的数量、最高优先级的 Bug 列表、目前的 on-call 工程师和最近进行的生产发布等。

  • 警报(alert)
  • 根源问题(root cause)

    如果这个问题被修复,就可以保证这种问题不会再以同样的方式发生。

  • 节点或机器(node/machine)
  • 推送(push)

为什么要监控

  • 分析长期趋势
  • 跨时间范围的比较,或者是观察实验组与控制组之间的区别
  • 报警

    某项东西出现故障了,需要有人立刻修复!或者某项东西可能很快会出现故障,需要有人尽快查看。

  • 构建监控台页面
  • 临时性的回溯分析(也就是在线调试)

    我们的请求延迟刚刚大幅增加,有没有其他的现象同时发生?

我们不应该仅仅因为"某些东西看起来有点问题"就发出警报。 高效的警报系统应该提供足够的信息,并且误报率非常低。 虽然平时看看流量图表可能很有意思,但是 SRE 团队通常会小心地避免任何需要某个人"盯着屏幕寻找问题"的情况。 监控系统最重要的一点就是整个"生产故障,人工处理紧急警报,简单定位和深入调试"的过程必须要保持非常简单,必须能被团队中任何一个人所理解。

现象与原因

监控系统应该解决两个问题: 什么东西出故障了,以及为什么出故障。 “什么东西出故障了"即为现象(symptom),“为什么"则代表了原因(可能只是中间原因,并不是根源问题)。

黑盒监控与白盒监控

黑盒监控与白盒监控最简单的区别是: 黑盒监控是面向现象的,代表了目前正在发生的,而非预测会发生的问题,即"系统现在有故障”。白盒监控则大量依赖对系统内部信息的检测,如系统日志、抓取提供指标信息的 HTTP 节点等。

4 个黄金指标

监控系统的 4 个黄金指标分别是 延迟、流量、错误和饱和度(saturation)

  • 延迟

    服务处理某个请求所需要的时间。这里区分成功请求和失败请求很重要。例如,某个由于数据库连接丢失或者其他后端问题造成的 HTTP 500 错误可能延迟很低。计算总体延迟时,如果将 500 回复的延迟也计算在内,可能会产生误导性的结果。

  • 流量

    使用系统中的某个高层次的指标对系统负载需求所进行的度量。对于 Web 服务器来说,该指标通常是每秒 HTTP 请求数量,同时可能按请求类型分类(静态请求与动态请求)。针对音频流媒体系统来说,这个指标可能是网络 I/O 速率,或者并发会话数量。针对键值对存储系统来说,指标可能是每秒交易数量,或每秒的读取操作数量。

  • 错误

    请求失败的速率,要么是显示失败(HTTP 500),要么是隐式失败(HTTP 200 回复中包含了错误内容)。

  • 饱和度

    服务容量有多"满”。通常是系统中目前最为受限的某种资源的某个具体指标的度量。(在内存受限的系统中,即为内存;在 I/O 受限的系统中,即为 I/O)。

如果我们度量所有这 4 个黄金指标,同时在某个指标出现故障时发出警报(或者对于饱和度来说,快要发生故障时),能做到这些,服务的监控就基本差不多了。

关于长尾问题

区分平均值的"慢"和长尾值的"慢"的一个最简单的办法是将请求按延迟分组计数(可以用来制作直方图): 延迟 0 ~ 10ms 之间的请求数量有多少,30 ~ 100ms 之间,100 ~ 300ms 之间等。将直方图的边界定义为指数型增长(这个例子中倍数为 3)是直观展现请求分布的最好方式。

度量指标时采用合适的精度

系统的不同部分应该以不同的精度进行度量

  • 观察 1 分钟内的 CPU 平均值可能会错失导致长尾延迟过高的某种较长时间的 CPU 峰值现象。
  • 对于一个每年停机时间小于 9 小时的 Web 服务来说(年度可用绿 99.9%),每分钟检测 1 次或 2 次的监控频率可能过于频繁。
  • 对目标可用率为 99.9%的某个服务每 1 分钟或者 2 分钟检查一次硬盘剩余空间可能也是没必要的。

简化,直到不能再简化

设计监控系统时一定要追求简化。在选择需要检测什么的时候,将下列信息记在心里

  • 那些最能反映真实故障的规则应该越简单越好,可预测性强,非常可靠。
  • 那些不常用的数据收集、汇总,以及警报配置应该定时删除(某些 SRE 团队的标准是一个季度没有用到一次即将其删除)。
  • 收集到的信息,但是没有暴露给任何监控台,或者被任何警报规则使用的应该定时删除。

将上述理念整合起来

  • 该规则是否能检测到一个目前检测不到的、紧急的、有操作性的,并且即将发生或者已经发生的用户可见故障?
  • 是否可以忽略这条警报?什么情况可能会导致用户忽略这条警报,如何避免?
  • 这条警报是否确实显示了用户正在受到影响?是否存在用户没有受到影响也可以触发这条规则的情况?例如测试环境和系统维护状态下发出的警报是否应该被过滤掉。
  • 收到警报后,是否要进行某个操作?是否需要立即执行该操作,还是可以等到第二天早上再进行?该操作是否可以被安全地自动化?该操作的效果是长期的,还是短期的?
  • 是否也会有其他人收到相关的紧急警报,这些紧急警报是否是不必要的?

以上这些问题反映了在应对紧急警报上的一些深层次理念

  • 每当收到紧急警报时,应该立即需要我进行某种操作。每天只能进入紧急状态几次,太多就会导致"狼来了"效应。
  • 每个紧急警报都应该是可以具体操作的。
  • 每个紧急警报的回复都应该需要某种智力分析过程。如果某个紧急警报只是需要一个固定的机械动作,那么它就不应该成为紧急警报。
  • 每个紧急警报都应该是关于某个新问题的,不应该彼此重叠。

第七章: Google 的自动化系统的演进

“黑科技"之外,就只剩自动化和机械化了。

自动化的价值

  • 一致性

    没有人能像机器一样永远保持一致。

  • 平台性
  • 修复速度更快
  • 行动速度更快
  • 节省时间

如果我们持续产生不可自动化的流程和解决方案,我们就继续需要人来进行系统维护。如果我们要雇佣人来做这项工作,就像是用人类的鲜血、汗水和眼泪养活机器。这就像是一个没有特效但是充满了愤怒的系统管理员的 Matrix 世界。

自动化分类的层次结构

自动化演进遵循以下路径

  1. 没有自动化

    手动将数据库主进程在多个位置之间转移

  2. 外部维护的系统特定的自动化系统

    SRE 在 TA 的主目录中保存了一份故障转移脚本

  3. 外部维护的通用自动化系统

    SRE 将数据库支持添加到了每个人都在使用的"通用故障转移"脚本中

  4. 内部维护的系统特定的自动化

    数据库自己发布故障转移脚本

  5. 不需要任何自动化的系统

    数据库注意到问题发生,在无需人工干预的情况下进行故障转移

第八章: 发布工程

发布工程师的角色

Google 是一个数据驱动的公司,发布工程也不例外。各种各样的工具提供各种各样的数据。例如,从代码修改提交到部署到生产环境一共需要多长时间(也就是发布速度)。大部分这些工具都是由发布工程师设计和开发的。

发布工程哲学

  • 自服务模型

    发布工程师开发工具,制定最佳实践,以便让产品研发团队可以自己掌控和执行自己的发布流程。发布过程是真正的自动化的,工程师仅仅在发生问题时才会进行干预。

  • 追求速度

    有些团队每小时构建一次,然后在所有可用的构建版本中选择某个版本进行发布。

  • 密闭(hermetic)性

    构建工具必须确保一致性和可重复性。

  • 强调策略和流程 多层安全和访问控制机制可以确保在发布过程中只有指定的人才能执行指定的操作。我们主要关注的操作有如下
    • 批准源代码改动 - 通过源代码仓库中的配置文件决定
    • 指定发布流程中需要执行的具体动作
    • 创建心的发布版本
    • 批准初始的集成请求(也就是一个以某个源代码仓库版本为基础的构建请求),以及后续的cherry picking请求
    • 实际部署某个发布版本
    • 修改某个项目的构建配置文件

配置管理

  • 使用主分支版本配置文件
  • 将配置文件与二进制文件打包在同一个包中
  • 将配置文件打包成配置文件包
  • 从外部存储服务中读取配置文件

总之,项目负责人在分发和管理配置文件时有多种选择,可以按需决定究竟那种最适合该服务。

小结

当采用合适的工具、合理化的自动化方式,以及合理的政策时,开发团队和 SRE 都无需担心如何发布软件。发布过程可以像一个按钮那么简单。

  • 如何管理包的版本?
  • 应该采用持续构建和部署的模型,还是应该定期构建?
  • 发布的频率应该怎样?
  • 应该使用什么策略管理配置文件?
  • 哪些发布过程的指标比较有用?

任何组织都应该先花一些时间定义自己的发布政策。

第九章: 简单化

可靠性只有靠对最大程度的简化不断追求而得到。

一个对 SRE 管理系统方法的总结是: “我们的工作最终是在系统的灵活性和稳定性上维持平衡”。

乏味是一种美德

关注必要复杂度和意外复杂度之间的区别非常关键。必要复杂度是一个给定的情况所固有的复杂度,不能从该问题的定义中移除,而意外复杂度则是不固定的,可以通过工程上的努力来解决。例如,编写一个 Web 服务器需要处理快速提供 Web 页面的必要复杂度。但是如果我们用 Java 编写该服务器,试图减少 GC 的影响就可能引入意外复杂度。

为了最小化意外复杂度,SRE 团队应该

  • 在他们所负责的系统引入意外复杂度时,及时提出抗议
  • 不断地努力消除正在接手的和已经负责运维的系统的复杂度

我绝对不放弃我的代码

工程师经常对于自己编写的代码形成一种情感依附,这些冲突在大规模清理源代码树的时候并不少见。一些人可能会提出抗议

  • 如果我们以后需要这个代码怎么办?
  • 我们为什么不只是把这些代码注释掉,这样稍后再使用它的时候会更容易吗?
  • 为什么不增加一个功能开关?

这些都是糟糕的建议。版本控制系统更改反转很容易,数百行的注释代码则会造成干扰和混乱(尤其是当源文件继续演进时);

那些由于功能开关没有启用而没有被执行的代码,就像一个定时炸弹一样等待爆炸。

最小 API

不是在不能添加更多的时候,而是没有什么可以去掉的时候,才能达到完美。

第十章: 基于时间序列数据进行有效报警

让查询来得更猛烈些吧,让寻呼机永远保持沉默!

一个大型系统不应该要求运维人员持续关注其中的无数个小组件,而是应该自动汇总所有的信息,自动抛弃其中的异常情况。监控系统应该主要从高级服务质量目标层面进行报警,但是也应该保持足够的粒度,可以追踪到某个具体的组件。

Borgmon

Google 之外的时间序列监控系统

  • Riemann
  • Heka
  • Bosun
  • Prometheus

第十一章: on-call 轮值

安全感

现代理论研究指出,在面临挑战时,一个人会主动或非被动(潜意识)地选择下列两种处理方法之一

  • 依赖直觉,自动化、快速行动
  • 理性、专注、有意识地进行认知类活动

当处理复杂系统问题时,第二种行事方式是更好的,可能会产生更好的处理结果,以及计划更周全的执行过程

在应急事故处理过程中,凭直觉操作和快速反应(例如服务出现问题就先重启服务器)看起来都是很有用的方法,但是这些方法都有自己的缺点。直觉很可能是错误的,而且直觉一般都不是基于明确的数据支持的。因此在处理问题的过程中,on-call 工程师很可能由于凭直觉去解释问题产生的原因而浪费宝贵的时间。快速反应主要是由习惯而产生的,习惯性的快速反应的动作后果一般都没有经过详细考虑,这可能会将灾难扩大。

在应急时间处理过程中,最理想的方法论是这样的: 在有足够的数据支撑的时候按步骤解决问题,同时不停地审视和验证目前所有的假设。

第十二章: 有效的故障排查手段

值得警惕的是,理解一个系统应该如何工作并不能使人成为专家。只能靠调查系统为何不能正常工作才行。

– Brian Redman

系统正常,只是该系统无数异常情况下的一种特例

– John Allspaw

新手们常常不能有效地进行故障排查,是因为这个过程理想情况下同时需要两个条件。

  1. 对通用的故障排查过程的理解(不依靠任何特定系统)
  2. 对发生故障的系统的足够了解

虽然只依靠通用性的流程和手段可以处理一些系统中的问题,但我们发现这样做通常是很低效的。对系统内部运行的了解往往限制了 SRE 处理系统问题的有效性,对系统设计方式的构建原理的知识是不可或缺的。

常见的陷阱

造成低效的故障排查过程的原因通常集中在定位(triage)、检查和诊断环节上,主要由于对系统不够了解而导致。下面列举了一系列常见的陷阱:

  1. 关注了错误的系统现象,或者错误地理解了系统现象的含义。这样会在错误的方向上浪费时间。
  2. 不能正确修改系统的配置信息、输入信息或者系统运行环境,造成不能安全和有效地测试假设。
  3. 将问题过早地归结为极为不可能的因素(例如认为是宇宙射线造成数据变化,虽然有可能发生,但是并不应该在解决问题初期做这个假设),或者念念不忘之前曾经发生过的系统问题,认为一旦发生过一次,就有可能再次发生。
  4. 试图解决与当前系统问题相关的一些问题,却没有认识到这些其实是巧合,或者这些问题其实是由于当前系统的问题造成的。(比如发现数据库压力大的情况下,环境温度也有所上升,于是试图解决环境温度的问题)

要避免第 1 点和第 2 点,需要更详细地学习系统的运行原理,同时了解分布式系统运行的基本模式。

要避免第 3 点,需要记住,不是所有的失败情况的出现概率都相同,就像谚语中说的"当你听到蹄子声响时,应该先想到马,而不是斑马。",而且尤其要注意的是,当所有的可能都存在的时候,我们应该优先考虑最简单的解释

在某些系统中某一类问题可能完全被排除了。例如,在细心设计的集群文件系统实现中,由于某个磁盘出现问题导致延迟问题是非常不可能的了。

熬卡姆剃刀原理(Occam’s Razor),系统中可能同时存在多个问题,尤其是有的时候是因为系统中存在一系列低危害性问题,联合起来,才可以解释目前的系统状态。而不是系统中存在一个非常罕见的问题,同时造成了所有的问题现象。

理解我们逻辑推理过程中的错误是避免这些问题发生的第一步,这样才能更有效地解决问题。区分我们知道什么,我们不知道什么,我们还需要知道什么可以让查找问题原因和修复问题更容易。

定位

在大型问题中,你的第一反应可能是立即开始故障排查过程,试图尽快找到问题根源,这是错误的!不要这样做。

正确的做法应该是:尽最大可能让系统恢复服务。这可能需要一些应急措施,比如将用户流量从问题集群导向其他还在正常工作的集群,或者将流量彻底抛弃以避免连锁过载问题,或者关闭系统的某些功能以降低负载。缓解系统问题应该是你的第一要务。在寻找问题根源的时候,不能使用系统的用户并没有得到任何帮助。当然,快速定位问题时仍应该及时保存问题现场,比如服务日志等,以便后续进行问题根源分析时使用。

什么哪里为什么

在一个异常系统中,该系统经常还在执行某种操作,只是这些操作不是你想让系统执行的操作。那么找出系统目前正在执行什么,然后通过询问该系统为什么正在做这些操作,以及系统的资源都被用在了`哪里 可以帮助你了解系统为什么出错。

第十三章: 紧急事件响应

所有的问题都有解决方案

时间和经验一再证明,系统不但一定会出问题,而且会以没有人能够想到的方式出问题,Google 学到的最关键的一课是,所有的问题都有对应的解决方案,虽然对一个面对着疯狂报警的工程师来说,它可能不是那么显而易见。如果你想不到解决方法,那么就在更大的范围寻求帮助。找到更多的团队成员,寻求更多的帮助,做你需要做的一切事情,但是要快。最高的优先级永远是将手头问题迅速解决。很多时候,触发这个事故的人对事故了解得最清楚,一定要充分利用这一点。

非常重要的是,一旦紧急事件过去之后,别忘了留出一些时间书写事后报告。

为事故保留记录

没有什么比过去的事故记录是更好的学习资料了。历史就是学习其他人曾经犯的错误。在记录中,请一定要诚实,一定要事无巨细。尤其重要的是,提出关键的问题。时刻寻找如何能在战术及战略上避免这项事故的发生。公布和维护事后报告,确保全公司的每个人都能从中学到你所学到的知识。

在事故结束后,确保自己和其他人切实完成事故中总结的待办事项。这样能够避免未来再次发生以同样的因素触发的同样的事故。一旦开始仔细学习过去的事故,我们就能更好地避免未来的事故。

提出那些大的,甚至不可能的问题: 假如… …

没有什么比现实更真实的测试了。我们应该提出一些大的、没有确切答案的问题。

  • 假如整栋楼的电源坏了怎么办?
  • 假如网络设备被泡在半米深的水里怎么办?
  • 如果主数据中心突然下线了怎么办?
  • 如果有人入侵了你的 Web 服务器怎么办?

你怎么处理?找谁联系?谁来付钱?有对应的应急计划吗?你知道你的系统会如何应对吗?如上述所说正在发生,你能够立即最小化灾难损失吗?坐在你旁边的人能做到同样的事吗?

第十四章: 紧急事故管理

无流程管理的事故剖析

每个人都在尽力解决问题,起码他们自己看起来是这样。那么问题是怎么变得月来越糟呢?在这次处理的过程中,有几个常见的问题导致了整个事故的失控。

  • 过于关注技术问题

    我们倾向于按技术能力指标聘请 Jack。所以他在灾难过程中忙着不断改变系统,英勇地尝试解决服务问题一点也不奇怪。由于他正忙着执行技术操作,所以根本没有时间和精力去思考如何能够通过其他手段缓解当前服务的问题。

  • 沟通不畅

    同样的原因,Jack 根本没有时间清晰和有效地与其他人进行沟通,没有人知道他们的同事正在做什么。业务部门领导十分愤怒,最终用户正在面临服务问题,而其他可以帮忙调试和处理问题的工程师却没有被充分地利用起来。

  • 不请自来

    Tom 正在出于善意修改系统,但是他没有通知其他的同事 – 甚至 Jack。严格地讲,Jack 是故障排除的主要负责人,Tom 的操作将服务状况变得更糟了。

紧急事故的流程管理要素

Google 的紧急事故管理系统是基于 ICS - Incident Command System的,这套体系以清晰和灵活性著称。

嵌套式职责分离

在事故处理中,让每个人清楚自己的职责是非常重要的。明晰职责反而能够使每个人可以更独立自主地解决问题,因为他们不用担心怀疑他们的同事都在干什么。

以下是系统中可以分配给某个人的角色

  • 事故总控 incident command
  • 事物处理团队 operational work
  • 发言人 communication
  • 规划负责人 planning

什么时候对外宣布事故

  • 是否需要引入第二个团队来帮助处理问题?
  • 这次事故是否正在影响最终用户?
  • 在集中分析一小时后,这个问题是否依然没有得到解决?

事故流程管理最佳实践

  • 划分优先级
  • 事前准备
  • 信任
  • 反思
  • 考虑替代方案
  • 练习
  • 换位思考

第十五章 事后总结: 从失败中学习

学习是避免失败的最好办法 - Devin Carraway

事后总结条件为

  • 用户可见的宕机时间或者服务质量降级程度达到一定标准
  • 任何类型的数据丢失
  • on-call 工程师需要人工介入的事故(包括回滚、切换用户流量等)
  • 问题解决耗时超过一定限制
  • 监控问题(预示着问题是有人工发现的,而非报警系统)

第二十二章: 处理连锁故障

缓存

应该确保每个新添加的缓存要么是延迟类缓存,要么是经过良好设计的、可安全使用的容量类缓存。有些时候加入缓存是为了提高性能,但是最后却变成了强制依赖。

连锁故障的触发条件

  • 进程崩溃
  • 进程更新
  • 新的发布
  • 自然增长
  • 计划中或计划外的不可用

解决连锁故障的立即步骤

  • 增加资源
  • 停止健康检查导致的任务死亡

    进程任务的健康检查 - 这个进程是否相应请求。对集群管理系统有用

    服务级别的健康检查 - 该进程是否能够回复这种类型的请求。对负载均衡器有用

  • 重启软件服务器
    • Java 服务器处于 GC 死亡螺旋中

      由于 CPU 资源减少,请求处理速度变慢,内存使用率上升,导致 GC 触发次数增多,导致 CPU 资源进一步的减少。我们将此称之为GC 死亡螺旋

    • 某些正在处理的请求因为没有截止时间设置而正在消耗资源(如正在占用线程)
    • 死锁
  • 丢弃流量
    1. 解决最初的触发原因(如增加容量)
    2. 将负载降低到一定水平,使得崩溃停止。考虑在这里激进一些,如果整个服务都在崩溃循环中,那么可以考虑降低流量到 1%的水平
    3. 允许大部分的软件服务器恢复健康
    4. 逐渐提升负载水平
  • 进入降级模式
  • 消除批处理负载
  • 消除有害的流量