say no to Jenkins as a deployment pipeline

事件:全球知名IT咨询公司ThoughtWorks于2016年11月发布了其最新一期技术雷达,其中将“Jenkins as a deployment pipeline”列为“暂缓”。

Jenkins以持续集成闻名,进入持续交付时代后,常被人们用来尝试搭建deployment pipeline,我本人以前也乐此不疲。遗憾的是Jenkins的设计是以单个job为核心,deployment pipeline的实现需要通过官方或社区插件来支持,看上去都能实现,但实际使用中总让我觉得差了点什么。最近的一则广告可以完美表达这种体验:

拼凑

不拼凑,纯pipeline?

ThoughtWorks坦言作此评价是相当冒险的,因为其在此领域有一款竞争产品:GoCD。GoCD曾是一款商业产品,现已追随开源大潮,身边一些朋友尝试之后褒贬不一,有人反馈它忠实还原了《持续交付》中提到的pipeline,也有人反馈较难上手。我想趁技术雷达这个热点分享一些GoCD的使用经验,希望对正在尝试GoCD的同学有所帮助。

利益相关:本人目前就职于ThoughtWorks,但在此之前就已经是GoCD的用户

建议一 :如果你不需要deployment pipeline,不要使用GoCD

no gocd if you don't need pipeline


用惯Jenkins job做持续集成的同学,往往一上来就被GoCD的配置界面搞得晕头转向:

我只是想运行一下mvn clean package怎么有这么多东西要设置,stage,job,task都是什么鬼?

信息量好大

GoCD在设计之初就以deployment pipeline作为“一等公民”,实现复杂交付流程是其强项,但如果你需要的只是持续集成,就有点杀鸡用牛刀了。因此,重要的话说三遍:

如果你不需要deployment pipeline,不要使用GoCD。

如果你不需要deployment pipeline,不要使用GoCD。

如果你不需要deployment pipeline,不要使用GoCD。

如果看到这里,你还没有关闭页面,那让我们来看一下GoCD的pipeline元素吧。

P代表pipeline,S代表stage,J代表job,T代表task

pipeline可由若干个stage组成,stage之间可以设置依赖关系,默认上游stage失败的时候不会触发下游stage。stage可由多个job组成,但多个job一般用在并行任务的用例中(例如并行构建多个模块),它们之间是没有依赖关系的,所以如果你希望某个stage执行一系列有依赖关系动作,应该使用单个job并为其设置多个task,而不是多个job。这里比较容易产生误会的是job,因为它和Jenkins job同名。一个典型的pipeline可能会按如下设计:

每个stage代表一个阶段,build&test负责构建和单元测试,Int_Deploy负责自动化端到端测试,UAT_Deploy负责手工测试,Prd_Deploy则负责部署生产环境,每个stage最简可由一个job组成,job中的task依次完成自动化任务

pipeline, stage, job, task使得GoCD可以组合串行、并行执行,实现复杂、精巧的工作流。但就像硬币的另一面,这些概念也提高了入门门槛,再加上原有的UI交互设计得比较繁琐,往往需要来回地切换编辑页面才能完成整个pipeline的设置,也难怪用户抱怨了。

改善

不过好消息是,“Have a better getting-started experience”已列入GoCD roadmap,一个全新的”quick edit”功能也已经发布(需要16.9.0以上)。

全新的”quick edit”功能,让你可以在单个页面即完成pipeline的配置,操作更简便

而对于熟手来说,本文后面提到的“实现pipeline as code”更合胃口 ​

建议二:必备插件——script-executor-task

gocd has plugins

曾经有朋友向我吐槽GoCD的task太难用了,每个task只能执行一条命令导致每个job都有十几个task。

琐碎的tasks,使用前

其实,你需要的是一款叫做“script-executor-task”的GoCD插件。是的,你没有看错,GoCD也是有插件的!有了这款插件后,你就可以像shell脚本一样编排指令从而愉快地合并臃肿的task了

使用后

不可矫枉过正

但是,值得一提的是,这个插件的初衷是简化task中命令的书写和排序,而不提倡滥用它编排大量琐碎的指令。不管是用Jenkins还是GoCD,最佳实践是将指令放到脚本文件中并纳入代码版本仓库(SCM)。可以签出的脚本方便团队所有人查看,更改也有迹可循,便于协作;另一方面脚本与工具的耦合也最小(往往就是一行命令),我们将在“实现Pipeline as Code”一节中继续讨论这个话题。

建议三: 你使用Artifact Repository了吗?

artifact repository

pipeline的各个环节本质上是在验证构建出的artifact(以下翻译为二进制包)是否符合质量标准,这就要求pipeline能够正确识别和传递artifact。只生成一次二进制包是pipeline设计中的一条重要原则,下游步骤应该重用上游步骤生成的二进制包。 相比每次从源代码构建二进制包,这节约了宝贵的反馈时间,更重要的是它实现了“你所测试的二进制包就是将要发布的二进制包”的配置管理需求。

GoCD对此提供内建支持:publish artifacts和fetch artifact task(相比Jenkins需要copy artifact plugin并且需要细心选择上游job,详见基于Jenkins实现的部署流水线中共享二进制包)。

