开源应用架构 (卷 2)
Firefox 发布工程

克里斯·阿特利,卢卡斯·布拉克,约翰·奥杜因和阿尔门·赞布拉诺·加斯帕里安

最近,Mozilla 发布工程团队在 Firefox 的发布自动化方面取得了许多进展。我们减少了签名和向利益相关者发送通知过程中人工干预的需求,并且自动化了许多其他小的手动步骤,因为流程中的每个手动步骤都是人为错误的机会。虽然我们现在所拥有的并不完美,但我们始终致力于简化和自动化我们的发布流程。我们的最终目标是能够一键启动并离开;最少的人工干预将消除我们在旧的部分手动、部分自动发布流程中遇到的许多麻烦和返工。在本章中,我们将探索并解释构成完整 Firefox 快速发布系统的脚本和基础设施决策,截至 Firefox 10 版本。

您将从一个可发布的 Mercurial 更改集的角度来了解系统,它将被转换为候选版本,然后是公开版本,供全球超过 4.5 亿的每日用户使用。我们将从构建和代码签名开始,然后是定制的合作伙伴和本地化重新打包、QA 流程,以及我们如何为每个受支持的版本、平台和本地化生成更新。在将版本推送到 Mozilla 社区的镜像网络(为我们的用户提供下载)之前,必须完成这些步骤中的每一个。

我们将研究一些为改进此流程而做出的决策;例如,帮助消除许多过去容易出现人为错误的健全性检查脚本;我们的自动签名脚本;我们将移动版本集成到桌面版本流程中;用于创建更新的修补程序;以及 AUS(应用程序更新服务),用于通过软件的多个版本向我们的用户提供更新。

本章描述了我们如何生成 Firefox 发布版本的机制。本章的大部分内容详细介绍了构建开始后发布流程中发生的重大步骤,但在发布工程开始生成发布版本之前,还有大量的复杂跨组沟通需要处理,所以让我们从那里开始。

2.1. 开始发布前先看看 N 种方法

图 2.1:从代码到“开始构建”

当我们开始改进 Mozilla 发布流程的项目时,我们首先假设 Firefox 越受欢迎,我们的用户就越多,Firefox 就会成为黑帽黑客寻找可利用的安全漏洞的目标,这也就越有吸引力。此外,Firefox 越受欢迎,我们就需要保护更多的用户免受新发现的安全漏洞的侵害,因此能够尽快提供安全修复就越重要。我们甚至为此制定了一个术语:“化工泄漏”发布(简称“化学物质泄漏”)。我们决定像对待每次发布都可能是“化工泄漏”发布一样进行计划,并相应地设计了我们的发布自动化,而不是对偶尔需要在定期发布之间进行“化工泄漏”发布感到惊讶。

这种思维方式有三个重要的后果

  1. 我们每次发布后都会进行事后分析,并查看下一次如何使流程更顺畅、更轻松、更快。如果可能,我们会立即找到并修复至少一件事情,无论它多么微不足道——在下次发布之前。我们对发布自动化的持续改进意味着我们始终在寻找新的方法来减少人工干预,同时提高稳健性和周转时间。大量工作都花在了使我们的工具和流程变得万无一失上,以便“罕见”事件(例如网络故障、磁盘空间问题或真人打字错误)尽早被捕获并处理。即使我们已经足够快以满足常规的非“化工泄漏”发布,我们也希望降低未来发布中人为错误的风险。在“化工泄漏”发布中尤其如此。
  2. 当我们确实进行“化工泄漏”发布时,发布自动化越健壮,发布工程中的人员压力就越小。我们习惯了以冷静的精准度尽可能快地进行操作,并且我们已经构建了工具以尽可能安全和稳健地执行此操作。压力越小,在一个经过充分排练的流程中,工作就越冷静和精确,这反过来有助于“化工泄漏”发布顺利进行。
  3. 我们创建了一个 Mozilla 范围内的“开始构建”流程。在进行常规(非“化工泄漏”)发布时,可以让每个人查看相同的错误分类查询,清楚地了解上次修复何时完成并成功测试,并就何时开始构建达成共识。但是,在“化工泄漏”发布中——几分钟都很重要——跟踪问题的全部细节以及后续的错误确认和修复变得非常棘手。为了降低复杂性和出错风险,Mozilla 现在安排了一名专职人员来跟踪代码的准备情况,以便做出“开始构建”的决策。在“化工泄漏”期间更改流程是有风险的,因此为了确保每个人在几分钟内都熟悉该流程,我们将此流程用于“化工泄漏”和常规发布。
