Day964.从持续构建到持续集成 -遗留系统现代化实战

从持续构建到持续集成

Hi,我是阿昌,今天学习记录的是关于从持续构建到持续集成的内容。

如何修改后的代码可以“火速”部署到生产环境里,这样才能提高整个端到端的交付效率,让每次改动工作都能及时得到反馈,尽快验证效果。


一、遗留系统的构建方式

遗憾的是,遗留系统的特点之一就是 DevOps 相当落后,甚至可以说完全没有。

在遗留系统中,一次上线前构建过程可能是这样的:

一位打包专员,在本地或远程的机器上拉取代码,完成集成、打包和测试的工作,并准备手动部署。

这时,即使再紧急的代码提交都会被拒绝,因为一个干净的打包环境来之不易,新引入的代码会导致所有的流程重来一遍,对打包专员来说是相当痛苦的。

这样的过程死板、低效、容易出错,一点也体现不出软件中“软”的特点(即灵活)。

所以,后来市面上出现了越来越多的自动构建工具,可以自动拉取代码、构建、集成、打包和部署,甚至还能运行自动化测试,这简直就是打包专员们的福音。

但是,如果只是在打包和部署时使用这些工具,那真是大材小用了。

而且,这样也没有解决软件开发中最难解决的问题之一,就是多人协作的情况下,代码集成的问题。

这里的集成是指,将多个人的工作成果合并在一起,并验证这些合并后的代码是否达到了一定的质量要求,是否可以工作的过程。

Kent Beck 早在上世纪 90 年代就提出了持续集成的概念,即集成的频率越高越好,极限情况下就是持续地每时每刻都在集成。

举个例子。相信肯定有类似的经历,如果家里一周不扫地,再扫地时就会发现很多尘土,每次大扫除也变得十分辛苦。

但如果你每天都坚持扫地,每次的工作就不会有很多,因为灰尘的积累时间只有短短的一天。

这其实就是极限编程的理念:越是痛苦的事情,就越要频繁地去做,这样每次做的时候就没有那么痛苦。

集成是软件开发中很痛苦的事情,因为会发生很多不可预知的情况,很容易就要花费比原始编程更多的时间。

越长时间不集成,不可预知的事情就越多,消耗的时间就越长。

因此我们就要更加频繁地去集成,这样每次集成就不会那么痛苦了。

对于遗留系统来说,如果连自动化的流水线都还不存在,那么你的首要任务,就是先打造这样一条流水线,先做到持续构建。


二、持续构建

持续构建是指,每次代码提交都会触发一次构建工作,并执行一些相关任务。

这个过程由持续集成工具自动化完成,大致过程如下:

  1. 开发人员将代码提交(PUSH)到远程代码仓库;
  2. 持续集成服务器按一定时间间隔(比如 1 分钟)轮询代码仓库,以便及时发现代码变更;
  3. 如果发现了代码变更,持续集成服务器就将代码拉取(PULL)到自己本地;
  4. 持续集成服务器按照指定的构建脚本执行各项任务,包括编译、单元测试、代码扫描、安全扫描、打包等;
  5. 任务执行完毕后,会把结果(成功或失败)反馈给开发团队。

有了持续构建,就能解决遗留系统最头疼的问题之一:不知道从哪去找可以部署到各个环境的软件包。

打包部署不再依赖于打包专员的手工操作,大大缩短和简化了部署流程。


三、在遗留系统中引入持续构建

可以使用 Jenkins 或 GoCD 这样的开源持续集成工具,来搭建持续集成服务器。

也可以搭配上 BitBucket 或 GitLab 之类的源代码管理工具,来提供 Pull Request 和 Code Review 等其他功能。

建议选用 Atlassian 公司的三件套:BitBucket、Jira 和 Confluence。

可以将代码管理、需求管理和知识管理打通,补齐遗留系统中缺失的这些内容。其他替代工具也是完全没有问题的。

很多时候,遗留系统的代码体量过于庞大,想要在本地构建一次需要很长时间,甚至会内存溢出。

