/**
* 原文出处:https://www.credera.com/blog/technology-insights/open-source-technology-insights/microservice-delivery-using-jenkins-and-docker/
* @author Tonny M<tonnyazusa@gmail.com> 2016-05-02
*/
介绍
你是否对项目日常的功能快速更新或快速修复感到满意?
什么时候才能为你的客户开放新开发的功能?
有多少人没有考虑清楚新功能怎么从开发环境转移到生产服务器的问题?
在容器化和微服务越来越火的现在,这些主题已经有大量有价值的相关参考信息。
你可能已经在研究相关的主题和工具能否对你应用的交付更新流程有改进作用。
我最近结束了一个客户端项目,在这个项目里我担任了团队的DevOps实施者的角色,使用Docker容器搭建微服务应用,实现了发布部署流程的流水化作业。
我喜欢这次项目的挑战,在博客上记下了这次经验。
发布部署流水线
发布部署流水线将传统的应用构建流程分成了几个阶段。
发布流水线的初期阶段会进行繁琐的编译工作以及开发一个工具进行项目版本部署。
这个工具通过自动化测试来验证功能、罕见问题和bug修复效果。
只要通过自动化测试,这个工具就可以部署起来开始用于一些重复性的质量保证和用户测试工作。
只要这个构建工具通过验收,你就能部署这个工具到生产环境上去。
我们流程中使用了Jenkins,每一次GitHub代码库的提交都会触发Jenkins构建,自动化持续地执行完成几个环节,一直到生产环境的确认部署页面。
我们的NodeJS应用使用了MongoDB,在单元测试阶段,首先会通过Docker构建好这个项目的镜像,然后执行Mocha集成测试,通过自动化配置部署工具Ansible playbooks将Docker镜像部署到开发环境。
一旦部署到开发环境中,Jenkins的工作流插件会提供给用户一个是否部署到生产环境中的确认按钮。
如果任何一个环节失败,JIRA和Slack会及时通知研发团队,然后研发团队以高优先级对这个问题进行处理。
图1 - 部署流水线
图2 - 部署流水线工具(用合适的工具来替换每个阶段)
部署策略
对于一个微服务的部署流水线需求,是需要一定时间进行每个环节的规划安排的。
必须考虑以下问题:
- 你的合作方需要你完成开发、完成后续阶段、部署生产环境的关键日期是什么时候?
- 到项目结束,你会开发出多少个后台服务?
- 你的应用程序和后台服务有编写过测试用例吗?
- 什么框架适合你项目里各种各样的应用同时使用、执行测试?
每一个新的阶段,对内会影响研发如何进行内部工作,对外则会影响项目的给第三方的交付。
这是个不小的事情,特别是从微服务大大增加参与开发的团队数量之后。
我们服务着五个不同的团队,帮助他们适应开发流水线的变化。
在研发流程中更新开发流水线,可以避免一些线上严重问题和阻碍,所以我们必须规划好时间帮助开发人员去适应这些流水线的变化。
例如,我们知道使用Docker从构建到运行的过程中,操作系统都必须是一致的。
我们的库不兼容就是因为Jenkins的基础容器的用了Debian,而应用程序容器则使用了Ubuntu。
不兼容的库版本造成了当应用程序容器开始执行时会出现一些问题,这个问题导致我们花了一些时间才解决并找到适合研发和我们的解决方案。
单元测试和构建
在feature分支模型里,这个流程从研发提交代码开始。
一旦研发完成的一个功能,他们提交代码合并到主分支触发Jenkins构建。
因为我们的客户端的所有项目都放在GitHub上,让Jenkins克隆最新的分支,并开始执行命令去安装应用的依赖包。
安装完成后,一个本地的MongoDB服务器会启动单元测试。
如果依赖包安装和单元测试都成功了,一个Bash脚本会使用Docker Remote API来构建Docker镜像。
最终,多个同时构建Node.js和MongoDB进程可能会引起冲突,这个问题我们可以通过在Docker容器子进程中执行构建来解决。
集成测试和质量保证
集成测试是在部署之前使用本地Docker镜像搭建的环境进行的。
这个脚本会从Quay.io平台(一个私有的Docker镜像库)中的开发环境上拉取一个最新的Docker发布镜像,然后启动容器以及在运行的容器中执行Mocha(一个JavaScript的测试框架)测试。
如果所有自动化测试都通过,这个Docker镜像就会根据打包的号码被打上一个标签,然后通过一个使用了Docker远程API的Bash脚本push这个镜像到私有镜像库去。
刚开始的初始测试方案,我们在几秒钟就完成了,但如果多个测试同时执行的时间过多时,将有依赖的测试单元进行分组分类是很有必要的。
例如,如果这些测试串行执行用了一小时,你可以考虑并行执行测试,启动在本地构建好的多个镜像组,每个镜像组都可以有单独的依赖包,在每个组的容器内并行运行这组测试用例。
因为项目的限制,我们没有最后实现阶段化的质量分析流程。
由于我们的应用都是基于Node.js的,我们会使用JSLint去检查代码,将所有的执行结果放到报告中。
如果代码质量得分低于规定的阈值,这次构建就会失败。
这里的质量分析的关键是找出正确的阈值,所以虽然执行过程中可能会存在不稳定,但这份报告还是很有价值的。
版本管理
所有开发团队都是使用语义化版本控制来进行微服务开发。
对于每次提交代码,研发都有责任要指定一个合适的版本号; 但是,我们测试过Gulp针对静态资源的自动版本号管理功能,它可以在项目的静态资源文件准备好更新的同时,自动更新package.json文件。
研发负责手动更新大版本号,然后每次代码提交自动更新小版本号。
不幸的是这个实验失败了,因为Gulp在提交代码以后会不断触发越来越多的冗余构建。
随着构建越来越多,我们可以对触发加些条件,比如基于特定的代码提交者才能触发构建,我们可以创建自己的一个单独的GitHub WebHook钩子服务来触发构建,在这个钩子服务里加一些规则比如只有Github Robot用户(Github机器人)才可以触发,或者只有项目开始时才触发构建,等等。
部署管理
一旦构建出来的镜像部署到Quay.io,一个部署Bash脚本会从独立的库里拉取最新的一份Ansible playbook脚本(一个基于OpenSSH的IT自动化工具),并执行相应的playbook脚本。
开发者将参考我创建的应用playbook脚本和下面的步骤编写自己的应用playbook脚本,一些步骤纪录在了confluence上。
下面的脚本例子有几个关键步骤是和部署Docker容器有关的:
1、在主机的配置路径上设置好管理者和相关的权限。
2、复制必要的密钥。
3、创建用于启动和重启主机上服务的systemd unit文件。
4、从Quay.io拉取Docker镜像。
5、启动容器。
图3 - my-docker-playbook.yml
一旦应用成功部署到开发环境,Jenkins工作流插件就会显示确认按钮,等待你确认是否需要立即部署到生产环境。
用户可以点击按钮确认已准备好进行生产环境部署,也可以马上中止这个job,让最新的Docker镜像仅部署在开发环境中。
将来,他们可以再增加一个步骤,在只有一部分功能需要更新时,我们只更新这部分独立的功能、服务,而不更新其他正在运行的功能、服务。
将来一个分阶段的测试环境也是在计划开发中; 但是,这个分阶段的测试流程会花费更多的资源在创建于生产环境一模一样的测试环境上。
部署开发环境和部署生产环境之间的一个区别是,生产环境中的密码和机密时需要严格管理的。
生产环境的机密(这里指密码、敏感数据库等)是需要放在一个代码仓库忽略的空间里,并且需要持续备份。
因此,我们在发布的容器里设置了一个独立的硬盘卷。
这个硬盘卷允许容器和playbook脚本对这些机密内容进行读访问。
构建失败
如果该应用程序在这个流水线上不同的阶段失败了,会触发两件事情。
首先,通过JIRA REST API会触发一个JIRA Ticket(问题跟踪)的创建。
该JIRA ticket被标记为一个缺陷,并将这次构建的URL发给对应的代码提交者。
如果提交者的电子邮件地址没有注册JIRA,Jira会分配这个问题给一个默认的用户。
然后,当这个脚本在Jenkins中执行完成后,这些触发产生的问题都会被赋予高优先级以最快的速度进行处理解决。
除了ticket,在Jenkins中还可以通过Slack插件来实现当构建失败或成功时自动发送消息给指定的开发者。
结论
就像我前面说的,这个流水线是需要早期的设计规划和开发部署流水线工具给微服务项目才能实现的。
从DevOps的角度,一个微服务架构会增加团队的数量和项目复杂程度。
我们的DevOps工作简单是因为所有的应用程序都部署在Docker容器里。
考虑在你自己的项目中使用Docker吧。
也许可以减少产品推出市场前的研发时间,减少成本投入和部署所花费的时间、精力。