0%

Rancher CI/CD Pipeline 初步学习

最近的工作任务是研究Docker、Rancher上CI/CD 流程,记录一些学习过程。

什么是CI/CD

CI/CD的意思就是持续集成(Continuous integration)和持续交付(continuous delivery)或持续部署(continuous deployment)。这三个步骤组成了现在软件开发的基本流程。

初次接触到这些概念基本是懵的,尤其是小公司或者传统软件行业,对软件迭代速度没那么高的要求。什么叫集成?怎么算持续?集成的意思就是说快速的把新开发的特性合并到主干上,可以理解为git上把某一个feature分支合并到master上,不过这一过程还包括了程序的构建和测试。持续集成一般是指每天进行很多次集成,它的目的主要是快速的更新产品,既能方便调整方向,又能及时发现bug,从而实现了“小步快跑”的策略。

而持续交付是指频繁的将产品交到用户或者质量评审团队手里,以供评审,通过之后就可以进入到生产。持续部署是指自动化部署到生产环境。通过实施CI/CD,可以在一天进行很多次的产品迭代,从而提升竞争力。

CI/CD的一般流程也就是软件开发的生命周期,只不过完全将其自动化。从git进行代码提交开始,webhook检测到提交事件之后,开始进行单元测试,测试通过之后,将代码合并到主分支,然后进行构建。构建完成在进行集成测试、系统测试,测试无误就可以交付了。然后进行部署,其中可能会有灰度发布,或者用户体验不佳然后进行版本回滚的操作。

基于Docker的CI/CD有什么优势

由于自身理解不深,搜集了一些博文的观点:

一个完整的流程入是这样的,用户(也就是开发人员)将包含Dockerfile的源码从本地push到Git服务器上,然后触发Jenkins进行构建源码,源码构建完成后紧接着进行Docker image的构建,一切构建完成之后,顺带将构建成功的image上传到企业内部的镜像仓库,到此刻为止,其实一个基本的CI(持续集成)已经算是结束,剩下的部分就是持续部署或者进行持续的交付开发产物了。在以前传统的软件发布模式中,持续集成的产物是编译打包好的代码,如果想要发布程序,发布系统需要在持续集成的制品库中去获得对应的代码,然后根据一系列的环境检查来准备应用的运行时环境,而在此过程中往往会涉及到比较多的基本组件依赖,所以在整体的发布周期内来看,还是有一些问题的。在Docker或者容器时代,我们将容器的镜像构建部分融入到持续集成(CI)环节,最终持续集成的产出物是一些已经处理好依赖关系,基本不需要人工进行二次干预的Docker image,而在CD环节,发布系统只需要设置和管理很少的信息就能够很快将image运行起来,快速地将业务发布出去。
在上面整个环节中,其实无非就是增加了Docker的那一层处理,但其实在整个软件开发的生命周期中,它是产生了极大的影响的。首先,部署系统不需要为统一的部署框架去做更多逻辑抽象,业务研发在开发代码的过程中选择自己依赖的base image即可,最终运行起来的业务也就是你当时提供的base image的模样;其次,由于base image已经处理好了相关的依赖,所以当发布系统拿到业务的image的时候,发布操作将会变得异常迅速,这对于互联网时代可谓是非常重要的;最后一点,也是我感受最深的,就是研发构建好的image可以在任何的Docker环境中run起来,研发人员不需要再关系环境一致性的问题,他们在自己本地的测试环境能够运行起来的应用,那么到生成环境也一定可以。
——基于Docker的CI/CD流水线实践

一个想象的Java CI/CD 流程

说了一堆理论的东西,回归到实际环境,由于我们公司主要开发语言是Java,我就先想象了一下对于Java语言,应该是个怎么样的CI/CD流程。首先,研发人员开发完成某一阶段之后,提交代码到主分支,然后触发webhook,开启pipeline。首先,应该在一个带有JDK的容器中下载代码,然后用maven下载依赖、编译、打包,然后运行测试,测试通过之后把class文件、jar包和配置文件转移到一个只含有jre的容器,进行镜像的构建步骤,然后推送到镜像仓库,最后将镜像发布,生成新的容器实例。

重点关注问题

关于CI/CD Pipeline,主要需要从以下几个方面着手考虑。

