分类: 新兴技术

事件:ThoughtWorks在2016年11月发布的技术雷达中将“Jenkins as a deployment pipeline”列为了“暂缓”。

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

(图片来自:http://t.cn/RX7EvfT)

不拼凑,纯pipeline?

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

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

用惯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的task太难用了,每个task只能执行一条命令,导致每个job都有十几个task。

(琐碎的tasks,使用前)

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

(使用后)

不可矫枉过正

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

建议三:你使用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

严格来说,不管是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,单独部署环境变量变更)

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

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

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

写在最后

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

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

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

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

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


更多精彩洞见,请关注微信公众号:思特沃克

Share

发表评论

评论