巅峰之战 据说75%的企业转型会以失败告终,但在互联网软件领域,有一家世界级的公司堪称传奇,在经历了创新者的窘境、被业界唱衰多年之后,凭借“云为先、移动为先”的战略东山再起,这就是市值刚刚突破万亿美元、成为目前全世界市值最高公司的微软。

这家PC时代的巨头在五年前遭遇着一系列的危机,在消费端硬件和移动互联网方面”全面溃败”:智能手机业务被苹果和Google绞杀;云计算业务亚马逊主导天下;Bing搜索持续烧钱,但份额不到6%;Win8是微软历史上被诟病最多的系统;微软市值处于低谷期,不足3000亿美元…

也许有些人对微软的印象还停留在卖”套装软件”的时期,重型的系统软件、漫长的研发周期,2-3年才能有重大的软件更新版本面世。

但是,也许你已经惊奇的发现,这家重回巅峰的公司现在已经完全不同了!尤其是软件研发效能方面指数级的快速提升:目前每天更新50万条工作项,每天运行5亿个测试,每月超过400万次构建,每天发布4.2万次…

是的,微软”刷新”了我们的认知,以至于近期有很多人从各种角度来分析微软的成功。使命、愿景、价值观这些词虽然听起来很励志,但总给人虚无缥缈的感觉。今天,就让我们从更落地、更实实在在的角度,来全面剖析下这家公司成功背后的技术转型实践,相信会对大家有所启发。 ** DevOps at Microsoft** 本系列文章我们会重点介绍微软的DevOps转型实践,为了确保其准确性,我会引用很多来自其Azure DevOps内部的”第一手资料”,力争做到客观全面。本系列文章预计会包含的实践内容如下:

微软的分支策略:Release Flow

  • 微软的敏捷原则和实践

  • 微软的DevSecOps文化转型

  • 微软的Live-Site生产优先模式

  • 微软从单体应用到云服务的转型

  • 微软如何利用功能开关开展实验

  • 测试左移以获得速度和可靠性

  • 测试右移以在生产环境进行验证

  • 微软的安全部署实践

  • 微软在云端的弹性工程实践

今天,我们先从微软工程效率的一个重要基石 - 分支策略开始。

微软的分支策略 转型之前 微软原来采用的分支策略非常重型:有一条共享的主干,每个大的产品组拉出自己的Group分支进行开发,然后基于Group分支拉出小组SubGroup分支,而在开发具体功能时还需要拉出Feature分支进行编码。每一级分支都需要跟上一级分支进行同步,然后把验证后的代码合并回上级分支(RI: Regression Integration)。当团队人数众多时,从叶子分支合并回主干会经历漫长的”合并地狱”,效率非常低下

进入到Git时代,**工作在单一主干上、并且使用Pull Request 工作流合并代码经常被看做是远离技术债、保证发布稳定的关键因素。**于是微软Azure DevOps团队逐步转型为一种被称为”Release Flow”的分支模型。

转型思路 为了能够更快速、更有规律的发布,Azure DevOps团队开始采用”主干开发”的分支策略。这种策略能够满足规模化的开发需求:帮助跨越三个主要办公室、工作在同一个代码库上,拥有数百名工程师的整个产品团队,高效地开发并部署代码到全球多个数据中心。

开发过程 1. Branch

**当开发人员想要实现一个Feature或修复一个Bug时,从主集成分支master拉出一条新的分支。**由于Git具有轻量级的分支管理,我们每次想要开发一些代码时都创建一条短时存在(Short-lived)的特性分支。开发人员被鼓励尽早提交代码,并通过使用功能开关来避免长期存活的长特性分支。

2. Push

当开发人员准备好对他们的变更进行集成,并共享给团队其他人的时候,他们Push本地的分支到远端服务器,并且创建一个Pull Request。因为我们有数百名工程师工作在代码库上,每个人都有很多条分支,我们使用一种命名约定来帮助减轻混淆。通常,开发人员的本地分支命名为:users/username/feature,其中username是开发人员的账户名。

3. Pull Request

**团队通过Pull Request来控制开发人员的特性分支合并到master。**Pull Request确保了我们的分支规则被满足:首先,构建想要合并的变更并运行快速验证测试。我们会在仅仅5分钟之内就运行60,000个L0和L1级别的测试。其中L0级别主要是单元测试,不依赖于产品被安装运行;L1级别测试也类似于单元测试,但是会依赖数据库等外部资源。注意,这并不是我们完整的测试矩阵,但是可以足够快速地给我们合并代码的高度自信。 接下来,我们需要团队的其他成员Review代码并审批代码变更。代码评审的目标是找出自动化测试没有发现的问题,特别是指出那些架构相关的问题。人工代码评审可以保证团队中的更多工程师对于变更的可见性,并确保代码质量保持在较高水准。

4. Merge

当所有的构建规则被满足,并且代码评审通过,Pull Request就完成了。这意味着特性分支合入到了主集成分支master。在合并之后,我们运行额外的、需要更长时间运行的验收测试。这类似于传统的代码提交后测试,我们运行这些测试进行更加全面的验证。这带给我们在快速反馈和完善的测试覆盖之间的良好平衡。

截止到目前,你可能会觉得这里的分支策略很类似典型的主干开发模型。但是,与其他开发模型(如GitHub Flow)不同的是,我们不会在代码通过Pull Request合并到主干之前部署并测试他们,也不会在Pull Request合并之后立即部署到生产环境。

