跳转至

02-自动化CICD-Pipeline

Pipeline 介绍

本章将介绍Jenkins Pipeline的所有方面,从运行Pipeline到写入Pipeline代码,甚至扩展Pipeline本身。

本章旨在让所有技能级别的Jenkins用户使用,但初学者可能需要参考“ 使用Jenkins ”的一些部分来了解本章涵盖的一些主题。

如果您还不熟悉Jenkins的基本术语和功能,请参考 Jenkins介绍

什么是Pipeline

Jenkins Pipeline是一套插件,支持将连续输送Pipeline实施和整合到Jenkins。Pipeline提供了一组可扩展的工具,用于通过PipelineDSL为代码创建简单到复杂的传送Pipeline。

通常,此“Pipeline代码”将被写入 Jenkinsfile项目的源代码控制存储库,例如

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any 

    stages {
        stage('Build') { 
            steps { 
                sh 'make' 
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml' 
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}

imgagent 表示Jenkins应该为Pipeline的这一部分分配一个执行者和工作区。

imgstage 描述了这条Pipeline的一个阶段。

imgsteps 描述了要在其中运行的步骤 stage

imgsh 执行给定的shell命令

imgjunit是由JUnit插件提供的 用于聚合测试报告的Pipeline步骤。

为什么是Pipeline

Jenkins从根本上讲是一种支持多种自动化模式的自动化引擎。Pipeline在Jenkins上添加了一套强大的自动化工具,支持从简单的连续集成到全面的连续输送Pipeline的用例。通过建模一系列相关任务,用户可以利用Pipeline 的许多功能:

  • 代码:Pipeline以代码的形式实现,通常被检入源代码控制,使团队能够编辑,审查和迭代其传送流程。
  • 耐用:Pipeline可以在计划和计划外重新启动Jenkins管理时同时存在。
  • Pausable:Pipeline可以选择停止并等待人工输入或批准,然后再继续Pipeline运行。
  • 多功能:Pipeline支持复杂的现实世界连续交付要求,包括并行分叉/连接,循环和执行工作的能力。
  • 可扩展:Pipeline插件支持其DSL的自定义扩展 以及与其他插件集成的多个选项。

虽然Jenkins一直允许基本形式的自由式工作联合起来执行顺序任务,Pipeline使这个概念成为Jenkins的最好的一个部分。

基于Jenkins的核心可扩展性,Pipeline也可以由Pipeline共享库用户和插件开发人员扩展。

下面的流程图是在Jenkins Pipeline中容易建模的一个连续发货方案的示例:

image-20211101134033891

Pipeline 条件

Step

单一任务,从基础中告诉了Jenkins应该*怎么*做。例如,要执行shell命令,请make使用以下sh步骤:sh 'make'。当插件扩展Pipeline DSL时,通常意味着插件已经实现了一个新的*步骤*。

Node

Pipeline执行中的大部分*工作*都是在一个或多个声明node步骤的上下文中完成的。将工作限制在Node步骤中有两件事情:

  1. 通过将项目添加到Jenkins队列来调度要运行的块中包含的步骤。一旦执行器在节点上空闲,步骤就会运行。
  2. 创建工作区(特定于该特定Pipeline的目录),可以从源代码控制中检出的文件完成工作。
根据您的Jenkins配置,某些工作空间在一段时间不活动后可能无法自动清除。

Stage

stage是定义整个Pipeline的概念上不同子集的一个步骤,例如:“Build”,“Test”和“Deploy”,许多插件用于可视化或呈现Jenkins Pipeline状态/进度。

Pipeline 入门

**Jenkins Pipeline**是一套插件,支持将连续输送Pipeline实施和整合到Jenkins。Pipeline 提供了一组可扩展的工具,用于通过Pipeline DSL为代码创建简单到复杂的传送Pipeline 。

本节介绍Jenkins Pipeline的一些关键概念,并帮助介绍在运行的Jenkins实例中定义和使用Pipelines的基础知识。

先决条件

要使用Jenkins Pipeline,您将需要:

  • Jenkins 2.x或更高版本(旧版本回到1.642.3可能会工作,但不推荐)
  • Pipeline插件

要了解如何安装和Pipeline插件,请参阅管理插件

Pipeline 定义

脚本Pipeline是用Groovy写的 。Groovy语法的相关位将在本文档中根据需要进行介绍,因此,当了解Groovy时,不需要使用Pipeline。

可以通过以下任一方式创建基本Pipeline:

  • 直接在Jenkins网页界面中输入脚本。
  • 通过创建一个Jenkinsfile可以检入项目的源代码管理库。

用任一方法定义Pipeline的语法是一样的,但是Jenkins支持直接进入Web UI的Pipeline,通常认为最佳实践是在Jenkinsfile Jenkins中直接从源代码控制中加载Pipeline。

在Web UI中定义Pipeline

要在Jenkins Web UI中创建基本Pipeline,请按照下列步骤操作:

  • 单击Jenkins主页上的New Item。

image-20211101134155244

  • 输入Pipeline的名称,选择Pipeline,然后单击**确定**。
Jenkins使用流水线的名称在磁盘上创建目录。包含空格的管道名称可能会发现不希望路径包含空格的脚本中的错误。

image-20211101134224782

  • 在**脚本**文本区域中,输入Pipeline,然后单击**保存**。

image-20211101134240539

单击**立即生成**以运行Pipeline

image-20211101134256803

单击“构建历史记录”下的**#1**,然后单击**控制台输出**以查看Pipeline的完整输出。

image-20211101134310602

上面的示例显示了在Jenkins Web UI中创建的基本Pipeline的成功运行,使用两个步骤。

Jenkinsfile (Scripted Pipeline)
node { 
    echo 'Hello World' 
}

imgnode 在Jenkins环境中分配一个执行器和工作空间。

imgecho 在控制台输出中写入简单的字符串

在SCM中定义管道

复杂的Pipeline难以在Pipeline配置页面的文本区域内进行写入和维护。为了使这更容易,Pipeline也可以写在文本编辑器中,并检查源控件,作为Jenkinsfile,Jenkins可以通过Pipeline脚本从SCM选项加载的控件。

为此,在定义Pipeline时,从SCM中选择Pipeline脚本。

选择SCM选项中的Pipeline脚本后,不要在Jenkins UI中输入任何Groovy代码; 您只需指定要从其中检索Pipeline的源代码中的路径。更新指定的存储库时,只要Pipeline配置了SCM轮询触发器,就会触发一个新构建。

文本编辑器,IDE,GitHub等将使用Groovy代码进行语法高亮显示, 第一行Jenkinsfile应该是#!/usr/bin/env groovy Jenkinsfile。

内置文档

Pipeline配有内置的文档功能,可以更轻松地创建不同复杂性的Pipeline。根据Jenkins实例中安装的插件自动生成和更新内置文档。

内置文档可以在全局范围内找到: localhost:8080 / pipeline-syntax /,假设您有一个Jenkins实例在本地端口8080上运行。同样的文档也作为管道语法链接到任何配置的Pipeline的侧栏中项目。

image-20211101134352272

代码段生成器

内置的“Snippet Generator”实用程序有助于为单个步骤创建一些代码,发现插件提供的新步骤,或为特定步骤尝试不同的参数。

Snippet Generator动态填充Jenkins实例可用的步骤列表。可用的步骤数量取决于安装的插件,它明确地暴露了在Pipeline中使用的步骤。

要使用代码段生成器生成步骤代码片段:

  1. 从配置的流水线或本地主机:8080 / pipeline-syntax导航到Pipeline语法链接(上面引用)。
  2. 在“ 样品步骤”下拉菜单中选择所需的步骤
  3. 使用“ 样品步骤”下拉列表下方的动态填充区域配置所选步骤。
  4. 单击生成Pipeline脚本以创建一个可以复制并粘贴到Pipeline中的Pipeline代码段。

image-20211101134409312

要访问有关所选步骤的其他信息和/或文档,请单击帮助图标(由上图中的红色箭头指示)。

全局变量引用

除了代码片段生成器之外,Pipeline还提供了一个内置的“ 全局变量引用”。像Snippet Generator一样,它也是由插件动态填充的。与代码段生成器不同的是,全局变量引用仅包含Pipeline提供的变量的文档,这些变量可用于Pipeline。

在Pipeline中默认提供的变量是:

  • ENV

脚本化Pipeline可访问的环境变量,例如: env.PATHenv.BUILD_ID。请参阅内置的全局变量参考 ,以获取管道中可用的完整和最新的环境变量列表。

  • PARAMS

将为Pipeline定义的所有参数公开为只读 地图,例如:params.MY_PARAM_NAME

  • currentBuild

可用于发现有关当前正在执行的Pipeline信息,与如属性currentBuild.resultcurrentBuild.displayName等等请教内置的全局变量引用 了一个完整的,而且是最新的,可用的属性列表currentBuild

Jenkinsfile使用

本节基于“ Jenkins入门”中介绍的信息,并介绍更有用的步骤,常见模式,并演示一些非平凡的Jenkinsfile示例。

创建一个Jenkinsfile被检入源代码控制,提供了一些直接的好处:

  • Pipeline上的代码审查/迭代
  • Pipeline的审计跟踪
  • Pipeline的唯一真实来源,可以由项目的多个成员查看和编辑。

Pipeline支持两种语法:Declarative(在Pipeline 2.5中引入)和Scripted Pipeline。两者都支持建立连续输送Pipeline。两者都可以用于在Web UI或者a中定义一个流水线Jenkinsfile,尽管通常被认为是Jenkinsfile将文件创建并检查到源代码控制库中的最佳做法。

创建Jenkins文件

如“ 入门” 部分所述,a Jenkinsfile是一个包含Jenkins Pipeline定义的文本文件,并被检入源代码控制。考虑以下Pipeline,实施基本的三阶段连续输送Pipeline。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                echo 'Building..'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing..'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying....'
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
node {
    stage('Build') {
        echo 'Building....'
    }
    stage('Test') {
        echo 'Building....'
    }
    stage('Deploy') {
        echo 'Deploying....'
    }
}

并非所有的Pipeline都将具有相同的三个阶段,但是对于大多数项目来说,这是一个很好的起点。以下部分将演示在Jenkins的测试安装中创建和执行简单的Jenkins。

假设已经有一个项目的源代码管理库,并且已经在Jenkins中按照这些说明定义了一个Jenkins

使用文本编辑器,理想的是支持Groovy语法突出显示的文本编辑器, Jenkinsfile在项目的根目录中创建一个新的。

上述声明性Pipeline示例包含实现连续传送Pipeline的最小必要结构。需要的代理指令指示Jenkins为Pipeline分配一个执行器和工作区。没有agent指令,不仅声明Pipeline无效,所以不能做任何工作!默认情况下,该agent伪指令确保源存储库已被检出并可用于后续阶段的步骤

该阶段的指令,和步骤的指令也需要一个有效的声明Pipeline,因为他们指示Jenkins如何执行并在哪个阶段应该执行。

要使用Scripted Pipeline进行更高级的使用,上面的示例node是为Pipeline分配执行程序和工作空间的关键第一步。在本质上,没有node Pipeline不能做任何工作!从内部node,业务的第一个顺序是检查此项目的源代码。由于Jenkinsfile直接从源代码控制中抽取,所以Pipeline提供了一种快速简便的方式来访问源代码的正确版本
Jenkinsfile (Scripted Pipeline)
node {
    checkout scm 
    /* .. snip .. */
}

img:该checkout步骤将检出从源控制代码; scm是一个特殊变量,指示checkout步骤克隆触发此Pipeline运行的特定修订。

建立

对于许多项目,Pipeline“工作”的开始就是“建设”阶段。通常,Pipeline的这个阶段将是源代码组装,编译或打包的地方。的Jenkinsfile是不为现有的构建工具,如GNU/Make,Maven, Gradle,等的替代品,而是可以被看作是一个胶层结合项目的开发生命周期的多个阶段(建设,测试,部署等)一起。

Jenkins有一些插件,用于调用几乎任何一般使用的构建工具,但是这个例子将只是make从shell步骤(sh)调用。该sh步骤假定系统是基于Unix / Linux的,因为bat可以使用基于Windows的系统。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'make' 
                archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true 
            }
        }
    }
}

