Facebook 是如何做大规模代码部署的?
(点击上方公众号,可快速关注)
编译:伯乐在线 - wrm,英文:code.facebook.com
如有好文章投稿,请点击 → 这里了解详情
随着时间的推移,软件行业已经提出了多种方法来更快更好更安全地交付代码。其中大部分的努力都集中在诸如持续集成、持续交付、敏捷开发、DevOps 和测试驱动开发等方面。所有这些方法都有一个共同的目标:让开发人员能够以安全、小型和渐进的步骤将代码快速正确地提供给用户。
Facebook 的开发和部署过程已经得到有机地发展,以涵盖这些快速迭代技术的大部分内容,同时也避免特殊依赖任何一项单独的技术。这种灵活、实用的方法使我们能够在快速的时间内成功地发布我们的网络和移动产品。
多年来,我们使用简单的主程序发布分支策略每天部署三次代码到 Facebook 前端。工程师们会从主干分支中选择一些通过了一系列自动化测试的代码变更推送到每天的发布分支上(这个过程也叫做“cherry-picking”)。总的来说,我们每天选择的变更(cherry-picks)数量为 500 到 700。剩下的没有被 cherry-picked 的变更就推入到每周的发布分支中。
从 2007 年的几位工程师到现在的数千名工程师,这个系统的扩展性一直很好。好消息是,随着我们增加了更多的工程师,我们完成的工作也越来越多——代码交付的速度与团队的规模成比例。然而,除了适当的工具和自动化系统之外,它还需要发布工程师花费一定的手工劳动,来完成每天和每周的代码推送和发布。我们知道,随着团队的不断扩大,批量处理日渐增多的交付代码块将无法持续。
到了 2016 年,我们看到 branch/cherry-pick 模型已经达到了极限。我们每天接收的推送到主分支上的变更超过 1000 个,而每周的接收的变更有时多达 10,000 个。因此每周需要进行大量的手工工作来协调和交付这样大型的发布任务,这是不可持续的。
2016年4月,我们决定将 facebook.com 移到一个准持续的“push-from-master”系统上。在接下来的一年里,我们逐渐把它推出,首先让50%的员工用上新代码,然后让0.1%的生产环境用上新代码,再到1%,再增加到10%。这些进程中的每一步都是对我们的工具和过程的测试,测试它们处理日益增加的推送频率的能力,从而得到真实的反馈。我们的主要目标是确保新系统能让人们的体验更好——或者至少,不会让它变得更糟。经过了几乎整整一年的规划和开发之后,在2017年4月的3天内,我们使100%的生产环境能够运行直接从master部署的代码。
大规模持续交付
虽然一个真正的持续交付系统会将每一次提交的代码变更及时发布到生产环境中,但根据Facebook的代码提交速度,我们需要开发一个每隔几个小时就能处理数十到数百个代码变更的系统。在这种准连续交付模式中所做的改变通常是小型且增量的,很少会对实际的用户体验有明显的影响。每一次发布都以分层的方式在几小时内部署到100%的生产环境,因此一旦发现任何问题,我们可以随时停止发布。
首先,代码变更通过一系列自动化的内部测试后才能被提交到主分支,进而被推送给Facebook的员工。在这一阶段引入的任何回归,都会使我们收到推送阻塞警报,而有个紧急停止按钮也可以使我们阻止代码的进一步发布。如果一切正常,我们会将变更推送到2%的生产环境,在那里我们会继续收集信号和监测警报,尤其是对于那些我们的自动测试和员工的内部测试没有发现的边界情况。最后,我们才将这些变更100%部署到生产环境中,由名为Flytrap的工具收集用户报告,并在异常时给我们发送警报。
许多变更最初都是由 Gatekeeper 系统控制的,这使得我们能够独立地发布移动端和 web 端代码而不依赖于新功能,同时有助于降低由任何特定更新导致问题的风险。如果确实发现了问题,我们只需要关闭 gatekeeper,而不是回退到之前的版本或修复当前版本。
这个准持续(”quasi-continuous”)发布周期有几个优点:
1. 它不再需要热补丁
在每天部署三次的策略下,如果一个关键变更必须立刻发布,而不是在它预定的推送时间里,这时就必须有人来打热补丁。这些带外推送是破坏性的,因为他们通常需要一些人力操作,而且可能会撞上下一个预定的推送。在新系统中,绝大多数需要热补丁的程序都可以简单地提交给master,并在下一个版本中进行发布。
2. 为全球工程师团队提供了更好支持
我们试着尽量合理安排每天三次部署的时间,以适应我们在世界各地的工程办公室。但即使是这样的努力,每周一次的发布也要求所有的工程师在某个特定的日期和时间内集中注意力,然而这些时间在他们的时区并不总是很方便。新的准持续系统意味着世界各地的工程师都可以根据需要开发并交付的代码。
3. 它迫使我们开发下一代工具、自动化和流程,以使公司能够扩大规模
我们所做的这个项目,可以作为跨越多个团队和系统的压力测试。我们改进了推动工具、diff 审查工具、测试基础架构、容量管理系统、流量路由系统,以及许多其他方面。这些团队都聚集在了一起,因为他们希望看到一个发布周期更快的自动化部署系统尽快成功。我们所做的改进将有助于确保公司为未来的发展做好准备。
4. 它使用户体验更好、更快
当需要数天或数周的时间来观察代码的运行状况时,工程师们可能已经转向了新的任务。在持续交付的情况下,工程师不必等待一周或更长的时间才能得到他们提交的代码的反馈。他们可以更快地了解到哪些地方不work,并及时进行小的增强或修复,而不是等到下一次大的发布。从基础设施的角度来看,新系统使我们能够更好地应对那些可能影响用户的稀有事件。最终,这将使工程师更加贴近用户,不仅有助于产品开发还有助于提高产品可靠性。
持续发布到移动端
在web平台上发展出一个准连续系统在某种程度上是可能的,因为我们拥有完整的技术栈,而且可以构建和改进我们需要的工具,使之成为现实。而移动平台上则面临更多的挑战,因为许多现有的移动平台的开发和部署工具使快速的迭代变得比较困难。
Facebook致力于改善这一情况,并建立和开源了一整套专门针对移动平台上快速开发的工具,包括Nuclide、Buck、Phabricator、各种iOS类库、React Native和Infer。总之,这一系列构建和测试栈使我们能够产生高质量的代码,以便快速部署到移动平台上。
我们的持续集成栈主要分成三层:构建、静态分析和测试。
当开发人员将代码提交到移动主分支时,会在所有受影响的产品上对代码进行构建。对于移动端来说,这意味着每次提交都要重新构建Facebook、Messenger、Pages Manager、Instagram以及其他应用程序。我们还为每个产品构建了多个版本,以确保能够涵盖这些产品支持的所有芯片架构和模拟器。
在构建过程中,我们会运行Infer,它集合了Linters(检查代码风格和错误的小工具)和静态分析工具,用于捕获空指针异常、资源和内存泄漏、未使用的变量和有风险的系统调用,并标记出违反Facebook编码规则的情况。
第三个并发系统是移动自动化测试,包括数千个单元测试、集成测试以及由Robolectric、XCTest、JUnit和WebDriver等工具驱动的端到端的测试。
不仅每次提交时会运行构建和测试栈,而且在代码变更的生命周期内也会运行多次。仅在安卓系统上,我们每天就能完成5万到6万次构建。
通过将传统的持续交付技术应用到移动栈,我们已经从四周发布一版发展到两周发布一版,再到现在的一周发布一版。目前我们在移动平台上使用的就是之前基于web的策略:branch/ cherry- pick模型。虽然我们每周只发布一个版本,但在现实环境中尽早测试代码仍然很重要,因为这样工程师就可以尽快得到反馈。我们每天都会为我们的金丝雀用户(包括大约100万个Android beta测试人员)提供新的移动候选版本。
与此同时,我们的发布频率增加了,我们的移动工程师团队已经增长了15倍,我们的代码交付速度也已经大大提高。尽管如此,从我们2012年到2016年的数据来看,无论是按代码行数还是按推送数量来衡量,工程师在Android和IOS方面的生产率都保持不变。同样,无论部署多少次,移动版本出现的关键问题的数量几乎没有变化,这说明我们的代码质量并没有随着代码规模的增大而受到影响。
随着现有的工具和方法不断取得进展,这是在发布工程领域工作的一个令人兴奋的时刻。我为Facebook的团队感到非常自豪,他们一起合作为我们提供了我认为是这个规模下最先进的web和移动部署系统。这一切能成为可能还有一部分原因是拥有一个强大的中央发布工程团队,因为它是基础设施工程领域的“第一类公民”(First-class citizen)。Facebook的发布团队将继续为开发人员和用户推动改善发布流程的计划,也会继续分享我们的经验、工具和最佳实践。
看完本文有收获?请转发分享给更多人
关注「程序员的那些事」,提高编程技能