Jenkins 2.x的核心能力是Pipeline as code。在Jenkins 1.x中,我们通过简单便捷的配置,可以快速完成基本CI、CD的Job创建和管理,但对于复杂的流程,Jenkins 1.x则无从下手。在软件的发布过程中,通常会有一整套复杂的流程,来支持从代码提交到最终的软件交付。通过Jenkins pipeline,我们可以用代码的方式,定义我们的工作流,从而在代码中编写工程的CI、CD业务逻辑。同时,Pipeline支持在不同节点运行不同的步骤,使得整个DevOps流程更加灵活。
Jenkins pipeline支持两种模式,一种是Declarative Pipeline,另一种是Scripted Pipeline。两种模式都遵循的是groovy语法,但在编写上会有差别。
Declarative Pipeline
Declarative Pipeline必须被包含在pipeline语句块中:
pipeline {
/* insert Declarative Pipeline here */
}一个完整的Hello World!示例如下:
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
post {
always {
echo 'I will always say Hello again!'
}
}
}agent指定整个Pipeline或特定stage将在Jenkins环境中被执行的节点,如Jenkins 的master节点或某个cluster节点。
在Declarative Pipeline模式的代码中,可能会在一个stages{…}中声明一窜嵌套的stages{…}, 并以顺序执行。需要指出的是,一个stage{…}必须有且只有一个steps{…}, 或者parallel{…} 或者stages{…}。
post用来定义将在Pipeline结束或stage结束时运行的操作。支持的post有: always,changed,failure,success,unstable,和aborted。这些块允许在Pipeline运行或stage结束时执行步骤,具体取决于Pipeline的状态。
还可以在parallel块中声明多个嵌套stage,这些stage将并行执行。需要注意的是一个stage有且只能有一个steps,stages或parallel。示例如下:
pipeline {
agent any
stages {
stage('Non-Parallel Stage') {
steps {
echo 'This stage will be executed first.'
}
}
stage('Parallel Stage') {
parallel {
stage('Branch A') {
agent {
label "for-branch-a"
}
steps {
echo "On Branch A"
}
}
stage('Branch B') {
agent {
label "for-branch-b"
}
steps {
echo "On Branch B"
}
}
stage('Branch C') {
agent {
label "for-branch-c"
}
stages {
stage('Nested 1') {
steps {
echo "In stage Nested 1 within Branch C"
}
}
stage('Nested 2') {
steps {
echo "In stage Nested 2 within Branch C"
}
}
}
}
}
}
}
}如果想在Declarative Pipeline中使用if判断、for循环等语句,那么需要使用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"
}
}
}
}
}
}Scripted Pipeline
Scripted Pipeline是基于Groovy来编码的pipeline,Groovy是基于JVM的脚本语言。由Groovy语言提供的大多数功能,都可以在Scripted Pipeline中使用。这使得Scripted Pipeline成为了一个非常富有表现力和灵活性的模式。在Scripted Pipeline可以自由的使用if、for语句,还可以调用各种Groovy中的函数,比如String.split、String.replace等等。一个简单的Scripted Pipeline示例如下:
node {
stage('Example') {
if (env.BRANCH_NAME == 'master') {
echo 'I only execute on the master branch'
} else {
echo 'I execute elsewhere'
}
}
}该pipeline也可以node('Node name') {}来开头,Node name就是从节点或master节点的名称。
Scripted Pipeline自身没有过多限制,可以自由组织代码流程。
在Scripted Pipeline中,处理错误行为需要使用Groovy 中的try/catch/finally块,例如:
node {
stage('Example') {
try {
sh 'exit 1'
}
catch (exc) {
echo 'Something failed, I should sound the klaxons!'
throw
}
}
}Declarative Pipeline vs Scripted Pipeline
在pipeline模式支持先后上,Scripted Pipeline是最开始被Jenkins支持的,Declarative Pipeline稍晚。如果是简单的工作流,那么可以选择使用Declarative Pipeline,如果是复杂的工作流或者掺杂了复杂的业务逻辑,那么Scripted Pipeline是更为灵活的一种编写方式。
有关两种Pipeline模式的详细语法,可以参考官方文档:Pipeline Syntax
在SCM中管理Pipeline
对于复杂的Pipeline,在Jenkins的UI中去编写和维护Pipeline是非常困难的。为了解决这个问题,Jenkins pipeline支持编写Jenkinsfile,然后将Jenkinsfile存放在SCM中,这样便增加了协作性,而且也可以享受到用IDE编写Pipeline job的好处。