Google Cloud 的变革方法

每年有数十亿用户与 Google 的产品和服务互动。搜索、Gmail、Google 地图、YouTube、Chrome 等关键产品(现在还有 Google Cloud)已无缝融入现代生活,帮助塑造 21 世纪的体验。这种全球影响力是由于我们的产品和服务的质量经受了考验,以及用户对 Google 始终可用性的期望。

在 Google Cloud,我们会不断对产品和服务进行代码更改,以确保提供尽可能出色的用户体验、提高安全性和可靠性,并遵守监管和合规要求。任何此类更改,无论大小,有时都会导致问题。为了应对这种风险,我们会在整个变更生命周期中优先考虑变更安全性。

本文档介绍了 Google Cloud 团队如何在 Google 数十年来对卓越开发的投资基础上,实现可靠性最佳实践和工程标准,以满足 Google Cloud 客户对开发速度和可靠性的期望。

Google Cloud 中的变更生命周期

Google Cloud 产品团队与 Google 的其他工程团队共享许多管理流程和工具。我们采用标准的软件开发方法来管理变更,并优先考虑持续集成 (CI) 和持续交付 (CD)。CI 包括频繁提交更改建议、测试和提交更改,通常每天针对任何给定的产品或服务进行多次。CD 是 CI 的扩展,在 CD 中,工程师会根据代码库的最新稳定快照持续准备候选版本。

这种方法优先考虑尽快创建并分阶段向 Google Cloud 客户发布更改,同时尽可能确保安全。我们会在编写任何代码之前考虑更改安全性,即使在生产环境中推出更改后,我们也会继续关注安全性。我们的变更管理模型大致分为四个阶段:设计、开发、资格认证和发布。下图显示了这四个阶段,本文档中将对这四个阶段进行更详细的讨论:

示意图:显示了设计、开发、资格认证和发布阶段的步骤。

原生安全

我们认识到,即使开发过程中早期出现的小错误,也会在后期导致严重问题,从而显著影响客户体验。因此,我们要求所有重大更改都应以已获批准的设计文档为起点。我们有一个通用的设计文档模板,供工程团队提出重大更改。此通用设计文档可帮助我们一致地评估 Google Cloud 产品的重大变更。下图展示了我们针对重大变更的标准设计流程:

设计阶段涉及的步骤的详细图示。

设计阶段从软件开发者提出的更改开始,该更改可满足业务、技术、成本和维护要求。提交相应更改后,系统会启动全面的审核和批准流程,包括可靠性和安全性专家以及技术主管在内的高级专家会参与其中。只有在提出设计的工程师解决了专家的所有反馈,并且每位专家都批准了设计后,才能继续实施更改。此设计和审核流程可降低 Google Cloud 产品团队甚至开始着手进行的更改可能会对生产环境中的客户产生负面影响的可能性。

安全性与开发时相同

我们的代码开发流程可提高代码的质量和可靠性。在建议的更改获得批准后,开发流程将从为新工程师提供全面的入职培训开始,包括培训、导师辅导以及针对建议的代码更改提供详细反馈。采用多层开发和测试方法,在开发的每个阶段使用手动测试和自动化测试持续验证代码。我们会仔细审核每项代码更改,以确保其符合 Google 的高标准。

下图提供了一个工作流,大致说明了我们的开发流程:

详细图示了开发阶段涉及的步骤。

当工程师开始编写代码以及相应的单元测试和集成测试时,开发阶段就开始了。在整个阶段中,工程师可以运行他们编写的测试以及一系列提交前测试,以确保代码添加和更改有效。完成代码更改和运行测试后,工程师会请求熟悉代码的其他人手动进行审核。此人工审核流程通常是迭代的,可能会导致额外的代码修订。当作者和审核者达成一致意见后,作者会提交代码。

编码标准可确保高质量的更改

