Microsoft 如何使用 DevOps 进行开发

Microsoft 致力于通过以 Git 分支和发布流为中心的稳定 DevOps 流程,从而使用 One Engineering System 来构建和部署所有 Microsoft 产品。 本文重点介绍实际实现、系统如何从小型服务扩展到大规模平台开发需求,以及跨各种 Microsoft 团队使用系统时所吸取的教训。

采用标准化开发流程是一项雄心之举。 不同 Microsoft 组织的要求大相径庭,组织内不同团队的需求也会随规模和复杂性而提高。 为满足这些需求,Microsoft 使用基于中继的分支策略来帮助快速开发产品、定期部署产品,以及安全地将更改交付到生产环节。

微软还按照平台工程原则来建设其 One Engineering System

Microsoft 发布流

每个组织都应满足标准代码发布流程,以确保团队之间的一致性。 Microsoft 发布流包含从开发到发布的 DevOps 流程。 发布流的基本步骤包括分支、推送、拉取请求和合并。

分支

若要修复 bug 或实现功能,开发人员则会在主集成分支中创建一个新分支。 Git 轻型分支模型可为每个代码贡献创建这些生存期较短的主题分支。 开发人员提前提交,并使用功能标志来避免出现长时间运行的功能分支。

Push

当开发人员准备好将更改集成并交付给团队其余成员时,他们会将本地分支推送到服务器上的分支,并提交拉取请求。 在多个分支中工作的数百名开发人员的存储库使用针对服务器分支的命名约定,从而缓解混淆问题和分支激增。 开发人员通常会创建名为 users/<username>/feature 的分支,其中 <username> 为他们的帐户名称。

拉取请求

拉取请求可控制传入主分支的主题分支合并,并确保满足分支策略。 拉取请求流程会构建建议的更改并运行快速测试。 第一级和第二级测试套件会在不到五分钟内运行约 60,000 次测试。 这不是完整的 Microsoft 测试矩阵,但足以快速保障拉取请求的完成。

接下来,团队的其他成员会查看代码并批准更改。 代码评审会选取自动测试的中断位置,而这对于发现体系结构问题特别有用。 手动代码评审可确保团队中的其他工程师能了解更改,同时确保代码质量仍然很高。

合并

拉取请求满足所有生成策略且审阅者已签核后,主题分支便会合并到主集成分支,拉取请求即告完成。

合并后,其他验收测试会接着运行,而这些测试需要更多时间才能完成。 这些传统的签入后测试会进行更彻底的验证。 此测试流程可在拉取请求评审期间快速测试以及发布前实现测试全覆盖之间提供良好的平衡。

与 GitHub Flow 的差异

GitHub Flow 是一种常用的基于中继的开发发布流,组织可通过它实现可缩放的 Git 方法。 但是,某些组织发现,随着需求的增长,它们必须与 GitHub Flow 的各个部分有所区别。

例如,通常被忽视的 GitHub Flow 部分是:拉取请求必须部署到生产环境进行测试,然后才能合并到主分支。 此流程意味着所有拉取请求都会在部署队列中等待合并。

某些团队有数百名开发人员在单个存储库中持续工作,他们每天可完成针对主分支的 200 多个拉取请求。 如果每个拉取请求都需要部署到全球多个 Azure 数据中心进行测试,开发人员便需花时间等待分支合并,而不是编写软件。

相反,Microsoft 团队将继续在主分支中进行开发,并将部署批处理成定时发布,这通常与为期三周的冲刺节奏保持一致。

实现详细信息

Microsoft 发布流的部分关键实现详细信息如下:

Git 存储库策略

不同团队有不同策略来管理其 Git 存储库。 某些团队将大部分代码保存在一个 Git 存储库中。 代码会划分为组件,而每个组件都位于其自己的根级文件夹中。 大型组件(尤其是较旧的组件)可能有多个子组件,而这些子组件在父组件中具有单独的子文件夹。

Screenshot showing a Git repository structure.

辅助存储库

某些团队还管理着辅助存储库。 例如,构建和发布代理任务时,会在 GitHub 上部署 VS Code 扩展开源项目。 配置更改会签入到单独的存储库。 团队依赖的其他包来自其他位置,并通过 NuGet 进行使用。

单存储库或多存储库

