本章主要介绍在生产环境中持续集成与持续部署的使用,主要通过实现Jenkins流水线脚本自动发布应用到Kubernetes集群当中。
CI(Continuous Integration,持续集成)/CD(Continuous Delivery,持续交付)是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。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管道中的各种测试和发布阶段,因此前期投资会很大。
本节主要讲解Jenkins的新功能——Jenkins流水线(Pipeline)的使用,首先介绍流水线的概念和类型,然后讲解流水线的基本语法和一些例子。
Jenkins流水线(或Pipeline)是一套插件,它支持实现并把持续提交流水线(Continuous Delivery Pipeline)集成到Jenkins。
持续提交流水线(Continuous Delivery Pipeline)会经历一个复杂的过程:从版本控制、向用户和客户提交软件,软件的每次变更(提交代码到仓库)到软件发布(Release)。这个过程包括以一种可靠并可重复的方式构建软件,以及通过多个测试和部署阶段来开发构建好的软件(称为Build)。
流水线提供了一组可扩展的工具,通过流水线语法对从简单到复杂的交付流水线作为代码进行建模,Jenkins流水线的定义被写在一个文本文件中,一般为Jenkinsfile,该文件“编制”了整个构建软件的过程,该文件一般也可以被提交到项目的代码仓库中,在Jenkins中可以直接引用。这是流水线即代码的基础,将持续提交流水线作为应用程序的一部分,像其他代码一样进行版本化和审查。创建Jenkinsfile并提交到代码仓库中的好处如下:
流水线主要分为以下几种区块:Pipeline、Node、Stage、Step等。
在声明式流水线语法中,Pipeline块定义了整个流水线中完成的所有工作,比如:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Build') { steps { // } } stage('Test') { steps { // } } stage('Deploy') { steps { // } } } }
说明:
在脚本化流水线语法中,会有一个或多个Node(节点)块在整个流水线中执行核心工作,比如:
Jenkinsfile (Scripted Pipeline) node { stage('Build') { // } stage('Test') { // } stage('Deploy') { // } }
说明:
一个以声明式流水线的语法编写的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' } } } }
常用参数说明:
上述声明式流水线等同于以下脚本式流水线:
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 { /* insert Declarative Pipeline here */ }
在声明式流水线中有效的基本语句和表达式遵循与Groovy的语法同样的规则,但有以下例外:
Sections
声明式流水线中的Sections通常包含一个或多个agent、Stages、post、Directives和Steps,本节首先介绍agent、Stages、post,有关Directives和Steps的说明见下一小节。
agent:
agent部分指定了整个流水线或特定的部分,在Jenkins环境中执行的位置取决于agent区域的位置,该部分必须在Pipeline块的顶层被定义,但是stage级别的使用是可选的。
①参数
为了支持可能有的各种各样的流水线,agent部分支持一些不同类型的参数,这些参数应用在pipeline块的顶层,或Stage指令内部。
agent { dockerfile { filename ‘Dockerfile.build’ dir ‘build’ label ‘my-defined-label’ additionalBuildArgs ‘--build-arg version=1.0.2’ } }
agent{ docker{ image ‘maven:3-alpine’ label ‘my-defined-label’ args ‘-v /tmp:/tmp’ } }②常见选项
agent { node { label 'my-defined-label' customWorkspace '/some/other/path' } }
③示例
示例1:在maven:3-alpine(agent中定义)的新建容器上执行定义在流水线中的所有步骤。
Jenkinsfile (Declarative Pipeline) pipeline { agent { docker 'maven:3-alpine' } stages { stage('Example Build') { steps { sh 'mvn -B clean verify' } } } }
示例2:本示例在流水线顶层定义agentnone,确保<<../glossary#executor, an Executor>> 没有被分配。使用agentnode也会强制stage部分包含它自己的agent部分。在stage('Example Build')部分使用maven:3-alpine执行该阶段步骤,在stage('Example Test')部分使用openjdk:8-jre执行该阶段步骤。
Jenkinsfile (Declarative Pipeline) pipeline { agent none stages { stage('Example Build') { agent { docker 'maven:3-alpine' } steps { echo 'Hello, Maven' sh 'mvn --version' } } stage('Example Test') { agent { docker 'openjdk:8-jre' } steps { echo 'Hello, JDK' sh 'java -version' } } } }
post:
post部分定义一个或多个steps,这些阶段根据流水线或stage的完成情况而运行(取决于流水线中post部分的位置)。post支持以下post-condition块之一:
示例:一般情况下post部分放在流水线的底部,比如本实例,无论stage的完成状态如何,都会输出一条I will always say Hello again!信息。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' } } } post { always { echo 'I will always say Hello again!' } } }
stages:
stages包含一个或多个stage指令,stages部分是流水线描述的大部分工作(work)的位置。建议stages至少包含一个stage指令,用于持续交付过程的某个离散的部分,比如构建、测试或部署。
示例:本示例的stages包含一个名为Example的stage,该stage执行echo 'Hello World'命令输出Hello World信息。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' } } } }
steps:
steps部分在给定的stage指令中执行的一个或多个步骤。
示例:在steps定义执行一条shell命令。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' } } } }
Directives
Directives用于一些执行stage时的条件判断,主要分为environment、options、parameters、triggers、stage、tools、input、when等,这里仅对常用的environment、parameters、stage和when进行介绍。
environment:
environment制定一个键-值对(key-value pair)序列,该序列将被定义为所有步骤的环境变量,或者是特定stage的步骤,这取决于environment指令在流水线内的位置。
该指令支持一个特殊的方法credentials(),该方法可用于在Jenkins环境中通过标识符访问预定义的凭证。对于类型为Secret Text的凭证,credentials()将确保指定的环境变量包含秘密文本内容,对于类型为Standardusernameandpassword的凭证,指定的环境变量为username:password,并且两个额外的环境变量将被自动定义,分别为MYVARNAME_USR和MYVARNAME_PSW。
示例:
Jenkinsfile (Declarative Pipeline) pipeline { agent any environment { CC = 'clang' } stages { stage('Example') { environment { AN_ACCESS_KEY = credentials('my-prefined-secret-text') } steps { sh 'printenv' } } } }
上述示例的顶层流水线块中使用的environment指令将适用于流水线中的所有步骤。在stage中定义的environment指令只会适用于stage中的步骤。其中stage中的environment使用的是credentials预定义的凭证。
parameters:
parameters提供了一个用户在触发流水线时应该提供的参数列表,这些用户指定参数的值可以通过params对象提供给流水线的step(步骤)。
可用参数
示例:定义string类型的变量,并在steps中引用。
Jenkinsfile (Declarative Pipeline) pipeline { agent any parameters { string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?') } stages { stage('Example') { steps { echo "Hello ${params.PERSON}" } } } }
stage:
stage指定在stages部分流水线所做的工作都将封装在一个或多个stage指令中。
示例:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' } } } }
when:
when指令允许流水线根据给定的条件决定是否应该执行stage。when指令必须包含至少一个条件。如果when包含多个条件,所有的子条件必须返回True,stage才能执行。
①内置条件
②在进入stage的agent前评估when
默认情况下,如果定义了某个stage的agent,在进入该stage的agent后,该stage的when条件才会被评估。但是可以通过在when块中指定beforeAgent选项来更改此选项。如果beforeAgent被设置为True,那么就会首先对when条件进行评估,并且只有在when条件验证为真时才会进入agent。
③示例
示例1:当branch为production时才会执行名为Example Deploy的stage。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example Build') { steps { echo 'Hello World' } } stage('Example Deploy') { when { branch 'production' } steps { echo 'Deploying' } } } }
示例2:当branch为production,environment的DEPLOY_TO为production才会执行名为Example Deploy的stage。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example Build') { steps { echo 'Hello World' } } stage('Example Deploy') { when { branch 'production' environment name: 'DEPLOY_TO', value: 'production' } steps { echo 'Deploying' } } } }
示例3:当branch为production并且DEPLOY_TO为production时才会执行名为Example Deploy的stage。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example Build') { steps { echo 'Hello World' } } stage('Example Deploy') { when { allOf { branch 'production' environment name: 'DEPLOY_TO', value: 'production' } } steps { echo 'Deploying' } } } }
示例4:当DEPLOY_TO等于production或者staging时才会执行名为Example Deploy的stage。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example Build') { steps { echo 'Hello World' } } stage('Example Deploy') { when { branch 'production' anyOf { environment name: 'DEPLOY_TO', value: 'production' environment name: 'DEPLOY_TO', value: 'staging' } } steps { echo 'Deploying' } } } }
示例5:当BRANCH_NAME为production或者staging,并且DEPLOY_TO为production或者staging时才会执行名为Example Deploy的stage。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example Build') { steps { echo 'Hello World' } } stage('Example Deploy') { when { expression { BRANCH_NAME ==~ /(production|staging)/ } anyOf { environment name: 'DEPLOY_TO', value: 'production' environment name: 'DEPLOY_TO', value: 'staging' } } steps { echo 'Deploying' } } } }
示例6:在进行agent前执行判断,当branch为production时才会进行该agent。
Jenkinsfile (Declarative Pipeline) pipeline { agent none stages { stage('Example Build') { steps { echo 'Hello World' } } stage('Example Deploy') { agent { label "some-label" } when { beforeAgent true branch 'production' } steps { echo 'Deploying' } } } }
steps
Steps包含一个完整的script步骤列表。Script步骤需要scripted-pipeline块并在声明式流水线中执行。对于大多数用例来说,script步骤并不是必要的。
示例:在steps添加script进行for循环。
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' script { def browsers = ['chrome', 'firefox'] for (int i = 0; i < browsers.size(); ++i) { echo "Testing the ${browsers[i]} browser" } } } } } }
脚本化流水线与声明式流水线一样都是建立在底层流水线的子系统上,与声明式流水线不同的是,脚本化流水线实际上是由Groovy构建。Groovy语言提供的大部分功能都可以用于脚本化流水线的用户,这意味着它是一个非常有表现力和灵活的工具,可以通过它编写持续交付流水线。
脚本化流水线和其他传统脚本一致都是从Jenkinsfile的顶部开始向下串行执行,因此其提供的流控制也取决于Groovy表达式,比如:if/else条件:
Jenkinsfile (Scripted Pipeline) node { stage('Example') { if (env.BRANCH_NAME == 'master') { echo 'I only execute on the master branch' } else { echo 'I execute elsewhere' } } }
另一种方法是使用Groovy的异常处理支持来管理脚本化流水线的流控制,无论遇到什么原因的失败,它们都会抛出一个异常,处理错误的行为必须使用Groovy中的try/catch/finally块,例如:
Jenkinsfile (Scripted Pipeline) node { stage('Example') { try { sh 'exit 1' } catch (exc) { echo 'Something failed, I should sound the klaxons!' throw } } }
上面讲过流水线支持两种语法,即声明式和脚本式,这两种语法都支持构建持续交付流水线。并且都可以用来在Web UI或Jenkinsfile中定义流水线,不过通常将Jenkinsfile放置于代码仓库中。
创建一个Jenkinsfile并将其放置于代码仓库中,有以下好处:
本节主要介绍Jenkinsfile常见的模式以及演示Jenkinsfile的一些特例。
Jenkinsfile是一个文本文件,它包含了Jenkins流水线的定义并被用于源代码控制。以下流水线实现了3个基本的持续交付:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Build') { steps { echo 'Building..' } } stage('Test') { steps { echo 'Testing..' } } stage('Deploy') { steps { echo 'Deploying....' } } } }
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) node { stage('Build') { echo 'Building....' } stage('Test') { echo 'Testing....' } stage('Deploy') { echo 'Deploying....' } }
注意:不是所有的流水线都有相同的三个阶段。
构建
对于许多项目来说,流水线中工作(work)的开始就是构建(build),这个阶段的主要工作是进行源代码的组装、编译或打包。Jenkinsfile文件不是替代现有的构建工具,如GNU/Make、Maven、Gradle等,可以视其为一个将项目开发周期的多个阶段(构建、测试、部署等)绑定在一起的粘合层。
Jenkins有许多插件用于构建工具,假设系统为Unix/Linux,只需要从shell步骤(sh)调用make即可进行构建,Windows系统可以使用bat:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Build') { steps { sh 'make' archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true } } } }
说明:
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) node { stage('Build') { sh 'make' archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true } }
测试
运行自动化测试是任何成功的持续交付过程中的重要组成部分,因此Jenkins有许多测试记录、报告和可视化工具,这些工具都是由插件提供。下面的例子将使用JUnit插件提供的junit工具进行测试。
在下面的例子中,如果测试失败,流水线就会被标记为不稳定,这时Web 界面中的球就显示为黄色。基于记录的测试报告,Jenkins也可以提供历史趋势分析和可视化:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Test') { steps { /* `make check` returns non-zero on test failures, * using `true` to allow the Pipeline to continue nonetheless */ sh 'make check || true' junit '**/target/*.xml' } } } }
说明:
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) node { /* .. snip .. */ stage('Test') { /* `make check` returns non-zero on test failures, * using `true` to allow the Pipeline to continue nonetheless */ sh 'make check || true' junit '**/target/*.xml' } /* .. snip .. */ }
部署
当编译构建和测试都通过后,就会将编译生成的包推送到生产环境中,从本质上讲,Deploy阶段只可能发生在之前的阶段都成功完成后才会进行,否则流水线会提前退出:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Deploy') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' } } steps { sh 'make publish' } } } }
说明:当前build结果为SUCCESS时,执行publish。
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) node { /* .. snip .. */ stage('Deploy') { if (currentBuild.result == null || currentBuild.result == 'SUCCESS') { sh 'make publish' } } /* .. snip .. */ }
本节主要介绍Jenkins使用中如何处理Jenkinsfile及Jenkinsfile的编写方式。
插入字符串
Jenkins使用与Groovy相同的规则进行字符串赋值,可以使用单引号或者双引号进行赋值。例如:
def singlyQuoted = 'Hello' def doublyQuoted = "World"
引用变量需要使用双引号:
def username = 'Jenkins' echo 'Hello Mr. ${username}' echo "I said, Hello Mr. ${username}"
运行结果:
Hello Mr. ${username} I said, Hello Mr. Jenkins
使用环境变量
Jenkins有许多内置变量可以直接在Jenkinsfile中使用,比如:
使用env.BUILD_ID和env.JENKINS_URL引用内置变量:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Example') { steps { echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}" } } } }
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) node { echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}" }
更多参数请参考: https://wiki.jenkins.io/displa ... ables 。
处理凭证
本节主要介绍在Jenkins的使用过程中对一些机密文件的处理方式。
机密文件、用户名密码、私密文件:
Jenkins的声明式流水线语法有一个credentials()函数,它支持secrettext(机密文件)、usernameandpassword(用户名和密码)以及secretfile(私密文件)。
①机密文件示例
本实例演示将两个Secret文本凭证分配给单独的环境变量来访问Amazon Web服务,需要提前创建这两个文件的credentials(下面章节会有演示),Jenkinsfile文件的内容如下:
Jenkinsfile (Declarative Pipeline) pipeline { agent { // Define agent details here } environment { AWS_ACCESS_KEY_ID = credentials('jenkins-aws-secret-key-id') AWS_SECRET_ACCESS_KEY = credentials('jenkins-aws-secret-access-key') } stages { stage('Example stage 1') { steps { // } } stage('Example stage 2') { steps { // } } } }
说明:上述示例定义了两个全局变量AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY,这两个变量引用的是credentials的两个文件,并且这两个变量均可以在stages直接引用($AWS_SECRET_ACCESS_KEY和$AWS_ACCESS_KEY_ID)。
注意:如果在steps中使用echo $AWS_ACCESS_KEY_ID,此时返回的是****,加密内容不会被显示出来。
②用户名密码
本示例用来演示credentials账号密码的使用,比如使用一个公用账户访问Bitbucket、GitLab、Harbor等,假设已经配置完成了用户名密码的credentials,凭证ID为jenkins-bitbucket-common -creds。
可以用以下方式设置凭证环境变量:
environment { BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds') }
这里实际设置了下面的3个环境变量:
此时,调用用户名密码的Jenkinsfile如下:
Jenkinsfile (Declarative Pipeline) pipeline { agent { // Define agent details here } stages { stage('Example stage 1') { environment { BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds') } steps { // } } stage('Example stage 2') { steps { // } } } }
注意:此时环境变量的凭证仅作用于stage 1。
处理参数
声明式流水线的参数支持开箱即用,允许流水线在运行时通过parametersdirective接受用户指定的参数。
如果将流水线配置为Build_With_Parameters选项用来接受参数,那么这些参数将会作为params变量被成员访问。假设在Jenkinsfile中配置了名为Greeting的字符串参数,可以通过${params.Greeting}访问该参数,比如:
Jenkinsfile (Declarative Pipeline) pipeline { agent any parameters { string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?') } stages { stage('Example') { steps { echo "${params.Greeting} World!" } } } }
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) properties([parameters([string(defaultValue: 'Hello', description: 'How should I greet the world?', name: 'Greeting')])]) node { echo "${params.Greeting} World!" }
处理失败
声明式流水线默认通过postsection支持健壮的失败处理方式,允许声明许多不同的postconditions,比如always、unstable、success、failure和changed,具体可参考Pipeline的语法。
比如,以下是构建失败发送邮件通知的示例:
Jenkinsfile (Declarative Pipeline) pipeline { agent any stages { stage('Test') { steps { sh 'make check' } } } post { always { junit '**/target/*.xml' } failure { mail to: team@example.com, subject: 'The Pipeline failed :(' } } }
对应的脚本式流水线如下:
Jenkinsfile (Scripted Pipeline) node { /* .. snip .. */ stage('Test') { try { sh 'make check' } finally { junit '**/target/*.xml' } } /* .. snip .. */ }
使用多个代理
流水线允许在Jenkins环境中使用多个代理,这有助于更高级的用例,例如跨多个平台执行构建、测试等。
比如,在Linux和Windows系统的不同agent上进行测试:
Jenkinsfile (Declarative Pipeline) pipeline { agent none stages { stage('Build') { agent any steps { checkout scm sh 'make' stash includes: '**/target/*.jar', name: 'app' } } stage('Test on Linux') { agent { label 'linux' } steps { unstash 'app' sh 'make check' } post { always { junit '**/target/*.xml' } } } stage('Test on Windows') { agent { label 'windows' } steps { unstash 'app' bat 'make check' } post { always { junit '**/target/*.xml' } } } } }
本文节选自《再也不踩坑的Kubernetes实战指南》。