摘要
Jenkins作为最为流行的持续集成工具,在结合使用容器技术, Kubernetes 集群的基础上, 该如何发挥出新的能力, 在应用微服务化的基础上, 提供更好的CI方式, 值得我们每一个开发人员去持续不断的摸索.
本次分享主要介绍我司如何使用Jenkins Pipeline, Container 和 Kubernetes Deployment的能力, 通过增加使用文本模版引擎, 扩展Kubernetes Config能力, 完成公司产品开发CI工作流的建立.
Jenkins 和 Kubernetes
Jenkins作为最流行的持续集成工具,有着丰富的用户群、强大的扩展能力、丰富的插件,是开发人员最为常见的CI工具。在Jenkins 加强其Pipeline功能后,更是可以通过丰富的step库,实现各种复杂的流程。同时随着Docker的流行,Jenkins内也增加了对Docker的支持,可以实现容器内流程的执行。
而Kubernetes随着版本迭代的速度越来越快,在容器圈内的热度也越来越高,同时每次版本发布,所新增的功能也不断增加。做为当前主流的容器管理平台,其强大的能力无需在此多做介绍。
应用容器化和应用微服务化设计思考
容器化不意味着微服务化,传统单体应用也可以容器化,但是很难享受到容器化后带来的好处。微服务化也不是一定要容器化,应用拆解为微服务后,一样可以不利用容器而是通过传统的运维来完成系统构建和部署。当微服务化和容器化相结合之后,就能充分利用各方优势,带来了弹性伸缩,简化部署,易于扩展,技术兼容等优点。
我们在针对应用进行微服务化拆分的过程中,主要先考虑到的是功能点、控制对象、开发组的人员配置安排,产品路线图规划等。
例如,针对现有开发组人员人数、分配和各自的技能熟练程度,就可以考虑到服务模块数量的控制,安排好服务模块开发小组;针对功能点和中远期产品规划,就可以将特定功能归纳到一个服务模块中,并在版本开发迭代的过程中,通过扩展这个服务模块的能力,来完成产品功能的开发,或者暂时将部分功能整合在一个模块中,随着功能增加或迭代开发,再进行进一步的模块拆分或拆解。
对于模块开发的要求,由于使用了容器技术,对于开发语言或特定框架的选型,可以交给具体的模块开发人员。在团队内,我们不做强制要求,但是做建议要求,避免出现过多的技术栈,导致后期的维护困难。
在我们团队内,就只集中在两种后端开发语言的使用,相应的框架或主要的开发库,也都有相应而且明确的选择。对于模块的API接口,使用REST,并且至少按照成熟度模型LEVEL2提供API。
容器环境下的编译和单元测试
我们整个CI工作流的驱动,都是由Jenkins完成,并且使用了Jenkins Pipeline。第一,Pipeline可以更好的组合job内的stage,重复利用模块间相同的部分,并且随着开发复杂度的增加来逐步增加扩展stage,实现更多所需的功能;
第二,将Pipeline Groovy脚本来源设置为源代码内,可以根据源代码功能点来控制流程,同时也完成了对脚本的版本管理。
由于有容器这么个工具,我们各个模块的编译环境,单元测试环境,也都放到了容器中。各个模块均可以安装自身模块的运行特性或环境要求,准备自身的编译环境、单元测试环境、运行环境,因此,代码库内会分别留存相应的Dockerfile,通过不同的Dockerfile完成不同环境镜像的准备。
同时,Jenkins现在也可以通过Docker Pipeline插件,支持在容器内运行step,因此我们利用其功能完成的实际的编译和测试流程是这样的:
- 使用编译环境的Dockerfile构建编译环境镜像。
- 使用编译环境镜像启动容器并在容器内完成编译,完成编译的中间产物也暂存在Workspace中。
- 使用测试环境的Dockerfile构件单元测试环境镜像。
- 使用单元测试环境镜像启动容器并在容器内运行单元测试,单元测试脚本来源于代码库,同时也使用到编译时生成的中间产物。
- 使用发布Dockerfile构建实际发布镜像并上传镜像库。
其中由于编译环境和单元测试环境不是经常变更,也可以抽出编译环境镜像准备和单元测试环境镜像准备两个步骤放到独立的CI job中去,需要时手工触发即可。
服务部署和升级
对于CI流程,在完成编译和打包后,需要做的就是服务启动和测试了。我们利用的是Kubernetes Deployment和service,在每次CI流程完成编译和打包后,通过拿到Build号,作为镜像的tag,完成镜像的上传归档;同时利用tag,修改Kubernetes中已经创建的Deployment,利用Deployment的Rolling Update,完成升级。
对Kubernetes服务模版和服务配置的扩展
我们在实际使用Kubernetes Deployment 升级的方式进行服务部署的过程中,发现其中还是存在很多不方便的地方。
例如:Kubernetes内的同名问题,Kubernetes Deployment升级时的镜像tag变更问题,等等各处需要随着CI流程可能存在变更的地方。
例如在有相同名字的Deployment存在的情况下,后来的Deployment会无法创建,这导致如果想以启动新的Deployment的方式来测试某个版本,需要修改名称,对于与Deployment相关的service也一样,在启动新的命名后的Deployment,也需要启动与其对应的service用于暴露服务。
对于Deployment升级所需的镜像tag修改,需要每次随着CI生成了新的镜像tag而做变更,因而每次需要修改相应yaml文件内的镜像tag,修改为实际CI流程中生成的值,然后再使用升级功能完成服务升级。
针对这些问题,我们使用了一套文本模板引擎,部署或升级用的yaml文件本身写成为模板,可能有变化或者需要根据CI流程变化的位置,使用模板标识占位,而具体的模板数据内容,则或者通过Jenkins的CI流程获取,或者使用特定的配置文件读取,或者从具体的输入参数来获取;
同时,模板数据内容,也会在实际部署时,做为Kubernetes的Configmap设置到系统中,因此数据内容也可以通过Kubernetes使用Configmap的方式来用到环境变量或启动命令中。通过文本模板引擎,将模板和数据合并后,生成的yaml文件,再作为后续Kubernetes操作所使用的内容。
通过利用这种方式,我们把需要部署的内容分离成了模板和配置;模板一般在服务架构,使用的镜像名,启动方式或配置参数没有大的变化的情况下保持不变,而通过不同配置的灵活使用,完成服务升级或拉起新部署,完成不同数据存储使用的指向,完成对各模块内部配置的修改。
通过利用这种方式,我们的可修改的内容,从Configmap本身只能覆盖到的环境变量或启动命令这块,扩展到了启动名称,Label,镜像等yaml文件内的各个可填值处,以此来解决同名,镜像修改,Label增加或变更等各种使用kubernetes时碰到的问题。
自动化测试
在通过Jenkins拉动完成编译打包和服务升级部署后,就可以拉动自动化测试了。测试框架我们选择了使用Robotframework。测试脚本通过Kubernetes service获取到服务的具体暴露端口,然后再根据测试脚本依次执行针对API的测试。
测试脚本的来源,部分是从各模块代码库中,由各模块开发人员提交的针对自身模块的API测试,部分是由测试人员完成撰写提交的针对跨模块的测试。针对自动化测试这块,我们的完成度并不是很高,仅仅是搭建起了基本的运行框架,能够与整个流程对接上。
版本发布
由于开发的产品本身就是由若干镜像构成,因此产品发布,可以归结为镜像的发布。在测试通过后,可以简单的利用镜像复制能力,将测试通过的相关镜像的版本,通过镜像库间的复制,由开发测试所用的内部镜像库,复制到外部发布镜像库,就可以完成版本发布,同时可以通过复制时的tag控制,发布为指定的版本号。
总结
如上介绍,说明了我们在自身开发过程中建立CI流程的做法。对于整个流程的建立,我们并没有太多需要特殊化处理的地方;对于各项工具的使用,也没有太多突出之处;我们仅仅根据自身需求,建立了和开发过程适配的CI流。在此介绍我们的CI流程的建立,也是希望抛砖引玉,能从更多处获得交流和向大家学习的机会。