[toc]

介绍

Pipeline在Unix/Linux系统中经常用到,Pipeline将一个命令/程序/进程的输出发送到另一个命令/程序/进程,以进行进一步处理。比如:cat test.txt | grep test1。Jenkins 中的Pipeline借用了Unix/Linux中的 Pipeline思路,实现像流水线一样来调度Jenkins任务,通过Jenkinsfile来描述整个持续集成流程。

Jenkins pipeline语法形式

Jenkinsfile支持两种语法形式:

  • Scripted pipeline - 脚本式流水线语法,基于 Groovy语言构建的通用 DSL(Domain-specific language,领域特定语言
  • Declarative pipeline - 声明式流水线语法,在v2.5之后引入,支持结构化方式,提供了更丰富的语法特性。

DSL是专注于某个应用领域的计算机语言。和Python、Java等这种通用语言(General-purpose Language, GPL)不同的是,DSL是一种为了特定领域而设计的开发语言,比如Web 应用使用的HTML、可扩展标记语言XML、SQL语言等。

本篇文章主要记录声明式流水线语法

Declarative pipeline

声明式流水线语法必须包含在一个 pipeline块内:

pipeline { 
	/* Declarative Pipeline */
}

pipeline块中主要由Sections, Directives, Steps,或者赋值语句组成。

pipeline {
    agent any
    stages {
        stage('begin') {
            steps {
                echo 'Hello pipeline'
                }
        }
    }
    post {
        always {
            echo 'say goodbay'
        }
    }
}

Sections

Sections包括agent、stages、steps和post。

agent

agent 是指定流水线运行在那个环境之中。目前主要有如下几种agent:

  • any:在任何环境中运行
pipeline {
  agent any
}
  • none:表示该 Pipeline 脚本没有全局的 agent 配置。当顶层的 agent 配置为 none 时,每个 stage 部分都需要包含它自己的 agent。
pipeline {
  agent none
    stages {
      stage('Stage For Build'){
        agent any
      }
    }
}
  • node:指定运行在某个标签的节点之上
pipline {
    agent {
        node {
            label "myslave"
            customWorkspace "myWorkspace"
        }
    }
}
  • docker:运行在 docker 之上相当于 dockerfile,可以直接使用 docker 字段指定外部镜像即可,可以省去构建的时间。比如使用 maven 镜像进行打包,同时可以指定 args:
pipeline {
  agent{
    docker{
      image 'maven:3-alpine'
      label 'my-defined-label'
      args '-v /tmp:/tmp'
    }
  }
}
  • Dockerfile:从某个 dockerfile 创建的 docker 容器中运行。使用从源码中包含的 Dockerfile 所构建的容器执行流水线或 stage。此时对应的 agent 写法如下:
pipeline {
  agent {
    dockerfile {
      filename 'Dockerfile.build'
      dir 'build'
      label 'my-defined-label'
      additionalBuildArgs '--build-arg version=1.0.2'
    }
  }
}
  • k8s:运行在某一个 k8s 集群之中
pipeline {
  agent {
    kubernetes {
      label podlabel
      yaml """
kind: Pod
metadata:
  name: jenkins-agent
spec:
  containers:
    - name: kaniko
      image: gcr.io/kaniko-project/executor:debug
      imagePullPolicy: Always
      command:
...
"""
    }
  }
}

stages

stages 主要是用来定义某一个流水线阶段的。其中还包含 stage、steps、script等。其中 stage 代表多个步骤,一般一个stages中包含多个stage;steps 代表的是具体执行的内容以及 script 代表具体执行的脚本。

pipeline{
    agent any
    stages{
        stage("first stage"){
            steps("first steps"){
                echo "Hello World!"
                script {
                    def browsers = ['chrome', 'firefox']
                    for (int i = 0; i < browsers.size(); ++i) {
                        echo "Testing the ${browsers[i]} browser"
                    }
                }
            }
        }
    }
}

steps

Steps 部分在给定的 stage 指令中执行的一个或多个步骤,比如在 steps 定义执行一条 shell 命令:

pipeline {
	agent any
		stages {
			stage('Example') {
				steps {
					echo 'Hello World'
				}
		}
	}
}

或者使用sh字段执行多条指令

pipeline {
	agent any
	stages {
		stage('Example') {
			steps {
				sh """
					echo 'Execute building...'
					mvn clean install
				"""
			}
		}
	}
}

注意:
在sh中进行变量可以分为两种情况:
1.调用jenkins 的变量(jenkins系统和pipeline定义的变量)使用:${变量名}
2.调用sh 中定义的变量使用:\${变量名}

在声明式的pipeline中默认无法使用脚本语法,但是pipeline提供了一个脚本环境入口:script{},通过使用script来包裹脚本语句,即可使用脚本语法。
判断处理:

pipeline {
    agent any
    stages {
        stage('stage 1') {
            steps {
                script{
                    if ( "1" == "1" ) {
                        echo "lalala"
                    }else {
                        echo "oooo"
                    }
                }
            }
        }
    }
}

异常处理,异常捕获只能捕获逻辑错误,代码错误将直接报错。

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

post

Post 一般用于流水线结束后的进一步处理,比如错误通知等。Post 可以针对流水线不同的结果做出不同的处理,就像开发程序的错误处理,比如 Python 语言的 try catch。Post 可以定义在Pipeline 或 stage 中,目前支持以下条件:

  • always:不管运行结果怎么样都运行
  • success:只有当流水线运行结果成功时才运行
  • changed:只有当前 Pipeline 或 stage 的完成状态与它之前的运行不同时,才允许在该post 部分运行该步骤
  • fixed:当本次 Pipeline 或 stage 成功,且上一次构建是失败或不稳定时,允许运行该post 中定义的指令
  • regression:当本次 Pipeline 或 stage 的状态为失败、不稳定或终止,且上一次构建的状态为成功时,允许运行该 post 中定义的指令
  • failure:只有当流水线运行失败时才运行
  • aborted:只有当流水线取消运行时才运行
  • unstable:只有当流水线运行不稳定时才运行

配置示例如下:

pipeline{
    agent any
    stages{
        stage("first stage"){
            steps("first steps"){
                echo "Hello World!"
                script {
                    def browsers = ['chrome', 'firefox']
                    for (int i = 0; i < browsers.size(); ++i) {
                        echo "Testing the ${browsers[i]} browser"
                    }
                }
            }
        }
    }
    post {
        always {
            echo "运行成功"
        }
    }
}

Directives

Directives可用于一些执行stage时的条件判断或预处理一些数据,和Sections一致,Directives不是一个关键字或指令,而是包含了 environment、options、parameters、triggers、stage、tools、input、when 等配置。

Environment

Environment 主要用于在流水线中配置的一些环境变量,根据配置的位置决定环境变量的作用域。可以定义在 pipeline 中作为全局变量,也可以配置在 stage 中作为该 stage 的环境变量。

pipeline{
    agent any
    environment {
        VERSION = "1.0.0"
    }
    stages{
        stage("first stage"){
            steps("first steps"){
                echo "Hello World!"
                echo "${VERSION}"
                script {
                    def browsers = ['chrome', 'firefox']
                    for (int i = 0; i < browsers.size(); ++i) {
                        echo "Testing the ${browsers[i]} browser"
                    }
                }
            }
        }
    }
    post {
        always {
            echo "运行成功"
        }
    }
}

credentials

在Environment中支持特殊方法credentials(),可以用于在 Jenkins 环境中通过标识符访问预定义的凭证。对于类型为 Secret Text 的凭证,credentials()可以将该 Secret 中的文本内容赋值给环境变量。对于类型为标准的账号密码型的凭证,指定的环境变量为 username 和 password,并且也会定义两个额外的环境变量,分别为 MYVARNAME_USR 和 MYVARNAME_PSW。
假如需要定义个变量名为 CC 的全局变量和一个名为 AN_ACCESS_KEY 的局部变量,并且用 credentials 读取一个 Secret 文本,可以通过以下方式定义:

pipeline {
	agent any
	environment { // Pipeline 中定义,属于全局变量
		CC = 'xxx'
	}
	stages {
		stage('Example') {
			environment { // 定义在 stage 中,属于局部变量
				AN_ACCESS_KEY = credentials('my-prefined-secret-text')
			}
			steps {
				sh 'printenv'
			}
		}
	}
}

Options

option指令用于配置整个jenkins pipeline本身的选项,根据具体的选项不同,可以将其放在pipeline块或者stage块中。
options 指令允许从流水线内部配置特定于流水线的选项。 流水线提供了许多这样的选项, 比如 buildDiscarder,但也可以由插件提供, 比如 timestamps.
具体参数如下:

buildDiscarder

作用域:只能在pipline块使用
buildDiscardr 保存最近历史构建记录的数量。当pipeline执行完成后,会在硬盘上保存记录,发布的内容和构建执行日志,如果长时间不清理会占用大量空间,设置此选项后会保存配置的数量。
例子:

pipeline {
    agent any
    
    options {
        buildDiscarder(logRotator(numToKeepStr: '3')) //保留上次构建记录
    }
    
    stages {
        stage('test-a') {
            steps {
                echo 'test'
            }
        }
    }
}

disableConcurrentBuilds

作用域:pipeline块
同一个pipeline,Jenkins默认可以同时点击多次,并发执行。这样会消耗资源,并造成发布失败等问题。
配置并发锁定,可以在同时只允许一个执行,在和代码仓库进行联动的时候容易遇到,同时分支进行多次提交。

options {
    disableConcurrentBuilds()
}

newContainerPerStage

作用域:pipeline块
当agent为docker或dockerfile时,指定在同一个Jenkins节点上,每个stage都分别运行在一个新的容器中,而不是所有stage都运行在同一个容器中。

options {
    newContainerPerStage()
}

retry

作用域:pipeline块或者stage块
当发生失败时进行重试,可以指定整个pipeline的重试次数。需要注意的是,这个次数是指总次数,包括第一次失败。

options {
    retry(3)
}

timeout

作用域:stage块
如果pipeline执行时间过长,超出设置的timeout时间,Jenkins将中止pipeline。
以下例子中以小时为单位,还可以把SECONDS(秒),MINUTES(分钟)为单位。

options {
    timeout(time: 1, unit: 'HOURS')
}

timestamps

作用域:stage块或者steps块
注意:需要安装Timestamper插件
在stage块声明,每个步骤执行完,日志里都会打印执行时间。

options {
    timestamps()
}

在steps块执行,包含内的每个步骤执行完,日志里都会打印执行时间。

timestamps {
    echo ‘这是第一个被执行的 stage.’
}

Triggers

在 Pipeline 中可以用 triggers 实现自动触发流水线执行任务,可以通过 Webhook、Cron、pollSCM 和 upstream 等方式触发流水线。

cron

接受一个cron风格的字符串来定义Pipeline触发的常规间隔。

# 每分钟运行一次

pipeline {
    agent any
    triggers {
        cron('* * * * *')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

pollSCM

接受一个cron风格的字符串来定义Jenkins检查SCM源更改的常规间隔。如果存在新的更改,则Pipeline将被重新触发。

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

Input

Input 字段可以实现在流水线中进行交互式操作,比如选择要部署的环境、是否继续执行某个阶段等。
配置 Input 支持以下选项:

  • message:必选,需要用户进行 input 的提示信息,比如:“是否发布到生产环境?”;
  • id:可选,input 的标识符,默认为 stage 的名称;
  • ok:可选,确认按钮的显示信息,比如:“确定”、“允许”;
  • submitter:可选,允许提交 input 操作的用户或组的名称,如果为空,任何登录用户均可提交 input;
  • parameters:提供一个参数列表供 input 使用。

假如需要配置一个提示消息为“还继续么”、确认按钮为“继续”、提供一个 PERSON 的变量的参数,并且只能由登录用户为 alice 和 bob 提交的 input 流水线:

pipeline {
	agent any
	stages {
		stage('Example') {
			input {
				message "还继续么?"
				ok "继续"
				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."
			}
		}
	}
}

when

when 指令允许 Pipeline 根据给定的条件确定是否执行该阶段。该 when 指令必须至少包含一个条件。如果 when 指令包含多个条件,则所有子条件必须为 stage 执行返回 true。这与子条件嵌套在一个 allOf 条件中相同(见下面的例子)。

branch

当正在构建的分支与给出的分支模式匹配时执行,例如:when { branch 'master' }。
请注意,这仅适用于多分支 Pipeline且branch匹配的分支为pipeline文件所在分支。

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'master'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

environment

当指定的环境变量设置为给定值时执行,例如: when { environment name: 'DEPLOY_TO', value: 'production' }

pipeline {
    agent any
    environment {
        DEPLOY_TO = "xxx"
    }
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                environment name: 'DEPLOY_TO', value: 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

not

当嵌套条件为false时执行。必须包含一个条件。例如:when { not { branch 'master' } }

pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                not {
                    branch 'master'
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

allOf

当所有嵌套条件都为真时执行。必须至少包含一个条件。例如:when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }

pipeline {
    agent any
    environment {
        DEPLOY_TO = "production"
    }
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                allOf {
                    branch 'master';
                    environment name: 'DEPLOY_TO', value: 'production' 
                } 
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

anyOf

当至少一个嵌套条件为真时执行。必须至少包含一个条件。例如:when { anyOf { branch 'master'; branch 'staging' } }

pipeline {
    agent any
    environment {
        DEPLOY_TO = "production"
    }
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                anyOf {
                    branch 'master';
                    environment name: 'DEPLOY_TO', value: 'production' 
                } 
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

parallel

通过将阶段设置为parallel来表明该stage为并行运行。

  • 一个stage只能有一个steps或者parallel
  • 嵌套的stages里不能使用parallel
  • parallel不能包含agent或者tools
  • 通过设置failFast 为true表示:并行的job中如果其中的一个失败,则终止其他并行的stage
  • 在并行执行过程中,所有job都同时开始运行,只有当其中一个job开始报错后,如果failFast 为true,对未执行完成的job进行终止跳过。需要考虑时间原因。
pipeline {
    agent any
    stages {
        stage('Non-Parallel Stage') {
            steps {
                echo 'Non-parallel'
            }
        }
        stage('Parallel Stage') {
            failFast true
            parallel {
                stage('parallel 1') {
                    steps {
                        echo "parallel 1"
                    }
                }
                stage('parallel 2') {
                    steps {
                        echo "parallel 2"
                    }
                }
            }
        }
    }
}

流水线语法生成器

流水线每一个插件都会有自己的 pipeline 语句,而Jenkins提供了上千种插件,由于插件太多所以语法方面不可能完全都记住,所以Jenkins提供了流水线语法生成器,可以通过配置关键项来生成对应插件的流水线代码。
image-1660398436245
image-1660398397756
image-1660398353809

星霜荏苒 居诸不息