持续集成的极致在哪里?

极限编程(Extreme programming,XP)是种软件工程方法,其理念在于如果软件开发领域存在某些公认有价值的方法或实践,那就将它们推到极致,进而达到更好的响应用户需求,构建更高质量软件的目标。例如代码评审有价值,推到极致就是结对编程;代码测试有价值,推到极致就是测试驱动开发;客户沟通有价值,推到极致就是现场客户。

那么同样是源自极限编程的持续集成,它本身的极致在哪里?

持续集成的由来

下面这段话来自软件巨匠 Martin Fowler 博客中对持续集成的定义:

持续集成是一种软件开发实践,团队成员频繁地集成他们的工作,通常每个人至少每天集成一次,从而达到每天多次的集成。每次集成都会通过自动化构建(包括测试)进行验证,以尽快检测到集成错误。许多团队发现,这种方法大大减少了集成问题,并允许团队更加快速地开发具有内聚性的软件。

为什么会出现集成问题?因为软件开发通常是一个协作性的团队活动,涉及多个开发人员、团队成员和不同的代码模块。而集成则是将不同部分或组件的代码整合到一个统一的系统中,以便进行联合构建、测试和验证的过程。传统软件开发中集成的问题众多,是由于较长时间分工并行开发,指定时间进行代码集成的模式导致。可以想象一下,要将十几个开发人员开发了一个月的代码,在某一个时间点全部整合到一起能够成功构建和运行,并通过相应的测试和验证是一件多么有挑战的事情。而在诸多现实世界的项目开发中,开发人员不会只有十几个,开发周期也不会只有一个月,这无疑让所有人都在集成环节饱受折磨,充满畏惧。

"If it hurts, do it more often." ,这句话应该是对持续集成实践最好的哲学表达。既然集成十几人开发了一个月的代码充满挑战,那我们是不是可以每天都至少集成一次甚至几次,这样每次集成问题出现的概率会更小,解决问题的复杂度也会更低。对于最终集成日的到来(甚至不复存在),团队也会充满信心,因为过往方式下一个月积累的未知问题,已经在新方式下被拆解到每天都给解决了。

尽管持续集成的概念并不复杂,但在实践过程中往往还是会存在一些误区,例如使用了 Jenkins 就是持续集成、自动化构建就是持续集成、每天定时触发构建就是持续集成等认知不在少数。这些不能说不对,也不能说全对,但始终与持续集成的真正内涵还是存在一些差距。

既然本文讨论的是极致,那么真正的持续集成(持续集成的极限)用一句话概括应该是——频繁推送的每一次代码,都能快速构建成一个生产就绪的交付件。极致持续集成涉及到软件开发过程的诸多方面,涵盖了从代码到制品的全过程。在代码方面强调版本控制、原子提交;在构建方面强调频繁集成、快速构建;而在制品方面则是自动化测试、易于获取;在协作维度强调团队纪律、小步快跑。下面从要素、原则和实践三个维度分别介绍达到极致持续集成目标的潜在方式方法。

极致持续集成的三个要素

分支模型

分支是版本控制中的一个概念,最早的版本控制系统可以追溯到上世纪80年代。而在真正革命性的分布式版本控制系统 Git 于2005年出现后,其分支管理的灵活性和高性能,才使得分支有模型或策略这一说。而业内公认最适合持续集成的分支模型就是主干开发(Trunk based development)。

图片

如上图所示,在主干开发分支模型下,开发者所有的代码的提交有两种方式:一是直接往主干(一般为 master 或 main )提交,第二种是间接通过临时开发分支提交后再合并到主干。除此之外不会再有其他的分支被用来提交代码。

前者是持续集成的极致,因为每次向主干的提交天然就是一次代码的集成,但是对开发人员要求高(高度依赖小步原子提交)、团队纪律性强(第一时间修复失败的流水线)、规模化不足(更加适用于较小的团队,如 AWS 提倡的 two-pizza team)。在解决暂未完成开发的功能会引入功能开关,但同时也引入了技术债务。