当然可以从代码和架构层面通过拆分代码库来解决,但这无法解决燃眉之急。

更快速的方式是,在合并代码的时候,触发 Web Hook,让持续集成服务器在远端的特性分支上先执行一次构建,用这次构建来替代本地的构建。然后合并代码,并在合并后的目标分支上再执行一次构建。

代码的构建解决了,下一步就是解决数据库的迁移脚本。在遗留系统中,往往各个环境中的数据库都不完全相同,因为各个环境都可能有手工改动的痕迹。

因此第一步要做的,就是以生产库为标准,统一所有环境数据库的 DDL,并以此作为基线。然后将后续的所有 DDL 和 DML 都通过 Flayway 管理起来,并版本化。

遗留系统要做到单次构建(如每日构建)还是相对容易的,但要想做到“持续”构建,开发人员就必须改变之前的一些工作习惯。

就拿提交代码来说,很多开发人员会等到所有的代码都编写完成,才进行一次提交。

这样,代码冲突的风险非常高,很可能出现代码写了两天,合并就用了半天的尴尬情况。

如果每个人都这样,就无法做到每天构建多次的目标。


四、任务分解

要避免这种局面,开发人员就要对自己编写的代码做出良好的规划,分解出若干小的任务,每个任务都能在很短的时间内完成(比如 15 分钟、40 分钟,或者是一个番茄钟的时间),而且任务最好是按照一个功能的端到端的场景来划分,而不要按技术层级去划分。

为什么不推荐按技术层级划分呢?

比如在开发一个需求时,大多数开发人员都是这样的:

先修改数据库层,看看是否需要增加表或字段;

再开发数据访问层,对新增加的表或字段编写映射代码;然后编写服务层的代码,这时可能才真正接触业务逻辑;

接下来是 Controller,可能要添加新的 API 或修改已有的 API;最后,可能还会涉及到一些前端的修改。

如果按照这样的顺序去开发,每次完成的小任务都是不能提交的,因为新的场景没有开发完,而旧的场景又可能被新添加的代码破坏。

正确的做法是按端到端的方式去分解任务,一个任务完成一个简单的业务场景。

每个任务开发完毕,一个端到端的小场景就开发完了,你可以针对这个小场景写单元测试,也可以在本地环境进行自测。

然后就可以提交(commit)代码了。

比如你要开发一个简单的登录功能,那么可以这样做任务分解:

  1. 用户可以使用用户名和密码来登录网站
  2. 用户名不存在,则登录失败
  3. 密码错误,则登录失败

从一个端到端的业务场景来描述一个任务的,而不是我要修改 Service 层的那几个方法。

完成第一个任务可能要耗费一些时间,搭建一些基础代码,而后面的任务就像是对第一个任务的增强,就像是一种迭代式的演进过程。

多个小任务完成之后,你感觉差不多了,就可以更新一下远端的代码,解决一下冲突,然后 PUSH。

这时每个 commit 都覆盖了一个小的场景,是系统的一个增量,是可以交付的。

这就好像是创作一幅水粉画,首先需要画出大概的轮廓,再逐层往上叠加各种颜料,直到最后完成。

这个过程中,每一次叠加别人都可能看出整体大致的样子,给出修改意见。

如果一开始就只精雕细琢局部一小部分,别人可能根本不知道你画的是什么。


五、小步提交

上面提到的按分解的任务开发并且提交的方式,就是小步提交。

有些人理解小步提交就是指每次 commit 尽量少的代码,这其实是错误的。

如果提交的内容很少,但却破坏了编译和测试,这样的提交也是不合格的,因为它没有办法 PUSH,没法做到持续提交。

真正的小步提交是指,每个 commit 都完成了一个端到端的功能点,因此都是可以 PUSH 到代码仓库的。

如果愿意,你可以针对这个 commit 进行验证、测试甚至部署。只有这样,最终才能做到真正的持续交付。