img:该sh步骤调用该make命令,只有在命令返回零退出代码时才会继续。任何非零退出代码将失败Pipeline。

imgarchiveArtifacts捕获与include pattern(**/target/*.jar)匹配的文件,并将它们保存到Jenkins主文件以供以后检索。

存档工件不能替代使用诸如Artifactory或Nexus之类的外部工件存储库,只能用于基本报告和文件归档。

测试

运行自动化测试是任何成功的连续传送过程的重要组成部分。因此,Jenkins有许多插件提供的测试记录,报告和可视化设备 。在基本层面上,当有测试失败时,让Jenkins在Web UI中记录报告和可视化的故障是有用的。下面的示例使用junit由JUnit插件提供的步骤。

在下面的示例中,如果测试失败,则Pipeline被标记为“不稳定”,如Web UI中的黄色球。根据记录的测试报告,Jenkins还可以提供历史趋势分析和可视化。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    stages {
        stage('Test') {
            steps {
                /* `make check` returns non-zero on test failures,
                * using `true` to allow the Pipeline to continue nonetheless
                */
                sh 'make check || true' 
                junit '**/target/*.xml' 
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
node {
    /* .. snip .. */
    stage('Test') {
        /* `make check` returns non-zero on test failures,
         * using `true` to allow the Pipeline to continue nonetheless
         */
        sh 'make check || true' 
        junit '**/target/*.xml' 
    }
    /* .. snip .. */
}

img:使用内联shell conditional(sh 'make || true')确保该 sh步骤始终看到零退出代码,从而使该junit步骤有机会捕获和处理测试报告。下面的“ 处理故障”部分将详细介绍其他方法。

imgjunit捕获并关联与包含pattern(**/target/*.xml)匹配的JUnit XML文件

部署

部署可能意味着各种步骤,具体取决于项目或组织的要求,并且可能是从构建的工件发送到Artifactory服务器,将代码推送到生产系统的任何步骤。

在Pipeline示例的这个阶段,“构建”和“测试”阶段都已成功执行。实际上,“部署”阶段只能在上一阶段成功完成,否则Pipeline将早退。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    stages {
        stage('Deploy') {
            when {
              expression {
                currentBuild.result == null || currentBuild.result == 'SUCCESS' 
              }
            }
            steps {
                sh 'make publish'
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
node {
    /* .. snip .. */
    stage('Deploy') {
        if (currentBuild.result == null || currentBuild.result == 'SUCCESS') { 
            sh 'make publish'
        }
    }
    /* .. snip .. */
}

img:访问该currentBuild.result变量允许Pipeline确定是否有任何测试失败。在这种情况下,值将是 UNSTABLE

假设一切都在Jenkins Pipeline示例中成功执行,每个成功的Pipeline运行都会将存档的关联构建工件,报告的测试结果和完整的控制台输出全部放在Jenkins中。

脚本Pipeline可以包括条件测试(如上所示),循环,try / catch / finally块甚至函数。下一节将详细介绍这种高级脚本Pipeline语法。

管道高级语法

字符串插值

Jenkins Pipeline使用与Groovy相同的规则 进行字符串插值。Groovy的字符串插值支持可能会让很多新来的语言感到困惑。虽然Groovy支持使用单引号或双引号声明一个字符串,例如:

def singlyQuoted = 'Hello'
def doublyQuoted = "World"

只有后一个字符串将支持基于dollar-sign($)的字符串插值,例如:

def username = 'Jenkins'
echo 'Hello Mr. ${username}'
echo "I said, Hello Mr. ${username}"

会导致:

Hello Mr. ${username}
I said, Hello Mr. Jenkins

了解如何使用字符串插值对于使用一些管道更高级的功能至关重要。

工作环境

Jenkins Pipeline通过全局变量公开环境变量,该变量env可从任何地方获得Jenkinsfile。假设Jenkins主机正在运行,在本地主机:8080 / pipeline-syntax / globals#env中记录了可从Jenkins Pipeline中访问的环境变量的完整列表 localhost:8080,其中包括:

  • BUILD_ID

当前版本ID,与Jenkins版本1.597+中创建的构建相同,为BUILD_NUMBER

  • JOB_NAME

此构建项目的名称,如“foo”或“foo / bar”。

  • JENKINS_URL

完整的Jenkins网址,例如example.com:port/jenkins/(注意:只有在“系统配置”中设置了Jenkins网址时才可用)

参考或使用这些环境变量可以像访问Groovy Map中的任何键一样完成 ,例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
node {
    echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
}

设置环境变量

根据是否使用Declarative或Scripted Pipeline,在Jenkins Pipeline中设置环境变量是不同的。

声明式Pipeline支持环境指令,而Scripted Pipeline的用户必须使用该withEnv步骤。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    environment { 
        CC = 'clang'
    }
    stages {
        stage('Example') {
            environment { 
                DEBUG_FLAGS = '-g'
            }
            steps {
                sh 'printenv'
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
node {
    /* .. snip .. */
    withEnv(["PATH+MAVEN=${tool 'M3'}/bin"]) {
        sh 'mvn -B verify'
    }
}

imgenvironment顶级pipeline块中使用的指令将适用于Pipeline中的所有步骤。

img:在一个environment意图中定义的一个指令stage将仅将给定的环境变量应用于该过程中的步骤stage

参数

声明式Pipeline支持开箱即用的参数,允许Pipeline在运行时通过parameters指令接受用户指定的参数。使用脚本Pipeline配置参数是通过properties步骤完成的,可以在代码段生成器中找到。

如果您使用“使用构建参数”选项来配置Pipeline以接受参数,那么这些参数可作为params 变量的成员访问。

假设一个名为“Greeting”的String参数已经在配置中 Jenkinsfile,它可以通过${params.Greeting}以下方式访问该参数:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    parameters {
        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
    }
    stages {
        stage('Example') {
            steps {
                echo "${params.Greeting} World!"
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
properties([parameters([string(defaultValue: 'Hello', description: 'How should I greet the world?', name: 'Greeting')])])

node {
    echo "${params.Greeting} World!"
}

故障处理

声明性Pipeline默认支持robust失败处理经由其post section,其允许声明许多不同的“post conditions”,例如:always,unstable,success,failure,和 changed。“ Pipeline语法”部分提供了有关如何使用各种帖子条件的更多详细信息。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                sh 'make check'
            }
        }
    }
    post {
        always {
            junit '**/target/*.xml'
        }
        failure {
            mail to: team@example.com, subject: 'The Pipeline failed :('
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
node {
    /* .. snip .. */
    stage('Test') {
        try {
            sh 'make check'
        }
        finally {
            junit '**/target/*.xml'
        }
    }
    /* .. snip .. */
}
但是脚本Pipeline依赖于Groovy的内置try/ catch/ finally该Pipeline的执行过程中处理故障的语义。

在上面的测试示例中,该sh步骤被修改为从不返回非零退出代码(sh 'make check || true')。这种方法虽然有效,但是意味着以下阶段需要检查currentBuild.result以确定是否有测试失败。

处理这种情况的另一种方法是保留Pipeline故障的早期退出行为,同时仍然junit有机会捕获测试报告,是使用一系列try/ finally块:

使用多个代理

在所有以前的例子中,只使用了一个代理。这意味着Jenkins将分配一个可用的执行器,无论它是如何标记或配置的。这不仅可以行为被覆盖,但Pipeline允许从内利用Jenkins环境中的多个代理商相同 Jenkinsfile,可为更高级的使用情况,如执行有帮助建立跨多个平台/测试。

在下面的示例中,“构建”阶段将在一个代理上执行,并且构建的结果将在“测试”阶段中分别标记为“linux”和“windows”的两个后续代理程序中重用。

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent none
    stages {
        stage('Build') {
            agent any
            steps {
                checkout scm
                sh 'make'
                stash includes: '**/target/*.jar', name: 'app' 
            }
        }
        stage('Test on Linux') {
            agent { 
                label 'linux'
            }
            steps {
                unstash 'app' 
                sh 'make check'
            }
            post {
                always {
                    junit '**/target/*.xml'
                }
            }
        }
        stage('Test on Windows') {
            agent {
                label 'windows'
            }
            steps {
                unstash 'app'
                bat 'make check' 
            }
            post {
                always {
                    junit '**/target/*.xml'
                }
            }
        }
    }
}

Toggle Scripted Pipeline (Advanced)

Jenkinsfile (Scripted Pipeline)
stage('Build') {
    node {
        checkout scm
        sh 'make'
        stash includes: '**/target/*.jar', name: 'app' 
    }
}

stage('Test') {
    node('linux') { 
        checkout scm
        try {
            unstash 'app' 
            sh 'make check'
        }
        finally {
            junit '**/target/*.xml'
        }
    }
    node('windows') {
        checkout scm
        try {
            unstash 'app'
            bat 'make check' 
        }
        finally {
            junit '**/target/*.xml'
        }
    }
}

img:该stash步骤允许捕获与包含模式(**/target/*.jar)匹配的文件,以在*同一*管道中重用。一旦Pipeline完成执行,垃圾文件将从Jenkins主站中删除。

imgagent/中的参数node允许任何有效的Jenkins标签表达式。有关详细信息,请参阅Pipeline语法部分。

imgunstash 将从Jenkins主机中检索名为“藏书”的管道当前工作空间。

img: 该bat脚本允许在基于Windows的平台上执行批处理脚本.

可选步骤参数

Pipeline遵循Groovy语言约定,允许在方法参数中省略括号。

许多Pipeline步骤还使用命名参数语法作为使用Groovy创建Map的简写,它使用语法[key1: value1, key2: value2]。发表如下功能等同的语句:

git url: 'git://example.com/amazing-project.git', branch: 'master'
git([url: 'git://example.com/amazing-project.git', branch: 'master'])

为方便起见,当仅调用一个参数(或只有一个必需参数)时,可能会省略参数名称,例如:

sh 'echo hello' /* short form  */
sh([script: 'echo hello'])  /* long form */

高级脚本管道

脚本Pipeline是 基于Groovy 的领域专用语言,大多数Groovy语法可以在脚本Pipeline中使用而无需修改。

同时执行

上面的例子在线性系列中的两个不同平台运行测试。在实践中,如果make check 执行需要30分钟完成,“测试”阶段现在需要60分钟才能完成!

幸运的是,Pipeline具有内置功能,用于并行执行Scripted Pipeline的部分,在适当命名的parallel步骤中实现。

重构上述示例以使用parallel步骤:

Jenkinsfile (Scripted Pipeline)
stage('Build') {
    /* .. snip .. */
}

stage('Test') {
    parallel linux: {
        node('linux') {
            checkout scm
            try {
                unstash 'app'
                sh 'make check'
            }
            finally {
                junit '**/target/*.xml'
            }
        }
    },
    windows: {
        node('windows') {
            /* .. snip .. */
        }
    }
}

而不是在“linux”和“windows”标签的节点上执行测试,它们现在将在Jenkins环境中存在必需容量的情况下并行执行。

Pipeline 语法

本节基于“ 入门指南”中介绍的信息,并应作为参考。有关如何在实际示例中使用Pipeline语法的更多信息,请参阅 本章的Jenkinsfile部分。从Pipeline插件2.5版开始,Pipeline支持两种离散语法,详细说明如下。对于每个的利弊,请参阅语法比较(下文中)。

如“ 入门指南 ”所述,Pipeline最基本的部分是“步骤”。基本上,步骤告诉Jenkins 要做什么,并且作为Declarative和Scripted Pipeline语法的基本构建块。

有关可用步骤的概述,请参阅 Pipeline步骤参考(下文中) ,其中包含Pipeline内置的完整列表以及插件提供的步骤。

声明Pipeline

声明性Pipeline是Jenkins Pipeline 的一个相对较新的补充, 它在Pipeline子系统之上提出了一种更为简化和有意义的语法。

所有有效的声明性Pipeline必须包含在一个pipeline块内,例如:

pipeline {
    /* insert Declarative Pipeline here */
}

声明性Pipeline中有效的基本语句和表达式遵循与Groovy语法相同的规则 ,但有以下例外:

  • Pipeline的顶层必须是块,具体来说是:pipeline { }
  • 没有分号作为语句分隔符。每个声明必须在自己的一行
  • 块只能包含章节, 指令,步骤或赋值语句。
  • 属性引用语句被视为无参数方法调用。所以例如,输入被视为input()

Sections

声明性Pipeline中的部分通常包含一个或多个指令或步骤。

agent

agent部分指定整个Pipeline或特定阶段将在Jenkins环境中执行的位置,具体取决于该agent 部分的放置位置。该部分必须在pipeline块内的顶层定义 ,但阶段级使用是可选的。

需要
参数 如下面所描述的
允许 在顶级pipeline块和每个stage块中。

参数

为了支持作者可能有的各种各样的pipeline用例, agent 部分支持一些不同类型的参数。这些参数应用在pipeline块的顶层, 或 stage 指令内部。

any

在任何可用的代理上执行Pipeline或stage。例如:agent any

none

当在pipeline块的顶层应用时,将不会为整个Pipeline运行分配全局代理,并且每个stage部分将需要包含其自己的agent部分。例如:agent none

label

使用提供的标签在Jenkins环境中可用的代理上执行Pipeline或阶段性执行。例如:agent { label 'my-defined-label' }

node

agent { node { label 'labelName' } }行为相同 agent { label 'labelName' },但node允许其他选项(如customWorkspace)。

docker

执行Pipeline,或阶段执行,用给定的容器将被动态地供应一个节点预先配置成接受基于Docker-based Pipelines,或匹配的任选定义的节点上 label的参数。 docker还可以接受一个args可能包含直接传递给docker run调用的参数的参数。例如:agent { docker 'maven:3-alpine' }

agent {
    docker {
        image 'maven:3-alpine'
        label 'my-defined-label'
        args  '-v /tmp:/tmp'
    }
}
  • dockerfile

使用从Dockerfile源存储库中包含的容器构建容器来执行Pipeline或阶段性执行 。为了使用此选项,Jenkinsfile必须从多分支Pipeline或“Pipeline从SCM”加载。通常这是Dockerfile源库的根源:agent { dockerfile true }。如果Dockerfile在另一个目录中建立,请使用以下dir选项:agent { dockerfile { dir 'someSubDir' } }。您可以docker build ...使用该additionalBuildArgs选项将其他参数传递给命令,如agent { dockerfile { additionalBuildArgs '--build-arg foo=bar' } }

常用选项

这些是可以应用两个或多个agent实现的几个选项。除非明确说明,否则不需要。

  • 标签

一个字符串。运行Pipeline或个人的标签stage。此选项对于nodedockerdockerfile,并且是必需的 node

  • customWorkspace

一个字符串。运行Pipeline或个人stageagent 是这个自定义的工作空间内的应用,而不是默认的。它可以是相对路径,在这种情况下,自定义工作区将位于节点上的工作空间根目录下,也可以是绝对路径。例如:

agent {
    node {
        label 'my-defined-label'
        customWorkspace '/some/other/path'
    }
}
  • reuseNode

一个布尔值,默认为false。如果为true,则在同一工作空间中,而不是完全在新节点上运行Pipeline顶层指定的节点上的容器。此选项适用于dockerdockerfile,并且仅在agent个人使用时才有效果stage

例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent { docker 'maven:3-alpine' } 
    stages {
        stage('Example Build') {
            steps {
                sh 'mvn -B clean verify'
            }
        }
    }
}

img:在给定名称和tag(maven:3-alpine)的新创建的容器中执行此Pipeline中定义的所有步骤。

Stage-level agent 部分

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent none 
    stages {
        stage('Example Build') {
            agent { docker 'maven:3-alpine' } 
            steps {
                echo 'Hello, Maven'
                sh 'mvn --version'
            }
        }
        stage('Example Test') {
            agent { docker 'openjdk:8-jre' } 
            steps {
                echo 'Hello, JDK'
                sh 'java -version'
            }
        }
    }
}

imgagent none在Pipeline顶层定义确保执行者不会被不必要地分配。使用agent none也强制每个stage部分包含自己的agent部分

img:使用此图像在新创建的容器中执行此阶段中的步骤

img:在新创建的容器中使用前一个阶段的不同图像执行此阶段中的步骤

post

该post部分定义将在Pipeline运行或阶段结束时运行的操作。一些条件后 的块的内支持post:部分 always,changed,failure,success,unstable,和aborted。这些块允许在Pipeline运行或阶段结束时执行步骤,具体取决于Pipeline的状态。

需要 没有
参数 没有
允许 在顶级pipeline块和每个stage块中。

条件

  • always

运行,无论Pipeline运行的完成状态如何。

  • changed

只有当前Pipeline运行的状态与先前完成的Pipeline的状态不同时,才能运行。

  • failure

仅当当前Pipeline处于“失败”状态时才运行,通常在Web UI中用红色指示表示。

  • success

仅当当前Pipeline具有“成功”状态时才运行,通常在具有蓝色或绿色指示的Web UI中表示。

  • unstable

只有当前Pipeline具有“不稳定”状态,通常由测试失败,代码违例等引起,才能运行。通常在具有黄色指示的Web UI中表示。

  • aborted

只有当前Pipeline处于“中止”状态时,才会运行,通常是由于Pipeline被手动中止。通常在具有灰色指示的Web UI中表示。

例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
    post { 
        always { 
            echo 'I will always say Hello again!'
        }
    }
}

img:通常,该post部分应放在Pipeline末端

img:后条件块包含的步骤相同的步骤部分

steps

包含一个或多个阶段指令的序列,该stages部分是Pipeline描述的大部分“工作”的位置。建议stages至少包含至少一个阶段指令,用于连续交付过程的每个离散部分,如构建,测试和部署。

需要
参数 没有
允许 只有一次,在pipeline块内。

例如

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

img:的stages部分将典型地遵循指令,例如agentoptions

脚步

该steps部分定义了 在给定指令中执行的一系列一个或多个步骤stage。

需要
参数 没有
允许 在每个stage块内。

例如

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

img:该steps部分必须包含一个或多个步骤

指令

环境

该environment指令指定一系列键值对,这些对值将被定义为所有步骤的环境变量或阶段特定步骤,具体取决于environment指令位于Pipeline中的位置。

该指令支持一种特殊的帮助方法credentials(),可以通过其在Jenkins环境中的标识符来访问预定义的凭据。对于类型为“Secret Text”的凭据,该 credentials()方法将确保指定的环境变量包含Secret Text内容。对于“标准用户名和密码”类型的凭证,指定的环境变量将被设置为, username:password并且将自动定义两个附加的环境变量:MYVARNAME_USR和MYVARNAME_PSW相应的。

需要 没有
参数 没有
允许 pipeline块内或stage指令内。

例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    environment { 
        CC = 'clang'
    }
    stages {
        stage('Example') {
            environment { 
                AN_ACCESS_KEY = credentials('my-prefined-secret-text') 
            }
            steps {
                sh 'printenv'
            }
        }
    }
}

imgenvironment顶级pipeline块中使用的指令将适用于Pipeline中的所有步骤

img:在一个environment意图中定义的一个指令stage将仅将给定的环境变量应用于该过程中的步骤stage

img:该environment块具有一个帮助方法credentials(),可用于在Jenkins环境中通过其标识符访问预定义的凭据

选项

该options指令允许在Pipeline本身内配置Pipeline专用选项。Pipeline提供了许多这些选项,例如buildDiscarder,但它们也可能由插件提供,例如 timestamps。

需要 没有
参数 没有
允许 只有一次,在pipeline块内。

可用选项

  • buildDiscarder

持久化工件和控制台输出,用于最近Pipeline运行的具体数量。例如:options { buildDiscarder(logRotator(numToKeepStr: '1')) }

  • disableConcurrentBuilds

不允许并行执行Pipeline。可用于防止同时访问共享资源等。例如:options { disableConcurrentBuilds() }

  • skipDefaultCheckout

agent指令中默认跳过来自源代码控制的代码。例如:options { skipDefaultCheckout() }

  • skipStagesAfterUnstable

一旦构建状态进入了“不稳定”状态,就跳过阶段。例如:options { skipStagesAfterUnstable() }

  • timeout

设置Pipeline运行的超时时间,之后Jenkins应该中止Pipeline。例如:options { timeout(time: 1, unit: 'HOURS') }

  • retry

失败后,重试整个Pipeline指定的次数。例如:options { retry(3) }

  • timestamps

预处理由Pipeline生成的所有控制台输出运行时间与发射线的时间。例如:options { timestamps() }

例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    options {
        timeout(time: 1, unit: 'HOURS') 
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

img:指定一个小时的全局执行超时,之后Jenkins将中止Pipeline运行。

完整的INFRA-1503完整列表可供选择 

参数

该parameters指令提供用户在触发Pipeline时应提供的参数列表。这些用户指定的参数的值通过该params对象可用于Pipeline步骤,具体用法见示例

需要 没有
参数 没有
允许 只有一次,在pipeline块内。

可用参数

string

  • booleanParam

一个布尔参数,例如: parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }

例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"
            }
        }
    }
}
INFRA-1503的完整列表可供参考 。

