必要的 Groovy 知识
虽然学习 Jenkin Pipeline 可以不需要任何 Groovy 知识,但是学习以下 Groovy 知识,对于我们写 Pipeline 如虎添翼。
- 虽然 Groovy 同时支持静态类型和动态类型,但是在定义变量时,在 Groovy 中我们习惯使用 def 关键字,比如 def x = “abc”,def y = 1。
- 不像 Java,Groovy 语句最后的分号不是必须的。
- Groovy 中的方法调用可以省略括号,比如 System.out.println “Hello world”。
- 方法支持命名参数,比如:
1 | def createName(String givenName, String familyName){ |
- 方法支持默认参数值,比如
1 | def sayHello(String name = "humans"){ |
- 支持单引号,双引号,双引号支持插值,单引号不支持。(类似shell 中的双引号和单引号)比如:
1 | def name = 'world' |
- 支持三引号。三引号分为三单引号和三引号。他们都支持换行,区别在于只有三双引号支持插值。比如:
1 | def name = 'world' |
- 支持闭包。闭包的定义方法如下:
1 | //定义闭包 |
- 还可以将闭包看做一个参数传递给另一个方法
1 | //定义一个 pipelne 函数,它接受一个闭包参数 |
- 闭包的另类用法。我们定义一个 stage 函数
1 | def stage(String name, closue){ |
这些知识点没有连贯性,我们浏览一遍后,有个大概印象就可以了。
pipeline 的组成
Jenkins pipeline 其实就是基于 Groovy 语言实现的一种 DSL(领域特定语言),用于描述整条流水线是如何进行的。流水线的内容包括执行编译,打包,测试,输出测试报告等步骤。
pipeline 最简结构
前文中,我们已经了解到:从软件版本控制库到用户手中这一过程可以分为很多阶段,每个阶段只专注处理一件事情,而这件事情又是通过多个步骤来完成的,这就是软件工程的 pipeline。
Jenkins 对这个过程进行抽象,设计出一个基本的 pipeline 结构
1 | pipeline{ |
- pipeline:代表整条流水线,包含整条流水线的逻辑;
- agent 部分:指定流水线的执行位置(Jenkins agent)。流水线中的每个阶段都必须在某个地方(物理机,虚拟机或 Docker容器)执行,agent 部分即指定具体在哪里执行。
- stages 部分:流水线中多个 stage 的容器。stages 部分至少包含一个 stage;
- stage 部分:阶段,代表流水线的阶段。每个阶段都必须有名称。本例中,build 就是此阶段的名称;
- steps 部分:代表阶段中的一个或多个具体步骤(step)的容器。steps 部分至少包含一个步骤,本例中,echo 就是一个步骤。在一个 stage 中有且只有一个 steps。
以上每个部分都是必需的,少一个 Jenkins 都会报错。
步骤
pipeline基本结构决定的是pipeline整体流程,但是真正“做事”的还是 pipeline 中的每一个步骤。步骤是 pipeline 中已经不能再拆分的最小操作。前文中,我们只看到两个步骤:sh
和 echo
。sh 是指执行一条shell 命令;echo 是指执行 echo 命令。这两个步骤只是 Jenkins pipeline 内置的大量步骤中的两个。
那是不是说,Jenkins pipeline 内置了所有可能需要用到的步骤呢?显示没有必要。因为有些步骤我们可能一辈子也不会用到。
更好的设计师:步骤是可插拔的,就像 Jenkins 的插件一样。如果现有的插件不用修改或者只需要简单修改,就能在 Jenkins pipeline 中当成一个步骤来使用,该多好?这样就不用重新实现一遍已经存在的插件了。
Jenkins 就是这样做的。只需要对现有的插件进行一些修改,就可以在 pipeline 中被当成一个步骤使用。这样大大降低了从现有依赖于界面的插件过度到 pipeline 中步骤的成本。
已经有哪些插件适配了 Jenkins pipeline 呢?pipeline plugin 的 GitHub 仓库 https://github.com/jenkinsci/pipeline-plugin/blob/master/COMPATIBILITY.md 给出了一个列表方便大家检索;
只要安装了这些适配 Jenkins pipeline 的插件,就可以使用其提供的 pipeline 步骤。
Jenkins 官方还提供了 pipeline 步骤参考文档(https://www.jenkins.io/doc/pipeline/steps/)
post 部分
在上一节中,我们已经见过 post 部分,在 pipeline 执行失败后,发送邮件到指定邮箱中。
1 | post { |
post 部分包含的是在整个 pipeline 或阶段完成后一些附加的步骤。post 部分是可选的,所以并不包含在 pipeline 最简结构中。但这并不代表它作用不大。
根据 pipeline 或阶段的完成状态,post 部份分成多种条件块,包括:
- always:不论当前完成状态时什么,都执行;
- changed:只要当前完成状态与上一次完成状态不同就执行;
- fixed:上一次完成状态为失败或不稳定(unstable),当前完成状态为成功时执行;
- regression:上一次完成状态为成功,当前完成状态为是啊比,不稳定或中止(aborted)时执行;
- aborted:当前执行结果为中止状态时(一般为人为中止)执行;
- failure:当前完成状态为失败时执行;
- success:当前完成状态为成功时执行;
- unstable:当前完成状态为不稳定时执行;
- cleanup:清理条件块。不论当前完成状态时什么,在其他所有条件块执行完成后都执行。
post 部分可以同时包含多种条件块。以下是 post 部分的完整示例:
1 | pipeline { |
pipeline 支持的指令
显然,基本的结构满足不了现实多变的需求。所以,Jenkins pipeline通过各种指令(directive)来丰富自己。指令可以被理解为对 Jenkins pipeline 基本结构的补充。
Jenkins pipeline 支持的指令有:
- environment:用于设置环境变量,可定义在 stage 或 pipeline 部分;
- tools:可定义在 pipeline 或 stage 部分,它会自动下载并安装我们指定的工具,并将其加入 PATH 变量中;
- input:定义在 stage 部分,会暂停 pipeline,提示你输入内容;
- options:用于配置 Jenkins pipeline 本身的的选项,比如 options { retry(3) } 指当前 pipeline 失败时再重试 2次。options 指令可定义在 stage 或 pipeline 部分;
- parallel:并行执行多个 step。在 pipeline 插件 1.2 版本后,parallel 开始支持多个阶段进行并行执行;
- parameters:与 input 不同,parameters 是执行 pipeline 之前传入的一些参数;
- triggers:用于定义执行 pipeline 的触发器;
- when:当满足 when 定义的条件时,阶段才执行。
在使用指令时,需要注意的是每个指令都有自己的 作用域
.如果指令使用的位置不正确,Jenkins 会报错。
配置 pipeline 本身
options 指令用于配置整个 Jenkins pipeline 本身的选项。根据具体的选项不同,可以将其放在 pipeline 块或 stage 块中。以下例子若没有特别说明,options 被放置在 pipeline 块中。
(本节内容,初学者可以跳过)
接下来我们介绍几个常用的选项。
- buildDiscarder:保存最新历史构建记录的数量。当 pipeline 执行完成后,会在硬盘上保存制品和构建执行日志,如果长时间不清理会占用大量空间,设置此选项后会自动清理。此选项只能在 pipeline 下的 options 中使用。示例如下:
1 | options { |
- checkoutToSubdirectory:Jenkins 从版本控制库拉取源码时,默认检出到工作空间的根目录中,此选项可以指定检出到工作空间的子目录中。示例如下:
1 | options { |
- disableConcurrentBuilds:同一个 pipeline,Jenkins 默认是可以同时执行多次的,此选项是为了禁止 pipeline 同时执行。示例如下:
1 | options { |
在某些 pipeline 存在抢占资源或调用冲突的场景下,此选项非常有用。
- newContainerPerStage:当 agent 为 Docker 或 dockerfile 时,指定在同一个 Jenkins 节点上,每个 stage 都分别运行在一个新的容器中,而不是所有 stage 都运行在同一个容器中。
1 | options { |
- retry:当发生失败时进行重试,可以指定整个 pipeline 的重试次数。需要注意的是,这个次数是指总次数,包括第一次失败。以下例子总共会执行4次。当使用 retry 选项时,options 可以被放在 stage 块中。
1 | pipeline { |
- timeout:如果 pipeline 执行时间过长,超出了我们设置的 timeout 时间,Jenkins 将中止 pipeline。以下例子中以小时为单位,还可以以 SECONDS(秒), MINUTES(分钟) 为单位。当使用 timeout 选项时,options 可以被放在 stage 块中。
1 | options { |
设置此选项后,强迫团队去处理执行时间过长的 pipeline,从而优化 pipeline 大反馈周期。通常将 timeout 设置为 10分钟就可以了。
在声明式 pipeline 中使用脚本
在使用声明式 pipeline 一段时间后,你会发现直接在 steps 块中写 if-else ,或者定义一个变量,Jenkins 都会报错。也就是不能直接在 steps 块中写 Groovy 代码。
Jenkins pipeline 专门提供了一个 script 步骤,你能在 script 步骤中像写代码一样写 pipeline 逻辑。比如分别在不同的浏览器上跑测试。
1 | pipeline { |
可以看出,在 script 块中的其实就是 Groovy 代码。大多数时候,我们是不需要使用 script 步骤的。如果在 script 步骤中写了大量的逻辑,则说明你应该把这些逻辑拆分到不同的阶段,或者放到共享库中。共享库是一种扩展 Jenkins pipeline 的技术。
另外,细心的读者可能已经注意到,这样串行的测试方法是低效的,而应该在不同的浏览器上并行跑测试。
pipeline 内置基础步骤
本节介绍 pipeline 内置的一些步骤,作为参考内容,跳过本节不影响整体阅读。
文件目录相关的步骤
deleteDir:删除当前目录
deleteDir 是一个无参数步骤,删除的是当前工作目录。通常它与 dir 步骤一起使用,用于删除指定目录下的内容。
dir: 切换到目录
默认 pipeline 工作在工作空间目录下,dir 步骤可以让我们切换到其他目录。使用方法如下:
1 | dir("/var/logs") { |
fileExists: 判断文件是否存在
fileExists(‘/tmp/a.jar’) 判断 /tmp/a.jar 文件是否存在。如果参数是相对路径,则判断在相对当前工作目录下,该文件是否存在。结果返回布尔类型。
isUnix: 判断是否为类 UNIX 系统
如果当前 pipeline 运行在一个类UNIX 系统上,则返回 true。
pwd:确认当前目录
pwd 与Linux的 pwd 命令一样,返回的是当前所在目录。它有一个布尔类型的可选参数:tmp,如果参数值为 true,则返回与当前工作空间关联的临时目录。
writeFile:将内容写入指定的文件中
writeFile 支持的参数有:
- file: 文件路径,可以是绝对路径,也可以是相对路径
- text: 要写入的文件内容
- encoding(可选): 目标文件的编码。如果留空,则使用操作系统默认的编码。如果写的是 Base64 的数据,则可以使用 Base64 编码。
readFile: 读取文件内容
读取指定文件的内容,以纯文本返回。readFile 支持的参数有:
- file: 路径,可以是绝对路径,也可以是相对路径
- encoding(可选): 读取文件时使用的编码
示例如下:
1 | script { |
制品相关的步骤
stash: 保存临时文件
stash 步骤可以将一些文件保存起来,以便被同一次构建的其他步骤或阶段使用。如果整个 pipeline 的所有阶段在同一台机器上执行,则 stash 步骤是多余的。所以,通常需要 stash 的文件都是要跨 Jenkins node 使用的。
stash 步骤会将文件存储在 tar 文件中,对于大文件的 stash 操作将会消耗 Jenkins master 的计算资源。Jenkins 官方文档推荐,当文件大小为 5~100MB时,应该考虑使用其他替代方案。
stash 步骤的参数列表如下:
- name: 字符串类型,保存文件的集合的唯一标识;
- allowEmpty: 布尔类型,允许 stash 内容为空;
- excludes: 字符串类型,将哪些文件排除。如果排除多个文件,则使用逗号分隔。留空代表不排除任何文件;
- includes: 字符串类型,stash 哪些文件,留空代表当前文件夹下的所有文件;
- useDefaultExcludes: 布尔类型,如果为 true, 则代表使用 Ant 风格路径默认排除文件列表。
除了 name 参数,其他的参数都是可选的。excludes 和 includes 使用的是 Ant 风格路径表达式。
unstash: 取出之前 stash 的文件
unstash 步骤只有一个 name 参数,即 stash 时的唯一标识。通常 stash 与 unstash 步骤同时使用。以下是完整示例:
1 | pipeline { |
stash 步骤在 master 节点上执行,而 unstash 步骤在 node2 节点上执行。
命令行相关步骤
与命令相关的步骤其实是 Pipeline:Nodes and Process 插件提供的步骤。由于它是 Pipeline 插件的一个组件,所以基本不需要单独安装。
sh:执行 shell 命令
sh 步骤支持的参数有:
- script:将要执行的 shell 脚本,通常在类 UNIX 系统上可以使多行脚本;
- encoding:脚本执行后输出日志的编码,默认值为脚本运行所在系统的编码;
- returnStatus:布尔类型,默认脚本返回的是状态码,如果返回的是一个非零的状态码,则会引发 pipeline 执行失败。如果 returnStatus 参数为 true,则不论状态码是什么,pipeline 的执行都不会受到影响;
- returnStdout:布尔类型,如果为 true,则任务的标准输出将作为步骤的返回值,而不是打印到构建日志中(如果有错误,则依然会打印到日志中);
除了 script 参数,其他的参数都是可选的。
returnStatus 与 returnStdout 参数一般不会同时使用,因为返回值只能有1个。如果同时使用,则只有 returnStatus 参数生效。
bat,powershell 步骤
bat 步骤执行的是 Windows 的批处理命令。powershell 步骤执行的是 PowerShell 脚本,支持 3+ 版本。这两个步骤支持的参数与 sh 步骤的一样,这里就不重复介绍了。
error:主动报错,终止当前 pipeline
error 步骤的执行类似于抛出一个异常。它只有一个必需参数:message。通常省略参数:error(“there’s an error”)。
tool: 使用预定义的工具
如果在 Global Tool Configuration(全局工具配置)中配置了工具,比如配置了 Docker,那么可以通过 tool 步骤得到工具路径。
1 | steps { |
tool 支持的参数有:
- name:工具名称
- type(可选):工具类型,指该工具安装类的全路径名称。
每个插件的 type 值都不一样,而且绝大多数插件的文档根本不写 type 值。除了到该插件的源码中查找,还有一种方法可以让我们快速找到 type 值,就是前往 Jenkins pipeline 代码片段中=生成器中生成该 tool 步骤的代码即可。
timeout:代码块超时时间
为 timeout 步骤闭包内运行的代码设置超时时间限制。如果超时,将抛出一个 org.jenkinsci.plugins.workflow.steps.FlowInterruptedExpection 异常。
timeout 步骤支持的参数如下:
- time:整型,超时时间;
- unit(可选):时间单位,支持的值有 NANOSECONDS,MICROSECONDS,MILLISECONDS,SECONDS,MINUTES(默认),HOURS,DAYS。
- activity(可选):布尔类型,如果值为 true,则只有当日志没有活动后,才真正算作超时。
waitUntil:等待条件满足
不断重复 waitUntil 块内的代码,直到条件为 true。waitUntil不负责处理块内代码的异常,遇到异常时直接向外抛出。waitUntil 步骤最好与 timeout 步骤共同使用,避免死循环。示例如下:
1 | timeout(50) { |
retry: 重复执行块
执行 N 次闭包内的脚本。如果其中某次执行抛出异常,则只中止本次执行,并不会中止整个 retry 的执行。同时,在执行 retry 的过程中,用户是无法中止 pipeline 的。
1 | steps { |
sleep: 让 pipeline 休眠一段时间
sleep 步骤可用于简单地暂停 pipeline,其支持的参数有:
- time: 整型,休眠时间
- unit: (可选),时间单位,支持的值有 NANOSECONDS,MICROSECONDS,MILLISECONDS,SECONDS(默认),MINUTES,HOURS,DAYS。
示例如下:
1 | sleep(120) //休眠 120 秒 |