在提交代码时,总结了一套行之有效的七步提交法,它的过程是这样的:

  1. PULL 最新代码,确保在最新的代码基础上开始开发;
  2. 本地编写代码;
  3. 本地构建:本地执行编译和单元测试等,以确保新编写的代码是可以工作的
  4. PULL 最新代码:需要先检查 CI 状态,如果是绿色则可以 PULL;
  5. 本地构建:再次执行编译和单元测试,以确保新编写的代码和最新代码可以成功集成;
  6. PUSH 代码:将本地修改 PUSH 到远端服务器;
  7. 流水线构建:触发 CI 流水线进行构建,并监控流水线状态,直到通过。

对于第二步编写的代码,既可以是与一个需求相关的若干 commit,也可以小到仅仅是一个 commit。

在这个 commit 中,既要包含功能代码,也要包含测试。

不要害怕编写测试,如果任务分解是基于业务场景的,而测试是根据分解的任务编写的,会发现这一切就会变得很容易,甚至测试驱动开发也是很自然而然的事情。


六、质量门禁

持续构建的过程不仅仅是对最新的代码进行编译和打包,还要进行一定的质量检查,也就是质量门禁。

效率最高的检查手段就是单元测试,它运行快,反馈快,可以在本地运行,能让你在第一时间知道自己的代码是否破坏了其他功能,或者是否达到了单元测试覆盖率的要求。

遗留系统很可能没法直接做单元测试。可以先对代码进行可测试化重构,再添加单元测试。

但这将是一个十分漫长的过程,因此在搭建遗留系统的持续集成流水线时,可以先跳过这一步骤,等有了单元测试之后,再加到流水线中来。除了单元测试,代码扫描也是一种检查质量的有效方式。

可以使用 SonarQube 这样的工具对代码进行静态扫描,检查代码的规范和各种潜在的错误。

一方面它能为我们敲响警钟,提升代码质量,另一方面也可以让 Code Review 更专注在代码的设计上。

一般来说,代码扫描的结果如果超过某个配置的阈值,就会阻断整个持续集成流水线。

但对于遗留系统来说,可能会扫出成千上万个代码漏洞。

这时可以选择不阻断流水线,只将扫描结果作为参考,也可以将某个结果作为基线,来验证新提交的代码是否包含新的漏洞。

如果遗留系统在 DevOps 方面还是一张白纸,建议你先引入工具和平台,做到持续构建。

在持续构建时,也可以考虑自身情况,进行一些剪裁,比如去除单元测试覆盖率检查,去除代码扫描,只做代码的编译和打包。

这样虽然看上去很单薄,但引入工具本身已经前进了一大步。

对很多遗留系统来说,能做到这一步,已经相当不容易了。

等到团队适应了新的工作方式,再逐步添加质量门禁和其他 DevOps 实践,做到持续集成。


七、持续集成

持续集成包含持续构建的所有步骤,并且在它后面还增加了部署到某个环境的流程,比如部署到测试环境,并且进行冒烟测试、接口测试等。

同时,在这个阶段,可以优化一下流水线,进一步提升效率。


八、分级构建

持续集成流水线的重要作用之一就是快速反馈。

对于编译不通过、测试失败、代码风格不符合标准等问题,都希望第一时间看到流水线失败,继而根据日志去分析失败原因,快速修复。

但遗留系统的代码库往往很庞大,仅仅编译可能就会很长时间,如果再跑测试和代码扫描,得到反馈的时间就会大大拉长。

Martin Fowler 在《持续集成》一文中提出了次级构建的概念,即对构建进行分级,把那些执行速度快、反馈质量高的步骤放到一级构建中,将执行速度慢的步骤放到次级构建环节。

比如单元测试执行的速度很快,就可以放到较早的构建环节;

而集成测试很慢,就可以放到次级构建;

对于代码扫描这种更慢的构建,甚至可以使用单独的流水线,在每天晚上执行一遍。

只有把不同构建过程按执行速度和反馈效果拆分为不同阶段,整个构建或集成过程才更像是一条真正的流水线。

在真正的工业流水线上,工人们围绕一个制品(artifact)进行组装,一个工人完成工作后,就把制品传递给下一个工人。

这就好像是一个构建阶段结束后,把制品传递给下一个构建阶段。