图 2.2:完整的发布时间线,以“化工泄漏”为例

2.2. “开始构建”

谁可以发送“开始构建”?

在发布开始之前,指定一个人负责协调整个发布。这个人需要参加分类会议,了解所有正在合并的工作的背景信息,公平地裁决错误严重性争议,批准合并最后一刻的更改,并做出艰难的回退决策。此外,在实际发布当天,此人负责与不同团队(开发人员、QA、发布工程、网站开发人员、公关、市场营销等)的所有沟通。

不同的公司使用不同的职位名称来描述此角色。我们听说过的一些职位名称包括发布经理、发布工程师、项目经理、项目经理、产品经理、产品负责人、发布驱动程序。在本章中,我们将使用“发布协调员”一词,因为我们认为它最清楚地定义了我们流程中上述角色。这里重点是,在发布开始之前,每个人都清楚地了解该角色及其最终权限,无论他们的背景或之前在其他地方的工作经验如何。在发布当天的关键时刻,重要的是每个人都知道要遵守并信任此人做出的协调决策。

发布协调员是发布工程之外唯一有权发送“停止构建”电子邮件的人员,如果发现有阻止发布的问题。任何关于疑似阻止发布问题的报告都会转交给发布协调员,发布协调员将进行评估,做出最终的执行/不执行决策,并及时将该决策传达给每个人。在发布当天的关键时刻,我们都必须遵守并信任此人做出的协调决策。

如何发送“开始构建”?

早期尝试通过 IRC 频道或电话口头发送“开始构建”导致了误解,偶尔会导致正在进行的发布出现问题。因此,我们现在要求每个版本的“开始构建”信号都通过电子邮件发送到一个邮件列表,该邮件列表包含参与发布流程的所有团队的所有人员。电子邮件的主题包括“开始构建”以及明确的产品名称和版本号;例如

go to build Firefox 6.0.1

类似地,如果在发布中发现问题,发布协调员将向相同的邮件列表发送一封新的“全部停止”电子邮件,并使用新的主题行。我们发现仅仅回复最近关于发布的电子邮件是不够的;一些电子邮件客户端中的电子邮件线程导致某些人没有注意到“全部停止”电子邮件,如果它位于一个长而无关的线程的底部。

“开始构建”电子邮件中包含什么?

  1. 构建中要使用的确切代码;理想情况下,是源代码存储库中要从中创建发布版本的特定更改的 URL。
    1. 诸如“使用最新代码”之类的指令永远不可接受;在一个版本中,在发送“开始构建”电子邮件并在构建开始之前,一位好心的开发人员在错误的分支中合并了一个更改,未经批准。发布版本在构建中包含了该不需要的更改。幸运的是,错误在我们发布之前被发现了,但我们确实不得不延迟发布,同时我们完全停止并重新构建了所有内容。
    2. 在像 CVS 这样的基于时间版本控制系统中,请完全明确要使用的确切时间;将时间精确到秒,并指定时区。在一个版本中,当 Firefox 仍然基于 CVS 时,发布协调员指定了要用于构建的截止时间,但没有给出时区。当发布工程注意到缺少时区信息时,发布协调员已经睡着了。发布工程正确地猜测意图是当地时间(在加州),但在深夜将 PDT 误认为 PST 的混淆中,我们最终错过了最后关键的错误修复。这在我们在发布之前被 QA 发现了,但我们不得不停止构建并使用正确的截止时间重新开始构建。
  2. 对本次发布的紧迫性的清晰认识。虽然听起来很明显,但在处理一些重要的边缘情况时很重要,因此以下是快速总结
    1. 一些发布是“例行”的,可以在正常的上班时间进行。它们是预定的发布,按计划进行,没有紧急情况。当然,所有发布版本都需要及时创建,但发布工程师无需熬夜加班并为例行发布而筋疲力尽。相反,我们提前正确地安排它们,以便一切按计划进行,人们在正常的工作时间工作。这使人们保持精力充沛,并且如果需要,能够更好地处理未安排的紧急工作。
    2. 一些发布是“化工泄漏”,并且紧急,几分钟都很重要。这些通常是为了修复已发布的安全漏洞,或者修复影响我们很大一部分用户群的新引入的顶级崩溃问题。“化工泄漏”需要尽快创建,并且通常不是预定的发布。
    3. 一些发布从例行发布变为“化工泄漏”发布,或从“化工泄漏”发布变为例行发布。例如,如果例行发布中的安全修复意外泄漏,它现在就变成了“化工泄漏”发布。如果业务需求(例如即将举行的会议公告的“特别抢先预览”发布)因业务原因而延迟,则发布现在从“化工泄漏”发布变为例行发布。
    4. 一些发布中,不同的人对发布是正常还是紧急持有不同的意见,这取决于他们对发布中正在修复的错误的看法。