GitHub Flow模型中经常会被忽略的一点,就是Pull Request实际上会在合并回master之前就直接交付到生产环境(做测试),这意味着开发人员需要在合并之前等待”部署队列”来验证他们的变更。

**Azure DevOps团队拥有数百名开发人员在代码库上工作,每天在主干上会有超过200个Pull Requests。**如果每个合并请求需要部署到全球多个数据中心,我们的开发人员将会浪费掉大量的时间等待队列而不是开发代码。

在Sprint里程碑进行发布 在每个Sprint结束,我们会基于master创建一个发布分支。比如在Sprint 129,我们会创建releases/M129。然后我们使用这个分布分支进行部署上线。 当我们拉出发布分支,master分支保持开放,开发人员可以合并变更。当然,这些变更我们并不会立即部署到生产环境,而是会等待下个迭代的部署。

发布Hotfix 很明显,有一些变更需要更快地发布到生产环境(而不是按迭代)。我们通常不会在迭代过程中添加大型的新特性,但有时我们需要把缺陷或可用性问题快速修复并发布到生产环境,解决影响用户的问题。

在这种情况下,我们从普通工作流开始:从master拉取分支,修改代码并评审通过,完成Pull Request并合并回主干。注意,我们总是从在master上进行变更开始,这样可以让我们快速创建变更内容,然后在本地验证它,而不需要切换到发布分支。

更重要的是,遵循这样的流程,我们可以保证我们得变更被合并到master。这对我们非常重要:如果我们先在发布分支修改缺陷,然后很可能会忘记把变更合并回master,那么下次发布的时候(比如从主干拉出的Spring 130发布分支)就会导致问题复发

因为在遇到生产环境问题或中断时,我们总是充满了混乱和压力,很可能忘记将代码合并回主干。所以,**我们要保证每次变更首先合入master,以便确保在master和发布分支上都会有本次变更。 **

当然,以上流程在Azure DevOps中也有支持,可以在Pull Request页面,选择Cherry-Pick一个Pull Request到不同的分支(Gitlab也有类似的功能)。为了能够让变更立即发布到生产环境,每当我们合并Pull Request到主干,我们Cherry-Pick这个变更到发布分支。这样会创建一个新的、目标为发布分支的Pull Request,用于移植主干上最新的变更内容。

通过创建一个新的Pull Request,我们通过分支策略获得了可追踪性和可靠性。使用Azure DevOps提供的Cherry-Pick功能可以让我们做得更快:我们不需要把发布分支的代码下载到本地电脑然后Cherry-Pick变更,因为这些都已经在服务端高效地完成了。如果你需要做一些变更,比如修改合并冲突或者因为两条分支的不同而修改一小部分内容,我们也可以在服务端完成。我们可以直接在工具内置的文本编辑器中修改内容,或者使用Pull Requet Merge Conflict插件完成。

当我们创建了目标为发布分支的Pull Request,仍然需要再次进行代码评审,评估分支规则并测试它。当变更被合并,就会在几分钟内部署到第一批次的服务器。从这里开始,我们会使用”部署环”逐步扩展部署到更多的用户群体,金丝雀用户->早期采用者->全部用户。随着变更暴露给更多的用户,我们将会监控其成功率,以确保变更修复了缺陷的同时,没有引入其他的问题及性能衰退。

勇往直前 在迭代完成后,我们完成了向Sprint 130中增加特性,那么我们可以部署这些变更了。为了进行部署,我们从master创建了新的发布分支releases/M130。

在这个时间点上,实际上我们有两条生产分支:由于我们使用”部署环”向生产环境安全地部署,”快速环”已经融入了Sprint 130的变更,与此同时”慢速环”仍然保持在Sprint 129,因为新的变更正在生产环境进行验证。这会带来一个有趣的问题:如果我们需要在部署期间做一个新的Hotfix变更,则需要同时在Sprint 129和Sprint 130发布上进行。

当所有的”部署环”都部署完成,Sprint 129的老发布分支将会被移除。我们不再需要它,因为我们已经非常小心地确保了任何加入到Sprint 129的Hotfix变更都同时存在于master分支了。那么这些变更也会出现在接下来我们创建的releases/M130分支中

总结 **Release Flow的分支模型是微软Azure DevOps团队开发方法论的核心。这种模型能够让我们使用简单、基于主干的开发策略支持在线服务。**避免了让开发人员困于部署队列、等待合并他们的变更,而让开发人员真正聚焦于做有价值的事情。下面是一张全景图:

Release Flow让微软跨越不同数据中心,有规律、有节奏地部署新功能,尽管有大量的开发人员工作在大规模的代码库上,我们可以让变更高效、快速、安全地部署到生产环境。

感谢你看到最后 :)

以上就是微软DevOps转型系列文章的第一篇,后面还将有更多丰富的内容。DevOps成功转型其实挺复杂的,需要实践、文化、技术、人员等多个层次的支撑。

我们不空谈、更聚焦落地,我认为只有全面、深度解析一家公司的案例才会真正有价值,也欢迎大家跟我一起持续精进。

作者简介 张乐,社群人称”乐神”,专注于DevOps及研发效能领域实践和研究,曾就职于多个国内一线互联网公司及世界五百强外企。长期在企业中实践和积累,同时是DevOpsDays中国区核心组织者,TiD、GIAC等多个技术大会DevOps专题出品人,DevOps Master Club 联席主席,EXIN DevOps全系列国际认证课程金牌讲师。

持续学习资源 申请加入DevOps Master Club,共同研究DevOps实践案例