九、制品晋级

持续集成流水线的产物也叫做制品(artifact),有时也翻译为工件。

一次代码轮询所触发的流水线构建,只会产生一个制品。在持续集成的时候,可以把构建阶段产生的软件包作为制品,存入制品库中。

在需要部署的时候,会从制品库中抽取最新的制品。

通常来说,不同的测试环境有各自的制品库,当一个制品满足了相应环境的要求后,就可以晋级到这个环境中。

举个例子,一次提交在成功构建后产生的制品,经历了单元测试、代码扫描等质量门禁,才会进入 QA 制品库。

这时这个制品就可以部署到 QA 环境中。在 QA 环境通过 QA 测试后,方能进入 UAT 制品库,进而部署到 UAT 环境。

以此类推,如果该制品通过了所有非生产环境的验证,就可以进入 PROD 制品库,作为部署到生产环境的候选制品了。

这种制品晋级的机制,是持续集成和持续交付的基础。很多号称做到持续交付的项目,其实只不过是交付的频率高一些而已,根本没有制品晋级的机制,并不是持续交付。

遗留系统在一开始做持续构建时,可以先不做制品晋级,只把制品作为部署的候选包。

等其他 DevOps 实践慢慢丰富以后,再考虑实现制品晋级。


十、总结

如何在一个没有持续集成流水线的遗留系统中,逐步搭建基础设施,从持续构建开始,慢慢做到持续集成的初级阶段。

这其中包含很多工作习惯的改变,比如任务分解、小步提交等,一开始你可能并不适应,但要知道,只有做好任务分解,才能做到小步提交,才能做到持续提交代码并持续构建。

这些都是 DevOps 文化的一部分。一个遗留系统要想真正做好 DevOps 现代化,就必须转变思想,摒弃成见,彻底拥抱 DevOps。

七步提交法,图解,供参考。

在这里插入图片描述

在做到持续构建之后,可以逐步引入分级构建、制品晋级等实践,慢慢向持续集成演进。


