Jenkins Pipeline

介绍

  Jenkins Pipeline(或简称为“ Pipeline”,以大写字母“ P”表示)是一套插件,是 Jenkins 2.0 的核心特性,可支持将持续交付过程中的多个步骤集成到 Jenkins 中。

   从根本上说,Jenkins 是一个支持多种自动化模式的自动化引擎。Pipeline 为 Jenkins 添加了一组强大的自动化工具,支持从简单的 CI 到全面的 CD 管道的用例。通过对一系列相关任务进行建模,用户可以利用管道的许多特性。实现了构建步骤代码化、结构化、构建过程视图化的能力。

Code: 代码性,Pipeline 是用代码实现的,通常会集成到源代码管理中(版本管理),从而使团队能够编辑,查看和迭代其交付流水线。

Durable: 持久性,Pipeline 可以在 Jenkins master 的计划重启和计划外重启中存活。

Pausable:暂停性,Pipeline 可以选择停止并等待人工输入或批准,然后再继续管道运行。

Versatile: 多样性,Pipelines 支持复杂的真实 CD 需求, 包括分支、级联、循环、并行执行等。

Extensible: 扩展性,Pipeline 插件支持对 DSL 自定义扩展,以及与其他插件集成实现复杂功能的能力。

官方介绍:https://www.jenkins.io/doc/book/pipeline/

概念

   以下概念是 Jenkins Pipeline 的关键,它们与 Pipeline 语法紧密相关。

Pipeline:一个 Pipeline 对应一个用户定义的 CD 模型。Pipeline 的代码描述了整个构建过程,通常包括构建、测试、验证、交付等阶段。

Node:一个 Node 对应一个 Jenkins 集群环境中的一个节点,它能够执行 Pipeline 定义的描述。

Stage:一个 Stage 块对应描述了在 Pipeline 中不同的任务子集(阶段),例如构建、测试、部署 Stages。

Step:一个 Step 对应一个步骤,它告诉 Jenkins 在某个特定时间点应该作什么。例如执行 shell 命令,执行 mvn 命令等。

官方介绍:https://www.jenkins.io/doc/book/pipeline/#pipeline

语法

  Jenkins Declarative Pipeline 代码块一般定义在名为 Jenkinsfile 的文件中。

pipeline {
  agent any // 1
  stages {stage('Build') { // 2
          steps {
              // 3
              sh 'echo build'
          }
      }
      stage('Test') { // 4
          steps {
              // 5
              sh 'echo test'
          }
      }
      stage('Deploy') { // 6
          steps {
              // 7
              sh 'echo deploy'
          }
      }
  }
}
  1. 在任何可用的 agent 节点上执行 Piepline 定义的描述。

  2. 定义”构建”阶段。

  3. 执行与”构建”相关的一些步骤。

  4. 定义”测试”阶段。

  5. 执行与”测试”相关的一些步骤。

  6. 定义”发布”阶段

  7. 执行与”发布”相关的一些步骤。

      Pipeline post:Jenkins Pipeline 支持使用 Post 在执行执行结束后的各种结果状态中进行定义操作。