触发器

该triggers指令定义了Pipeline应重新触发的自动化方式。对于与源代码集成的Pipeline,如GitHub或BitBucket,triggers可能不需要基于webhook的集成可能已经存在。目前只有两个可用的触发器是cron和pollSCM。

需要 没有
参数 没有
允许 只有一次,在pipeline块内。
  • cron

接受一个cron风格的字符串来定义Pipeline应重新触发的常规间隔,例如: triggers { cron('H 4/* 0 0 1-5') }

  • pollSCM

接受一个cron风格的字符串来定义Jenkins应该检查新的源更改的常规间隔。如果存在新的更改,则Pipeline将被重新触发。例如:triggers { pollSCM('H 4/* 0 0 1-5') }

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

例如:

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

stage

该stage指令在该stages部分中,应包含步骤部分,可选agent部分或其他特定于阶段的指令。实际上,Pipeline完成的所有实际工作都将包含在一个或多个stage指令中。

需要 最后一个
参数 一个强制参数,一个用于舞台名称的字符串。
允许 stages部分内。

例如:

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

工具

定义自动安装和放置工具的部分PATH。如果agent none指定,这将被忽略。

需要 没有
参数 没有
允许 pipeline块或stage块内。

支持的工具

  • mavenjdkgradle例如:
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    tools {
        maven 'apache-maven-3.0.1' 
    }
    stages {
        stage('Example') {
            steps {
                sh 'mvn --version'
            }
        }
    }
}

