发布工程师@google
关注我,每天收获一个新技能!
本文内容来自谷歌的《SRE book》 第 8 章。
1
配备合适的工具、适当的自动化和定义明确的政策后,开发人员和 SRE 就不必担心发布软件。发布可以像按下按钮一样轻松。
无论规模大小或使用何种工具,大多数公司都会回答同样的发布工程问题:
* 如何处理软件包的版本控制?
* 是否应使用持续构建和部署模型,还是执行定期构建?
* 发布频率应是多少?
* 应使用哪些配置管理策略?
* 哪些发布指标值得关注?
发布工程师必须首先定义这些政策,才能在相应的工具平台上添加适当的功能,并且所有公司都应努力定义其发布流程,无论这些流程是否可以自动化和/或强制执行。
2
发布工程通常是事后才想到的,随着平台和服务的规模和复杂性不断增长。
这种思维方式必须改变。
团队应该在产品开发周期开始时就为发布工程资源做好预算。尽早实施良好的实践和流程比以后再改造系统更划算。
开发人员、SRE 和发布工程师必须通力合作。发布工程师需要了解代码的构建和部署意图。开发人员不应该构建代码后就“把结果扔到一边”让发布工程师处理。
由于发布工程仍然是一门相对年轻的学科,管理人员并不总是在项目的早期阶段就为发布工程制定计划和预算。
因此,在考虑如何纳入发布工程实践时,请务必考虑其在产品或服务的整个生命周期中的作用 - 尤其是早期阶段。
3
Google 是一家数据驱动的公司,发布工程也同样如此。我们拥有可报告一系列指标的工具,例如将代码更改部署到生产中需要多长时间(换句话说,发布速度)以及构建配置文件中使用了哪些功能的统计数据。
这些工具中的大多数都是由发布工程师构想和开发的。
发布工程师定义了使用我们工具的最佳实践,以确保使用一致且可重复的方法发布项目。
我们的最佳实践涵盖发布过程的所有元素,例如包括编译器标志、构建标识标签的格式以及构建期间所需的步骤。
确保我们的工具默认运行正确且有充分的文档记录,使团队可以轻松地专注于功能和用户,而不是在发布软件时花时间(糟糕地)重新发明轮子。
Google 拥有大量 SRE,他们负责安全部署产品并保证 Google 服务正常运行。
为了确保我们的发布流程满足业务要求,发布工程师和 SRE 会共同制定策略,以发布变更、在不中断服务的情况下推出新版本以及回滚出现问题的功能。
4
发布工程以工程和服务理念为指导,该理念通过四个主要原则来表达。
1. 自助服务模式
为了实现规模化工作,团队必须能够自给自足。发布工程已经开发出最佳实践和工具,使我们的产品开发团队能够控制和运行自己的发布流程。尽管我们有数千名工程师和产品,但我们可以实现较高的发布速度,因为各个团队可以决定发布其产品新版本的频率和时间。发布流程可以自动化到几乎不需要工程师参与的程度,并且许多项目都是使用我们的自动构建系统和部署工具的组合自动构建和发布的。发布是真正自动化的,只有在出现问题时才需要工程师参与。
2. 频繁且高速
面向用户的软件(例如 Google 搜索的许多组件)经常被重建,因为我们的目标是尽快推出面向客户的功能。我们秉承这样的理念:频繁发布可以减少版本之间的更改。这种方法使测试和故障排除更加容易。
一些团队每小时进行一次构建,然后选择基于测试结果和给定构建中包含的功能,从生成的构建池中选择实际部署到生产中的版本。
其他团队采用了“绿色推送”发布模型,并部署通过所有测试的每个构建。
3. 密封构建
构建工具必须使我们能够确保一致性和可重复性。
如果两个人试图在不同机器上的源代码存储库中以相同的修订号构建相同的产品,我们期望得到相同的结果。
构建是封闭的,这意味着它们对构建机器上安装的库和其他软件不敏感。相反,构建依赖于已知版本的构建工具(例如编译器)和依赖项(例如库)。构建过程是独立的,不得依赖于构建环境外部的服务。
当我们需要修复生产环境中运行的软件中的错误时,重建旧版本可能是一项挑战。我们通过在与原始版本相同的修订版本上重建并包含在此时间点之后提交的特定更改来完成此任务。我们将此策略称为“挑选”。
我们的构建工具本身是根据正在构建的项目的源代码存储库中的修订版本进行版本控制的。因此,如果需要挑选,上个月构建的项目将不会使用本月版本的编译器,因为该版本可能包含不兼容或不需要的功能。
4. 政策和程序的执行
发布项目时,多层安全和访问控制决定了谁可以执行特定操作。门控操作包括:
* 批准源代码变更——此操作通过分散在整个代码库中的置文件进行管理
* 指定发布过程中要执行的操作
* 创建新版本
* 批准初始集成提案(即在源代码存储库中以特定修订号行构建的请求)以及后续的精选
* 部署新版本
* 更改项目的构建配置
几乎所有代码库更改都需要代码审查,这是集成到我们正常开发人员工作流程中的一项简化操作。
我们的自动发布系统会生成一份包含发布中包含的所有更改的报告,该报告与其他构建工件一起存档。
通过让 SRE 了解项目新发布中包含哪些更改,此报告可以在发布出现问题时加快故障排除速度。
5
Google 开发了一个名为Rapid 的自动发布系统。
Rapid 是一个利用多种 Google 技术来提供框架的系统,该框架可提供可扩展、密封且可靠的版本。以下部分介绍了 Google 的软件生命周期以及如何使用 Rapid 和其他相关工具来管理它。
1. 构建
Blaze是 Google 的首选构建工具。它支持使用多种语言构建二进制文件,包括我们的标准语言 C++、Java、Python、Go 和 JavaScript。工程师使用 Blaze 定义构建目标(例如构建的输出,如 JAR 文件),并指定每个目标的依赖项。执行构建时,Blaze 会自动构建依赖项目标。
二进制文件和单元测试的构建目标在 Rapid 的项目配置文件中定义。项目特定的标志(例如唯一构建标识符)由 Rapid 传递给 Blaze。所有二进制文件都支持显示构建日期、修订号和构建标识符的标志,这使我们能够轻松地将二进制文件与其构建方式的记录关联起来。
2.分支
所有代码都签入源代码树的主分支(主线)。但是,大多数主要项目不会直接从主线发布。相反,我们会在特定修订版中从主线分支,并且永远不会将分支中的更改合并回主线。错误修复会提交到主线,然后挑选到分支中以包含在发布中。这种做法可以避免无意中挑选自原始构建发生以来提交到主线的无关更改。使用这种分支和挑选方法,我们知道每个版本的确切内容。
3. 测试
每次提交更改时,持续测试系统都会针对主线中的代码运行单元测试,这使我们能够快速检测构建和测试失败。
1. 发布工程建议持续构建测试目标与控制项目发布的测试目标相对应。
2. 还建议在成功完成所有测试的最后一个持续测试构建的修订号(版本)处创建发布。
这些措施降低了对主线进行的后续更改在发布时执行的构建期间导致失败的可能性。
在发布过程中,我们使用发布分支重新运行单元测试,并创建审计线索以显示所有测试均已通过。此步骤非常重要,因为如果发布涉及挑选,则发布分支可能包含主线上任何地方都不存在的代码版本。我们希望保证测试在实际发布的环境中通过。
为了补充持续测试系统,我们使用独立的测试环境,对打包的构建工件运行系统级测试。
这些测试可以手动启动,也可以从 Rapid 启动。
4. 打包
软件通过 Midas 软件包管理器 (MPM)分发到我们的生产机器上。
MPM 根据 Blaze 规则组装软件包,该规则列出了要包含的构建工件及其所有者和权限。软件包被命名(例如search/shakespeare/frontend),使用唯一哈希进行版本控制,并签名以确保真实性。MPM 支持将标签应用于软件包的特定版本。Rapid 应用包含构建 ID 的标签,这保证可以使用软件包名称和此标签唯一地引用软件包。
标签可以应用于 MPM 软件包,以指示软件包在发布过程中的位置(例如,dev、canary或production)。
如果您将现有标签应用于新软件包,则该标签会自动从旧软件包移动到新软件包。例如:如果软件包标记为canary,则随后安装该软件包的 Canary 版本的用户将自动收到带有标签 的软件包的最新版本canary。
5. Rapid 系统
下图显示了 Rapid 系统的主要组件。Rapid 使用名为 blueprints 的文件进行配置。
blueprints 以内部配置语言编写,用于定义构建和测试目标、部署规则和管理信息,如项目所有者(腾讯PCG的大运河平台参考了 blueprints 这一设计)。
基于角色的访问控制列表确定谁可以对 Rapid 项目执行特定操作。
每个 Rapid 项目都有工作流,用于定义发布过程中要执行的操作。工作流操作可以连续或并行执行,并且工作流可以启动其他工作流。Rapid 将工作请求分派到作为 Borg 作业在我们的生产服务器上运行的任务。由于 Rapid 使用我们的生产基础设施,因此它可以同时处理数千个发布请求。
典型的发布过程如下:
Rapid 使用请求的集成修订号(通常从我们的持续测试系统自动获取)来创建发布分支。
Rapid 使用 Blaze 编译所有二进制文件并执行单元测试,这两个步骤通常并行执行。编译和测试在专门用于这些特定任务的环境中进行,而不是在执行 Rapid 工作流的 Borg 作业中进行。这种分离使我们能够轻松地并行化工作。
然后,构建的工件可用于系统测试和金丝雀部署。典型的金丝雀部署涉及在系统测试完成后在我们的生产环境中启动一些作业。
记录流程中每一步的结果。创建自上次发布以来所有变更的报告。
Rapid 使我们能够管理我们的发布分支和 cherry pick;可以批准或拒绝将单个 cherry pick 请求纳入发布中。
6. 部署
Rapid 通常用于直接驱动简单部署。它根据蓝图文件中的部署定义和专门的任务执行器更新 Borg 作业以使用新建的 MPM 包。
对于更复杂的部署,我们使用 Sisyphus,这是 SRE 开发的通用部署自动化框架。部署是一个逻辑工作单元,由一个或多个单独的任务组成。Sisyphus 提供了一组 Python 类,可以扩展以支持任何部署流程。它有一个仪表板,可以更精细地控制部署的执行方式,并提供一种监控部署进度的方法。
在典型的集成中,Rapid 在长期运行的 Sisyphus 作业中创建部署。Rapid 知道与其创建的 MPM 包关联的构建标签,并且可以在 Sisyphus 中创建部署时指定该构建标签。Sisyphus 使用构建标签来指定应部署哪个版本的 MPM 包。
借助 Sisyphus,部署过程可以根据需要变得简单或复杂。例如,它可以立即更新所有相关作业,也可以在几个小时内将新的二进制文件部署到连续的集群中。
我们的目标是使部署过程适应特定服务的风险状况。在开发或预生产环境中,我们可能每小时构建一次,并在所有测试通过后自动推送版本。对于面向用户的大型服务,我们可能先在一个集群中启动,然后呈指数级扩展,直到所有集群都更新完毕。对于敏感的基础设施,我们可能会将部署时间延长数天,并将它们交错部署在不同地理区域的实例中。
5
配置管理是发布工程师和 SRE 之间特别密切合作的一个领域。
尽管配置管理最初看起来似乎是一个看似简单的问题,但配置变更是潜在的不稳定源。因此,我们发布和管理系统和服务配置的方法随着时间的推移发生了很大的变化。
如今,我们使用几种模型来分发配置文件,如下文所述。所有方案都涉及将配置存储在我们的主要源代码存储库中并执行严格的代码审查要求。
1. 使用主线进行配置。这是在 Borg(以及 Borg 之前的系统)中配置服务的第一种方法。使用此方案,开发人员和 SRE 可以在主分支的头部修改配置文件。更改经过审核,然后应用于正在运行的系统。因此,二进制版本包和配置更改是分离的。
虽然从概念和程序上来说很简单,但这种技术通常会导致配置文件的签入版本与配置文件的运行版本之间出现偏差,因为必须使用更新作业才能获取更改。
2. 将配置文件和二进制文件包含在同一个 MPM 包中。对于配置文件很少的项目或文件(或文件子集)随每个发布周期而变化的项目,配置文件可以与二进制文件一起包含在 MPM 包中。虽然这种策略通过将二进制文件和配置文件紧密绑定而限制了灵活性,但它简化了部署,因为它只需要安装一个包。
3. 将配置文件打包到 MPM“配置包”中。我们可以将密封原则应用于配置管理。二进制配置往往与特定版本的二进制文件紧密绑定,因此我们利用构建和打包系统来快照和发布配置文件及其二进制文件。与我们对二进制文件的处理类似,我们可以使用构建 ID 来重建特定时间点的配置。
例如,可以使用配置该功能的标志设置来发布实现新功能的更改。通过生成两个 MPM 包(一个用于二进制文件,一个用于配置),我们保留了单独更改每个包的能力。也就是说,如果该功能发布时带有标志设置,first_folio但我们意识到它应该是bad_quarto,我们可以将该更改挑选到发布分支上,重建配置包并部署它。这种方法的优点是不需要新的二进制构建。
我们可以利用 MPM 的标签功能来指示应一起安装哪些版本的 MPM 包。可以将标签much_ado应用于上一段中描述的 MPM 包,这允许我们使用这个标签获取两个包。当构建项目的新版本时,标签much_ado将应用于新包。由于这些标签在 MPM 包的命名空间内是唯一的,因此只会使用具有该标签的最新包。
4. 从外部存储读取配置文件。有些项目的配置文件需要频繁或动态更改(即在二进制文件运行时)。这些文件可以存储在 Chubby、Bigtable 或我们基于源的文件系统中。
总之,项目所有者会考虑分发和管理配置文件的不同选项,并根据具体情况决定哪种选项最有效。