所以后者是目前更为推崇的方式,因为不但能够达到与前者类似的效果,而且相对更加容易落地实践。采用临时开发分支与需求或缺陷一一对应的方式来替代功能开关(只要临时分支不合并到主干,这些功能或者缺陷就不会被发布),每次向临时开发分支推送代码即可触发一次集成(一些流水线工具的高级功能可以实现临时开发分支和主干分支代码的预合并),基于 PR 和 MR 的合并过程实现线上的代码评审。而缺点在于对需求拆分粒度的要求较高,因为一旦需求粒度过大,那临时开发分支就容易变成长周期开发分支,从而导致分支代码大幅度偏离主干,回到了过去以月为单位集成的时代。

需求粒度

粒度过大的需求(后面都以用户故事陈述)是持续集成的阻碍,那么什么样的粒度才是合适的呢?下图是用户故事拆分的 INVEST 原则。对于粒度而言,我们会更关注 Estimable、Small 和 Testable 三个维度。

  • Estimable 指的是交给开发人员实现的用户故事是可估算的,而且估算的结果以团队能够在迭代的时间盒里完成为宗旨;

  • Small 指的是用户故事应该足够小(非无限小)到能够有效地进行开发和测试;

  • Testable 指的是用户故事是可测试的,它应该是一个完整的软件功能,能够通过相应的验收标准,有明确的完成定义。

图片

在推荐的实践中,我们会将用户故事、临时分支和合并请求一一对应起来,每一次向临时分支的代码推送都会触发持续集成流水线来验证代码是否具备合并(集成)的条件。在最终代码评审通过后,临时分支合并到主干,这也就意味着该用户故事已开发完成,临时分支会被删除,用户故事也会被关闭。这样带来的好处是我们能更容易地实现需求和代码之间的双向追溯,确保集成到主干中的代码构建出的交付件涵盖了相对应的需求。

团队结构

康威定律指出设计系统的架构受制于产生这些设计的组织的沟通结构,即系统设计本质上反映了企业的组织架构。无论是前面提到的分支模型和需求粒度,还是后面会提到的持续集成原则,都高度依赖团队的组织结构。

如果你发现一款软件的分支模型是以环境来划分(如DEV,SIT,UAT,PRD),那大概率该软件团队是基于职能型结构来组织。开发人员、测试人员和运维人员分属于不同的部门或中心,项目经理以软件项目为载体,将人员以资源的形式调配到一起。软件全生命周期各阶段的任务有明显的移交过程,因此来自不同部门的人需要关注不同的分支才能够达到相对独立的工作。项目做完后大家各奔东西,等待下一次被召唤。

如果你发现一款软件的持续集成流水线除了基本的编译构建步骤之外,还包含了代码质量扫描、代码安全扫描、软件成分分析、单元测试、自动/手工部署、接口测试、端到端测试等等非常全面的步骤,那大概率他们是一个全功能团队,开发、测试、安全和运维等多个角色都在这一个团队中协同工作,他们共同决定软件的演进方向和工程实践,并为最终的软件价值负责。

图片

相较于资源池形式构建的职能型团队,一个稳定的市场型团队(又称产品团队、全功能或跨功能团队)更有可能将持续集成实践做到极限。通过全面且高度自动化的持续集成流水线,以及团队内部达成一致的持续集成纪律,实现每一次集成到主干的代码都能构建出生产就绪的交付件并不是不可达到的目标。

极致持续集成的三个原则

Build Once, Run Anywhere

"Build Once, Run Anywhere"是一个软件开发原则,通常与跨平台兼容性相关。这个原则的核心思想是,一旦你成功构建(编译和打包)了应用程序,无需对代码进行修改或重新构建,它就应该能够在多个不同的平台或环境中运行。这个实践一般是在代码集成过程完成后,基于主干分支代码构建出的交付件应该能够不同的环境中部署、运行和测试,而不再是针对不同环境的分支代码进行重新的构建出包。

图片

该原则的好处在于更加容易实现每个环境中部署的交付件的一致性,不论是在开发测试环境中验证的对象,还是最终到生产环境中运行对象,他们都是同一个,只是针对不同环境的用途使用的配置不一样而已。只有这样,前序的验证结果才能后续的工作带来价值,进而降低投产上线的风险,提高团队信心。

