本文主要介绍在生产环境中持续集成与持续部署的使用,主要通过实现Jenkins流水线脚本自动发布应用到Kubernetes集群当中。
CI/CD介绍
CI(Continuous Integration,持续集成)/CD(Continuous Delivery,持续交付)是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案,CI/CD主要针对在集成新代码时所引发的问题(亦称“集成地狱”)。
具体而言,CI/CD在整个应用生命周期内(从集成和测试阶段到交付和部署)引入了持续自动化和持续监控,这些关联的事务通常被称为“CI/CD管道”,由开发和运维团队以敏捷方式协同支持。
CI和CD的区别
CI/CD中的CI指持续集成,它属于开发人员的自动化流程。成功的CI意味着应用代码的最新更改会定期构建、测试并合并到共享存储中。该解决方案可以解决在一次开发中有太多应用分支,从而导致相互冲突的问题。
CI/CD中的CD指的是持续交付或持续部署,这些相关概念有时会交叉使用。两者都事关管道后续阶段的自动化,但它们有时也会单独使用,用于说明自动化程度。
持续交付通常是指开发人员对应用的更改会自动进行错误测试并上传到存储库(如GitLab或容器注册表),然后由运维团队将其部署到实时生产环境中,旨在解决开发和运维团队之间可见性及沟通较差的问题,因此持续交付的目的就是确保尽可能减少部署新代码时所需的工作量。
持续部署指的是自动将开发人员的更改从存储库发布到生产环境中以供客户使用,它主要为解决因手动流程降低应用交付速度,从而使运维团队超负荷的问题。持续部署以持续交付的优势为根基,实现了管道后续阶段的自动化。
CI/CD既可能仅指持续集成和持续交付构成的关联环节,也可以指持续集成、持续交付和持续部署这三个方面构成的关联环节。更为复杂的是有时持续交付也包含了持续部署流程。
纠缠于这些语义其实并无必要,只需记得CI/CD实际上就是一个流程(通常表述为管道),用于在更大程度上实现应用开发的持续自动化和持续监控。
持续集成(CI)
现代应用开发的目标是让多位开发人员同时开发同一个应用的不同功能。但是,如果企业安排在一天内将所有分支源代码合并在一起,最终可能导致工作繁琐、耗时,而且需要手动完成。这是因为当一位独立工作的开发人员对应用进行更改时,有可能会有其他开发人员同时进行更改,从而引发冲突。
持续集成可以帮助开发人员更加频繁地将代码更改合并到共享分支或主干中。一旦开发人员对应用所做的更改被合并,系统就会通过自动构建应用并运行不同级别的自动化测试(通常是单元测试和集成测试)来验证这些更改,确保更改没有对应用造成破坏。这意味着测试内容涵盖了从类和函数到构成整个应用的不同模块,如果自动化测试发现新代码和现有代码之间有冲突,持续集成(CI)可以更加轻松地快速修复这些错误。
持续交付(CD)
完成持续集成中构建单元测试和集成测试的自动化流程后,通过持续交付可自动将已验证的代码发布到存储库。为了实现高效的持续交付流程,务必要确保持续交付已内置于开发管道。持续交付的目标是拥有一个可随时部署到生产环境的代码库。
在持续交付中,每个阶段(从代码更改的合并到生产就绪型构建版本的交付)都涉及测试自动化和代码发布自动化。在流程结束时,运维团队可以快速、轻松地将应用部署到生产环境中。
持续部署
对于一个成熟的CI/CD管道来说,最后的阶段是持续部署。作为持续交付(自动将生产就绪型构建版本发布到代码存储库)的延伸,持续部署可以自动将应用发布到生产环境中。由于生产之前的管道阶段没有手动门控,因此持续部署在很大程度上都得依赖于精心设计的测试自动化。
实际上,持续部署意味着开发人员对应用的更改在编写后的几分钟内就能生效,这更加便于持续接收和整合用户反馈。总而言之,所有这些CI/CD的关联步骤都有助于降低应用的部署风险,因此更便于以小件的方式(非一次性)发布对应用的更改。不过,由于还需要编写自动化测试以适应CI/CD管道中的各种测试和发布阶段,因此前期投资会很大。
Jenkins 流水线介绍
本节主要讲解Jenkins的新功能——Jenkins流水线(Pipeline)的使用,首先介绍流水线的概念和类型,然后讲解流水线的基本语法和一些例子。
什么是流水线
Jenkins流水线(或Pipeline)是一套插件,它支持实现并把持续提交流水线(Continuous Delivery Pipeline)集成到Jenkins。
持续提交流水线(Continuous Delivery Pipeline)会经历一个复杂的过程:从版本控制、向用户和客户提交软件,软件的每次变更(提交代码到仓库)到软件发布(Release)。这个过程包括以一种可靠并可重复的方式构建软件,以及通过多个测试和部署阶段来开发构建好的软件(称为Build)。
流水线提供了一组可扩展的工具,通过流水线语法对从简单到复杂的交付流水线作为代码进行建模,Jenkins流水线的定义被写在一个文本文件中,一般为Jenkinsfile,该文件“编制”了整个构建软件的过程,该文件一般也可以被提交到项目的代码仓库中,在Jenkins中可以直接引用。这是流水线即代码的基础,将持续提交流水线作为应用程序的一部分,像其他代码一样进行版本化和审查。创建Jenkinsfile并提交到代码仓库中的好处如下:
自动地为所有分支创建流水线构建过程
在流水线上进行代码复查/迭代
对流水线进行审计跟踪
流水线的代码可以被项目的多个成员查看和编辑
Jenkins流水线概念
流水线主要分为以下几种区块:Pipeline、Node、Stage、Step等。
Pipeline(流水线),Pipeline是用户定义的一个持续提交(CD)流水线模型。流水线的代码定义了整个的构建过程,包括构建、测试和交付应用程序的阶段。另外,Pipeline块是声明式流水线语法的关键部分。
Node(节点),Node(节点)是一个机器,它是Jenkins环境的一部分,另外,Node块是脚本化流水线语法的关键部分。
Stage(阶段),Stage块定义了在整个流水线的执行任务中概念不同的子集(比如Build、Test、Deploy阶段),它被许多插件用于可视化Jenkins流水线当前的状态/进展。
Step(步骤),本质上是指通过一个单一的任务告诉Jenkins在特定的时间点需要做什么,比如要执行shell命令,可以使用sh SHELL_COMMAND。
声明式流水线
在声明式流水线语法中,Pipeline块定义了整个流水线中完成的所有工作,比如:
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Build') {
steps {
//
}
}
stage('Test') {
steps {
//
}
}
stage('Deploy') {
steps {
//
}
}
}
}
说明:
agentany:在任何可用的代理上执行流水线或它的任何阶段。
stage('Build'):定义Build阶段。
steps:执行某阶段相关的步骤。
脚本化流水线
在脚本化流水线语法中,会有一个或多个Node(节点)块在整个流水线中执行核心工作,比如:
Jenkinsfile (Scripted Pipeline)
node {
stage('Build') {
//
}
stage('Test') {
//
}
stage('Deploy') {
//
}
}
说明:
node:在任何可用的代理上执行流水线或它的任何阶段。
stage('Build'):定义build阶段。stage块在脚本化流水线语法中是可选的,然而在脚本化流水线中实现stage块,可以清楚地在Jenkins UI界面中显示每个stage的任务子集。
流水线示例
一个以声明式流水线的语法编写的Jenkinsfile文件如下:
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make'
}
}
stage('Test'){
steps {
sh 'make check'
junit 'reports/**/*.xml'
}
}
stage('Deploy') {
steps {
sh 'make publish'
}
}
}
}
常用参数说明:
pipeline是声明式流水线的一种特定语法,定义了包含执行整个流水线的所有内容和指令。
agent是声明式流水线的一种特定语法,指示Jenkins为整个流水线分配一个执行器(在节点上)和工作区。
stage是一个描述流水线阶段的语法块,在脚本化流水线语法中,stage(阶段)块是可选的。
steps是声明式流水线的一种特定语法,它描述了在这个stage中要运行的步骤。
sh是一个执行给定shell命令的流水线step(步骤)。
junit是一个聚合测试报告的流水线step(步骤)。
node是脚本化流水线的一种特定语法,它指示Jenkins在任何可用的代理/节点上执行流水线,这实际等同于声明式流水线特定语法的agent注:后续的例子中要用到。
上述声明式流水线等同于以下脚本式流水线:
Jenkinsfile (Scripted Pipeline)
node {
stage('Build') {
sh 'make'
}
stage('Test') {
sh 'make check'
junit 'reports/**/*.xml'
}
stage('Deploy') {
sh 'make publish'
}
}
Pipeline语法
本节主要从流水线的两种类型出发讲解Pipeline的语法。
声明式流水线
声明式流水线是在流水线子系统之上提供了一种更简单、更有主见的语法。
所有有效的声明式流水线必须包含在一个Pipeline块中,比如以下是一个Pipeline块的格式:
pipeline {
/* insert Declarative Pipeline here */
}
在声明式流水线中有效的基本语句和表达式遵循与Groovy的语法同样的规则,但有以下例外:
流水线顶层必须是一个block,pipeline{}。
没有分号作为语句分隔符,每条语句都在自己的行上。
块只能由Sections、Directives、Steps或assignment statements组成。
Sections
声明式流水线中的Sections通常包含一个或多个agent、Stages、post、