发布协调员的作用是权衡所有事实和意见,做出决定,然后在所有团队中一致地传达该关于紧急性的决定。如果出现新信息,发布协调员会重新评估,然后将新的紧急情况传达给所有相同的团队。让一些团队认为发布是“化工泄漏”发布,而其他团队认为相同的发布是例行发布,可能会破坏跨团队的凝聚力。

最后,这些电子邮件对于衡量发布过程中时间花费的位置也变得非常有用。虽然它们的精度仅限于挂钟时间分辨率,但在确定下一步在哪里集中精力提高速度方面,这种精度确实很有帮助。正如一句老话所说,在改进某件事之前,必须能够衡量它。

在 Firefox 的整个 Beta 循环中,我们也从我们的 mozilla-beta 代码库 进行每周发布。每一次 Beta 发布都经过我们通常的完整发布自动化流程,并且与我们定期的最终发布几乎完全相同。为了最大程度地减少发布期间的意外情况,我们的目标是在开始最终发布构建时,发布自动化或基础设施中没有任何新的未经测试的更改。

2.3. 标记、构建和源代码包

图 2.3:自动化标记

为了准备开始自动化,我们最近开始使用一个由发布工程暑期实习生最初编写的 脚本release_sanity.py。这个 Python 脚本帮助发布工程师仔细检查发布的所有配置是否与我们工具和配置存储库中签入的内容相匹配。它还检查指定发布代码修订版中 mozilla-release 和此发布的所有(人类)语言的内容,构建和语言重新打包将由此生成。

该脚本接受将使用的任何发布配置(例如桌面或移动设备)的 buildbot 配置文件、要查看的分支(例如,mozilla-release)、构建和版本号,以及要构建的产品名称(例如“fennec”或“firefox”)。如果发布存储库与配置中的内容不匹配,如果区域设置存储库更改集与我们的发货区域设置和本地化更改集文件不匹配,或者如果发布版本和构建号与使用产品、版本和构建号生成的标记提供给我们的构建工具的内容不匹配,则它将失败。如果脚本中的所有测试都通过,它将重新配置运行脚本的 buildbot 主服务器以及将触发发布构建器的位置,然后生成启动自动化发布过程的“发送更改”。

在发布工程师启动构建器后,Firefox 发布过程中的第一个自动化步骤是对所有相关的源代码存储库进行标记,以记录此版本的发布候选版本和构建号正在使用源代码、语言存储库和相关工具的哪个修订版。这些标记使我们能够在发布存储库中保留 Firefox 和 Fennec(移动版 Firefox)发布的版本和构建号的历史记录。对于 Firefox 发布,一个示例标记集为 FIREFOX_10_0_RELEASE FIREFOX_10_0_BUILD1 FENNEC_10_0_RELEASE FENNEC_10_0_BUILD1