Keeping Pipeline Always Green

"Keeping Pipeline Always Green" 通常用于描述持续集成和持续交付(CI/CD)过程中的一种目标。它的核心意思是团队需要努力保持 CI/CD 流水线(通常是指自动化构建和测试流水线)始终处于“绿色”的状态,即所有自动化测试或步骤都成功通过,没有失败。我们可以将软件世界的 CI/CD 流水线类比工业世界的生产制造流水线,一旦流水线出现了故障,那就意味着的整个生产被阻塞或停滞了。

图片

通常团队都会设立一个仪表盘来监控和通知流水线的状态。绿色状态的 CI 流水线确保了软件开发过程的可靠性,它表示代码库处于稳定的状态,新的代码更改不会破坏现有的软件功能,基于主干分支检出的代码也不会对新功能的开发带来任何影响。CI流水线的通过也意味着软件的质量和安全符合团队的标准和要求,它可以作为代码评审环节中分支合并的一个前置条件,确保每次合并到主干的代码都通过了 CI 流水线的验证。

而红色状态的 CI 流水线则意味着一个或多个步骤因出现问题而失败,可能是编译构建失败、可能是自动化测试失败、也可能是质量或安全门禁没有通过,总之这意味着一个需要尽快处理和解决的负面状态。

CI流水线的 MTBF 和 MTTR 指标能够帮助我们识别出团队持续集成实践的成熟度水平。

图片

其中:

  • MTBF(Mean Time Between Failures)平均故障间隔时间,呈现CI流水线持续正常运转的时间,衡量可靠性(Reliability),这个数值越大越好;

  • MTTR(Mean Time To Recover)平均故障修复时间,呈现CI流水线在故障后恢复到正常状态的时间,衡量是弹性(Resiliency),这个数值越小越好。

个人认为 MTTR 比 MTBF 更加重要,因为 CI 的重要价值之一就是快速地获取反馈,识别并修复错误。

Keeping Master Always Green

"Keeping Master Always Green" 是确保软件代码库中主干始终处于可部署状态,可部署意味着在该分支上的代码不存在冲突的情况,也没有严重的质量或安全问题,且能够成功编译构建出交付件。

图片

如上图,当开发人员需要在项目上进行工作时,他们通常会从主干创建自己的分支,并在分支上进行开发,最终将通过CI流水线验证和代码评审的分支代码合并到主干。因此,主干通常被视为持续集成的核心,因为它包含了最新、最稳定的代码。

我们必须保证从主分支检出的代码没有问题,才能保证在分支上的功能开发工作顺利开展。同时,我们也需要保证向主干合并的任何代码都不应该破坏主干,才能保证主干的代码始终可部署。主干的稳定性对整个团队的开发工作至关重要,因为所有新开发任务的起点就在这里,而持续集成流水线则是验证主干是否健康的重要手段。

极致持续集成的三个实践

前面介绍了持续集成的要素和原则,这部分我们看看如何有效借助工具的能力实现极致的持续集成实践。

图片

上图来自 GitLab Workflow,呈现了单次代码集成的全过程,涵盖了用户故事、合并请求、持续集成流水线和代码评审等实践。

  • 用户故事(Issue)和合并请求(Merge Request)是一一对应的关系。整体逻辑是:在用户故事进入到开发阶段时,就需要创建相应的合并请求,当合并请求被合并时,相应的用户故事就会被关闭。

  • 每次创建合并请求时,选择自动创建对应的临时分支(如图中所示的 FEATURE 分支),软件功能的开发过程围绕临时分支开展,即新的业务代码提交到临时分支;

  • 每一次的代码推送都会触发持续集成流水线。持续集成流水线应包含代码质量扫描、代码安全扫描、软件成分分析、单元测试、编译构建、自动/手工部署、接口测试、端到端测试等步骤。在任务尽可能全面的情况下,做到尽可能快速的执行。

  • 应用预览(Review App)的使用场景非常灵活,一般会与自动/手工部署结合使用。例如当CI流水线构建任务完成后,我们可以直接自动地将新交付件部署到动态可回收的开发环境进行冒烟测试,以便快速开启开发自测工作。又比如因为新功能可能无法第一时间做到完全的自动化测试,那么我们也可以将交付件一键式的部署到静态可持续的测试环境中(如SIT),便于测试人员开展相关的手工测试验证。

  • 代码评审(Code Review)是开发过程中不可或缺的环节,全功能团队的成员根据CI流水线执行的结果(如质量报告、安全报告、测试报告等)进行讨论,基于变化的代码内容进行评审,确保分支代码达到合并到主干的要求。最终通过灵活的审批规则定义(如多人审批、特殊角色审批、代码责任人审批等)完成代码合并前的所有确认工作。

  • 合并请求被合并意味着用户故事开发的结束,临时分支被删除,主干上就是最新的稳定代码。

