Enable javascript in your browser for better experience. Need to know to enable it? Go here.
环境分支和环境流水线是一种反模式

环境分支和环境流水线是一种反模式

在上一篇文章中,我分享了反模式系列的第一篇:特性分支。今天,我打算来介绍一下DevOps中另一个常见的反模式:环境分支和环境流水线。

 

反模式:环境分支与环境流水线

 

在大多数研发组织中,软件要想发布到生产环境,会经过一系列非生产环境的测试。这些非生产环境一般包括:开发环境、测试环境、集成(测试)环境、验收环境、准生产环境等(不同组织可能采用不同的环境组合)。

 

这本没有问题,但很多组织或团队会在开发时针对每个环境拉出一条分支,比如测试分支、集成分支、验收分支、准生产分支等,开发人员拉出特性分支,从特性分支向不同的环境分支合并代码,每条环境分支会对应一条环境流水线,如测试流水线、集成流水线、验收流水线、准生产流水线等,特性分支到环境流水线的合并会触发环境流水线的构建,流水线通过后会把生成的软件包部(制品)署到相应的环境,进行手工测试。

 

更有甚者,甚至会给生产环境一个单独的环境分支,特性通过各个环境的测试后,再将特性分支合并到生产分支,触发生产流水线,最终将流水线制品部署到生产环境。

 

如下图所示:

我把这样的分支和流水线策略叫做环境分支和环境流水线

 

需要注意的是,有些分支策略也包含环境分支,但特性分支并不会直接合并到这些环境分支,而是从主干或公共的开发分支上合并到环境分支,这样可以确保环境分支的代码和主干或公共分支上的代码保持一致。这样的环境分支和环境流水线是可以接受的。不过它往往也会忽略持续集成中的一个重要实践,这个我们后面再聊。

 

造成的问题

 

乍一看好像没什么问题。一个环境对应一个分支,当特性开发完成开始测试时,很自然地将特性分支合并到对应环境分支并开始测试,从而发挥特性分支的最大功效——不同特性彼此独立、互不影响。这下不但开发彼此独立,连测试都互不影响了。

 

然而实际上问题则很大。我们来举个例子。

  1. 特性XXX和YYY同一迭代开始开发,YYY先开发完成提测,将特性分支feature/YYY上的代码合并到了test分支,同时触发流水线并产生制品部署到test环境

     

  2. 但YYY的测试发现了bug,被打回修复了

     

  3. 特性XXX开发完成提测,将特性分支feature/XXX上的代码合并到了test分支,同时触发流水线并产生制品部署到test环境

     

  4. XXX测试通过

     

  5. XXX提集成环境测试,将特性分支feature/XXX上的代码合并到了integration分支,同时触发流水线并产生制品部署到integration环境

     

  6. XXX集成测试通过

     

  7. XXX提准生产环境测试,将特性分支feature/XXX上的代码合并到了pre-prod分支,同时触发流水线并产生制品部署到pre-prod环境

     

  8. XXX准生产环境测试通过

     

  9. XXX提生产,准备上线,将特性分支feature/XXX上的代码合并到了prod分支,同时触发流水线并产生制品准备部署到prod环境

     

你是否在这个流程里面发现了问题呢?

 

XXX在test环境的测试是包含YYY的代码的(尽管feature/XXX分支上没有这些代码),XXX的代码尽管在其他环境上也测试通过,但与test环境所测的代码是不同的(不含YYY)。有可能XXX的代码存在bug,但被YYY的某一段代码恰好修复了,但feature/XXX分支上并没有这些代码,在其他环境的测试又恰好没有发现,从而导致生产事故。

 

你也许会说,测试没发现导致的生产事故,这就是测试的问题了,不是分支策略或流水线策略的问题。确实如此,但实际工作中,很有可能在准生产环境的测试包含其他特性代码,但其他特性测出了问题,临时决定不上线了,而XXX在准生产环境“测试通过了”,可以上线,于是就合并代码上生产了。这时的生产代码与准生产代码已经完全不一样了,但团队却认为XXX是“安全的”。这样一来产生线上事故的风险就十分高了。

 