img:工具名称必须在Jenkins 管理Jenkins全局工具配置中**预**配置

when

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

更复杂的条件结构可使用嵌套条件建:not,allOf或anyOf。嵌套条件可以嵌套到任意深度。

需要 没有
参数 没有
允许 stage指令内

内置条件

  • branch

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

  • environment

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

  • expression

当指定的Groovy表达式求值为true时执行stage,例如: when { expression { return params.DEBUG_BUILD } }

  • not

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

  • allOf

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

  • anyOf

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

例如:

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'
                environment name: 'DEPLOY_TO', value: 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                allOf {
                    branch 'production'
                    environment name: 'DEPLOY_TO', value: 'production'
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'
                anyOf {
                    environment name: 'DEPLOY_TO', value: 'production'
                    environment name: 'DEPLOY_TO', value: 'staging'
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                expression { BRANCH_NAME ==~ /(production|staging)/ }
                anyOf {
                    environment name: 'DEPLOY_TO', value: 'production'
                    environment name: 'DEPLOY_TO', value: 'staging'
                }
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

Steps

声明性Pipeline可以使用“ Pipeline步骤”引用中记录的所有可用步骤 ,其中包含一个完整的步骤列表,并附加以下列出的步骤,仅在声明性PipelinePipeline Pipeline 中支持。

script

该script步骤需要一个script Pipeline,并在声明性Pipeline中执行。对于大多数用例,script声明Pipeline中的步骤不是必须的,但它可以提供一个有用的“escape hatch”。script不平凡的大小和/或复杂性的块应该转移到共享库中。

例如:

Jenkinsfile (Declarative Pipeline)
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,如声明式Pipeline,构建在底层Pipeline子系统之上。不像声明,Scripted Pipeline有效地是一个通用的DSL构建与Groovy。由Groovy语言提供的大多数功能都提供给Scripted Pipeline的用户,这意味着它可以是一个非常富有表现力和灵活性的工具,可以通过这些工具来创建连续的传送Pipeline。

Flow Control

Scripted Pipeline从顶部顺序执行,与Jenkinsfile Groovy或其他语言中的大多数传统Scripted一样。因此,提供流量控制取决于Groovy表达式,例如 if/else条件,例如:

Jenkinsfile (Scripted Pipeline)
node {
    stage('Example') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}

可以管理Scripted Pipeline流控制的另一种方式是使用Groovy的异常处理支持。当步骤由于任何原因而导致异常时。处理错误行为必须使用try/catch/finallyGroovy 中的块,例如:

Jenkinsfile (Scripted Pipeline)
node {
    stage('Example') {
        try {
            sh 'exit 1'
        }
        catch (exc) {
            echo 'Something failed, I should sound the klaxons!'
            throw
        }
    }
}

Steps

如“ 入门指南 ”所述,Pipeline最基本的部分是“步骤”。从根本上说,步骤告诉Jenkins 要做什么,并且作为Declarative和Scripted Pipeline语法的基本构建块。

Scripted Pipeline并没有介绍这是专门针对它的语法的任何步骤; Pipeline步骤参考 ,其中包含Pipeline和插件提供的完整步骤列表。

与简单的Groovy的区别

为了提供耐久性,这意味着运行Pipeline可以在重新启动Jenkins主站后保留,Scripted Pipeline必须将数据序列化回主站。由于这个设计要求,一些Groovy成语如collection.each { item -> /* perform operation */ }没有完全支持。有关 更多信息,请参见 JENKINS-27421JENKINS-26481

语法比较

当Jenkins Pipeline首次创建时,Groovy被选为基础。Jenkins长期运用嵌入式Groovy引擎,为管理员和用户提供高级脚本功能。此外,Jenkins Pipeline的实施者发现Groovy是建立现在称为“Scripted Pipeline”DSL的坚实基础。

由于它是一个功能齐全的编程环境,Scripted Pipeline为Jenkins用户提供了极大的灵活性和可扩展性。Groovy学习曲线通常不适用于给定团队的所有成员,因此,创建声明性Pipeline是为了创作Jenkins Pipeline提供一个更简单和更有见解的语法。

两者基本上是下面相同的Pipeline 子系统。它们都是“Pipeline代码”的持久实现。他们都能够使用Pipeline内置的插件或插件提供的步骤。两者都可以利用共享库

不同之处在于语法和灵活性。声明性限制了用户具有更严格和预定义结构的可用性,使其成为更简单连续输送Pipeline的理想选择。脚本化提供了极少的限制,因为Groovy本身只能对结构和语法进行限制,而不是任何Pipeline专用系统,使其成为电力用户和具有更复杂要求的用户的理想选择。顾名思义,Declarative Pipeline鼓励声明式编程模型。 尽管Scripted Pipeline遵循更命令性的编程模型。

更多资料

https://www.w3cschool.cn/jenkins/jenkins-epas28oi.html