转载

生产环境下CI/CD的使用

本章主要介绍在生产环境中持续集成与持续部署的使用,主要通过实现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、Directives和Steps,本节首先介绍agent、Stages、post,有关Directives和Steps的说明见下一小节。

agent:

agent部分指定了整个流水线或特定的部分,在Jenkins环境中执行的位置取决于agent区域的位置,该部分必须在Pipeline块的顶层被定义,但是stage级别的使用是可选的。

①参数

为了支持可能有的各种各样的流水线,agent部分支持一些不同类型的参数,这些参数应用在pipeline块的顶层,或Stage指令内部。

  • any:在任何可用的代理上执行流水线或stage。例如:agent any。
  • none:当在pipeline块的顶部没有全局agent,该参数将会被分配到整个流水线的运行中,并且每个stage部分都需要包含它自己的agent,比如:agent none。
    在提供了标签的Jenkins环境中可用代理上执行流水线或stage。例如:agent { label ‘my-defined-label’}。
  • node:agent { node { label ‘labelName’} } 和 agent { label ‘labelName’ }一样,但是node允许额外的选项(比如customWorkspace)。
  • dockerfile:执行流水线或stage,使用从源码包含的Dockerfile所构建的容器。为了使用该选项,Jenkinsfile必须从多个分支流水线中加载,或者加载Pipelinefrom SCM(下面章节会涉及)。通常,这是源码根目录下的Dockerfile:agent { dockerfile true }。如果在其他目录下构建Dockerfile,使用dir选择:agent { dockerfile { dir ‘someSubDir’} }。如果Dockerfile有另一个名字,可以使用filename选项指定该文件名。也可以传递额外的参数到dockerbuild,使用additionalBuildArgs选项提交,比如:agent { dockerfile { additionalBuildArgs ‘--build-arg foo=bar’ } }。例如一个带有build/Dockerfile.build的仓库,在构建时期望一个参数version:
    agent {
    
    dockerfile {
    
    filename ‘Dockerfile.build’
    
        dir ‘build’
    
        label ‘my-defined-label’
    
        additionalBuildArgs ‘--build-arg version=1.0.2’
    
    }
    
    } 
    
  • docker:使用给定的容器执行流水线或stage。该容器将在预置的node(节点)上,或在由label参数指定的节点上,动态地接受基于Docker的流水线。Docker也可以接受args参数,该参数可能包含直接传递到dockerrun调用的参数及alwaysPull选项,alwaysPul选项强制dockerpull,即使镜像(image)已经存在。比如:agent { docker ‘maven:3-alpine’ }或:
    agent{
    
    docker{
    
    image ‘maven:3-alpine’
    
        label ‘my-defined-label’
    
        args ‘-v /tmp:/tmp’
    
    }
    
    } 
    
    ②常见选项
  • label:一个字符串,该标签用于运行流水线或个别stage。该选项对node、docker和dockerfile可用,node必须选择该选项。
  • customWorkspace:一个字符串,在自定义工作区运行流水线或stage。它可以是相对路径,也可以是绝对路径,该选项对node、docker和dockerfile可用。比如:
    agent {
    
    node {
    
    label 'my-defined-label'
    
    customWorkspace '/some/other/path'
    
    }
    
    } 
    
  • reuseNode:一个布尔值,默认为false。如果是true,则在流水线顶层指定的节点上运行该容器。这个选项对docker和dockerfile有用,并且只有当使用在个别stage的agent上才会有效。

③示例

示例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块之一:

  • always:无论流水线或stage的完成状态如何,都允许在post部分运行该步骤。
  • changed:只有当前流水线或stage的完成状态与它之前的运行不同时,才允许在post部分运行该步骤。
  • failure:只有当前流水线或stage的完成状态为失败(failure),才允许在post部分运行该步骤,通常这时在Web 界面中显示为红色。
  • success:当前状态为成功(success),执行post步骤,通常在Web界面中显示为蓝色或绿色。
  • unstable:当前状态为不稳定(unstable),执行post步骤,通常由于测试失败或代码违规等造成,在Web 界面中显示为黄色。
  • aborted:当前状态为放弃(aborted),执行post步骤,通常由于流水线被手动放弃触发,这时在Web 界面中显示为灰色。

示例:一般情况下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:字符串类型的参数,例如:parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }。
  • booleanParam:布尔参数,例如: parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }。

示例:定义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才能执行。

①内置条件

  • branch:当正在构建的分支与给定的分支匹配时,执行这个stage,例如:when { branch 'master' }。注意,branch只适用于多分支流水线。
    λ environment:当指定的环境变量和给定的变量匹配时,执行这个stage,例如:when { environment name: 'DEPLOY_TO', value: 'production' }。
  • expression:当指定的Groovy表达式评估为True,执行这个stage,例如:when { expression { return params.DEBUG_BUILD } }。
  • not:当嵌套条件出现错误时,执行这个stage,必须包含一个条件,例如:when { not { branch 'master' } }。
  • allOf:当所有的嵌套条件都正确时执行这个stage,必须包含至少一个条件,例如:when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
  • anyOf:当至少有一个嵌套条件为True时执行这个stage,例如:when { anyOf { branch 'master'; branch 'staging' } }。

②在进入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

    }

}

} 

Jenkinsfile的使用

上面讲过流水线支持两种语法,即声明式和脚本式,这两种语法都支持构建持续交付流水线。并且都可以用来在Web UI或Jenkinsfile中定义流水线,不过通常将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 

        }

    }

}

} 

说明:

  • steps的shmake表示如果命令的状态码为0,则继续,为非零则失败。
  • archiveArtifacts用于捕获构建后生成的文件。

对应的脚本式流水线如下:

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' 

        }

    }

}

} 

说明:

  • 当sh步骤状态码为0时,调用junit进行测试。
  • junit捕获并关联匹配**/target/*.xml的Junit 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 .. */

} 

处理Jenkinsfile

本节主要介绍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中使用,比如:

  • BUILD_ID:当前构建的ID,与Jenkins版本1.597+中的BUILD_NUMBER完全相同。
  • JOB_NAME:本次构建的项目名称。
  • JENKINS_URL:Jenkins完整的URL,需要在System Configuration设置。

使用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个环境变量:

  • BITBUCKET_COMMON_CREDS,包含一个以冒号分隔的用户名和密码,格式为username:password。
  • BITBUCKET_COMMON_CREDS_USR,仅包含用户名的附加变量。
  • BITBUCKET_COMMON_CREDS_PSW,仅包含密码的附加变量。

此时,调用用户名密码的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实战指南》。

原文  http://dockone.io/article/9385
正文到此结束
Loading...