我个人在第一次听说这样的实践时一度无法理解,甚至十分震惊。不同环境测试的是不同的包,这样的包怎么敢部署到生产的?

 

除此之外,如果你认同特性分支是一种反模式,无法做到真正持续集成,你就发现,环境分支和环境流水线也同样无法做到持续集成。它甚至不知道在跟谁集成……

 

使用反模式的原因

 

环境分支和环境流水线是特性分支的衍生物。团队在使用特性分支时,很自然地会为不同环境创建分支,以进一步提升特性分支在测试阶段的灵活性(特性分支本身是为了提升开发阶段的灵活性)。毕竟开发已经分开了,如果测试不能分开的话,不同特性之间就会形成等待,影响交付效率。

 

有了环境分支,再为每个环境分支分别创建一个流水线,以确保合并后触发构建、产生制品并初步验证制品的质量,似乎也成为了顺理成章的事。

 

还有一种使用环境分支的原因就是多版本。如果代码需要对外提供多个并行的版本,每个版本的迭代不能彼此影响,因此测试也需要隔离。有些团队会在不同的环境测试不同的版本,比如在验收环境测试v1.0,在测试环境测v1.2。我会在下一节讨论它的问题。

 

替代模式

 

要彻底摒弃环境分支和环境流水线,你首先想到的应该就是要摒弃特性分支(手动狗头)。当然,两者并不是紧绑定的。正如上一篇文章中的例子,虽然使用了特性分支,但并没有环境分支,特性分支还是会在一条公共分支上做集成。

 

其实特性分支并没有原罪,只是它特别容易导致长分支或环境分支,导致无法持续集成,才成为DevOps中的毒瘤。

 

在DevOps中替代环境分支和环境流水线的模式是单流水线和制品晋级。其过程如下:

 

  1. 代码push或PR合并到主干或公共分支后,触发流水线进行集成和构建,产生制品。

     

  2. 通过流水线将这个制品部署到测试环境,开始测试。

     

  3. 测试通过后,将相同的制品部署到下一个测试环境(如集成环境)进行测试。

     

  4. 测试通过后,还是相同的制品,继续晋级到下一个测试环境(如验收环境或准生产环境)

     

  5. 如此类推,直到这个制品最终部署到生产环境。

     

所有的构建和部署都是在单一流水线上执行,当然部署环节可能需要手工触发。同样一个制品通过在不同环境的测试和验证,最终晋级生产环境。这样我们才能确保最终部署到生产环境的包是通过层层验证的、有质量信心的包。

 

前面说过,有一种环境分支是从主干或公共分支上合并的,这虽然能确保不同环境分支上的代码一致,但由于环境流水线的存在,没有引入制品晋级机制,不同流水线构建出来的包仍然有可能会产生或多或少的差异,从而带来隐患。

 

至于多版本问题,更是用错了解决方案。不同的测试环境是用来进行不同级别的测试,不是测试不同的版本。如果代码库要支持多版本,就应该为每个支持的版本准备一套流水线环境。

 

总结

 

这就是我们今天要介绍的第二个反模式:环境分支和环境流水线。它的常见程度、危害程度和治理难度如下所示:

 

常见程度:★★★★

 

很多使用了特性分支的团队都在使用环境分支和环境流水线,即使现在没有使用,未来也很可能会使用。所以最根本的治理方案还是摒弃环境分支。

 

危害程度:★★★★★

 

根本做不到持续集成,而且不同环境测试的都是不同的制品,这与DevOps的理念和主张背道而驰。很有可能产生生产事故。可能有些团队暂时没有产生问题,那只是还没有产生而已。或者产生了问题,但把责任归结于开发人员,而没有意识到这是团队的问题。

 

治理难度:★★

 

相对于特性分支来说要好治理一些。只要把多个环境分支合并成一个公共分支,并且只对这一条公共分支建立流水线即可。你甚至可以仍然使用特性分支。

 

参考资料

 

[1] Martin Fowler在他的Patterns for Managing Source Code Branches一文中提到了环境分支,尽管它说的环境分支和国内所使用的环境分支并不完全相同,但危害是一样的(https://martinfowler.com/articles/branching-patterns.html)。

 

免责声明:本文内容仅表明作者本人观点,并不代表Thoughtworks的立场