虽然某些团队选择拥有单个整体存储库(单存储库),但其他 Microsoft 产品会使用多存储库方法。 例如,Skype 拥有数百个小存储库,这些存储库采用各种组合进行拼凑,从而创建多个不同的客户端、服务和工具。 尤其是对于采用微服务的团队,多存储库更可能是正确的方法。 通常,从整体式开始的较旧产品会发现单存储库方法是最容易过渡到 Git 的方法,其代码组织也反映了这一点。

发布分支

Microsoft 发布流会始终确保可构建主分支。 开发人员会在合并到 main 的短期主题分支中操作。 当团队准备好交付时,无论是在冲刺结束时还是要进行重大更新,他们都会从主分支启动新的发布分支。 发布分支永不会合并回主分支,因此可能需精心挑选重要的更改。

下图显示了蓝色的短生存期分支以及黑色的发布分支。 附带需精心挑选的提交的一个分支显示为红色。

Diagram showing Git release branch structure.

分支策略和权限

Git 分支策略有助于强制实施发布分支结构,并使主分支保持干净。 例如,分支策略可阻止针对主分支的直接推送。

为保持分支层次结构的整洁,团队使用权限来阻止在层次结构的根级创建分支。 在以下示例中,每个人均可在 users/features/teams/ 等文件夹中创建分支。 只有发布管理员有权在 releases/ 下创建分支,且某些自动化工具有权使用 integrations/ 文件夹。

Screenshot that shows branches.

Git 存储库工作流

在存储库和分支结构中,开发人员会执行日常工作。 工作环境因团队和个人而异。 某些开发人员更喜欢命令行,有些喜欢 Visual Studio,还有一些则会在不同的平台上工作。 Microsoft 存储库中的结构和策略可确保建立稳定且一致的基础。

典型的工作流涉及以下常见任务:

构建新功能

构建新功能是软件开发人员工作的核心。 此流程的非 Git 部分包括查看遥测数据、提出设计和规范以及编写实际代码。 然后,开发人员通过同步到 main 上的最新提交开始使用存储库。 由于主分支始终可进行构建,因此可确保它是一个很好的起点。 开发人员签出新功能分支,进行代码更改、提交、推送到服务器,然后启动新的拉取请求。

使用分支策略和检查

创建拉取请求后,自动化系统会确保新代码已生成、不会破坏任何内容、也不会违反任何安全或合规性策略。 此流程不会阻止其他工作并行执行。

分支策略和检查可能要求成功进行构建,其中包括通过各项测试、由负责人对所有涉及的代码进行签核,以及完成多个外部检查来验证公司策略,然后才能完成拉取请求。

Screenshot showing the checks on a pull request.

与 Microsoft Teams 集成

很多团队会配置与 Microsoft Teams 的集成,从而向开发人员的团队成员公布新的拉取请求。 所有所涉及代码的负责人都将自动添加为审阅者。 Microsoft 团队通常对涉及许多人的代码(如 REST 客户端生成和共享控件)使用可选审阅者来重点关注这些更改。

Screenshot showing Teams integration.

Screenshot showing Teams notification of a pull request.

使用功能标志进行部署

满足审阅者、代码负责人和自动化要求后,开发人员即可完成拉取请求。 如果出现合并冲突,开发人员将获得有关如何同步到冲突、修复冲突以及重新推送更改的说明。 自动化功能会在修复后的代码上再次运行,但无需再次进行人工签核。

分支会合并到 main,而新代码会部署到下一冲刺或重大发布中。 这并不意味着新功能将立即发布。 Microsoft 使用功能标志将新功能的部署和公开进行分离。

即使该功能在准备隆重发布之前需完成更多工作,但如果产品会进行构建和部署,也可放心地转到 main。 进入 main后,代码将成为正式构建的一部分,同时会再次测试该构建、确认满足策略并进行数字签名。

左移以提前检测问题

此 Git 工作流提供多种优势。 首先,在单个主分支中操作几乎可消除合并债务。 其次,拉取请求流提供了一个常见点,以便在管道早期强制实施测试、代码评审和错误检测。 这种左移策略有助于缩短开发人员的反馈周期,因为它可在几分钟(而不是几小时或几天)内检测错误。 此策略还有助于安心重构,因为所有更改都会不断测试。