单个 Firefox 发布使用来自大约 85 个版本控制存储库的代码,这些存储库托管产品代码、本地化字符串、发布自动化代码和辅助实用程序等内容。标记所有这些存储库对于确保发布自动化的后续步骤都使用同一组修订版至关重要。它还具有一些其他好处:Linux 发行版和其他贡献者可以使用与官方构建中完全相同的代码和工具来复制构建,并且它还记录了每个发布的基础上使用的源代码和工具的修订版,以便将来比较发布之间发生了哪些变化。一旦所有存储库都被分支和标记,一系列相关的构建器会自动启动:每个发布平台的构建器以及包含发布中使用的所有源代码的源代码包。源代码包和构建的安装程序都上传到发布目录,并在可用时提供。这允许任何人都可以查看发布中包含的代码,并提供一个快照,使我们能够在需要时重新创建构建(例如,如果我们的 VCS 出现故障)。对于 Firefox 构建的源代码,有时我们需要从较早的存储库导入代码。例如,对于 Beta 版发布,这意味着从 Mozilla-Aurora(我们比 Nightly 存储库更稳定的存储库)中提取 Firefox 10.0b1 的已签核修订版。对于发布版,这意味着将来自 Mozilla-Beta(通常与 10.0b6 使用的代码相同)的已批准更改提取到 Mozilla-Release 存储库。然后,此发布分支被创建为一个命名分支,其父更改集是由发布协调员提供的“转到构建”中的已签核修订版。发布分支可用于对源代码进行特定于发布的修改,例如增加版本号或确定将要构建的区域设置集。如果将来发现严重的安全性漏洞需要立即修复(化学泄漏),则将解决此漏洞的一组最少的更改合并到此 relbranch 中,并从中生成和发布 Firefox 的新版本。当我们需要为特定发布构建另一轮构建时,buildN,我们会使用这些 relbranch 获取为“转到构建”签核的相同代码,任何对该发布代码的更改都将在此处合并。自动化过程再次启动,并将标记更改为该 relbranch 上的新更改集。我们的标记过程对本地和远程 Mercurial 存储库执行了 *大量* 操作。为了简化一些最常见的操作,我们编写了一些工具来帮助我们:retry.pyhgtool.pyretry.py 是一个简单的包装器,可以接受给定的命令并运行它,如果失败则重试几次。它还可以监视异常输出条件,并在这些情况下重试或报告失败。我们发现它对于围绕大多数可能因外部依赖项而失败的命令包装 retry.py 很有用。对于标记,Mercurial 操作可能会因临时网络中断、Web 服务器问题或后端 Mercurial 服务器暂时过载而失败。能够自动重试这些操作并继续节省了我们大量时间,因为我们不必手动恢复、清理任何后果,然后重新启动发布自动化。hgtool.py 是一个实用程序,它封装了几个常见的 Mercurial 操作,例如克隆、拉取和使用单个调用更新。它还添加了对 Mercurial 的 share 扩展的支持,我们广泛使用它来避免在同一台机器上的不同目录中拥有多个存储库的完整克隆。添加对共享本地存储库的支持大大加快了我们的标记过程,因为可以避免大多数产品和区域设置存储库的完整克隆。开发这些工具的一个重要动机是使我们的自动化尽可能地可测试。因为像 hgtool.py 这样的工具是构建在可重用库之上的小型、单一用途的实用程序,所以它们更容易独立测试。

如今,我们的标记在两个并行作业中完成:一个用于桌面版 Firefox,由于它包括标记 80 多个区域设置存储库,因此大约需要 20 分钟才能完成;另一个用于移动版 Firefox,由于我们目前为移动版发布提供的区域设置较少,因此大约需要 10 分钟才能完成。将来,我们希望简化我们的发布自动化流程,以便我们 *并行* 标记所有各种存储库。一旦产品代码和工具需求存储库被标记,就可以立即开始初始构建,而无需等待所有区域设置存储库被标记。在这些构建完成时,其余存储库将被标记,以便可以完成本地化重新打包和后续步骤。我们估计这可以将构建准备就绪的总时间缩短 15 分钟。

2.4. 本地化重新打包和合作伙伴重新打包

一旦桌面构建生成并上传到 ftp.mozilla.org,我们的自动化就会触发本地化重新打包作业。“本地化重新打包”会获取原始构建(包含 en-US 区域设置),将其解压缩,用我们要在此版本中发布的另一个区域设置的字符串替换 en-US 字符串,然后将所有文件重新打包(这就是我们称之为重新打包的原因)。我们对发布中包含的每个区域设置重复此操作。最初,我们按顺序执行所有重新打包。但是,随着我们添加更多区域设置,这需要很长时间才能完成,如果在中途出现任何故障,我们必须从头开始重新启动。

图 2.4:为每个本地化重新打包 Firefox

现在,我们改为将整套重新打包拆分为六个作业,每个作业在六台不同的机器上同时处理。这种方法大约在六分之一的时间内完成了工作。这还允许我们在单个重新打包失败时重新执行重新打包的子集,而无需重新执行所有重新打包。(我们可以将重新打包拆分为更多、更小、并发的作业,但我们发现它从池中占用过多机器,这会影响开发人员在我们的持续集成系统上触发的其他无关作业。)

移动设备(Android 上)的过程略有不同,因为我们只生成两个安装程序:一个英文版本和一个多语言版本,其中包含十几个语言内置在安装程序中,而不是每个区域设置一个单独的构建。这个多语言版本的尺寸是一个问题,尤其是在下载速度慢的小型移动设备上。未来的一项提议是,让其他语言作为附加组件按需从 addons.mozilla.org 请求。

