首先来解释一下CICD的意义
CI和CD看似是两个东西,实际上是三个概念,是由一个CI和两个CD组成的。
一个CI指的是 Continuous Integration,意思为 持续集成。
两个CD分别指的是 Continuous Delivery 和 Continuous Deployment。
Continuous Delivery 的意思为持续交付 ,Continuous Deployment 的意思为持续部署。
所以CICD指代的是 持续集成 + 持续交付 + 持续部署
先来说 持续集成(Continuous Integration)
持续集成的定义其实就是持续频繁的、多次的将本地开发的代码及时的合并到主干分支的同时并保证主干分支的可用。
这样做的好处是能大大提高发布代码的流程,将之前手动进行的提交、编译、部署等流程变为自动,这样既可以提高开发人员的发布频次也可以减少开发人员对研发以外事情的精力消耗。
持续集成中有个核心措施就是代码审核,要把代码中的问题提前抛出。所以在集成到主干分支之前,要进行代码规范扫描、代码安全扫描、编译、自动化测试等流程。如果代码在以上几个流程出现了问题,那么就要及时的阻止此次集成并通知相关人员进行问题排查。
比如笔者之前的公司就集成过PMD和Sonar,在合并到主分支的时候自动根据预设的规则进行代码检查,如果没有检查通过,则终止合并并通知相关人员。
相关的工具如 Jenkins、GitlabCI、CircleCl等,笔者见的和用的最多的就是Jenkins了。
实际中可能出现的问题:
没有严格的测试。有多少公司的CI阶段会引入正式的测试流程呢,在集成到主干的时候需要所有的case全部通过才可以进行集成,但在实际中很多公司都仅仅是把CI当成是一个自动化检查、合并、打包的一个工具而已。
CI部分的构建速度远没有我们想象的那么快,尤其是再引入了代码审查、安全扫描、编译、打包等流程后,每一次的CI时间可以达到5分钟以上。如果在多个微服务同时进行的时候或者单个服务在短时间频繁集成主干时,也可能会出现CI变慢的问题。当然这个速度问题也有可能是一些配置原因导致的。
再来说 持续交付(Continuous Delivery)和 持续部署(Continuous Deployment)
这两个CD流程其实我觉得应该放一块说,因为这两者并不是有先后顺序的,而是都属于CI流程的下一步流程。
但其实此处有不同的解释,有一种观点认为持续交付在前,持续部署在后,持续交付是发布到类生产环境而不是生产环境,而持续部署是发布到生产环境。但我觉得二者其实并无区别,如果非要说个先后流程的话,我还是觉得持续交付和持续部署是同一个流程中的两个选择,而非先后关系。
持续交付和持续部署干的事其实就是持续、频繁的将软件的新版本交付发布到UAT、Prod环境或交付给测试、产品验收。相比于持续集成,持续交付除了交付到类生产或生产环境之外,还会执行一些集成测试、API测试等流程,确保交付的产物可以直接部署。
而二者的区别在于持续交付的触发方式是手动发布,而持续部署的触发方式为手动触发。如果代码成功的通过了之前的所有阶段,那么就该将此版本的代码部署到类生产或生产环境中。
设想一下一位开发人员在CICD流程下的开发情况:
第一步 | 从远程分支clone代码到本地进行修改,本地测试完成后通过git提交到远程仓库 |
第二步 | push的过程触发了CI的代码审查,几分钟后,因为代码问题push被拒绝。 |
第三部 | 重新修改代码提交并push,再次自动触发CI流程,此次代码审查、扫描、编译等问题都成功通过,自动发布到对应环境。 |
第四步 | 开发人员通知测试或产品进行测试或验收,如果依然需要修改,则返回第一步。 |
第五步 | 持续部署和持续交付会隔一段时间或者手动触发来进行发布生产环境,当然是有内容变动的时候。 |
第六步 | 如果在在CD环节的审查或者测试有不通过的情况,则通知相应的人员进行处理。返回第一步 |
第七步 | CD环节如果没有问题且正常发布后,通知相应人员发布成功。 |
当然这只是在一个开发的角度来看CICD给他带来的变化。
在CICD的实现层面上,有更多的问题要考虑。
就拿笔者之前公司使用的自动部署的方案来说,使用的组件由
Gitlab + Jenkins + Maven + Harbor + Rancher 来构成的。
Gitlab:Gitlab就是用来管理代码版本的工具,相信每个开发人员都很熟悉,无非是有些用的是Github,有的是Gitee或Gitlab罢了。但本质上都是一样的功能。
Jenkins:jenkins是一个用Java开发的持续集成的工具,它有一个可视化界面可以操作,所以操作比较方便,他可以帮助我们在构建、部署、测试等流程的自动化,也是我们实现CICD的核心组件之一。
Maven:Maven基本不用解释了吧,Java项目的构建、依赖管理。
Harbor:一个镜像仓库。是用于储存和分发docker镜像的企业级镜像仓库。
Rancher:开源的企业级容器管理平台,他可以提供docker和K8s的全栈化容器部署和管理,而且还带可视化界面,而且也在集成自动化方面有不小的用途。
在使用上分别有两种方式
一种为手动触发
一种为自动触发
二者的区别主要在前期的步骤。
手动触发模式:
假设我们要发布微服务A的Test分支的066b21e08版本号的代码。
(因为手动发布可以自由选择分支下的不同版本的代码,比如我要发布本次合并前的一版的代码,就可以直接去git上查找上一次合并的版本号即可)
第一步 | 进入rancher平台,找到A服务所在的工作负载 |
第二步 | 修改该工作负载,在docker镜像输入框中把镜像名:版本号写入,然后保存 |
第三部 | 此时rancher会向Jenkins发送请求,让Jenkins拉取gitlab中这个版本号的代码进行操作 |
第四步 | Jenkins在拉取完对应版本的代码后,进行打包操作,maven就是在这个步骤参与的。 |
第五步 | 在打包完后进行docker镜像打包,顺便把docker镜像的镜像名修改为服务:版本号 |
第六步 | 把打包完的docker镜像推送到harbod镜像仓库 |
第七步 | Jenkins通过rancher的API进行通知,通知rancher进行拉取和运行 |
第八步 | rancher根据第二步输入的镜像版本号从harbor中拉取docker镜像 |
第九步 | 拉取成功后,假设一个工作负载有2个pod,那么rancher首先关闭一个,然后另外启动一个新版本的服务,在启动过程中,流量都会打到剩下的一个pod上。当新的pod启动后,剩下的一个pod会进行销毁,并启动第二个新版本的pod,直到发布结束 |
这里补充一点,如果在第二步中不填充版本号的话,是可以配置为直接手动运行该分支中最新的一版代码的,这其实是一个中间方案。既不想手动的查最新的版本号是多少,也不想太自动的每次提交都发布。
自动触发模式:
当每次代码提交到指定分支后会自动触发
第一步 | 当分支A请求合并到Test分支并成功合并后触发 |
第二步 | 在Gitlab完成合并分之后,请求Jenkins进行操作 |
第三部 | Jenkins在拉取完改分支最新的代码后,进行打包操作,maven就是在这个步骤参与的。 |
第四步 | 在打包完后进行docker镜像打包,顺便把docker镜像的镜像名修改为服务:版本号 |
第五步 | 把打包完的docker镜像推送到harbod镜像仓库 |
第六步 | Jenkins通过rancher的API进行通知,通知rancher进行拉取和运行 |
第七步 | rancher从harbor中拉取改服务的最新docker镜像 |
第八步 | 拉取成功后,假设一个工作负载有2个pod,那么rancher首先关闭一个,然后另外启动一个新版本的服务,在启动过程中,流量都会打到剩下的一个pod上。当新的pod启动后,剩下的一个pod会进行销毁,并启动第二个新版本的pod,直到发布结束 |
可以发现二者的区别就是前边几步,一个是在rancher中指定版本号手动触发,一个是在提交合并到目标分之后自动触发。前者更灵活,后者更自动。所以根据情况而定的选择适合自己公司的。
当然这只是众多CICD自动化实现的方案之一,还有更多的组件可以帮助我们实现这些功能,如果有些公司的项目规模不大,或者是单体项目的话,一个git一个Jenkins其实就可以大大的减少研发人员在发布上的精力浪费,而且运维的压力也不会有那么大,甚至可以让开发去兼顾着维护这一套简易的自动的CICD流程。