目前,具有 200 多个拉取请求的产品每天可能会生成 300 多个持续集成构建,相当于每 24 小时运行 500 多次测试。 如果没有基于中继的分支和发布工作流,便无法进行这种级别的测试。

在冲刺里程碑处进行发布

在每个冲刺结束时,团队都会从主分支创建发布分支。 例如,在冲刺 129 结束时,团队会创建一个新的发布分支 releases/M129。 然后,团队会将冲刺 129 分支放入生产环节。

创建发布分支的分支后,主分支将保持打开状态,以便开发人员合并更改。 这些更改将在三周后的下一冲刺部署中进行部署。

Illustration of the release branch at sprint 129.

发布修补程序

有时,更改需快速进入生产环节。 Microsoft 通常不会在冲刺中间添加新功能,但有时想快速引入 bug 修复,以解除阻止用户。 问题可能很小,例如拼写错误或是过大,从而会导致可用性问题或实时站点事件

纠正这些问题将从正常工作流开始。 开发人员会从 main 创建分支,接受代码评审,然后完成拉取请求以将其合并。 此流程始终会先在 main 中进行更改。 这样,便可快速创建修补程序并在本地进行验证,而无需切换到发布分支。

遵循此流程还可保证将更改传入 main,这一点至关重要。 修复发布分支中的 bug 而不将更改带回 main 意味着当冲刺 130 从 main 发布分支时,该 bug 将在下一部署期间重现。 中断期间可能会引发混乱和压力,此时很容易忘记更新 main。 将更改引入 main 意味着始终在主分支和发布分支中保存这些更改。

Git 功能支持此工作流。 为立即将更改引入生产环节,一旦开发人员将拉取请求合并到 main 后,他们便可使用拉取请求页面将更改精心挑选到发布分支中。 此流程将创建一个面向发布分支新拉取请求,从而回送刚刚合并到 main 中的内容。

Illustration of cherry-picking a hotfix commit into branch 129.

使用精心挑选功能可快速提交拉取请求,从而实现分支策略的可追溯性和可靠性。 精心挑选可在服务器上执行,而无需将发布分支下载到本地计算机。 由于两个分支之间的差异,进行更改、修复合并冲突或进行轻微更改,都可能发生在服务器上。 团队可直接从基于浏览器的文本编辑器或通过拉取请求合并冲突扩展来编辑更改,从而享受更高级的体验。

将拉取请求面向发布分支后,团队会再次审查代码、评估分支策略、测试拉取请求并将其合并。 合并后,修补程序会在几分钟内部署到服务器的第一。 在此,团队会使用部署环逐步将修补程序部署到更多帐户。 随着更改被部署到更多用户,团队会监视是否成功并验证更改是否修复了 bug,同时不引入任何缺陷或速度放缓因素。 修复程序最终会部署到所有 Azure 数据中心。

继续转到下一冲刺

在接下来的三周内,团队会完成向冲刺 130 添加功能,并准备好部署这些更改。 他们会在 main 中创建新的发布分支 releases/M130,然后部署该分支。

此时,生产环节实际上有两个分支。 借助基于环的部署,可以安全地将更改引入生产环节,快速环将获取冲刺 130 的更改,而慢环服务器会继续执行冲刺 129,同时还会在生产环节验证新更改。

在部署中间为更改创建修补程序可能需要修复两个不同的发布:冲刺 129 发布和冲刺 130 发布。 团队会将修补程序同时传送并部署到这两个发布分支。 130 分支会使用修补程序重新部署到已升级的环。 129 分支会使用修补程序重新部署到尚未升级到下一冲刺版本的外环。

部署所有环后,旧的冲刺 129 分支将被放弃,因为作为修补程序引入冲刺 129 分支的所有更改也已在 main 中实现。 因此,这些更改也将进入 releases/M130 分支。

Illustration of a release branch at sprint 130.

总结

发布流模型是 Microsoft 如何使用 DevOps 进行开发以提供联机服务的核心。 此模型使用基于中继的简略分支策略。 但是,Microsoft 发布流可让开发人员继续工作,而不是让开发人员停滞在部署队列中等待合并其更改。

此发布模型还允许定期在 Azure 数据中心部署新功能,而不考虑 Microsoft 代码库的大小以及在其中工作的开发人员数量。 此模型还允许将修补程序快速、高效地引入生产环节。