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 自定义扩展,以及与其他插件集成实现复杂功能的能力。
概念
以下概念是 Jenkins Pipeline 的关键,它们与 Pipeline 语法紧密相关。
Pipeline:一个 Pipeline 对应一个用户定义的 CD 模型。Pipeline 的代码描述了整个构建过程,通常包括构建、测试、验证、交付等阶段。
Node:一个 Node 对应一个 Jenkins 集群环境中的一个节点,它能够执行 Pipeline 定义的描述。
Stage:一个 Stage 块对应描述了在 Pipeline 中不同的任务子集(阶段),例如构建、测试、部署 Stages。
Step:一个 Step 对应一个步骤,它告诉 Jenkins 在某个特定时间点应该作什么。例如执行 shell 命令,执行 mvn 命令等。
语法
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'
}
}
}
}
在任何可用的 agent 节点上执行 Piepline 定义的描述。
定义”构建”阶段。
执行与”构建”相关的一些步骤。
定义”测试”阶段。
执行与”测试”相关的一些步骤。
定义”发布”阶段
执行与”发布”相关的一些步骤。
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}
}
}
- 构建结束后,结果无论是什么状态,总会执行该代码块中的内容。
- 构建结束后,结果是成功状态时执行该代码块中的内容。
- 构建结束后,结果是失败状态时,执行该代码块中的内容。
- 构建结束后,结果是不稳定状态时,执行该代码块的内容。
- 构建结束后,结果运行状态与之前的状态不同时,执行该代码块的内容。
- 构建结束后,结果是终止状态时,执行该代码块中的内容。
指令
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 过程中,有很多插件支持我们的发布流程,常用的有如下插件,大家可以事情选择:
- Pipeline:Pipeline 流水线支持。
- Blue Ocean:Pipeline 流水线美化插件。
- SonarScanner:SonarQube 集成插件。
- user build vars:Pipeline 中获取构建人时需要用到。
- Sonar Quality Gates:SonarQube 扫描结果获取插件。
- Pipeline:InputStep:Input 等待用户输出需要用到该插件。
- Extended Choice Parameter:使用 Choice Parameter 参数选项时需要使用。
愿景
我们使用 Jenkins Pipeline 流水线实现 CI/CD 的最终愿景是自动化重复性操作,并提升应该交付效率。流程如下图:
多团队
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!