Loading

devops-2:Jenkins的使用及Pipeline语法讲解

DevOps-Jenkins

Jenkins简介

Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件项目可以进行持续集成。

什么是持续集成(CI)?

CI(Continuous integration,中文意思是持续集成)是一种软件开发实践。持续集成强调开发人员提交了新代码之后,立刻进行构建、编译、(单元)测试等这个过程,每次提交新代码都要进行此类重复操作,为了提高工作效率,避免重复工作及重复工作导致差别化问题。

什么是持续部署(CD)?

CD(Continuous Delivery, 中文意思持续交付)是在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境中,也就是说它是CI后紧跟的一个环节,当代码审核完毕打包成成品后,需要部署到真实的环境中去,这个过程也会根据代码的跟新持续的进行。

Jenkins安装

采用docker形式安装,首先安装好docker服务,然后在安装机器中对要存储Jenkins数据的目录进行赋权:

# groupadd jenkins -g 1000 && useradd jenkins -u 1000 -g jenkins
# chown -R 1000:1000 /jenkins

uid 和 gid必须是1000,和Jenkins镜像中的对应

开始安装:

# docker network create jenkins

# docker run \
  --name jenkins-docker \
  --detach \
  --privileged \
  --network jenkins \
  --network-alias docker \
  --env DOCKER_TLS_CERTDIR=/certs \
  --volume /jenkins/certs:/certs/client \
  --volume /jenkins/data:/var/jenkins_home \
  --publish 8080:8080 \
  --publish 50000:50000 \
  jenkins/jenkins:latest

## 执行此命令获取初始密码
# docker exec -it jenkins-docker cat /var/jenkins_home/secrets/initialAdminPassword

这个时候就可以通过 serverip:8080 来访问到Jenkins。

流水线(pipeline)概括

想要更好的使用Jenkins,必须掌握其流水线(pipeline)的使用。

流水线概述

  • 默认文件名为 Jenkinsfile
  • 采用 Groovy 语法
  • 其可以实现对代码的整合、编译、质量检测或部署等一系列操作,相当于我们说的“脚本”

流水线脚本的分类包括:脚本式、声明式

  • 脚本式语法结构

    node {
        stage('Example') {
            try {
                sh 'exit 1'
            }
            catch (exc) {
                echo 'Something failed, I should sound the klaxons!'
                throw
            }
        }
    }
    
  • 声明式语法结构

    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"
                        }
                    }
                }
            }
        }
    }
    

我们应该用哪种方式来编写 Jenkinsfile?

推荐使用声明式语法结构来编写 Jenkinsfile,声明式语法结构的灵活性及可读性都要比脚本式语法结构要高,并且声明式结构中可以包含脚本式结构,可以说更强大一些,并且便于学习。

如何快速上手Jenkinsfile?

  1. 多看Jenkinsfile示例文件,及官方的语法介绍:Pipeline Syntax (jenkins.io),对语法有一个基础掌握。
  2. 学会使用脚本生成器

