源宝导读:在敏捷迭代的过程中需要能够快速的把开发的代码集成打包部署到各个环节对应的环境中。为了高效稳定的完成这个工作,我们引入了DevOps实践理论,并形成了配套的CI/CD工具。本文将介绍云创的CI/CD工具如何演进的过程。
一 、传统构建
在最开始阶段,诉求很简单。将gitlab的代码自动部署到测试,预发布环境。当时市面上这类工具也不多,基本扫一遍大部分都是用jenkins。自然而然也就选用了jenkins,毕竟有问题google,baidu起来也容易找到答案。
由于这个时期面对的是php和开发同学在本地编译好的js。对于Jenkins的应用就十分简单,在界面给配置仓库和分支再用ssh工具给rsync到对应的服务器。后续再就是再加装一些插件做下sonar扫描,做一些参数配置构建。基本上都还是一些十分基础的使用也就不在占用过多篇幅了。
二 、流水线
随着云创开始推动DevOps,原有的构建方式想覆盖代码到制品交付的全过程有比较大的难度。为了DevOps的推进工作,必须引入具备pipeline能力的工具。
2.1、Jenkins2-流水线执行器
DevOps越来越火,市面上也出现很多CI/CD工具,同时jenkins也推出了2.0版本。我们对一些工具也做了一些尝试(gitlab-ci,drone),有很多工具的理念确实很好,最终我们还是选择了jenkins2.0来实现我们的pipeline。主要如下几个原因:
定义方式:Jenkins支持脚本式的定义流水线,其他的都是声明式。这样看Jenkins的灵活度更高
生态:Jenkins社区有几千个插件提供了各种各样的能力,其他工具都需要自己想办法实现这些能力。
门槛:Jenkins文档丰富并且我们也一直在使用,相对来说门槛是最低的。
成熟度:Jenkins已经发展了十多年完全具备生产可用的条件,其他产品都还没到生产就绪状态。
2.2、基于Kubernetes的动态构建环境
随着团队对于质量的要求逐渐变高(单测,sonar,api测试,等等),开发语言也不再只是解释型语言,前端专业线也有源码直接在线完成编译打包的诉求等等一系列原因。构建所需要的环境开始成为我们需要考虑的问题了,基于过往认知我们列出了下列方案:
在jenkins的master节点安装所有需要的工具以及语言环境 。
常备几台ecs作为slave节点,每台负责一到两种语言的构建任务。
利用docker插件在启动构建任务时拉取准备好的环境镜像运行容器进行构建。
这几个方案都面临环境的管理的问题,都需要在镜像或者机器里面准备好 sonar,各种语言的单元测试环境,api测试工具这些,每一次步骤的改变或者工具的版本升级都可能需要去折腾一遍镜像或者机器。这会给后期带来高昂的维护成本,所以必须找到更好的方案。在Jenkins插件仓库中发现了解题思路kubernetes-plugin。kubernetes-plugin可以在构建开始时调用K8S的接口创建一个POD作为slave来进行构建任务,POD是一组容器的集合这一组容器能共享网络和文件。利用kubernetes-plugin有了下图的设计:
在定义pipeline的时候按照需求把需要的镜像组合一下。这样只需要维护一些环境很单一的镜像即可,新增步骤或者改变工具或环境版本只是加多一个镜像或者改一个镜像tag。
在每次任务启动,都会在集群节点启动一个 Slave 的 Pod 容器组进行任务构建,构建完成后容器自动销毁。
2.3、利用shared library实现参数化定义流水线
基于上面的动态环境的方案,我们尝试开始定义了几条流水线我们意识到了问题,需要写大量的groovy脚本并且业务团队很难自己写好这些脚本,这样流水线的落地会变的很艰难。需要寻找一个好的办法来解决这些问题,shared library可以很好的解决这个问题。
所有的job的定义都是一样的,都是加载shared library并运行,shared library根据参数给出的信息来运行流水线。
这样就不需要每个项目维护一个脚本而是维护一些简单的参数即可,所有项目共用shared library如果有些调整也能够统一调整。
采用了这种模式,云擎实现流水线管理只管理参数,不需要跟jenkins深度耦合。
基于jenkins2.0和Kubernetes我们实现了可以动态生成构建环境,能快速接入新任务并且能够统一调整的pipeline构建系统。
三 、低成本高并发的弹性构建
Pipeline全面推广并且所有业务团队接入云擎使用特性分支的开发方式,每次特性合并触发构建会同时触发多个仓库进行构建,这样一下给构建用的集群带来了极大的压力发生多次崩溃。只能先控制并发构建的数量,然后立马开始进行优化。
3.1、Kubernetes节点自动伸缩
第一个尝试的方案是对构建集群配置节点自动伸缩。
节点自动伸缩组件是基于 kubernetes 资源调度的分配情况进行伸缩判断的,节点中资源的分配是通过资源请求(Request)进行计算的。当 Pod 由于资源请求(Request)无法满足并进入等待(Pending)状态时,节点自动伸缩组件会根据配置的弹性伸缩组配置信息中的资源规格以及约束配置,计算所需的节点数目,如果可以满足伸缩条件,则会触发伸缩组的节点加入。当一个节点在弹性伸缩组中且节点上 Pod 的资源请求低于阈值时,节点自动伸缩组件会将节点进行缩容。因此只需要资源请求(Request)的正确、合理设置,开启自动伸缩功能就具备了节点自动伸缩的能力。
下图只是设置集群缩容的阈值:
设置好节点自动伸缩,问题并没有很好的解决,因为新的节点就绪需要时间比较长(分钟级别接近十分钟),当新节点准备就绪的时候已经有一部分构建完成了,后续的构建任务可以直接在原有的节点执行。实际上结果就是节点扩充出来了却不在需要这些节点了。
3.2、Serverless
流水线构建属于高度动态的行为,为了动态的构建操作而维护一个固定的计算资源池对成本是不利的,但是没有固定的计算资源又会导致响应延迟。有没有好的办法能兼顾呢?serverless-kubernetes集群解君愁。
在介绍serverless-kubernetes集群之前,有必要先了解一个项目Virtual Kubelet。
Virtual Kubelet的作用很简单,就是将各大公有云厂商提供的容器服务与K8S的apiserver打通,实现通过K8S的api编排云厂商的无服务器容器服务(如:AWS的Fargate,Azure的ACI,阿里的ECI等)。原理上就是向K8S的apiserver注册一个伪造的kubelet(相当于加入一个节点)接收apiserver调度过来的pod,只不过真实的kubelet接收到负载之后是在自身管理的node上进行启动pod等操作,Virtual Kubelet接收到负载后调用注册的api。下图是Virtual Kubelet官网的架构描述:
阿里云提供两种形态的Virtual Kubelet应用,一种是对真实节点的集群加入一个Virtual Kubelet进行扩展,一种是完全无真实节点node在托管的master节点下面挂载Virtual Kubelet实现serverless-kubernetes集群。
Serverless 集群中只有 pod 运行时才会收费精确到秒,这意味着我们不需要准备固定的计算资源等待构建任务。同时又提供了可以快速启动容器的能力,这就解决了响应延迟的问题。
Serverless也有一个限制导致我们并不能直接使用,为了安全ECI是不允许运行的容器挂载宿主机的docker.sock。这样之前将宿主机的docker.sock挂载到容器内再使用docker build命令构建docker镜像的方式就行不通了。这一点导致利用Serverless进行构建的想法一直没有落地,直到我们找到了google开源的镜像构建工具kaniko,kaniko的构建方式和工作原理与docker build十分类似,只不过不需要docker.sock和root权限,并且支持更多的参数使用起来更方便。搞定docker镜像构建的问题后,立马全面应用了serverless集群进行构建。
为了使用Kaniko也费了一番功夫,这里就不详细描述踩过的坑了,有需要的可以找我们直接要可用的方法即可。
3.3 、slave启动速度优化
Serverless应用之后,虽然成本问题和并发问题都很好的解决了。不过新的问题出现了,单次构建的耗时比较长,经过分析发现主要耗时在启动slave的时候拉取镜像比较耗时(平均约三分钟),因为ECI是没有持久化存储。在钉钉上跟ECI的产品经理提出了我们的诉求,希望ECI能够提供把镜像持久化的功能避免每次都要花费大量时间拉取镜像。
给ECI团队提出了诉求之后等待了三个月,他们终于给出了一个解决方案—镜像缓存(imc)同步在K8S这边提供了对应的CRD。镜像缓存的工作原理就是在ECI实例启动时挂载一个包含用户定义的镜像的磁盘,ECI实例就可以在本地直接使用镜像启动容器。
定义缓存镜像例子:
apiVersion: eci.alibabacloud.com/v1
kind: ImageCachemetadata
name: imagecache-jenkinsspec
images: - registry-vpc.cn-hangzhou.aliyuncs.com/example-jnlp:kaniko - ...... - ......
imageCacheSize: 20
将该功能实装之后,之前需要耗时约三分钟拉取镜像的动作现在变成命中缓存启动容器,整个slave启动时间从之前的三分钟以上优化到半分钟到一分钟完成。
在尝试Serverless的方案时还遇到了一些阻碍性的问题,我们将问题反馈到阿里产品团队后都及时得到了解决,目前来说只需要使用最新版本的Serverless集群按照我们的方案已经不存在这些问题了,所以这里也就不再浪费篇幅了。
四、未来展望
完成这个优化之后,整套构建方案中需要解决的大问题都被解决掉了,实现了一个生产可用低成本高并发能力的构建系统。之后主要是增强功能提高系统可用性两个方向进行优化:
云擎整合多个jenkins-master节点统一管理,构建任务按照标签匹配master运行并保持至少有2台master可以匹配。
云擎具备自动创建master以及构建用serverless-k8s集群的能力。
优化shared library,可以更加灵活的定义每一个流水线步骤。
------ END ------
作者简介
尹同学: 运维负责人,目前负责明源云SaaS产品的后台运维工作。
也许您还想看
云客大数据架构实践
云客大数据管理保障体系
回归统计在DMP中的实战应用
k8s中流量分离以及资源隔离实战
研发协同平台持续集成Jenkins作业设计演进