崇伟提到重点关注的几个问题

  1. git分支管理大致怎样?
  2. 构建镜像的版本号如何管理?
  3. 测试环节,如何整合自动化测试?
  4. 推送镜像的目标harbor,是否区分开发环境/测试环境/正式环境?
  5. 发布流程是否可以控制,比如整合到运维的发布流管理系统?
  6. 灰度发布,调研时也需要关注

分支管理

Rancher Pipeline的触发是用git某一个分支的webhook。每一个stage可以设置触发条件,包括commit ID、分支名、仓库url这三种进行区分。

这里需要考虑的主要问题是要如何指导研发人员的的git工作流,怎么设置分支,目标是尽量少的对现有方案进行改动。

这里可以参照两篇文章关于两种CI/CD策略与git分支模型的思考A successful Git branch model

版本号

Pipeline构建过程中可以使用以下变量:

NAME DESC
CICD_GIT_COMMIT git commit sha
CICD_GIT_BRANCH git branch
CICD_GIT_URL git repository url
CICD_PIPELINE_ID pipeline id
CICD_PIPELINE_NAME pipeline name
CICD_TRIGGER_TYPE trigger type
CICD_NODE_NAME jenkins node name
CICD_ACTIVITY_ID pipeline history record id
CICD_ACTIVITY_SEQUENCE run number of pipeline history record

都可以作为版本号,其中视频教程中推荐使用CICD_ACTIVITY_SEQUENCE,这基本可以当做一个Pileline的自增序号。

这里主要考虑得是在发布流中对版本号的控制。

测试环节整合自动化测试

在一个步骤中选择类型为task之后,都可以执行shell,在这里可以调用自动化测试的命令。

harbor是否区分环境

可以区分,不过通过仓库、镜像名、tag都可以作为区分,是不是还有必要分开多个环境?

发布流程

发布流程每一步都可以设置权限控制,相关人员确认后再进行下一步。

关于是否可以整合Jenkins插件,视频中说现在不支持,建议不要混搭。

灰度发布

灰度发布的关键在于流量控制和版本控制,要求能快速切换版本,控制每个版本的流量。Rancher Pipeline中现有的相关操作是upgrade service可以设置步长,即选择保留的版本数量,同时可以选择先上线新版本再关闭旧版本,或是先关闭再上线,这个操作的时间间隔也可以设置。在Rancher操作界面中也可以随时会滚版本。关于流量控制可以使用HAproxy等负载均衡来做。

现有的工具

询问多个同事得知,各个项目组还没有统一的流程,旭哥的eSim团队可能是相对比较健全的,之前使用过GitLab和Jenkins,现在基于方便考虑,切换到了同一家出品的Bitbucket+Bamboo。以ESim为例,当前的发布流程是这样:

  1. 提交代码到各自feature分支
  2. 每日把各自feature分支合并到develop分支(触发自动化build),同时执行单元测试用例。有冲突解决冲突,直至冲突解决完成
  3. develop拉release分支提测
  4. 测试通过在bamboo上将release发布

Rancher Pipeline Demo 环境搭建

启动环境

Rancher Pipeline的搭建非常简单,首先需要满足Rancher server版本在v1.6.13以上,这里我们选择了v1.6.14。然后在商店里搜索pipeline就可以看到,不需要额外配置,启动服务就可以了。

启动完成之后,Rancher UI界面顶部菜单会出现一个新的按钮”流水线”,点击即可进入Pipeline界面。

仓库认证和授权

进入Pipeline界面之后,点击添加流水线,它会让你先完成仓库授权和添加代码仓库:

当前支持的代码仓库只有两个,GitHub和GitLab,我这里选择了自己部署的GitLab。首先打开Gitlab,登陆,点击右上角自己的头像,在下拉菜单中选择setting:

然后在新的页面里选择application,name里面取个名字,Redirect URI是Pipeline提供给你的,复制过来就行,然后选一个权限,我选择的api,最后保存:

其中,Redirect URI 在这里:

都完成之后,GitLab会生成一个ID和密钥,将这两个值复制到Pipeline中:

然后别忘了选中”使用私有GitLab部署”,然后填上ip,就可以了,最后点击gitlab验证。

页面会跳到GitLab,点击认证,这一步就完了。

流水线配置

回到流水线界面,点击添加流水线,会让你选择git用户、仓库和分支,按照各自情况选好需要跟踪的分支,点击添加,第一个阶段就完成了。

