人毕竟要与时俱进,所以我这次尝试了下用这种标题来哗众取宠一下,最终我还是活成了自己最讨厌的样子
言归正传,写这篇文章是想记录下上周我做的一件事情:
改造了原先公司内部自研的CICD发布系统
采用基于Tekton的CICD发布系统
很多基础的概念就不多做介绍了,CI/CD作为DevOps中重要部分,在公司内部承担了每个应用系统的日常迭代工作。
许多公司会采用自研的方式来构建自己的CICD平台,已实现许多环节的自定义需求,另外一些公司会基于开源的CICD平台,例如大家都听说过的Jenkins,构建应用流水线来实现。
而这次基于Tekton的CICD发布系统,也是一种新的尝试。
当前CICD架构介绍
CI/CD分为两个环节,CI(持续集成)和CD(持续发布)。CI是生产应用对应的制品(war、jar、可执行二进制文件、镜像),CD是将制品以各种方式部署到对应的环境。
我们公司的发布系统是自研的,采用自研的原因是当时还没有很成熟的开源CICD平台(Jenkins还没有像现在这么成熟,JenkinsFile还没有出现),另外自研能够更加灵活的满足需求,提前是我们拥有自研的人力和能力。
另外对CICD系统,在设计的时候,我们引入了标准化应用的概念,针对不同语言的应用工程,将其应用的目录结构,配置文件等制定对应的规范,发布系统会根据对应的规范进行CICD操作。这样的好处是,减少应用之间的差异性,对于开发人员和运维人员开发和排除问题都提高了效率。
原先的架构如下:
- 门面工程:用于处理页面请求,应用的管理、CICD的配置等等
- 流水线工程:根据应用的类型,装配该应用的流水线。流水线应用属于核心应用,举个例子对于应用需要前端代码部署的,在流水线中可以增加前端构建这个节点,或是云原生化的应用,在代码构建完成后,可以新增镜像构建的节点,来完成docker build的任务。
- 原子任务处理工程:当流水线装配完成后,整条流水线是由一个个原子任务组成,该工程就是处理这些原子任务,然后将结果返回。
分析整个架构,流水线工程是核心工程,它控制着整个流水线装配、运行。但是真正处理任务的工程是原子任务处理工程,当公司业务发展,应用的日常部署的增加或减少,对应的原子任务处理工程是需要支持弹性的伸缩,来面对任务数量的变化。
当前的架构,在原子任务处理工程,是用Python写的系统,通过MQ接收原子任务,然后可以横向部署云服务器,通过NFS或者COS等方式,进行任务间的文件传递。
在前几年进行应用云原生化推进的工作中,我们也对CICD进行了改造,这样对业务开发人员而言,云原生化的推进相对而言是无感知的,和原先的差异并不是很大。在完成整体的推进之后,反观整个CICD平台,我觉得有个点可以值得探索和尝试下,就是接下来的内容:
Tekton和Argo Workflow
在之前为了云原生发布对CICD系统改造的过程中,我也分析了当前主流的开源CICD平台。
对于大家都耳熟能详的Jenkins,也推出了围绕 Kubernetes的Jenkins X技术栈,提供了一种更适合云原生时代的 DevOps 方式。
对Jenkins,的确当前已经成为开源CICD的标准,很多公司都使用Jenkins来实现应用发布,我也调研过Jenkins,尝试过去编写标准化应用的JenkinsFile,来尝试替换流水线工程和原子任务处理工程。但是在我看来,Jenkins的功能的确太强大了,可以装各种插件来满足各种需求,而分析我们的应用CICD过程,相对比较简单,如果基于Jenkins为底座,进行CICD的上层开发,那还要投入精力去维护Jenkins,但是往往只使用了它一小部分的功能,另外对于非标准的应用其实也可以编写自己的JenkinsFile来定制自己的CICD,但是语法也并不是通用的语言。
另外在看CNCF的Landscape,我关注到了其中的两个开源项目:Tekton和Argo Workflow。
- Tekton
Welcome to Tekton | Tektonhttps://tekton.dev/docs/
- Argo Workflow
Argo Workflows | Argohttps://argoproj.github.io/workflows/
这两个项目之所以吸引我的一点就是 当底层的环境从云服务器迁移到k8s集群后,应用以工作负载的方式运行在node上,同时支持HPA水平自动伸缩,而对于CICD中的每个原子任务,也可以以POD的形式运行在Node上,任务完成后销毁,这正是Tekton和ArgoCD的一个特点。相对于原先发布系统原子任务处理工程以云服务器的方式水平伸缩,这种方式更加灵活,更加节约资源。举个最直观的例子,当凌晨的时间点,相对发布任务比较少,云服务器的方式,资源的利用率就很低,但是服务器还是运行着,如果是以POD的方式,资源的消耗会更小。
为什么选择了Tekton
对于我最看重的一点,资源消耗的减少,Tekton和Argo Workflow都是以POD的方式运行,从初步分析来说,Argo 更胜一筹,原因有以下两点:
- Argo是CNCF孵化项目,而Tekton是从Knative中剥离出来的项目,部署的时候使用的镜像仓库都是在谷歌gcr中,国内被墙了,部署起来就需要稍微繁琐一下,而Argo就不需要。
- Argo有很多子项目,如果使用了Argo workflow来进行整个CI过程,可以更好的和Argo CD进行结合,在我看来,Argo CD+GitOps这一套还是可以的。
但是为什么最终我选择了Tekton,原因就是Tekton的流程粒度更细。
当你初步了解了Tekton,就会知道他的运行结构就是每次都运行一个PipelineRun。
对于一个Pipeline是由Task组成的,每个Task都会申请一个POD来运行对应的任务。
但是最重要的一点是对于每个Task,里面可以配置多个Step,每个Step都是一个POD中的一个容器,可以控制每个任务的步骤,在Argo workflow里面也可以通过ContainSet来实现,但是在我使用过程中,支持得不够好(这次调研是在今年年初做的,那个时候,我看了Argo workflow的文档,没有和Step一样的概念),就因为这点,我选择了Tekton
就以上这点,我也举个例子,对于应用CI中的构建和镜像制作,我选择放在一个Task中的两个Step,在完成构建过程后,再执行镜像制作这个Step,选择Tekton就会更加清爽。
以上是我个人的感受和分析,也希望大家可以各抒己见,可以私信我讨论。
CICD + Tekton
基于以上的分析,我开始尝试设计和开发基于Tekton的CICD平台
改造流程
以下的流程细节就不讲了,毕竟每个公司的分支策略,应用构建方式和规范不一样,只大致讲下整体流程
Tekton Pipeline构建
分析原先的流水线流程,以最基本的Java项目的CI为例,最基本的流程如下:
- 代码合并(有的公司合代码的操作是人为操作的,这步可以忽略)
- 代码构建
如果是Maven项目,那最基本的都是mvn package
- 镜像构建
基于构建得到的产出,来进行镜像的制作
直观来看,可以讲以上三个步骤转换成三个Task,然后在对应的容器中,执行对应的Shell脚本便可以完成。而我的处理方式是将代码构建和镜像构建放在一个Task中分为两个Step,原因就是我不想再引入COS和CFS,如果分为两个Task,就需要考虑构建物的传递,因为Task是以单独的POD形式运行的,如果在一个Task中的两个Step运行,那么构建物的传递就会简单很多。因此流水线的构建因人而异。
完成整个流水线的装配,之后可以在tekton的dashboard中创建一个PipelineRun运行下,观察下结果是否满足要求。
上层平台的开发
原先CICD平台采用的是后端Java+Python,前端React
新的平台我采用的Go + Vue (选择go是因为go更适合云原生相关的开发,顺便前端用Vue再写一把,支持下国产)
下面就讲下整体结合Tekton串联的流程:
接下来对流程图进行简要说明:
- 当发起CICD时,首先需要根据应用的类型匹配到对应的Pipeline(引入Tekton后的一个好吃就是相对以前,我们虽然支持了标准应用的发布,但是对于非标准应用的发布,都是开发人为上机器进行操作发布,如果需要CICD平台支持,则需要开发对应的代码,并不是很灵活,而现在只需要去为这个应用制作对应的Pipeline)
- 最后将所有的PipelineRun信息存档,删除对应的CRD,毕竟这些都是存在ETCD中,日常发布量大,数据多。
- 对于如何获取PipelineRun的结果,我采用的方式是在Pipeline的finally中添加一个callback的Task,将需要获取的结果回调到平台中。
- 对于实时展示,方式有很多种,前端的Interval进行调用获取,或者可以使用SSE的方式
- 可以通过Tekton的Clientset来创建对应的PipelineRun
由于整体的UI以及交互没有大的改动,因此前后端的开发大致花了我一周的时候就完成了。
但是以上只是完成了CI的部分,对于CD的部分,我们原先的方式一直是以自研的方式来实现相应策略的部署,因此这里没有进行改动。
对比
完成整体开发以后,与原先的架构对比,优势如下:
- 资源的节约
关于原子任务处理工程侧的资源节约是显而易见的,同时在流水线装配工程这一侧,Tekton在不安装dashboard的场景下,也只有controller和webhook,其他PipelineRun和TaskRun以CRD的方式运行,结束后销毁,所占资源较小。
- 对非标准应用的支持
这一点在上面提到了,就不讲了
下一步计划
在完成初步改造之后,后续还有以下几点可以再进行优化:
- 对项目工程进行下一步改造,Clientset的方式改成informer的方式获取CRD资源的信息
- 对于不同构建任务的调度策略
- 节点的构建缓存
- 观察不同时期的任务执行速度,例如在集群资源紧张时,运行Task的Pod pending的时间,是否需要固定几个运行构建任务的节点。
总结
- 对于底层基础技术的升级,往往会对平台提供新的改造思路,来实现系统的优化,例如像原先使用的activity的工作流平台,在云原生化改造后,也可以得到系统的优化。
- 对于选择Tekton的这个策略,让我意识到有的时候并不是功能越强大就是越好,最合适的才是最好的。
- 现在慢慢的第一语言从Java转变到Go,语言之间虽然是融会贯通,但是还是存在一些思想上的差异。例如在平台创建的Tekton的PipelineRun,我的这么多年Java开发经验告诉我,去找找有没有对应的Tekton Api的依赖引进来,接口方法调用下,因为之前有看到过Java的Fabric8io里封装了tekton的接口,后来看了tekton cli的源码才恍然大悟,在项目的Go mod文件里引了整个pipeline的项目,然后利用Clientset来创建Crd
- 开发人员还是得有自己工作场景的脚手架,这样开发的效率就会提高不少。
文章篇幅问题,关于Tekton的使用,项目细节的开发,以后有机会还会再写文章分享,也可以私信我相互探讨下
我最后决定从这篇文章开始,以后都用一句话来结尾:
大隐隐于市!