Google 的工程文化、实践和工具旨在确保我们的代码正确、清晰、简洁且高效。Google 的代码开发是在我们的单一代码库中进行的,这是全球最大的集成代码库。Monorepo 包含数百万个源文件、数十亿行代码,以及数千万个提交历史记录(称为更改列表)。它会持续快速增长,每天工作日都会添加数万个新的更改列表。Monorepo 的主要优势在于,它有助于代码重用、简化依赖项管理,并在产品和服务之间强制执行一致的开发者工作流。

代码重复利用非常有用,因为我们已经很好地了解了重复使用的代码在生产环境中的表现。通过利用现有的优质代码,更改本身会更稳健,并且更容易按照所需标准进行维护。这种做法不仅可以节省时间和精力,还可以确保代码库的整体健康状况保持良好,从而打造出更可靠的产品。

基于高质量开源软件的 Google Cloud 服务可能会使用其他代码库(通常是 Git)来补充单一代码库,以便使用分支来管理开源软件。

关于训练的注意事项

在工程师加入团队时,就应开始注重代码质量。如果工程师是 Google 新员工,或者对团队的基础架构和架构不太熟悉,则需要进行全面的新手入门培训。在入职培训期间,他们会学习样式指南、最佳实践和开发指南,并手动完成实操练习。此外,新工程师提交的每个单独的变更列表都需要额外的审批。针对特定编程语言的更改将由工程师进行审批,这些工程师已根据其专业知识通过了一系列严格的考核,并在该编程语言中获得了可读性。任何工程师都可以针对某种编程语言获得可读性评分 - 大多数团队都有多个审批人来审核他们所使用的编程语言。

左移以安全提高速度

“左移”原则是指在开发过程中提前进行测试和验证。这一原则基于我们的观察,即在发布过程中,我们发现并修复 bug 的时间越晚,成本就大幅增加。在极端情况下,请考虑客户在生产环境中发现的 bug。此 bug 可能会对客户的工作负载和应用产生负面影响,并且客户可能还需要完成 Cloud Customer Care 流程,然后相关工程团队才能缓解此 bug。如果分配来解决问题的工程师与最初引入包含 bug 的更改的工程师不同,新工程师将需要熟悉代码更改,这可能会增加重现 bug 并最终修复 bug 所需的时间。整个过程需要客户和 Google Cloud 支持团队花费大量时间,并且要求工程师放下手头的工作来解决问题。

相反,请考虑在工程师处理正在开发中的更改时,自动化测试失败捕获的 bug。当工程师看到测试失败时,可以立即进行修复。由于我们的编码标准,工程师甚至无法提交测试失败的更改。这种早期检测意味着工程师可以修复 bug 而不会对客户造成影响,并且没有上下文切换开销。

后一种情况对所有相关人员来说都是无限可取的。因此,多年来,Google Cloud 一直在这一“左移”原则上投入大量资金,将传统上在变更资格认证和发布阶段执行的测试直接转移到开发循环中。目前,在工程师提出代码更改建议时,所有单元测试(除了最复杂的集成测试)和大量静态分析和动态分析都会并行完成。

自动化提交前测试可强制执行编码标准

提交前测试是在提交给定目录中的任何更改之前运行的检查。预提交测试可以是针对更改的单元测试和集成测试,也可以是针对任何更改运行的常规测试(例如静态分析和动态分析)。过去,提交前测试是作为提交代码库更改的最后一步运行的。如今,部分由于左移原则和我们实现的 CI,我们会在工程师在开发环境中进行代码更改时以及在将更改合并到单个代码库之前持续运行提交前测试。工程师还可以在开发界面中单击一下,手动运行提交前测试套件,并且在人工代码审核之前,每个变更列表都会自动运行每个提交前测试。

提交前测试套件通常涵盖单元测试、模糊测试(模糊测试)、密封集成测试以及静态和动态代码分析。对于对核心库或在 Google 中广泛使用的代码所做的更改,开发者会运行全局预提交。全局预提交测试会针对整个 Google 代码库测试更改,最大限度地降低所提议的更改对其他项目或系统产生负面影响的风险。

单元测试和集成测试