图 2.4 中,您可以看到我们目前依赖三个不同的来源来获取区域设置信息:shipped_localesl10_changesetsl10n-changesets_mobile-release.json。(有一个计划将这三个文件合并到一个统一的 JSON 文件中。)这些文件包含有关我们拥有的不同本地化的信息,以及某些平台例外情况。具体来说,对于给定的本地化,我们需要知道在给定的发布中使用存储库的哪个修订版,并且我们需要知道该本地化是否可以在我们所有支持的平台上构建(例如,Mac 上的日语来自完全不同的存储库)。其中两个文件用于桌面版本,一个用于移动版本(此 JSON 文件包含平台列表和更改集)。

谁决定我们发布哪些语言?首先,本地化人员自己为给定的发布提名其特定的更改集。提名的更改集将由 Mozilla 的本地化团队进行审查,并显示在 Web 仪表板中,该仪表板列出了每种语言所需的更改集。发布协调员在发送“转到构建”电子邮件之前会审查此内容。在发布当天,我们会检索此更改集列表,并相应地重新打包它们。

除了本地化重新打包之外,我们还会生成合作伙伴重新打包。这些是为我们的一些合作伙伴定制的构建,他们希望为其客户定制体验。主要类型的更改是自定义书签、自定义主页和自定义搜索引擎,但许多其他内容也可以更改。这些定制的构建是为最新的 Firefox 发布生成的,而不是为 Beta 版生成的。

2.5. 签名

为了让用户确信他们下载的 Firefox 副本确实是来自 Mozilla 的未经修改的构建,我们会对构建应用几种不同类型的数字签名。

第一种签名类型用于我们的 Windows 版本。我们使用 Microsoft Authenticode (signcode) 签名密钥来签署所有 .exe.dll 文件。Windows 可以使用这些签名来验证应用程序是否来自可信来源。我们还使用 Authenticode 密钥签署 Firefox 安装程序可执行文件。

接下来,我们使用 GPG 为所有平台上的所有版本生成一组 MD5 和 SHA1 校验和,并为校验和文件以及所有版本和安装程序生成分离的 GPG 签名。镜像和其他社区成员使用这些签名来验证其下载内容。

出于安全考虑,我们在专用签名机器上进行签名,该机器通过防火墙和 VPN 阻止外部连接。我们的密钥短语、密码和密钥库仅在发布工程师之间通过安全渠道(通常是当面)传递,以最大程度地降低暴露风险。

图 2.5:签署 Firefox 安装程序

直到最近,此签名过程都涉及发布工程师在一个专用服务器(“签名主机”)上工作近一个小时,手动下载版本、对其进行签名,然后将其上传回 ftp.mozilla.org,然后才能继续自动化。一旦主机上的签名完成并且所有文件都上传完毕,所有签名活动的日志文件就会上传到 ftp.mozilla.org 上的候选版本目录。此日志文件出现在 ftp.mozilla.org 上表示人工签名工作结束,从那时起,监视该日志文件的相关构建器可以恢复自动化。最近,我们在签名步骤周围添加了额外的自动化包装器。现在,发布工程师在签名主机上打开一个 Cygwin shell 并设置一些与版本相关的环境变量,如 VERSIONBUILDTAGRELEASE_CONFIG,这些变量帮助脚本在 ftp.mozilla.org 上找到正确的目录并知道何时下载了版本的所有交付物,以便可以开始签名。在检出最新版本的签名工具后,发布工程师只需运行 make autosign。然后,发布工程师输入两个密码短语,一个用于 gpg,另一个用于 signcode。一旦这些密码短语被 make 脚本自动验证,自动化就会启动一个下载循环,该循环监视上传的版本和来自版本自动化的重新打包,并在它们可用时下载它们。一旦所有项目都下载完毕,自动化就会立即开始签名,无需人工干预。

无需人工签名有两个重要原因。首先,它降低了人为错误的风险。其次,它允许在非工作时间进行签名,而无需发布工程师当时在电脑前保持清醒。

所有交付物都生成了 MD5SUM 和 SHA1SUM,并且这些哈希值被写入同名文件。这些文件将被上传回候选版本目录,并在发布上线后同步到 ftp.mozilla.org 上的最终位置,以便任何从我们的镜像之一下载 Firefox 安装程序的人员都可以确保他们获得了正确的对象。当所有已签名位都可用并经过验证后,它们将与签名日志文件一起上传回 ftp.mozilla.org,自动化正在等待该文件以继续进行。