上述过程描述单次代码合并的全过程,但团队里不可能只有一个开发人员,极致的持续集成每天都存在若干个相同的上述过程在进行,团队应该如何简单高效地保证每一次的集成都是成功(成功意味着分支代码在集成到主干后是可工作的)的呢?下面推荐三个实践。

一夫当关——流水线门禁

之前提到持续集成流水线则是验证主干是否健康的重要手段。那么我们可以基于流水线的状态设置一个代码合并的门禁。对于分支到主干的合并,流水线运行通过,则允许;流水线不通过,则拒绝。

图片

极狐GitLab 提供了合并检查的功能,通过配置开启是否将流水线成功作为合并的前置条件。

预测未来——合并结果流水线

通常情况下,持续集成流水线基于分支上的代码运行。那么在分支代码通过了CI的验证后,合并到主干时就一定没问题吗?答案是否定的。那么我们在运行CI流水线的时候,不再只针对分支代码进行验证,而是直接拉取分支和主干集成后的代码进行验证,这样就能够更大程度的保护主干。因为在正式合并之前,我们已经预测了一把未来的合并结果。

图片

极狐GitLab CI 得合并结果流水线就是做这个事情的,它使用源分支和目标分支合并后的结果创建一个临时的内部提交,然后针对该临时提交运行配置好的CI流水线进行验证。本质上讲,是实现源分支到目标分支的预合并验证。

万无一失——合并列车

即然是对合并结果的预测,那就会出现不准确的可能。一般情况下,从CI流水线通过到最终合并请求进行合并是有一段时间间隔的,如何在这个时间内主干的代码发生了改变(如其他队友的新代码合并到了主干),我们又没有重新运行自己的CI流水线进行与合并验证,这时候合并代码就可能会出现问题。如何确保万无一失呢?

图片

极狐GitLab CI 合并队列(也叫合并列车)是多个合并结果流水线的集合,它会帮助团队根据时间线将合并请求一个个地加入到一个队列中,在正式合并之前再次去进行CI流水线的预合并验证,通过了就直接合并到主干,不通过就剔除队列修复后重来。而且在这个时候验证的内容并不仅仅是你自己分支和主干代码,而是基于时间线加入到队列中的所有合并请求所涉及的代码。

图片

如上图,假设你的MR1是第一个上车的,那队列中的预合并验证的就是「MR1+Main」的代码,属于专车待遇;如果此时你以及其他同学代码还未合并,同学小D的MR4上车了,那么队列中就会验证「MR1+MR2+MR3+MR4+Main」的代码,那就是拼车待遇了。这时如果小C同学的MR3失败了,那他就得下车了。此时同学小D就会变得不开心,因为他得在车上挪个位置,即需要拿「MR1+MR2+MR4+Main」的代码重新来验证。

通过这样的方式,我们可以确保只要是成功合并到主干的代码,它就一定就是可工作的。从这里大家也可以看出来合并列车也践行了持续集成的原则,代码应尽早提交尽早集成,因为没有人希望当同学N,就跟绝大多数人都喜欢专车而非拼车一样。

总结

持续集成在如今已不是一个时髦的概念,但做到真正极致的持续集成、激活其最大化价值的团队却并不多。本文内容也无法涵盖持续集成的方方面面,还有很多经典的理念和实践值得我们去理解和探索。希望文中涉及到的三大要素、三个原则和三个落地实践能够给读者带来一些启发。

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值