全面测试是代码开发流程中不可或缺的一环。每个人都必须针对代码更改编写单元测试,我们会持续跟踪项目级别的代码覆盖率,以确保我们验证了预期的行为。此外,我们要求所有关键用户历程都包含集成测试,以便我们验证所有必要组件和依赖项的功能。

单元测试以及除大型集成测试以外的所有测试都旨在快速完成,并在分布式环境中以高并行度逐步执行,从而快速且持续地提供自动化开发反馈。

模糊测试

与单元测试和集成测试有助于我们通过预先确定的输入和输出验证预期行为不同,模糊测试是一种通过随机输入轰炸应用的技术,旨在揭露可能导致安全漏洞或崩溃的隐藏缺陷或弱点。模糊测试可让我们主动发现并解决软件中的潜在弱点,在客户与更改互动之前提升产品的整体安全性和可靠性。此测试的随机性特别有用,因为用户有时会以我们意想不到的有趣方式与我们的产品进行交互,而模糊测试有助于我们考虑到我们未手动考虑到的情况。

静态分析

静态分析工具在维护开发工作流中的代码质量方面发挥着至关重要的作用。静态分析从早期使用正则表达式进行 lint 检查,到现在可以识别存在问题的 C++ 代码模式,已经有了显著的演变。目前,静态分析涵盖了所有 Google Cloud 生产语言,并可发现错误、低效或已废弃的代码模式。

借助高级编译器前端和 LLM,我们可以在工程师编写代码时自动提出改进建议。我们会对每项提议的代码更改进行静态分析审核。随着时间的推移,我们会添加新的静态检查,并不断扫描整个代码库以确保合规性,并自动生成并发送修复程序以供审核。

动态分析

静态分析侧重于识别可能导致问题的已知代码模式,而动态分析则采用不同的方法。它涉及编译和运行代码,以发现仅在执行期间出现的问题,例如内存违规和竞态条件。Google 在利用动态分析方面拥有丰富的经验,甚至与更广泛的开发者社区分享了多种工具,包括:

  • AddressSanitizer:检测缓冲区溢出和释放后使用等内存错误
  • ThreadSanitizer(C++Go):查找数据争用和其他线程 bug
  • MemorySanitizer:发现未初始化的内存用量

这些工具和类似工具对于捕获单靠静态分析无法检测到的复杂 bug 至关重要。通过同时使用静态分析和动态分析,Google 力求确保其代码结构良好、没有已知问题,并且在实际场景中表现符合预期。

人工代码审核可验证更改和测试结果

当工程师在代码中达到关键里程碑并希望将其集成到主代码库中时,他们会通过提交变更列表来发起代码审核。代码审核请求包含以下内容:

  • 描述更改的目的以及任何其他背景信息
  • 实际修改的代码
  • 针对修改后的代码的单元测试和任何集成测试
  • 自动化提交前测试结果

在开发流程的此阶段,另一个人会介入。一个或多个指定的审核者会使用附加的测试和提交前结果作为指南,仔细检查变更列表的正确性和清晰度。每个代码目录都有一组指定的审核者,负责确保该代码库的子集的质量,并且必须获得这些审核者的批准才能在该目录中进行更改。审核者和工程师协作,以发现并解决提出的代码更改可能出现的任何问题。当变更列表符合我们的标准时,审核人员会予以批准(“LGTM”是“looks good to me”的缩写)。不过,如果工程师仍在接受所用编程语言的培训,则需要获得在该编程语言中获得可读性认证的专家的额外批准。

在变更列表通过测试和自动检查并收到 LGTM 后,提出更改的工程师只允许对代码进行最小更改。任何重大更改都会使审批失效,并需要进行另一轮审核。即使是细微的更改,系统也会自动向原审核人员发出标记。工程师提交最终确定的代码后,该代码会在将更改列表合并到单一代码库之前再进行一轮完整的提交前测试。如果任何测试失败,系统会拒绝提交内容,开发者和审核者会收到提醒,要求他们采取修正措施,然后再尝试重新提交更改。