刚刚发布的ThoughtWorks技术雷达 建议技术团队“暂缓或谨慎”使用反模式“CI theatre(伪CI,可以理解为不完整的持续集成)”。 “伪CI”描述的是实践持续集成(CI)过程中的一些错觉,然而这些并不是真正的CI实践。 基于持续集成,我和同事 Emily Luke做了一些研究, 我将分享伪CI是什么样的,为什么我们建议你“暂缓或谨慎使用”,以及预防伪CI的方法。持续集成我最喜欢的CI定义来自于continuousdelivery.comCI开发人员定期(至少每天)将他们所有的工作集成到主干(也称为主线或主干分支)这个引用中暗含了CI实践的两个基本原则。第一个是“把他们所有的工作集成到主干”;第二个是“至少每天”。对于CI还有一系列其他原则和实践,例如:将所有内容都检入您的代码库,构建每个提交,自动化构建,保持快速构建,并有可以自我验证的代码, 还有Martin Fowler 关于持续集成的评论中的可视化故障并立即修复故障等。我个人认为 每天至少检入代码到主干分支一次 是CI的基础。没有达到这一点就只是伪CI而不是真正意义上的CI。伪CI是什么样的?这是我们调研到的一个故事,一位经验丰富的开发人员(让我们称他为David)来自湾区的一个中型创业公司,每周有两次产品交付。 David说他的组织正在践行CI,他说:“是的,我们用Circle CI”,他描述了一个具有挑战性的场景,曾经在一个分支上工作了一段时间,大约修改了100个文件和7000行代码,然后在代码审查阶段就开始招架不住了,因为他要向他的同事解释所有的代码变更的原因。“最具挑战性的事情是你最终要将一大堆功能集中到一个提交里,因为它们都是这个组件的一部分”,他解释说:“我希望有一个更好的方式来分解这些提交,因为很难把所有事情(变更历史)记在脑子里。”如果这个情况你听起来很熟悉,那么你也在做伪CI。 如果有下面的这些场景,那么你们就是在做伪CI:当有人问起你们在实践CI吗? 如果你说我们有一个CI服务器并且我们使用X工具在我们的调查中,只有10%的参与者承认有CI服务器与CI践行不一样。 相反,90%的个人表示他们正在践行CI,无论他们是否有专门从事CI的基础知识。 所以简单的认为只要有一个CI服务器就是“在做CI”,这就清楚地表明是在做“伪CI”。使用长期开发分支,但不会定期检入master主干在David的故事中,他们并没有实践每天检入master主干,这就是“伪CI”的标志。 这是我们在调研中常看到的一种模式,其中团队在master主干上运行CI,但不频繁构建,也不是每天都在提交。 或者他们在分支上运行CI,但不会频繁的集成到master主干。 只对特性分支运行CI,其实应该称之为持续隔离(continuous isolation)才对.合并分支时感到焦虑和疲惫真正的持续集成要把代码所有者的责任意识扩展到整个团队。 这改变了团队内部人员的观点以及他们对失败构建的态度。 不再是“我的宝贵的分支”,或是“我的错误导致构建被破坏”,而是“我们的代码”和“我们的失败”。David遇到焦虑和疲惫的事实清楚地表明,他忽略了CI的一个重要的优势:持续反馈和代码集体所有权。伪CI还有更多的一些现象,虽然我们发现有一些并不那么常见,但它们仍然存在一些问题,构建的时候,仅有极少的测试覆盖允许构建长时间处于失败状态虽然David的团队引入了一个备受尊崇的CI工具和常见的流程,如特征分支和代码审查,但他们并没有实施全套CI实践,因此错过了许多好处。 我们遗憾的发现,在我们的研究的组织中90%发生了这种情况。一些组织实施伪CI中反而错失了CI的主要优势 - 快速反馈,代码集体所有权,并准备达成持续交付如何避免,预防和解决伪CI的问题?如果您确定可能正在遭受伪CI之苦,则可以通过三种方式来解决问题并进行持续改变。1. 提交更频繁回到根本,尽量频繁地提交,每天至少提交一次应该是最低目标。 如果你还没有开始做CI,这就是你可以开始的地方,即使你在做CI,依然会有改进的空间。我喜欢“频率降低难度”的说法。 往往我们在做一些事情时,任务变得越小,则其更容易被实现。 持续集成就是是一个典型例子。 我的建议是要更加频繁地检入你的代码到代码库并且将开发分支集成到主干分支,至少每天集成一次”。2. 基于主干分支开发有很多论坛在讨论基于主干还是基于开发分支进行开发,我不想讨论那些血淋淋的细节。 然而,在我们的调研中,当我们与一些曾经在实践CI过程中感到痛苦的人交谈时,没有引入主干开发的团队对此有更深刻的感受。 DevOps现状调查报告-2016 还发现,基于主干开发和持续集成是达成持续交付的关键因素,同时也能建设更高效能的团队。基于主干的开发肯定会有一定的挑战,但它可以把问题提前暴露出来,而不是要等到代码合并、代码审查甚至到延迟发布的时候。如果你想达到更好效果的CI和CD,我建议在主干上做开发,或者如果你更愿意使用短周期的临时分支(合并到主干之前不到一天)进行开发,那么最好同时不要超过三个以上的活跃分支。3. 自动化自动化是做好持续集成的基石,所以如果你还没有自动化一切,现在是开始的好时机。 如果你认为已经实现了所有自动化的功能,那么每次有人手动地做任何事情超过一次,便要问自己“这个为什么不能自动化”? 我已经观察到自动化不仅可以帮助您在CI中变得更好,还可以帮助您开始持续交付。总结现在你知道什么是伪CI了,如果你的团队正在这么实践伪CI,那么你可以阻止这种情况继续发生了。 如果您仍然感到困惑,我建议你在Martin Fowler的博客“CI Certification test”做一个测试, 以确认你的组织是否正在做可靠的CI。 如果你通过了CI测试,那太好了,现在是考虑您是否准备好实施持续交付的时候了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿昌喜欢吃黄桃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值