我们计划进行的下一轮签名流程改进将创建一个工具,允许我们在构建/重新打包时签署位。此工作需要创建一个签名服务器应用程序,该应用程序可以接收来自版本构建机器的签名文件请求。它还需要一个签名客户端工具,该工具将联系签名服务器,将自身认证为可信请求签名的机器,上传要签名的文件,等待构建签名,下载已签名的位,然后将其作为打包构建的一部分包含在内。一旦这些增强功能投入生产,我们就可以停止当前的全部一次性签名流程,以及我们的全部一次性生成更新流程(更多信息请参见下文)。我们预计这项工作将缩短我们当前版本的端到端时间几个小时。

2.6. 更新

创建更新是为了让用户能够使用我们内置的更新程序快速轻松地更新到 Firefox 的最新版本,而无需下载和运行独立安装程序。从用户的角度来看,更新包的下载在后台悄无声息地进行。只有在下载更新文件并准备好应用后,Firefox 才会提示用户选择应用更新并重新启动。

问题在于,我们生成了大量更新。对于产品线上的一系列版本,我们为该产品线中所有受支持的先前版本生成到新最新版本的更新。对于 Firefox LATEST,这意味着为 Firefox LATEST-1LATEST-2LATEST-3 等中的每个平台、每个语言环境和每个安装程序生成更新,包括完整形式和部分形式。我们一次对几个不同的产品线执行所有这些操作。

我们的更新生成自动化修改每个版本构建的更新配置文件的一个分支,以维护我们关于需要创建哪些版本号、平台和本地化更新才能为用户提供此最新版本的规范列表。我们提供“代码片段”形式的更新。如下面的示例所示,此代码片段只是一个托管在我们 AUS(应用程序更新服务)上的 XML 指针文件,它通知用户 Firefox 浏览器完整和/或部分 .mar(Mozilla 存档)文件托管在哪里。

主要更新与次要更新

<updates>
  <update type="minor" version="7.0.1" extensionVersion="7.0.1"
          buildID="20110928134238"
          detailsURL="https://www.mozilla.com/en-US/firefox/7.0.1/releasenotes/">
    <patch type="complete"
           URL="http://download.mozilla.org/?product=firefox-7.0.1-complete&os=osx&lang=en-US&force=1"
           hashFunction="SHA512"
           hashValue="7ecdbc110468b9b4627299794d793874436353dc36c80151550b08830f9d8c5afd7940c51df9270d54e11fd99806f41368c0f88721fa17e01ea959144f473f9d"
           size="28680122"/>
    <patch type="partial"
           URL="http://download.mozilla.org/?product=firefox-7.0.1-partial-6.0.2&os=osx&lang=en-US&force=1"
           hashFunction="SHA512"
           hashValue="e9bb49bee862c7a8000de6508d006edf29778b5dbede4deaf3cfa05c22521fc775da126f5057621960d327615b5186b27d75a378b00981394716e93fc5cca11a"
           size="10469801"/>
    </update>
</updates>
图 2.6:示例更新代码片段

图 2.6所示,更新代码片段具有一个 type 属性,该属性可以是 majorminor。次要更新使人们保持更新到其发布列车中可用的最新版本;例如,它会将所有 3.6.* 版本用户更新到最新的 3.6 版本,将所有快速发布 Beta 用户更新到最新的 Beta 版本,将所有 Nightly 用户更新到最新的 Nightly 版本,等等。大多数情况下,更新是次要的,除了确认应用更新并重新启动浏览器之外,不需要任何用户交互。

当我们需要向用户宣传最新版本可用时,我们会使用主要更新,提示他们“Firefox 的新版本可用,您是否要更新?”并显示一个展示新版本主要功能的广告牌。我们的新快速发布系统意味着我们不再需要进行如此多的主要更新;一旦 3.6.* 发布列车不再受支持,我们就可以停止生成主要更新。

完整更新与部分更新

在构建时,我们生成包含新版本所有文件的“完整更新”.mar 文件,使用 bz2 压缩,然后归档到 .mar 文件中。完整更新和部分更新都通过用户 Firefox 注册的更新通道自动下载。我们有不同的更新通道(即,发布用户在发布通道上查找更新,Beta 用户在 Beta 通道上查找更新,依此类推),以便我们可以例如在与向 Beta 用户提供更新不同的时间向发布用户提供更新。