pipeline {
  agent any
  stages {stage('Test') {
          steps {...}
      }
  }
  post {always { // 1}
      success {// 2}
      failure {// 3}
      unstable {// 4}
      changed {// 5}
      aborted {// 6}
  }
}
  1. 构建结束后,结果无论是什么状态,总会执行该代码块中的内容。
  2. 构建结束后,结果是成功状态时执行该代码块中的内容。
  3. 构建结束后,结果是失败状态时,执行该代码块中的内容。
  4. 构建结束后,结果是不稳定状态时,执行该代码块的内容。
  5. 构建结束后,结果运行状态与之前的状态不同时,执行该代码块的内容。
  6. 构建结束后,结果是终止状态时,执行该代码块中的内容。

指令

environment:环境变量,一系列键值对,定义在 steps 外将对所有 step 可见,定义在 step 内仅对该 step 可见。该指令支持一种特殊的方法 credentials(),可以通过其在 Jenkins 环境中的标识符来访问预定义的凭据。

pipeline {
  agent any
  environment {JAVA_HOME = "/usr/local/java" // 全局变量}
  stages {stage(" 构建 ") {
          environment {NAME = "Raindrop" // 只在 Stage 中生效}
          steps {
              sh 'echo "Hello $NAME"'
              sh 'echo "Java_HOME $JAVA_HOME"'
          }
      }
      stage(" 扫描 ") {
          environment {NAME = "Tom" // 只在 Stage 中生效}
          steps {
              sh 'echo "Hello $NAME"'
              sh 'echo "Java_HOME $JAVA_HOME"'
          }
      }
  }
}

options:选项,Pipeline 内置了很多选项,用于在构建阶段中进行功能强化。例如 timestamps、buildDiscarder 等。

pipeline {
  agent any
  // 定义管道特殊选项
  options {skipDefaultCheckout() // 忽略 checkout 步骤
      timeout(time: 1, unit: 'HOURS') // 设置管道执行超时时间
      retry(3) // 设置管道失败重试次数
      timestamps() // 控制台输出时,显示时间戳}
  stages {stage("Example") {
          steps {echo "Hello pipeline!"}
      }
  }
}

parameters:参数,在触发 Pipeline 时可用的参数列表。参数列表中的值可以通过 params 对象获取。

pipeline {
  agent any
  parameters {
      // 字符串参数
      string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'This is string param')
      // 布尔值参数
      booleanParam(name: 'DEBUG', defaultValue: true, description: 'This is boolean param')
      // 选择参数
      choice(name: 'ENV_TYPE', choices: ['test', 'dev', 'prod'], description: 'This is choice param')
      // 文件参数
      file(name: 'FILE_NAME', description: 'This is file param')
      // 密码参数
      password(name: 'PASSWORD', defaultValue: '123456', description: 'This is password param')
  }
  stages {stage(" 字符串参数 ") {
          steps {
              script {echo "String param ${params.PERSON}"
              }
          }
      }
      stage(" 布尔值参数 ") {
          steps {
              script {echo "Boolean param ${params.DEBUG}"
              }
          }
      }
      stage(" 选择参数 ") {
          steps {
              script {echo "Choice param ${params.ENV_TYPE}"
              }
          }
      }
      stage(" 文件参数 ") {
          steps {
              script {echo "File param ${params.FILE_NAME}"
              }
          }
      }
      stage(" 密码参数 ") {
          steps {
              script {echo "Password param ${params.PASSWORD}"
              }
          }
      }
  }
}

triggers:触发器,定义了 Pipeline 自动化的触发方式。目前支持 cron、pollSCM 两种触发器,都是定时方式的实现。

pipeline {
  agent any
  // 触发器:
  // 如果使用的是 Gitlab、Github 等,有 webhook 功能的版本管理库,则可以不用使用。
  // 如果使用的是 Svn 就可以使用 Cron/PollSCM 两种方式触发。
  triggers {cron('H 4/* 0 0 1-5') // linux 语法
      pollSCM('H 4/* 0 0 1-5') // Jenkins 语法
  }
  stages {stage("Example") {
          steps {echo "Hello pipeline!"}
      }
  }
}

tools:工具,通过 tools 可以自动安装工具,并配置到环境变量 PATH 中。如果 agent none 则忽略。

pipeline {
    agent any
    tools {
        // 工具名称必须在 Jenkins 管理 Jenkins → 全局工具配置中预配置。
        maven 'apache-maven-3'
        java 'jdk8'
    }
    stages {stage('Example') {
            steps {
                sh 'mvn -version'
                sh 'java -version'
            }
        }
    }
}

when:分支判断,根据给定的条件判断是否执行该阶段任务。when 指令至少包含一个条件。如果 when 指令包含多个条件,则所有条件都为 true 时才会执行该阶段。

pipeline {
    agent any
    environment {ROLE_NAME = 'ADMIN'}
    stages {stage(' 初始化 ') {
            steps {
                script {
                    BRANCH_NAME = 'trunk'
                    SUBMITTER = "admin"
                }
            }
        }
        stage(' 分支判断 ') {
            when {branch 'trunk'}
            steps {echo "trunk 分支部署 "}
        }
        stage(' 变量判断 ') {
            when {
                environment name: 'ROLE_NAME',
                        value: 'ADMIN'
            }
            steps {echo " 角色为 ADMIN"}
        }
        stage(' 表达式判断 ') {
            when {
                expression {return true}
            }
            steps {echo " 表达式为 true"}
        }
        stage(' 为真判断 ') {
            when {
                equals expected: 'admin',
                        actual: SUBMITTER
            }
            steps {echo " 条件为真 "}
        }
        stage(' 为假判断 ') {
            when {
                not {branch 'dev'}
            }
            steps {echo " 条件为假 "}
        }
        stage(' 全部为真判断 ') {
            when {
                allOf {equals expected: 'admin', actual: SUBMITTER; environment name: 'ROLE_NAME', value: 'ADMIN'}
            }
            steps {echo " 全部条件为真 "}
        }
        stage(' 任意为真判断 ') {
            when {
                anyOf {equals expected: 'admin', actual: SUBMITTER; environment name: 'ROLE', value: 'ADMINA'}
            }
            steps {echo " 任意条件为真 "}
        }
    }
}

parallel:并行,对耗时较长,相互不存在依赖关系的 stage 可以使用此方式提升执行效率。

pipeline {
    agent any
    stages {stage('Non-Parallel') {
            steps {echo 'Non-Parallel'}
        }
        stage('Parallel Stage') { // 一个 stage 只能有一个 steps 或者 parallel
            failFast true // 并行的 job 中如果其中的一个失败,则终止其他并行的 stage
            parallel { // parallel 不能包含 agent 或者 tools
                stage('parallel stage 1') { // 嵌套的 stages 里不能使用 parallel
                    steps {echo "parallel stage 1"}
                }
                stage('parallel stage 2') { // 嵌套的 stages 里不能使用 parallel
                    steps {echo "parallel stage 2"}
                }
            }
        }
    }
}

if-else:分支,当满足条件是执行 if 代码块,否则执行 else 代码块。

pipeline {
    agent any
    environment {NAME = "Tom"}
    stages {stage('Case') {
            steps {
                script {if ("$NAME" == "Tom") {echo "true"} else {echo "false"}
                }
            }
        }
        stage('Not-Case') {
            steps {
                script {if ("$NAME" == "LiLi") {echo "true"} else {echo "false"}
                }
            }
        }
    }
}

input:用户输入,阻塞当前构建阶段,显示提示等待用户选择。只有接收到有效选择才会继续当前构建阶段。

/**
 * message
 * 这个 option 是必须的,这个 message 会在用户提交构建的页面显示,提示用户提交相关的 input 条件。
 * id
 * 这个 id 是一个可选的 option,可以作为这个 input 的标记符,默认的标记符是这个 stage 的名称。
 * ok
 * 这个 ok 也是一个可选的 option, 主要是在 ok 按钮上显示一些文本,在 input 表单里。
 * submitter
 * 这个 submitter 也是一个可选的 option,里面可以写多个用户名称或者组名称,用逗号隔开。意思就是,只有这写名称的对应用户登陆 jenkins,才能提交这个 input 动作,如果不写,默认是任何人都可以提交 input。
 * parameters
 * 这个也是一个可选的 option, 和我们前面学的 parameters 没有区别,就是定义一些参数的地方。
 */
// Input
pipeline {
    agent any
    stages {stage('Deploy Application') {
            input {
                message " 是否发布应用?"
                ok " 发布 "
                submitter "admin"
            }
            steps {echo "Hello pipeline nice to meet you."}
        }
    }
}

try-catch:异常捕获,对可能出现异常的代码块进行捕获,在出现异常时进行显示处理。

pipeline {
    agent any
    stages {stage('trycache') {
            steps {
                script {
                    try {sh 'exit 1'} catch (exc) {echo "something failed $exc"}
                }
            }
        }
    }
}

Scripted Pipeline

  Scripted pipeline 是 Jenkins 提供的基于 Groovy 脚本进行 pipeline 描述的一种格式。Groovy 脚本不一定适合所有使用者,因此 Jenkins 创建了 Declarative pipeline(声明式),为编写 Jenkins 管道提供了一种更简单、更有主见的语法。但是不可否认,由于脚本化的 pipeline 是基于 groovy 的一种 DSL 语言,所以 Scripted pipeline 相较于 Declarative pipeline 为 Jenkins 用户提供了更巨大的灵活性和可扩展性。

  pipeline 脚本同其它脚本语言一样,从上至下顺序执行,它的流程控制取决于 Groovy 表达式,如 if/else 条件语句,举例如下:

node {
    checkout scm // 代码检出
    stage('Build') { // 构建阶段
        docker.image('maven:3.3.3').inside {sh 'mvn --version'}
    }
    stage('Deploy') { // 发布阶段
        if (env.BRANCH_NAME == 'master') {echo 'Deploy master version.'} else {echo 'Deploy other version.'}
    }
}

高级特性

notification:通知,利用 Jenkins 提供的插件机制,在 Pipeline 构建阶段进行制定通知。

pipeline {
    agent any
    stages {stage('Example') {...}
    }
    post {
        always {
            script {
                // BUILD_USER 需要 user build vars plugin 插件支持
                wrap([$class: 'BuildUser']) { // 邮件通知
                    mail to: "xxxxx@163.com", // 收件人地址
                    subject: "PineLine '${JOB_NAME}' (${BUILD_NUMBER}) result", // 主题
                    body: "${env.BUILD_USER}'s Pineline '${JOB_NAME}' (${BUILD_NUMBER}) run success\n 请及时前往 ${env.BUILD_URL} 进行查看 " // 内容
                }
            }
        }
    }
}
pipeline {
    agent any
    stages {stage('Example') {...}
    }
    post {
        always {
            script {
                // 需要 DingTalk 插件支持
                dingtalk(
                        robot: 'robotid', // 机器人 ID
                        type: 'LINK', // 消息类型
                        title: ' 监控报警 ', // 消息标题
                        text: ["msg"], // 消息内容
                        messageUrl: "build_url", // 消息连接地址
                        picUrl: "image_url" // 图片连接地址
                )
            }
        }
    }
}

注意 :以上邮件通知、钉钉通知都需要先在全局工具配置中进行对应配置,才可正常使用。

项目示例

   工单系统:

// 工单系统 Jenkinsfile
pipeline {
    agent any
    parameters {string(name: 'svnUrl', defaultValue: 'https://10.28.1.160/svn/sourcecode/ticketSystem/code/branch/develop-1.0.0/ticket-system', description: 'SVN 代码路径 ')
        string(name: 'sonarParam', defaultValue: '-Dsonar.projectKey=${JOB_NAME} -Dsonar.projectName=${JOB_NAME} -Dsonar.projectVersion=${v} -Dsonar.language=java -Dsonar.java.source=1.8 -Dsonar.sourceEncoding=UTF-8 -Dsonar.sources=src/main/java -Dsonar.java.binaries=target/classes -Dsonar.modules=ticket-system-backend,ticket-system-common', description: 'SonarQube 项目参数 ')
    }
    environment {SONAR_HOME = tool 'SonarScanner4.2' // 这里这个 tool 是直接根据名称,获取自动安装的插件的路径}
    stages {stage(" 拉取代码 ") {
            steps {
                script {echo "Checkout ticket system from svn, branch name: ${env.BRANCH_NAME}; job name: ${env.JOB_NAME}"
                    def scmVars = checkout([$class                : 'SubversionSCM',
                                            additionalCredentials : [],
                                            excludedCommitMessages: '',
                                            excludedRegions       : '',
                                            excludedRevprop       : '',
                                            excludedUsers         : '',
                                            filterChangelog       : false,
                                            ignoreDirPropChanges  : false,
                                            includedRegions       : '',
                                            locations             : [[cancelProcessOnExternalsFail: true,
                                                                      credentialsId               : '85475088-cf46-4bc1-a77e-70ae5fb65f8e',
                                                                      depthOption                 : 'infinity',
                                                                      ignoreExternalsOption       : true,
                                                                      local                       : '.',
                                                                      remote                      : '${svnUrl}']],
                                            quietOperation        : true,
                                            workspaceUpdater      : [$class: 'UpdateUpdater']])
                    svnVersion = scmVars.SVN_REVISION
                    echo "Current svn version: ${svnVersion}"
                }
            }
        }
        stage(" 代码编译 ") {
            steps {
                script {echo "Compile ticket system code. branch name: ${env.BRANCH_NAME}; job name: ${env.JOB_NAME}"
                    sh "mvn clean install"
                }
            }
        }
        stage(" 单元测试 ") {
            steps {
                script {echo "Unit test for ticket system. branch name: ${env.BRANCH_NAME}; job name: ${env.JOB_NAME}"
                    sh "mvn test"
                }
            }
        }
        stage(" 扫描代码 ") {
            steps {echo "Starting codeAnalyze with SonarQube... branch name: ${env.BRANCH_NAME}; job name: ${env.JOB_NAME}"
                withSonarQubeEnv("GroupamaSonar") {sh "${SONAR_HOME}/bin/sonar-scanner ${sonarParam}"
                }
                script {
                    // 此处由于 SonarQube 响应较慢,需要叫上超时处理
                    timeout(5) {
                        // 利用 sonarqube webhook 通知 pipeline 代码扫描结果,未通过则 fail
                        def qg = waitForQualityGate()
                        if (qg.status != 'OK') {error " 未通过 SonarQube 的代码检查,请及时修改! failure: ${qg.status}"
                        }
                    }
                }
            }
        }
        stage(" 构建代码 ") {
            steps {
                script {echo "Build ticket system code. branch name: ${env.BRANCH_NAME}; job name: ${env.JOB_NAME}"
                    sh "mvn clean package -DskipTests"
                }
            }
        }
        stage("Deploy Application") {
            input {
                message " 是否发布应用?"
                ok " 发布 "
                submitter "admin"
            }
            steps {
                script {sh "sshpass root@xxxxxx sh -c /app/deploy.sh"}
            }
        }
    }
    post {
        success {
            script {def msg = "【${JOB_NAME}】 项目打包成功,请及时处理!"
                def imageUrl = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1729441498,1472936606&fm=26&gp=1.jpg"
                // 需要 DingTalk 插件支持
                dingtalk(
                        robot: 'ff6ce1f1-589a-4243-8d81-a4391ae21102',
                        type: 'LINK',
                        title: ' 监控报警 ',
                        text: ["${msg}"],
                        messageUrl: "${BUILD_URL}",
                        picUrl: "${imageUrl}"
                )
                println " 构建成功!"
                currentBuild.description = " 工单系统生产环境构建成功!"
            }
        }
        failure {
            script {def msg = "【${JOB_NAME}】项目打包失败,请及时处理!"
                def imageUrl = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1729441498,1472936606&fm=26&gp=0.jpg"
                // 需要 DingTalk 插件支持
                dingtalk(
                        robot: 'ff6ce1f1-589a-4243-8d81-a4391ae21102',
                        type: 'LINK',
                        title: ' 监控报警 ',
                        text: ["${msg}"],
                        messageUrl: "${BUILD_URL}",
                        picUrl: "${imageUrl}"
                )
                println " 构建失败!"
                currentBuild.description = " 工单系统生产环境构建失败!"
            }
        }
        aborted {
           script {def msg = "【${JOB_NAME}】 项目打包终端,请及时处理!"
                def imageUrl = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1729441498,1472936606&fm=26&gp=2.jpg"
                // 需要 DingTalk 插件支持
                dingtalk(
                        robot: 'ff6ce1f1-589a-4243-8d81-a4391ae21102',
                        type: 'LINK',
                        title: ' 监控报警 ',
                        text: ["${msg}"],
                        messageUrl: "${BUILD_URL}",
                        picUrl: "${imageUrl}"
                )
                println " 构建中断,请联系相关服务人询问中断原因!"
                currentBuild.description = " 工单系统生产环境构建中断!"
            }
        }
    }
}

插件推荐

   在使用 Jenkins Pipeline 过程中,有很多插件支持我们的发布流程,常用的有如下插件,大家可以事情选择:

  1. Pipeline:Pipeline 流水线支持。
  2. Blue Ocean:Pipeline 流水线美化插件。
  3. SonarScanner:SonarQube 集成插件。
  4. user build vars:Pipeline 中获取构建人时需要用到。
  5. Sonar Quality Gates:SonarQube 扫描结果获取插件。
  6. Pipeline:InputStep:Input 等待用户输出需要用到该插件。
  7. Extended Choice Parameter:使用 Choice Parameter 参数选项时需要使用。

愿景

   我们使用 Jenkins Pipeline 流水线实现 CI/CD 的最终愿景是自动化重复性操作,并提升应该交付效率。流程如下图:




多团队



DevOps     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!