使用 Jenkinsfile
-
创建 Jenkinsfile
- 构建
- 测试
- 部署
-
编写和使用 Jenkinsfile
-
使用环境变量
- 设置环境变量
- 动态设置环境变量
-
处理凭据
-
密文、用户名与密码以及秘密文件
- 密文
- 用户名与密码
- 秘密文件
-
其他凭据类型
- 在同一步骤中组合凭据
-
-
字符串插值
- 敏感环境变量的插值
- 通过插值注入命令
- 处理参数
-
处理失败
- 错误处理步骤
- 使用多个代理
- 可选的步骤参数
-
高级脚本式流水线
- 并行执行
-
本节在“流水线入门”的基础上介绍更多实用步骤和常见模式,并展示一些较复杂的 Jenkinsfile 示例。
创建 Jenkinsfile 并将其纳入源代码管理 [1],可以立即带来以下好处:
-
对流水线进行代码评审和迭代。
-
保留流水线的审计记录。
-
为流水线建立单一事实来源 [2],项目中的多个成员可以共同查看和编辑。
流水线支持两种语法:声明式流水线(在 Pipeline 2.5 中引入)和脚本式流水线。两者都能构建持续交付流水线,也都可以在网页界面或 Jenkinsfile 中定义流水线。不过,通常推荐创建 Jenkinsfile 并将其提交到源代码仓库。
创建 Jenkinsfile
如“在 SCM 中定义流水线”所述,Jenkinsfile 是包含 Jenkins 流水线定义、并纳入源代码管理的文本文件。下面的流水线实现了一个基本的三阶段持续交付流程。
Jenkinsfile(声明式流水线)
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building..'
}
}
stage('Test') {
steps {
echo 'Testing..'
}
}
stage('Deploy') {
steps {
echo 'Deploying....'
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
node {
stage('Build') {
echo 'Building....'
}
stage('Test') {
echo 'Testing....'
}
stage('Deploy') {
echo 'Deploying....'
}
}
并非所有流水线都采用这三个阶段,但对大多数项目来说,这是一种合适的起点。下面将演示如何在用于测试的 Jenkins 安装中创建并执行简单流水线。
|
这里假设项目已经建立源代码仓库,并按照相关说明在 Jenkins 中定义了流水线。 |
使用文本编辑器,在项目根目录新建 Jenkinsfile;最好选择支持 Groovy 语法高亮的编辑器。
上面的声明式流水线示例包含实现持续交付流水线所需的最小结构。必需的 agent 指令告诉 Jenkins 为流水线分配执行器和工作区。没有 agent 指令,声明式流水线不仅无效,也无法执行任何工作。默认情况下,agent 指令会确保检出源代码仓库,使后续阶段的步骤可以使用这些代码。
有效的声明式流水线也必须包含 stages 和 steps 指令,它们告诉 Jenkins 要执行什么,以及在哪个阶段执行。
对于更高级的脚本式流水线,上例中的 node 是关键的第一步,因为它为流水线分配执行器和工作区。没有 node,流水线就无法开展工作。在 node 内,首先要 checkout 项目的源代码。由于 Jenkinsfile 本身直接来自源代码管理系统,流水线提供了一种快捷方式来访问正确的代码修订版本。
Jenkinsfile(脚本式流水线)
node {
checkout scm (1)
/* .. snip .. */
}
| 1 | checkout 步骤从源代码管理系统检出代码;scm 是一个特殊变量,它告诉 checkout 步骤克隆触发本次流水线运行的特定修订版本。 |
构建
许多项目的流水线从构建阶段开始实际工作。在此阶段通常会组装、编译或打包源代码。Jenkinsfile 并不替代 GNU/Make、Maven、Gradle 等现有构建工具,而是把项目开发生命周期中的构建、测试和部署等多个阶段连接起来。
Jenkins 提供了许多插件,可以调用几乎所有常用构建工具。本例直接通过 shell 步骤 sh 调用 make。sh 假定运行环境是 Unix/Linux;Windows 系统可改用 bat。
Jenkinsfile(声明式流水线)
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make' (1)
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true (2)
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
node {
stage('Build') {
sh 'make' (1)
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true (2)
}
}
| 1 | sh 步骤调用 make 命令,只有命令返回零退出码时才继续。任何非零退出码都会使流水线失败。 |
| 2 | archiveArtifacts 收集符合包含模式 **/target/*.jar 的构建文件,并将其保存到 Jenkins 控制器,以便之后提取。 |
|
归档构建产物不能替代 Artifactory 或 Nexus 等外部制品仓库,只应将其用于基本报告和文件归档。 |
测试
运行自动化测试是成功实现持续交付的关键组成部分。Jenkins 通过多个插件提供测试记录、报告和可视化功能。测试失败时,应让 Jenkins 记录失败信息,以便在网页界面中报告和展示。下例使用 JUnit 插件提供的 junit 步骤。
下例中,如果测试失败,流水线会被标记为“不稳定”,网页界面用黄色圆球表示。Jenkins 还可以根据记录的测试报告分析并展示历史趋势。
Jenkinsfile(声明式流水线)
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' (1)
junit '**/target/*.xml' (2)
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
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' (1)
junit '**/target/*.xml' (2)
}
/* .. snip .. */
}
| 1 | 通过内联 shell 条件 sh 'make check || true',可以确保 sh 步骤始终获得零退出码,让 junit 步骤有机会收集和处理测试报告。后面的“处理失败”一节会详细介绍其他方法。 |
| 2 | junit 收集并关联符合包含模式 **/target/*.xml 的 JUnit XML 文件。 |
部署
部署所包含的步骤取决于项目或组织的需求,可能是将构建产物发布到 Artifactory 服务器,也可能是将代码推送到生产系统。
在示例流水线执行到此处时,构建和测试阶段都已成功执行。部署阶段只有在前面的阶段成功完成后才会运行,否则流水线会提前退出。
Jenkinsfile(声明式流水线)
pipeline {
agent any
stages {
stage('Deploy') {
when {
expression {
currentBuild.result == null || currentBuild.result == 'SUCCESS' (1)
}
}
steps {
sh 'make publish'
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
node {
/* .. snip .. */
stage('Deploy') {
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') { (1)
sh 'make publish'
}
}
/* .. snip .. */
}
| 1 | 访问 currentBuild.result 变量,可以判断流水线中是否出现测试失败;如果出现,该值为 UNSTABLE。 |
假设示例 Jenkins 流水线中的所有步骤都成功执行,那么每次成功运行都会在 Jenkins 中保存相关构建产物、测试报告和完整控制台输出。
脚本式流水线可以包含上述条件判断、循环、try/catch/finally 代码块,甚至函数。下一节会进一步介绍这些高级语法。
编写和使用 Jenkinsfile
以下各节详细说明如何处理:
-
Jenkinsfile中的特定流水线语法; -
构建应用或流水线项目所必需的流水线语法特性与功能。
使用环境变量
Jenkins 流水线通过全局变量 env 暴露环境变量,Jenkinsfile 中的任何位置都可以访问它。可用环境变量的完整列表见 ${YOUR_JENKINS_URL}/pipeline-syntax/globals#env,其中包括:
- BUILD_ID
-
当前构建 ID。对于 Jenkins 1.597 及以后版本创建的构建,它与 BUILD_NUMBER 相同。
- BUILD_NUMBER
-
当前构建编号,例如 153。
- BUILD_TAG
-
格式为 jenkins-${JOB_NAME}-${BUILD_NUMBER} 的字符串,适合放入资源文件、JAR 文件等位置,便于识别。
- BUILD_URL
-
查看本次构建结果的 URL,例如 http://buildserver/jenkins/job/MyJobName/17/。
- EXECUTOR_NUMBER
-
在同一台机器的执行器中,标识当前执行此构建的执行器的唯一编号。它与“构建执行器状态”中显示的编号对应,但从 0 而不是 1 开始。
- JAVA_HOME
-
如果任务配置为使用特定 JDK,该变量就设为该 JDK 的 JAVA_HOME。设置此变量时,PATH 也会更新,以包含 JAVA_HOME 下的 bin 子目录。
- JENKINS_URL
-
Jenkins 的完整 URL,例如 https://example.com:port/jenkins/。注意:只有在系统配置中设置了 Jenkins URL,该变量才可用。
- JOB_NAME
-
本次构建所属项目的名称,例如 foo 或 foo/bar。
- NODE_NAME
-
当前构建运行所在节点的名称。对于 Jenkins 控制器,该值为 master。
- WORKSPACE
-
工作区的绝对路径。
可以像访问 Groovy Map 中的键一样引用或使用这些环境变量,例如:
Jenkinsfile(声明式流水线)
pipeline {
agent any
stages {
stage('Example') {
steps {
echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
node {
echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
}
设置环境变量
在 Jenkins 流水线中设置环境变量的方法,取决于采用声明式还是脚本式流水线。
声明式流水线支持 environment 指令,脚本式流水线则必须使用 withEnv 步骤。
Jenkinsfile(声明式流水线)
pipeline {
agent any
environment { (1)
CC = 'clang'
}
stages {
stage('Example') {
environment { (2)
DEBUG_FLAGS = '-g'
}
steps {
sh 'printenv'
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
node {
/* .. snip .. */
withEnv(["PATH+MAVEN=${tool 'M3'}/bin"]) {
sh 'mvn -B verify'
}
}
| 1 | 在顶层 pipeline 块中使用 environment 指令,会影响流水线中的所有步骤。 |
| 2 | 在某个 stage 内定义的 environment 指令,只会将指定环境变量应用于该阶段中的步骤。 |
动态设置环境变量
环境变量可以在运行时设置,并供 shell 脚本 sh、Windows 批处理脚本 bat 和 PowerShell 脚本 powershell 使用。每个脚本都可以使用 returnStatus 或 returnStdout。更多信息参见脚本文档。
下例在声明式流水线中使用 sh,同时展示 returnStatus 和 returnStdout。
Jenkinsfile(声明式流水线)
pipeline {
agent any (1)
environment {
// Using returnStdout
CC = """${sh(
returnStdout: true,
script: 'echo "clang"'
)}""" (2)
// Using returnStatus
EXIT_STATUS = """${sh(
returnStatus: true,
script: 'exit 1'
)}"""
}
stages {
stage('Example') {
environment {
DEBUG_FLAGS = '-g'
}
steps {
sh 'printenv'
}
}
}
}
| 1 | 必须在流水线顶层设置 agent。如果设为 agent none,此例会失败。 |
| 2 | 使用 returnStdout 时,返回字符串末尾会附加空白字符;使用 .trim() 可将其移除。 |
处理凭据
可以在流水线中直接使用 Jenkins 已配置的凭据。更多信息见“使用凭据”。
在 Jenkins 中正确处理凭据
密文、用户名与密码以及秘密文件
Jenkins 声明式流水线提供 credentials() 辅助方法,在 environment 指令中使用,支持密文、用户名和密码,以及秘密文件凭据。若要处理其他类型,请参见“其他凭据类型”。
密文
下面的代码演示如何使用环境变量,在流水线中使用密文凭据。
本例将两个密文凭据分配给不同的环境变量,用于访问 Amazon Web Services(AWS)。这些凭据应已在 Jenkins 中配置,其 ID 分别为 jenkins-aws-secret-key-id 和 jenkins-aws-secret-access-key。
Jenkinsfile(声明式流水线)
pipeline {
agent {
// Define agent details here
}
environment {
AWS_ACCESS_KEY_ID = credentials('jenkins-aws-secret-key-id')
AWS_SECRET_ACCESS_KEY = credentials('jenkins-aws-secret-access-key')
}
stages {
stage('Example stage 1') {
steps {
// (1)
}
}
stage('Example stage 2') {
steps {
// (2)
}
}
}
}
| 1 | 在此阶段的 steps 中,可以通过 $AWS_ACCESS_KEY_ID 和 $AWS_SECRET_ACCESS_KEY 引用流水线 environment 指令定义的两个凭据环境变量。例如,可使用分配给这些变量的密文凭据向 AWS 认证。为降低敏感信息泄露到控制台和日志的风险,如果任务在流水线中显示凭据变量的值,例如 echo $AWS_SECRET_ACCESS_KEY,Jenkins 只会返回 ****;凭据 ID 中的敏感信息(如用户名)也会在运行输出中显示为 ****。这只能降低意外泄露风险,无法阻止恶意用户通过其他手段获取凭据值。使用凭据的流水线也能泄露凭据。不要允许不可信的流水线任务使用可信凭据。 |
| 2 | 本例中,分配给两个 AWS_… 环境变量的凭据在整个流水线中全局有效,因此也可以在此阶段的步骤中使用。如果将 environment 指令移入某个特定阶段,如后面的用户名与密码示例所示,那么这些 AWS_… 环境变量就只在该阶段的步骤中有效。 |
| 在 Jenkins 凭据中保存静态 AWS 密钥并不十分安全。如果 Jenkins 本身(至少其代理)可以运行于 AWS,最好为计算机或 EKS 服务账户使用 IAM 角色,也可以使用 Web 身份联合。 |
用户名与密码
以下代码片段演示如何通过环境变量,在流水线中使用用户名与密码凭据。
本例把用户名和密码凭据分配给环境变量,以访问组织公共账户或团队下的 Bitbucket 仓库;这些凭据应已在 Jenkins 中配置,ID 为 jenkins-bitbucket-common-creds。
在 environment 指令中设置凭据环境变量时:
environment {
BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds')
}
实际会设置以下三个环境变量:
-
BITBUCKET_COMMON_CREDS:包含用冒号分隔的用户名和密码,格式为username:password。 -
BITBUCKET_COMMON_CREDS_USR:额外变量,仅包含用户名。 -
BITBUCKET_COMMON_CREDS_PSW:额外变量,仅包含密码。
|
按照惯例,环境变量名通常全部大写,并使用下划线分隔单词。不过,也可以使用合法的小写变量名。注意, |
下面是该示例的完整流水线:
Jenkinsfile(声明式流水线)
pipeline {
agent {
// Define agent details here
}
stages {
stage('Example stage 1') {
environment {
BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds')
}
steps {
// (1)
}
}
stage('Example stage 2') {
steps {
// (2)
}
}
}
}
| 1 | 下列凭据环境变量定义于此流水线的 environment 指令中,可在此阶段的步骤内使用,引用语法如下:
例如,可以使用分配给这些变量的用户名和密码向 Bitbucket 认证。如果任务在流水线中显示凭据变量的值,Jenkins 对用户名与密码变量的保护行为,与上面的密文示例相同。这只能降低意外泄露风险,不能阻止恶意用户通过其他方式取得凭据值。使用凭据的流水线也能泄露凭据。不要允许不可信的流水线任务使用可信凭据。 |
| 2 | 本例中的三个 BITBUCKET_COMMON_CREDS… 环境变量仅在 Example stage 1 内有效,因此无法在 Example stage 2 的步骤中使用。如果将 environment 指令移到 pipeline 块直接内部,如上面的密文示例所示,它们就会在整个流水线中全局有效,可供任意阶段使用。 |
秘密文件
秘密文件是一种保存在文件中、并上传到 Jenkins 的凭据。它适用于以下凭据:
-
内容过于繁杂,不便直接输入 Jenkins;以及/或者
-
采用二进制格式,例如 GPG 文件。
本例使用一个 Kubernetes 配置文件,该文件已经配置为名为 my-kubeconfig 的秘密文件凭据。
Jenkinsfile(声明式流水线)
pipeline {
agent {
// Define agent details here
}
environment {
// The MY_KUBECONFIG environment variable will be assigned the value of a temporary file.
// For example:
// /home/user/.jenkins/workspace/cred_test@tmp/secretFiles/546a5cf3-9b56-4165-a0fd-19e2afe6b31f/kubeconfig.txt
MY_KUBECONFIG = credentials('my-kubeconfig')
}
stages {
stage('Example stage 1') {
steps {
sh("kubectl --kubeconfig $MY_KUBECONFIG get pods")
}
}
}
}
其他凭据类型
如果流水线需要处理密文、用户名与密码、秘密文件之外的其他凭据,例如 SSH 密钥或证书,请使用 Jenkins 经典界面中的代码片段生成器。
进入流水线项目的代码片段生成器:
-
在 Jenkins 仪表板中选择流水线项目名称。
-
在左侧导航栏选择 Pipeline Syntax,并确认导航栏顶部提供 Snippet Generator。
-
在 Sample Step 字段选择 withCredentials: Bind credentials to variables。
-
在 Bindings 下点击 Add,然后从下拉列表选择:
-
SSH User Private Key:处理 SSH 公钥/私钥对凭据,可设置:
-
Key File Variable:绑定此凭据的环境变量名称。Jenkins 实际将此临时变量设为私钥文件的安全存储位置,该文件用于 SSH 密钥对认证。
-
Passphrase Variable(可选):绑定 SSH 公钥/私钥对口令的环境变量名称。
-
Username Variable(可选):绑定 SSH 公钥/私钥对关联用户名的环境变量名称。
-
Credentials:选择保存在 Jenkins 中的 SSH 公钥/私钥凭据。此字段的值是凭据 ID,Jenkins 会将其写入生成的代码片段。
-
-
Certificate:处理 PKCS#12 证书,可设置:
-
Keystore Variable:绑定此凭据的环境变量名称。Jenkins 将此临时变量设为证书密钥库文件的安全存储位置,该文件用于证书认证。
-
Password Variable(可选):绑定证书密码的环境变量名称。
-
Alias Variable(可选):绑定证书唯一别名的环境变量名称。
-
Credentials:选择保存在 Jenkins 中的证书凭据。此字段的值是凭据 ID,Jenkins 会将其写入生成的代码片段。
-
-
Docker client certificate:处理 Docker 主机证书认证。
-
-
点击 Generate Pipeline Script,Jenkins 会针对指定凭据生成 withCredentials(…) { … } 流水线步骤片段,可复制到声明式或脚本式流水线中。注意:
-
上面的 Credentials 字段显示 Jenkins 中配置的凭据名称;点击 Generate Pipeline Script 后,这些值会转换为凭据 ID。
-
若要在一个 withCredentials(…) { … } 步骤中组合多个凭据,参见下方“在同一步骤中组合凭据”。
-
SSH User Private Key 示例
withCredentials(bindings: [sshUserPrivateKey(credentialsId: 'jenkins-ssh-key-for-abc', \
keyFileVariable: 'SSH_KEY_FOR_ABC', \
passphraseVariable: '', \
usernameVariable: '')]) {
// some block
}
最终流水线代码可以删除可选的 passphraseVariable 和 usernameVariable 定义。
Certificate 示例
withCredentials(bindings: [certificate(aliasVariable: '', \
credentialsId: 'jenkins-certificate-for-xyz', \
keystoreVariable: 'CERTIFICATE_FOR_XYZ', \
passwordVariable: 'XYZ-CERTIFICATE-PASSWORD')]) {
// some block
}
最终流水线代码可以删除可选的 aliasVariable 和 passwordVariable 定义。
下面展示实现上述 SSH User Private Key 与 Certificate 片段的完整流水线:
Jenkinsfile(声明式流水线)
pipeline {
agent {
// define agent details
}
stages {
stage('Example stage 1') {
steps {
withCredentials(bindings: [sshUserPrivateKey(credentialsId: 'jenkins-ssh-key-for-abc', \
keyFileVariable: 'SSH_KEY_FOR_ABC')]) {
// (1)
}
withCredentials(bindings: [certificate(credentialsId: 'jenkins-certificate-for-xyz', \
keystoreVariable: 'CERTIFICATE_FOR_XYZ', \
passwordVariable: 'XYZ-CERTIFICATE-PASSWORD')]) {
// (2)
}
}
}
stage('Example stage 2') {
steps {
// (3)
}
}
}
}
| 1 | 在此步骤中,通过 $SSH_KEY_FOR_ABC 引用凭据环境变量。例如,可以使用已配置的 SSH 公钥/私钥对凭据向 ABC 应用认证,其中 SSH 用户私钥文件的位置赋给了 $SSH_KEY_FOR_ABC。 |
| 2 | 在此步骤中,通过 $CERTIFICATE_FOR_XYZ 和 $XYZ-CERTIFICATE-PASSWORD 引用凭据环境变量。例如,可以使用已配置的证书凭据向 XYZ 应用认证;证书的密钥库文件和密码分别赋给这两个变量。 |
| 3 | 本例中的 $SSH_KEY_FOR_ABC、$CERTIFICATE_FOR_XYZ 和 $XYZ-CERTIFICATE-PASSWORD 环境变量,只在各自的 withCredentials(…) { … } 步骤内有效,因此 Example stage 2 中不能使用它们。 |
如果在这些 withCredentials(…) { … } 步骤内尝试输出凭据变量的值,SSH 密钥对和证书变量也会采用上面密文示例所述的保护行为。这只能降低意外泄露风险,不能阻止恶意用户通过其他方式取得凭据值。使用凭据的流水线也能泄露凭据。不要允许不可信的流水线任务使用可信凭据。
|
在同一步骤中组合凭据
通过代码片段生成器,可以按以下步骤在单个 withCredentials(…) { … } 中提供多个凭据:
-
在 Jenkins 仪表板中选择流水线项目名称。
-
在左侧导航栏选择 Pipeline Syntax,并确认导航栏顶部提供 Snippet Generator。
-
在 Sample Step 字段选择 withCredentials: Bind credentials to variables。
-
在 Bindings 下点击 Add。
-
从下拉列表选择要加入 withCredentials(…) { … } 步骤的凭据类型。
-
填写凭据的 Bindings 详情;相关字段说明见上面的“其他凭据类型”步骤。
-
每增加一项或一组凭据,都从上面的“点击 Add”开始重复操作。
-
选择 Generate Pipeline Script,生成最终的 withCredentials(…) { … } 步骤片段。
字符串插值
Jenkins 流水线的字符串插值规则与 Groovy 相同。Groovy 的插值机制可能让初学者困惑。虽然可以用单引号或双引号声明字符串,例如:
def singlyQuoted = 'Hello'
def doublyQuoted = "World"
但只有双引号字符串支持基于美元符号 $ 的字符串插值,例如:
def username = 'Jenkins'
echo 'Hello Mr. ${username}'
echo "I said, Hello Mr. ${username}"
输出结果为:
Hello Mr. ${username}
I said, Hello Mr. Jenkins
掌握字符串插值,对于使用流水线的一些高级功能至关重要。
敏感环境变量的插值
|
绝不要对凭据使用 Groovy 字符串插值。 |
Groovy GString(双引号或三双引号字符串)会在命令发送给代理之前展开 ${TOKEN}。秘密值因此被复制到进程参数中,可通过 ps 等工具看到;秘密值或用户可控值中的 shell 元字符也会在命令执行时生效。应始终向 sh、bat、powershell 或 pwsh 传递字面命令字符串,让 shell 从环境中读取秘密值;withCredentials 或 environment 块已经将这些值写入环境。
Jenkinsfile(声明式流水线)
pipeline {
agent any
environment {
API_TOKEN = credentials('example-token-id')
}
stages {
stage('Example') {
steps {
/* WRONG */
sh "curl -H 'Authorization: Bearer ${API_TOKEN}' https://example.com"
"""
}
}
}
}
Jenkinsfile(声明式流水线)
pipeline {
agent any
environment {
API_TOKEN = credentials('example-token-id')
}
stages {
stage('Example') {
steps {
/* CORRECT */
sh 'curl -H "Authorization: Bearer ${API_TOKEN}" https://example.com'
}
}
}
}
任何避免插值的 Groovy 写法,例如 sh(script: ‘curl … $API_TOKEN’, label: ‘call API’),都符合这一原则;关键是让秘密值远离 GString,只由 shell 展开它们。
通过插值注入命令
|
Groovy 字符串插值可能借助特殊字符向命令解释器注入恶意命令。 |
还要注意:在 sh、bat、powershell 或 pwsh 等把参数交给命令解释器的步骤中,对用户可控变量使用 Groovy 插值,可能引发类似 SQL 注入的问题。用户可控变量通常是环境变量,常见来源是传给构建的参数。当含有 /、反斜杠、$、&、%、^、>、<、|、; 等特殊字符的值经 Groovy 插值传入这些步骤时,就可能发生问题。例如:
Jenkinsfile(声明式流水线)
pipeline {
agent any
parameters {
string(name: 'STATEMENT', defaultValue: 'hello; ls /', description: 'What should I say?')
}
stages {
stage('Example') {
steps {
/* WRONG! */
sh("echo ${STATEMENT}")
}
}
}
}
本例中,sh 的参数由 Groovy 求值,STATEMENT 被直接插入参数,等同于在流水线中写了 sh('echo 。代理执行时,不会原样输出 hello; ls /,而是先输出 hello,再列出代理的整个根目录。任何能够控制被插值变量的用户,都可能让 sh 在代理上运行任意代码。为避免这个问题,对引用参数或其他用户可控环境变量的 sh、')hello; ls /bat 等步骤参数使用单引号,避免 Groovy 插值。
Jenkinsfile(声明式流水线)
pipeline {
agent any
parameters {
string(name: 'STATEMENT', defaultValue: 'hello; ls /', description: 'What should I say?')
}
stages {
stage('Example') {
steps {
/* CORRECT */
sh('echo ${STATEMENT}')
}
}
}
}
包含特殊字符的凭据经 Groovy 插值传给步骤时,还可能发生凭据变形。变形后的值既不再有效,也不再会被控制台日志遮蔽。
Jenkinsfile(声明式流水线)
pipeline {
agent any
environment {
EXAMPLE_KEY = credentials('example-credentials-id') // Secret value is 'sec%ret'
}
stages {
stage('Example') {
steps {
/* WRONG! */
bat "echo ${EXAMPLE_KEY}"
}
}
}
}
这里 bat 接收到 echo sec,Windows 批处理 shell 会去掉 %,输出 %retsecret。由于与原凭据相差一个字符,secret 不会被遮蔽。虽然它并非实际凭据的完整值,仍然构成严重的敏感信息泄露。使用单引号也可以避免此问题。
Jenkinsfile(声明式流水线)
pipeline {
agent any
environment {
EXAMPLE_KEY = credentials('example-credentials-id') // Secret value is 'sec%ret'
}
stages {
stage('Example') {
steps {
/* CORRECT */
bat 'echo %EXAMPLE_KEY%'
}
}
}
}
处理参数
声明式流水线原生支持参数,可通过 parameters 指令在运行时接收用户指定的参数。脚本式流水线使用 properties 步骤配置参数,可在代码片段生成器中找到它。
如果通过 Build with Parameters 配置流水线接受参数,就可以通过 params 变量的成员访问这些参数。
假设 Jenkinsfile 中配置了名为 Greeting 的字符串参数,可以通过 ${params.Greeting} 访问:
Jenkinsfile(声明式流水线)
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(脚本式流水线)
properties([parameters([string(defaultValue: 'Hello', description: 'How should I greet the world?', name: 'Greeting')])])
node {
echo "${params.Greeting} World!"
}
处理失败
声明式流水线默认通过 post 节提供完善的失败处理,可以声明 always、unstable、success、failure 和 changed 等不同的后置条件。具体用法见流水线语法文档。
Jenkinsfile(声明式流水线)
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(脚本式流水线)
node {
/* .. snip .. */
stage('Test') {
try {
sh 'make check'
}
finally {
junit '**/target/*.xml'
}
}
/* .. snip .. */
}
脚本式流水线则依赖 Groovy 内置的 try/catch/finally 语义,处理流水线执行期间的失败。
前面的测试示例通过 ,使 sh 永不返回非零退出码。此方法有效,但意味着后续阶段必须检查 sh 'make check || true'currentBuild.result,才能知道是否发生测试失败。
另一种方法是使用 try/finally 块:既保留流水线失败时提前退出的行为,又让 junit 有机会收集测试报告:
错误处理步骤
Jenkins 流水线提供专用步骤进行灵活的错误处理,控制流水线如何响应错误和警告。这些步骤可以在 Jenkins 中清楚地呈现错误与警告,并决定流水线失败、继续执行,还是仅报告警告。更多信息参见:
使用多个代理
前面的示例都只使用一个代理。这意味着 Jenkins 会在可用位置分配执行器,不论其标签或配置如何。此行为可以覆盖;流水线也允许在同一个 Jenkinsfile 中使用 Jenkins 环境中的多个代理,适用于跨平台构建或测试等高级场景。
下例在一个代理上执行构建,然后在测试阶段由两个后续代理复用构建结果;这两个代理分别具有 linux 和 windows 标签。
Jenkinsfile(声明式流水线)
pipeline {
agent none
stages {
stage('Build') {
agent any
steps {
checkout scm
sh 'make'
stash includes: '**/target/*.jar', name: 'app' (1)
}
}
stage('Test on Linux') {
agent { (2)
label 'linux'
}
steps {
unstash 'app' (3)
sh 'make check'
}
post {
always {
junit '**/target/*.xml'
}
}
}
stage('Test on Windows') {
agent {
label 'windows'
}
steps {
unstash 'app'
bat 'make check' (4)
}
post {
always {
junit '**/target/*.xml'
}
}
}
}
}
Toggle Scripted Pipeline
(Advanced)
Jenkinsfile(脚本式流水线)
stage('Build') {
node {
checkout scm
sh 'make'
stash includes: '**/target/*.jar', name: 'app' (1)
}
}
stage('Test') {
node('linux') { (2)
checkout scm
try {
unstash 'app' (3)
sh 'make check'
}
finally {
junit '**/target/*.xml'
}
}
node('windows') {
checkout scm
try {
unstash 'app'
bat 'make check' (4)
}
finally {
junit '**/target/*.xml'
}
}
}
| 1 | stash 步骤收集符合包含模式 **/target/*.jar 的文件,以便在同一次流水线中复用。流水线执行结束后,暂存文件会从 Jenkins 控制器删除。 |
| 2 | agent/node 中的参数可以是任何有效的 Jenkins 标签表达式。更多信息见流水线语法文档。 |
| 3 | unstash 将指定名称的暂存内容从 Jenkins 控制器取回到流水线当前工作区。 |
| 4 | bat 可以在 Windows 平台执行批处理脚本。 |
可选的步骤参数
流水线遵循 Groovy 约定,允许省略方法参数两侧的括号。
许多流水线步骤还使用命名参数语法,作为创建 Groovy Map 的简写;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 */
高级脚本式流水线
脚本式流水线是基于 Groovy 的领域专用语言 [3];大多数 Groovy 语法无需修改就能使用。
并行执行
上例在两个不同平台上依次运行测试。实际使用中,如果 make check 需要 30 分钟,那么整个测试阶段就要 60 分钟。
流水线内置并行执行脚本式流水线部分代码的功能,由 parallel 步骤实现。
将上面的示例改为使用 parallel:
Jenkinsfile(脚本式流水线)
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 .. */
}
}
}
只要 Jenkins 环境具有所需容量,带 linux 和 windows 标签的节点就会并行执行测试,而不再依次执行。
正文相关链接
- Creating a Jenkinsfile
- Build
- Test
- Deploy
- Working with your Jenkinsfile
- Using environment variables
- Setting environment variables
- Setting environment variables dynamically
- Handling credentials
- above
- Secret text
- Usernames and passwords
- secret file
- For other credential types
- Combining credentials in one step
- String interpolation
- Interpolation of sensitive environment variables
- Injection via interpolation
- Handling parameters
- Handling failure
- Error-handling steps
- section above
- Optional step arguments
- Advanced Scripted Pipeline
- Parallel execution
- Getting started with Pipeline
- two syntaxes
- Defining a Pipeline in SCM
- these instructions
- agent directive
- stages directive
- steps directives
- number of plugins
- JUnit plugin
- More information on scripts
- configured in Jenkins
- Using credentials
- web identity federation
- EKS service account
- computer
- SSH public/private key pair credentials
- passphrase
- PKCS#12 certificates
- above
- parameters directive
- post section
- Pipeline Syntax
- Groovy syntax











暂无评论内容