上游构建stage将artifact到GoCD自带的artefact repository

下游部署stage从构建stage抓取artifact

一个容易出现误解的地方是,在没有使用publish/fetch artifact功能的情况下,试图在同一个pipeline的stage间共享artifacts,这很可能造成artifact传递错误,严重的时候可能造成向生产环境发布未经测试的二进制包。

如上例中,分别在commit-stage和acceptance-stage中取消publish/fetch build/version,只要这两个stage都分配在同一个go-agent上执行,也不会报错。假设现在pipeline build number为892,运行pipeline build number为890的acceptance-stage也会取得一份build/version文件,但这份文件的来源是该go-agent上最近一次commit-stage运行后生成的(很可能是由pipeline build number892),未必是pipeline build number为890的commit-stage生成的,这样就出现了artifact版本错位。

隐蔽的artifact版本错误

重视artifact repository并且正确实现artifact共享是一条合格的deployment pipeline的重要标志,只有这样artifact的来源才能够回溯,才能检查它是否符合了发布的标准,才有信心真正实现一键发布到生产环境。

应用publish/fetch artifact是生成正确的Value Stream Map的前提,通过Value Stream Map可以直观地观测artifact经历的质量检查步骤和结果,作为是否发布此artifact的前置条件。

专用artifact repository

最后多嘴一句,虽然GoCD提供了内置的artifact repository,但我强烈推荐使用专用的artifact repository产品(例如java常见的sonatype nexus或者私有Docker Registry)。这些产品往往有更好的GUI和周边工具支持,可以帮助你更好地管理artifacts。在这种方案中,我建议使用GoCD的artifact repository来publish/fetch artifact的唯一标识符(通常以文件形式),在各pipeline及其stage之间共享这个唯一标识符,而artifact本身的publish/fetch则交给专用的artifact repository,并通过唯一标识符来识别。

专用artifact repository方案

建议四: 实现Pipeline as Code

pipeline as code

严格来说,不管是GoCD还是Jenkins,早就可以通过编辑config文件或使用API来实现pipeline as code了,但它们都不易使用。前者因为config文件掌管着全局配置,粒度太粗,实际上只可能由专人维护,成为瓶颈。后者则往往需要开发客户端程序。随着infrastructure as code概念的流行,开发团队希望更灵活且更可靠地管理自己的pipeline(如果你用过Travis CI,会对这种方式很熟悉)。从16.7开始,GoCD提供了更友好的pipeline as code支持,可以将通过yaml或json定义pipeline,并将配置文件放到SCM(git或其他)中,GoCD会自动获取定义文件并生成pipeline。

GoCD可以兼容手工配置和文件配置,所以你可以在部分pipeline上尝试这种技术

configuration deployment

那么pipeline定义文件是放在应用源代码仓库还是单独放在独立代码仓库呢?我的建议是:都可以。但是如果deployment-pipeline含有部署环节,且部署不同环境需要不同的环境变量时,我建议把流水线本身拆开:

  1. 构建环节作为一条单独的pipeline,这条pipeline由自动触发的stage组成,目标是构建artifact,如果有条件的话还进行一些端到到的自动化验证。这条pipeline的定义文件可以和项目源码仓库放在一起,因为pipeline的改变常常也影响了artifact本身的构建,它们的变化节奏应该是一致的。

  2. 部署环节作为一条(或多条,视环境数量决定)单独的pipeline,这条pipeline由fetch artifact开头,其定义文件可以和部署脚本及环境变量放在一起,它们的变化节奏应该是一致的。与负责构建的pipeline分开的原因是,当你想为QA环境部署一次配置变更时(如果你使用了特性开关,这种情况很常见),往往并不希望等待pipeline重新再构建一次artifact。

部署pipeline与构建pipeline分离,可以实现 configuration deployment,单独部署环境变量变更

期待:缺乏统计报表类功能/插件

metrics

deployment-pipeline不是设计出来的,而是演化来的

​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ——出自模板:XXX不是设计出来的,而是演化来的

deployment-pipeline的初衷是希望能够通过自动化和可视化来消除交付活动中的瓶颈,但如果不精心维护,pipeline自身可能也会出现瓶颈。例如随着自动化测试用例逐渐增多,反馈周期也会随之变长,这时需要重构pipeline以便消除瓶颈,但如何重构,重构的效果是需要用数据来度量的。Jenkins有一些插件可以统计job的平均执行时间,job失败后的平均恢复时间等指标,可以用来指导团队重构pipeline。遗憾的是,GoCD对此没有内建的功能支持,而plugin还不够丰富,暂时存在空白。让我们期待官方和社区在这方面有所作为吧 :) ​

写在最后


感谢你耐心看完本文,最后把重要的话再说三遍:

如果你不需要deployment pipeline,不要使用gocd。

如果你不需要deployment pipeline,不要使用gocd。

如果你不需要deployment pipeline,不要使用gocd。

但是这年头,应用软件交付怎么会不需要deployment pipeline呢?



comments powered by Disqus
© Copyright2014-2021