安全版本资格认证

虽然提交前测试非常全面,但这并不是 Google 测试流程的结束。团队通常还有其他测试(例如大规模集成测试),这些测试在初始代码审核期间无法运行(它们可能需要更长时间才能运行或需要高保真测试环境)。此外,团队还需要了解由不可控因素(例如外部依赖项的更改)导致的任何失败。

因此,Google 要求在开发阶段之后进行资格认证阶段。此资格认证阶段使用持续构建和测试流程,如下图所示:

详细图示了资格认证阶段涉及的步骤。

此流程会定期针对自上次运行以来受到直接或间接更改影响的所有代码运行测试。所有故障都会自动升级到负责的工程团队。在许多情况下,系统能够自动识别导致破坏的更改列表,并将其回滚。这些大规模集成测试是在一系列预演环境中执行的,从部分模拟环境到整个实体位置。

测试具有各种资格认证目标,从基本可靠性和安全性到业务逻辑。这些资格测试包括针对以下内容测试代码:

  • 提供所需功能的能力,通过大规模集成测试进行测试
  • 满足业务要求的能力,通过客户工作负载的综合表示进行测试
  • 能够承受底层基础架构故障,这通过在整个堆栈中注入故障来进行测试
  • 持续提供服务的能力,可通过负载测试框架进行测试
  • 能够安全地回滚

安全发布

即使采用最强大的开发、测试和资格认证流程,缺陷有时也会潜入生产环境,对我们的用户产生负面影响。在本部分中,我们将介绍 Google Cloud 发布流程如何限制有缺陷的更改的影响,并确保快速检测任何回归问题。我们会将此方法应用于部署到生产环境的所有类型的更改,包括二进制文件、配置、架构更新、容量更改和访问控制列表。

更改传播和监督

我们采用一致的方法在整个 Google Cloud 中部署更改,以尽量减少对客户的不利影响,并将问题隔离到各个逻辑和物理故障网域。该流程以我们数十年的 SRE 可靠性实践全球规模的监控系统为基础,旨在尽快检测并缓解错误的更改。快速检测功能可让我们更快地通知客户,并采取纠正措施,以系统化的方式防止类似故障再次发生。

大多数 Google Cloud 产品都是区域性或可用区级的。这意味着,在区域 A 中运行的区域化商品与在区域 B 中运行的同一商品是相互独立的。同样,在区域 A 内的区域 C 中运行的区域产品与在区域 A 内的区域 D 中运行的同一区域产品无关。这种架构可最大限度地降低服务中断对其他区域或单个区域内的其他可用区造成影响的风险。某些服务(例如 IAM 或 Google Cloud 控制台)提供跨所有区域的全球一致层,因此我们将其称为全球均可运行的服务。全球均可运行的服务会在各个区域复制,以避免任何单点故障并最大限度地缩短延迟时间。共享的 Google Cloud 发布平台会了解服务是区域性、地区性还是全球性,并相应地协调生产更改。

Google Cloud 发布流程会将部署到多个目标位置的服务的所有副本分成多个批次。初始波次包含少量副本,更新会按序进行。初始阶段的迁移工作在保护大多数客户工作负载的同时,最大限度地扩大工作负载多样性,以便尽早发现问题,并包含模拟常见客户工作负载模式的合成工作负载。

如果在目标位置更新服务副本后发布仍然成功,后续发布波的规模会逐渐增大,并引入更多并行性。尽管需要一些并行处理来考虑 Google Cloud 位置数量,但我们不允许同时更新不同阶段的 Google Cloud 位置。如果某个分批发布延长到夜间或周末,则可以完成其进度,但在负责发布的团队开始工作之前,不会开始新的分批发布。

下图是一个工作流示例,说明了我们在 Google Cloud 中针对区域产品和服务使用的发布逻辑:

详细图示了发布阶段涉及的步骤。