通过比较旧版本的完整 .mar 和新版本的完整 .mar 来创建部分更新 .mar 文件,以创建包含任何已更改文件的二进制差异和清单文件的“部分更新”.mar 文件。如图 2.6中的示例代码片段所示,这会导致部分更新的文件大小大大减小。这对使用较慢或拨号上网的用户非常重要。

在我们旧版本的更新自动化中,为所有语言环境和平台生成部分更新可能需要 6 到 7 个小时才能完成一个版本,因为完整 .mar 文件被下载、比较并打包成部分更新 .mar 文件。最终发现,即使跨平台,许多组件更改也是相同的,因此可以重复使用许多差异。使用缓存每个差异部分哈希值的脚本,我们的部分更新创建时间缩短到大约 40 分钟。

上传代码片段并在 AUS 上托管后,将运行更新验证步骤以 a) 测试下载代码片段,以及 b) 使用下载的 .mar 文件运行更新程序以确认更新是否正确应用。

部分更新 .mar 文件以及所有更新代码片段的生成目前在签名完成后进行。我们这样做是因为部分更新的生成必须在两个版本的已签名文件之间进行,因此代码片段的生成必须等到已签名版本可用后才能进行。一旦我们能够将签名集成到构建流程中,我们就可以在完成构建或重新打包后立即生成部分更新。加上对 AUS 软件的改进,这意味着一旦我们完成构建和重新打包,我们就可以立即推送到镜像。这有效地并行化了所有更新的创建,减少了我们的总时间几个小时。

2.7. 推送到内部镜像和 QA

验证发布流程是否生成预期的交付物至关重要。这是通过 QA 的验证和签核流程来实现的。

已签名版本可用后,QA 开始进行手动和自动测试。QA 依靠不同时区的社区成员、承包商和员工的组合来加快此验证过程。同时,我们的版本自动化为所有语言和所有平台生成所有受支持版本的更新。这些更新代码片段通常在 QA 完成已签名版本的验证之前就已准备就绪。然后,QA 验证用户是否可以使用这些更新安全地从各种先前版本更新到最新版本。

从机制上讲,我们的自动化将二进制文件推送到我们的“内部镜像”(Mozilla 托管的服务器),以便 QA 验证更新。只有在 QA 完成对版本和更新的验证后,我们才会将其推送到我们的社区镜像。这些社区镜像对于处理全球用户负载至关重要,允许他们从本地镜像节点而不是直接从 ftp.mozilla.org 请求更新。值得注意的是,在 QA 签核之前,我们不会在社区镜像上提供版本和更新,因为如果 QA 在最后一刻发现了一个重大问题并且候选版本需要撤回,则会出现一些并发症。

版本和更新生成后的验证过程是

请注意,在 QA 签核且发布协调员发送电子邮件请求将版本和更新推送到生产环境之前,用户不会收到更新。

2.8. 推送到公共镜像和 AUS

一旦发布协调员获得 QA 和 Mozilla 其他各个团队的签核,他们就会指示发布工程将文件推送到我们的社区镜像网络。我们依靠我们的社区镜像来处理未来几天内数亿用户下载更新的情况。此时,所有安装程序以及所有平台和语言环境的完整和部分更新都已在我们内部镜像网络上。将文件发布到我们的外部镜像涉及更改公共镜像模块的 rsync 排除文件。一旦此更改完成,镜像将开始同步新的版本文件。每个镜像都有一个与其关联的分数或权重;我们监控哪些镜像已同步文件,并将它们各自的分数相加以计算总“采用率”分数。一旦达到一定的采用率阈值,我们会通知发布协调员镜像的采用率足以处理发布。

这是版本正式发布的节点。发布协调员发送最终的“上线”邮件后,发布工程团队会更新 Web 服务器上的符号链接,以便访问我们 Web 和 FTP 站点的用户可以找到最新版本的 Firefox。我们还会将所有更新片段发布到 AUS,供使用旧版 Firefox 的用户使用。

安装在用户机器上的 Firefox 会定期检查我们的 AUS 服务器,查看是否有可用的更新版本。一旦我们发布这些更新片段,用户就可以自动将 Firefox 更新到最新版本。

2.9. 经验教训