声明式常用语法介绍

  1. 最外层的pipeline,整条流水线的开始,里边定义了流水线所有内容

    pipeline{
    
    }
    
  2. agent,指定了流水线的执行节点,可以在pipeline的下行指定,也可以在stages的下行指定,agent有四个可选参数:

    • any:在任意节点执行pipeline
    • none:未定义,当顶层的agent none时,必须在每个stage下再单独定义agent
    • label:指定运行节点的label
    • node:自定义运行节点配置,例如指定label或customWorkspace,agent { node { label 'labelName' } } 和 agent { label 'labelName' }相同, 但 node支持其他选项 (比如customWorkspace).
    pipeline{
        agent{
            node{
                label "container"
                customWorkspace '/some/other/path'  //指定自定义的工作空间,其实就是指定自定义路径
            }
        }
    }
    
  3. stages(阶段):包含一个或多个stage的序列,大部分执行操作都在这里,将每个离散部分串接起来,比如构建、测试和部署。

  4. stage:包含在stages中,pipeline完成的所有实际工作都需要包含到stage中,每一个阶段都要有一个名字。

  5. steps(步骤):1个stage中,一般都要运行1个steps,里边可以直接运行linux命令。相当于3-5点是一个连续的、互相关联的,例如:

    pipeline {
        agent any
        stages { 
            stage('Example') {
                agent {  //选择执行节点,可以在此层级指定
                  label 'nginx'
                }
                steps {  //执行的步骤
                    echo 'Hello World'
                }
            }
        }
    }
    
  6. post:定义Pipeline或stage运行结束时的操作有以下多个参数可选:

    • always:无论Pipeline运行的完成状态如何都会运行

    • changed:只有当前Pipeline或steps运行的状态与先前完成的Pipeline的状态不同时,才能运行

    • failure:仅当当前Pipeline或steps处于“失败”状态时才运行

    • success:仅当当前Pipeline或steps具有“成功”状态时才运行

    • unstable:只有当前Pipeline或steps具有“不稳定”状态才能运行

    • aborted:只有当前Pipeline或steps处于“中止”状态时才能运行

    • unsuccessful:只有当前Pipeline或steps运行为“未成功”状态才能运行

    • cleanup:无论Pipeline或steps运行后为什么状态,只要前边定义的状态都没匹配到,则运行

    示例:

    pipeline {
        agent any
        stages {
            stage('Example') {
                steps {
                    echo 'Hello World'
                }
            }
        }
        post { 
            always { 
                echo 'I will always say Hello again!'
            }
        }
    }
    
  7. environment:环境变量,可以定义全局变量和特定的阶段变量,取决于其在流水线的位置,该指定支持一个特殊的方法credentials(),此方法可用于在Jenkins环境中通过标识符访问预定义的凭证,其有很多种类型:

    • Secret Text:指定的环境变量将设置为密文内容
    • Secret File:指定的环境变量将设置为临时创建的文件的位置
    • Username and password:指定的环境变量将设置为username:password,并将自动定义为两个环境变量:MYVARNAME_USR和MYVARNAME_PSW
    • SSH with Private Key:指定的环境变量将设置为临时创建的SSH密钥文件的位置,并且可以自动定义为两个环境变量:MYVARNAME_USR和MYVARNAME_PSW

    示例1:

    pipeline {
        agent any
        environment { 
            CC = 'clang'
        }
        stages {
            stage('Example Secret Text') {
                environment { 
                    // 需要提前将凭据创建好Secret Text类型的凭据
                    AN_ACCESS_KEY = credentials('my-predefined-secret-text') 
                }
                steps {
                    sh 'printenv'
                }
            }
        }
    }
    

    示例2:

    pipeline {
        agent any
        stages {
            stage('Example Username/Password') {
                environment {
                    // 提前创建好Username and Password类型的凭据
                    SERVICE_CREDS = credentials('my-predefined-username-password')
                }
                steps {
                    sh 'echo "Service user is $SERVICE_CREDS_USR"'
                    sh 'echo "Service password is $SERVICE_CREDS_PSW"'
                }
            }
            stage('Example SSH Username with private key') {
                environment {
                    // 提前创建好SSH Username with private key类型的凭据
                    SSH_CREDS = credentials('my-predefined-ssh-creds')
                }
                steps {
                    sh 'echo "SSH private key is located at $SSH_CREDS"'
                    sh 'echo "SSH user is $SSH_CREDS_USR"'
                    sh 'echo "SSH passphrase is $SSH_CREDS_PSW"'
                }
            }
        }
    }
    
  8. options:此指令允许从Pipeline本身中配置一些特定的选项。Pipeline提供了许多这样的选项,例如buildDiscarder,也可以由插件提供,例如timestamps。分享一些参数:

    • buildDiscarder:保持构建的最大个数,示例:options { buildDiscarder(logRotator(numToKeepStr: '1')) }

    • checkoutToSubdirectory:在workspace的子目录中执行源代码自动管理切换,示例:options { checkoutToSubdirectory('foo') }

    • disableConcurrentBuilds:不允许同时执行管道。可用于防止同时访问共享资源等,示例:options { disableConcurrentBuilds() }

    • disableResume:如果控制器重新启动,则不允许恢复pipeline,示例: options { disableResume() }

    • newContainerPerStage:使用docker或dockerfile agent时。每个stage将在同一节点上的新容器实例中运行,而不是在同一容器实例中运行的所有stages。

    • overrideIndexTriggers:

    • retry:失败时,按指定次数重试整个管道,可以针对Pipeline或者Stage,示例: options { retry(3) }

    • skipStagesAfterUnstable:一旦构建状态变得不稳定,就跳过各个stages。示例: options { skipStagesAfterUnstable() }

    • timeout:设置管道运行的超时时间,此参数可以针对Pipeline或者Stage,例如:options { timeout(time: 1, unit: 'HOURS') }

    • timestamps: 所有控制台输出前加上线路发出的时间,此参数可以针对Pipeline或者Stage,示例:options { timestamps() }

      pipeline {
          agent any
          options {
              timeout(time: 1, unit: 'HOURS') 
              timestamps()
              buildDiscarder(logRotator(numToKeepStr: '10'))
          }
          stages {
              stage('Example') {
                  options {
                      timeout(time: 1200, unit: 'SECONDS') 
                  }
                  steps {
                      echo 'Hello World'
                  }
              }
          }
      }
      
  9. parameters:为Pipeline运行前提供参数,有几种参数类型供选择:

    • string:字符串类型,示例:parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }
    • text:文本类型,可以包含多行,示例: parameters { text(name: 'DEPLOY_TEXT', defaultValue: 'One\nTwo\nThree\n', description: '') }
    • booleanParam:布尔类型,示例: parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }
    • choice:选择类型,多个选项任选其一,示例: parameters { choice(name: 'CHOICES', choices: ['one', 'two', 'three'], description: '') }
    • password:密码类型,可以提前设置密码,示例: parameters { password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'A secret password') }
    pipeline {
        agent any
        parameters {
            string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
            text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')
            booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')
            choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
            password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
        }
        stages {
            stage('Example') {
                steps {
                    echo "Hello ${params.PERSON}"
                    echo "Biography: ${params.BIOGRAPHY}"
                    echo "Toggle: ${params.TOGGLE}"
                    echo "Choice: ${params.CHOICE}"
                    echo "Password: ${params.PASSWORD}"
                }
            }
        }
    }
    

    使用parameters参数有一个bug,首次构建时不会让你选择参数,第二次才可以选择。

  10. triggers:触发器,定义了Pipeline自动化触发的方式,可触发的方式有:

    • cron:计划任务定期触发,示例: triggers { cron('H */4 * * 1-5') }

    • pollSCM:与cron方式类似,但是必须发现有源码的变化,才会触发,示例: triggers { pollSCM('H */4 * * 1-5') }

      pollSCM触发器仅在Jenkins 2.22或更高版本中可用。

    • upstream:接受以逗号分隔的作业字符串和阈值。当字符串中的任何作业以最小阈值结束时,将会触发,示例:triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }

    pipeline {
        agent any
        triggers {
            cron('H */4 * * 1-5')
        }
        stages {
            stage('Example') {
                steps {
                    echo 'Hello World'
                }
            }
        }
    }
    

    Jenkins cron syntax

    Jenkins cron语法遵循cron公共的语法(略有不同),每行由5个字段组成,由制表符或空格分隔。

    分钟 小时
    Minutes within the hour (0–59) The hour of the day (0–23) The day of the month (1–31) The month (1–12) The day of the week (0–7) where 0 and 7 are Sunday.

    要为一个字段指定多个值,可以使用以下运算符。按优先顺序展示:

    • * 指定所有的值
    • M-N 指定值得范围
    • M-N/X or */X 在指定的范围或者整个范围内,按照 X的值为间隔步长
    • A,B,…,Z指定多个值

    为了允许定期调度的任务在系统上产生均匀负载,应尽可能使用符号H(表示“哈希”),使其执行pipeline的时间分散,更好的利用资源。

    此外,支持使用 @yearly, @annually, @monthly, @weekly, @daily, @midnight, and @hourly 作为别名,他们都等同于使用哈希自动的进行均衡,例如@hourlyH * * * *相同,表示1小时内任意时间执行即可。

    举一些cron例子:

    1. 每15分钟执行一次,可以是 at :07, :22, :37, :52:triggers{ cron('H/15 * * * *') }
    2. 在每小时的前30分钟,每10分钟执行一次:triggers{ cron('H(0-29)/10 * * * *') }
    3. 每周的周一到周五的,9点-16点之间的第45分钟每两个小时执行一次:triggers{ cron('45 9-16/2 * * 1-5') }
    4. 1-11月的每个1号和15号都要随机执行一次:triggers{ cron('H H 1,15 1-11 *') }
  11. tools:工具,目前仅支持三种工具:maven、jdk和gradle。工具的名称必须在系统设置—>全局工具配置中定义。定义好后即可开始调用:

    pipeline {
        agent any
        tools {
            maven 'apache-maven-3.0.1' 
        }
        stages {
            stage('Example') {
                steps {
                    sh 'mvn --version'
                }
            }
        }
    }
    

    第一次运行此工具时,如果没有会去下载,或者会根据你指定的获取方式去获取,比如直接从其他机器打包过来解压等方式。

  12. input:stage 的 input 指令允许你使用 input step提示输入。 在应用了 options 后,进入 stage 的 agent 或评估 when 条件前, stage 将暂停。 如果 input 被批准, stage 将会继续。 作为 input 提交的任何参数都将在环境中用于其他 stage 使用。可选配置:

    • message:必需的。 呈现给用户的信息。
    • id:可选的, 默认为 stage 名称。
    • ok:可选的,表单中 ok 按钮的描述文本。
    • submitter:可选的,以逗号分割的用户列表,只有列表用户才可以提交input内容。
    • parameters:可选参数列表,与前文介绍的相同。

    示例:

    pipeline {
        agent any
        stages {
            stage('Example') {
                input {
                    message "Should we continue?"
                    ok "Yes, we should."
                    submitter "alice,bob"
                    parameters {
                        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
                    }
                }
                steps {
                    echo "Hello, ${PERSON}, nice to meet you."
                }
            }
        }
    }
    
  13. when:逻辑判断,可以给定条件决定要不要执行此stage,when指令必须包含至少一个条件,如果when包含多个条件,则必须所有的条件都返回true,stage才会运行。内置条件有:

    • brance:分支匹配,当正在构建的分支与预设给定的分支匹配时,执行此stage,例如: when { branch 'master' },此参数仅适用于多分支流水线。
    • 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:当所有的嵌套条件都正确时(当when中有多个条件时,默认就是此策略),执行这个此stage,必须包含至少一个条件,例如: when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
    • anyOf:当至少有一个嵌套条件为真时,执行这个阶段,必须包含至少一个条件,例如: when { anyOf { branch 'master'; branch 'staging' } }
    • beforeAgent:默认为false,默认情况下,如果在stage中包含agent,则会在执行when之前会先执行agent,如果将beforeAgent设置为true,则表示先执行when条件,when条件判断为执行stage后,才执行agent。

    示例:

    pipeline {
        agent any
        parameters {
            choice(name: 'BRANCH_NAME', choices: ['production', 'staging'], description: 'Pick something')
            string defaultValue: 'production', name: 'DEPLOY_TO', trim: true
        }
        stages {
            stage('Example Build') {
                when {
                    expression { BRANCH_NAME ==~ /(production|staging)/ }
                }
                steps {
                    echo "Start building ${BRANCH_NAME}"
                }
            }
            stage('Example Deploy') {
                when {
                    anyOf {
                        environment name: 'DEPLOY_TO', value: 'production'
                        environment name: 'DEPLOY_TO', value: 'staging'
                    }
                }
                steps {
                    echo 'Deploying'
                }
            }
        }
    }
    
  14. Parallel:并行,声明式流水线的stage可以在他们内部声明多个嵌套stage, 它们将并行执行。注意,一个stage必须只有一个 stepsparallel ,嵌套的stage本身不能再进入 parallel 阶段,任何包含parallelstage不能包含agenttools,因为它们没有相关的step

    另外,可以添加 failFast true 到包含 parallelstage 中,表示当其中一个进程失败时,所有的 parallel 阶段都将被终止。

    示例:

    pipeline {
        agent any
        options {
            parallelsAlwaysFailFast() //表示后续所有的parallel阶段中都设置为failFast true
            timestamps()
        }
        stages {
            stage('Build Stage') {
                steps {
                    echo 'This stage is Build.'
                }
            }
            stage('Parallel Stage --> Deploy') {
                when {
                    not{
                        branch 'prod'
                    }
                }
                parallel {
                    stage('Branch dev') {
                        agent {
                            label "dev"
                        }
                        steps {
                            echo "On Branch dev deploy"
                        }
                    }
                    stage('Branch int') {
                        agent {
                            label "int"
                        }
                        steps {
                            echo "On Branch int deploy"
                        }
                    }
                    stage('Branch test') {
                        agent {
                            label "test"
                        }
                        stages {
                            stage('Nested 1') {
                                steps {
                                    echo "In stage Nested 1 within Branch test"
                                }
                            }
                            stage('Nested 2') {
                                steps {
                                    echo "In stage Nested 2 within Branch test"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    此示例中:只要当branch不是prod时,则将同时在dev/int/test三个环境中deploy,test环境的deploy又分为两个stage,此两个stage并不是并行执行,而是串行执行。

  15. script:脚本式语法,可以在声明式流水线中使用,包含在step里,调用script { }块,示例如下:

    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"
                        }
                    }
                }
            }
        }
    }
    

脚本式语法

脚本式pipeline也是建立在底层流水线上的,与声明式不同的是, 脚本化流水线实际上是由 Groovy构建的通用 DSL, Groovy 语言提供的大部分功能都可以用于脚本化流水线。

  1. 脚本化流水线从 Jenkinsfile 的顶部开始向下串行执行, 就像 Groovy 或其他语言中的大多数传统脚本一样。 因此,提供流控制取决于 Groovy 表达式, 比如 if/else 条件, 例如:

    node {
        stage('Example') {
            if (env.BRANCH_NAME == 'master') {
                echo 'I only execute on the master branch'
            } else {
                echo 'I execute elsewhere'
            }
        }
    }
    
  2. 另一种方法是使用Groovy的异常处理支持来管理脚本化流水线流控制。当 步骤 失败 ,无论什么原因,它们都会抛出一个异常。处理错误的行为必须使用Groovy中的 try/catch/finally 块 , 例如:

    node {
        stage('Example') {
            try {
                sh 'exit 1'
            }
            catch (exc) {
                echo 'Something failed, I should sound the klaxons!'
                throw
            }
        }
    }
    
posted @ 2022-08-03 14:35  塔克拉玛攻城狮  阅读(1147)  评论(2编辑  收藏  举报