然后点击添加第二个阶段,这里可以选择并行或者串行,这里的并行或者串行是指本构建阶段(stage)下的每个步骤(step)执行逻辑。同时可以看到,这里有两个可选选项,分别提供条件筛选和审核。如果不满足条件,步骤不执行。或者执行到这一步的,等待审核人审核,审核通过之前不会执行。

添加完阶段之后添加一个步骤,Rancher教程以go语言为例,选择步骤类型为task,然后镜像为golang:1.8,输入需要执行的shell,这一步的作用就是把代码复制到相应的容器中,然后进行编译,使用容器作为编译环境保证了环境的一致性。这里的那些参数只是为了演示的时候加快变异速度,其实最重要的就是一步go build

进行到这里,无误的话源代码已经编译完成,应该执行单元测试。所以下一个阶段基本相同,再添加一个阶段,添加一个步骤,步骤类型task,命令稍有不同。

单元测试完成,开始构建镜像。添加一个阶段,添加一个步骤,这里步骤类型选择build,这里的build是指build image。Dockerfile可以在这里填写,或者直接从代码中拉取,这里选择的是从代码中拉,由于只是demo,Dockerfile非常简单:

1
FROM alpine
2
EXPOSE 8080
3
COPY ./bin/outyet /usr/bin/outyet
4
ENTRYPOINT /usr/bin/outyet

同时可以看到镜像标签那里使用了上面说到的变量CICD_ACTIVITY_SEQUENCE,可以看做是一个自增序列。
这一步顺利完成之后,就构建好了一个名为outyet的镜像,版本号是一个数字,然后被推送到2.8.0.3:8001/pipeline仓库。

进行到这里,自动集成的步骤就完成了。然后应该把镜像推送到Rancher中,产生容器实例,进行自动发布。新建一个阶段,添加一个步骤,这里步骤类型选择为upgradeStack。编写docker-compose配置文件,Pipeline会自动从仓库中拉取镜像,然后产生容器实例,更新之前发布的版本。

实际应用下的步骤

上面基于GoLang语言的Demo是一个最小化的步骤示例,其实对于go语言来讲,编译是很简单的一个步骤,因为没有其他语言复杂的包依赖,构建产物也是直接的可执行文件,最后直接执行即可。但是对于公司广泛应用的Java来说,构建步骤需要考虑Jar包依赖的情况,而一般以来的Jar包都是通过maven管理,git上面是不会记录的,所以拉下来的源代码没办法直接进行编译。

第一反应是想到了两种方式:

  1. 研发使用统一的基础镜像,在本地进行编译和打包,我们后续只对镜像进行管理。Intellij IDEA提供了插件倒是方便了直接生成镜像。
  2. 提供maven环境,统一在服务器上进行编译和打包,研发还是只提供源码就好。

第一种想法很快就被舍弃,因为研发小伙伴未必都对docker了解,水平参差不齐的话很难对镜像进行要求和管理,学习成本略高。maven环境的话只需要把第二个阶段里golang镜像换成一个包含jdk、maven的镜像即可。

所以,结合现有的git flow,建议一个完整的开发流程如下:

  1. 研发人员在各自负责的模块新开feature分支,强制要求每日合并到develop分支,强制要求编写单元测试用例。
  2. 在完成阶段性开发之后,develop分支合并到release分支或者master分支,触发webhook,开启Pipelin。
  3. Rancher Pipeline 将代码下载到一个包含JDK和maven的镜像中,进行编译和打包。
  4. 执行单元测试。
  5. 测试无误之后,以一个只含有JRE的基础镜像中构建一个新的应用镜像,推送到Harbor。
  6. 测试环境从Harbor拉取镜像,产生容器示例,供QA进行完整的测试。
  7. 测试无误后发布到正式环境,这一步应当选择QA人员为审核人,审核通过的话自动发布。

其他

了解相关信息的过程中,也听到了一些其他的方案,比如Gogs搭配jenkins,或者GitOps和Kubernetes。但是前者集成度不佳,后者依赖于我们尚未使用的K8s,所以暂不做考虑。Rancher分享中提到的Drone似乎也是一个不错的选择,可以直接对接企业微信,推送状态,精力原因还没做详细了解,后续再进行对比。

参考:
Rancher Pipeline Referince Guide