作为软件工程师,我们很容易急于解决眼前看起来最直接、最明显的技术问题。然而,发布工程涉及到不同的领域——既有技术领域,也有非技术领域——因此,了解技术和非技术问题非常重要。

其他利益相关者参与的重要性

确保所有利益相关者都了解我们缓慢且脆弱的发布工程给组织和用户带来的风险非常重要。这涉及到组织所有层级都承认缓慢且脆弱的自动化带来的业务机会损失和市场风险。此外,随着用户数量的增长,Mozilla 保护用户并以超快的速度发布新版本的能力变得越来越重要,这反过来也使我们成为更具吸引力的目标。

有趣的是,有些人职业生涯中只经历过脆弱的发布自动化,因此来到 Mozilla 时抱有很低的期望,认为“情况总是这样糟糕”。解释通过稳健、可扩展的发布自动化流程获得的业务收益,帮助每个人理解我们即将开展的“不可见”的发布工程改进工作的重要性。

与其他团队合作

为了使发布流程更高效、更可靠,需要发布工程团队和其他 Mozilla 团队共同努力。然而,有趣的是,人们经常将“发布需要很长时间”误解为“发布工程团队发布需要很长时间”。这种误解忽略了发布工程团队之外的其他团队所做的发布工作,并且会降低发布工程师的积极性。纠正这种误解需要教育 Mozilla 的各个团队,在发布过程中不同团队实际花费了多少时间。我们通过在跨团队明确的交接邮件中添加低技术含量的“挂钟”时间戳,以及一系列详细描述时间花费的“挂钟”博客文章来做到这一点。

建立清晰的交接

我们许多“发布工程”问题实际上是人员问题:团队之间的沟通不畅;缺乏明确的领导;以及由此产生的压力、疲劳和在重大版本发布期间的焦虑。通过建立清晰的交接来消除这些人为沟通问题,我们的发布立即开始变得更加顺利,跨团队的人际互动也迅速改善。

管理人员流动

我们开始这个项目时,团队成员流失率过高。这本身就很糟糕。然而,缺乏准确的最新文档意味着发布流程的大部分技术理解都是通过口口相传和口述历史记录下来的,每当有人离开时,这些信息就会丢失。我们需要紧急扭转这种局面。

我们认为,提高士气并表明情况正在改善的最佳方法是确保人们能够看到我们有一个改善事物的计划,并且人们可以控制自己的命运。我们通过确保在每次发布后都留出时间来修复至少一件事情(任何事情!)来做到这一点。我们通过协商在发布后立即安排一两天“请勿打扰”时间来实现这一点。解决人们记忆犹新的、眼前的小问题,帮助消除了干扰,以便人们能够专注于后续版本中更大的长期问题。更重要的是,这让人们感觉我们重新掌握了一些对自身命运的控制权,并且情况确实在好转。

管理变更

由于市场压力,Mozilla 在我们改进发布流程的过程中,对发布流程的业务和产品需求发生了变化。这并不罕见,应该预料到。

我们知道我们必须继续使用当前的发布流程发布版本,同时构建新的流程。我们决定不尝试在支持现有系统的同时构建一个单独的“全新项目”;我们认为当前的系统非常脆弱,以至于我们实际上没有时间做任何新事情。

我们从一开始就假设我们并不完全了解哪里出了问题。每次增量改进都让我们能够退一步,检查是否有新的意外情况,然后再开始下一项改进工作。在整个项目中,每当我们发现新的意外情况时,我们经常会听到诸如“排干沼泽”、“剥洋葱”和“这怎么可能起作用?”之类的短语。

鉴于所有这些,我们决定对现有流程进行许多小的、持续的改进。每次迭代改进都使下一次发布略微好一些。更重要的是,每次改进都在下一次发布中节省了一点时间,这使得发布工程师有更多时间进行下一次改进。这些改进不断积累,直到我们发现自己已经过了临界点,并且能够腾出时间进行重大的主要改进。在这一点上,发布优化的收益真正显现出来。

2.10. 更多信息

我们对迄今为止所做的工作以及它为 Mozilla 在竞争日益激烈的全球浏览器市场带来的能力感到非常自豪。

四年前,在一个月内进行两次重大版本发布会在 Mozilla 内部引发热议。相比之下,上周,第三方库中发布的漏洞导致 Mozilla 在两天内轻松地发布了八个重大版本。

与任何事物一样,我们的发布自动化仍有很大的改进空间,我们的需求也在不断变化。有关我们正在进行的工作,请参阅