Google Cloud 发布流程使用 Canary Analysis Service (CAS) 在整个发布期间自动执行 A/B 测试。部分副本会成为信标(即在服务中部分且有时间限制地部署更改),其余副本则组成不包含更改的对照组。在发布流程的每个步骤中,都有一个烘烤时间,用于在进入下一步之前捕获慢性问题,以确保服务的所有功能都得到良好执行,并且 CAS 能够检测到潜在的异常。烘烤时间经过精心定义,以平衡检测慢性问题与开发速度。Google Cloud 发布通常需要一周时间。

下图简要概述了 CAS 工作流程:

CAS 工作流中遵循的步骤的示意图。

工作流从部署工具将更改部署到信标副本开始。然后,发布工具会向 CAS 请求判定结果。CAS 会根据对照组评估信标副本,并返回 PASS 或 FAIL 判定结果。如果任何健康信号失败,系统会为服务所有者生成提醒,并暂停或回滚发布的运行步骤。如果更改会导致外部客户中断服务,则会声明外部事件,并使用 Personalized Service Health 服务通知受影响的客户。事件也会触发内部审核。Google 的事故分析理念可确保识别并采取正确的纠正措施,以最大限度地降低类似故障再次发生的可能性。

监控信号和发布后安全性

软件缺陷并不总是立即显现,有些缺陷可能需要特定情况才能触发。因此,在发布完成后,我们会继续监控生产系统。多年来,我们注意到,即使某次发布不会立即触发任何问题,不良发布仍是生产突发事件最可能的原因。因此,我们的生产 Playbook 会指导事件响应人员将近期的发布与所观察到的问题相关联,并在事件响应人员无法排除近期更改是事件根本原因的情况下,默认回滚近期的发布。

在发布后监控中,我们使用了在发布期间用于自动化 A/B 分析的同一组监控信号。Google Cloud 监控和提醒理念结合了两种监控类型:自省(也称为白盒)和合成(也称为黑盒)。内省监控使用 CPU 利用率、内存利用率和其他内部服务数据等指标。合成监控从客户的角度查看系统行为,跟踪服务错误率以及对探测器服务的合成流量的响应。综合监控以症状为导向,可识别活跃问题;而自省式监控可让我们诊断已确认的问题,并在某些情况下识别即将发生的问题。

为了帮助检测仅影响部分客户的事件,我们会将客户工作负载聚合到相关工作负载的同类群组中。一旦某个同类群组的效果偏离常态,系统就会触发提醒。借助这些提醒,即使总体表现似乎正常,我们也能检测到客户级的回归问题。

软件供应链保护

每当 Google Cloud 团队引入变更时,我们都会使用名为 Binary Authorization for Borg (BAB) 的安全检查来保护我们的软件供应链和 Cloud 客户免受内部人员风险的影响。BAB 从代码审核阶段开始,并会针对部署到生产环境的代码和配置创建审核跟踪记录。为确保生产环境的完整性,BAB 仅接受符合以下条件的更改:

  • 防篡改且已签名
  • 来自已标识的 build 方和已标识的来源位置
  • 已由与代码作者不同的一方审核并明确批准

如果您有兴趣在自己的软件开发生命周期中应用一些相同的概念,我们已将 BAB 的主要概念纳入到名为软件工件的供应链级别 (SLSA) 的开放式规范中。SLSA 充当在生产环境中开发和运行代码的安全框架。

总结

Google Cloud 是 Google 数十年来在开发卓越方面所做的投资的成果。代码健康状况和生产环境健康状况是 Google 所有工程团队的文化准则。我们的设计审核流程可确保在早期考虑对代码和生产环境运行状况的影响。我们的开发流程基于左移原则和广泛的测试,可确保设计理念得到安全且正确的实现。我们的资格认证流程进一步扩展了测试范围,涵盖大规模集成和外部依赖项。最后,我们的发布平台可让我们逐步确信给定更改的实际行为符合预期。从构思到生产,我们的方法让我们能够满足 Google Cloud 客户对开发速度和可靠性的期望。