Jenkins Pipeline 实际上是一套插件,通过这些插件,用户可以持续集成和交付。
要使用pipeline的前提条件是:
不管是声明式的还是脚本式的pipeline,都是通过DSL(Domain Specific Language)来写的。脚本式的pipeline需要用到Groovy语法。
Pipeline可以通过以下方式来创建:
前面两种方式参见参考资料[1],本文主要介绍第三种方式。
简单的pipeline直接通过经典的Pipeline UI界面在文本框中编写确实比较方便,但复杂的Pipeline就不合适的。这时候我们一般使用IDE来编写Jenkinsfile文件,并将其放在项目中。接下来:
上文介绍了SCM方式创建Pipeline的配置,在配置项中需要指定Jenkinsfile文件。接下来介绍Jenkinsfile的具体信息。
Jenkinsfile是一个文本文件,包含了Jenkins Pipeline的定义。下面是一个包含三个阶段的pipeline:
// 声明式pipeline
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building..'
}
}
stage('Test') {
steps {
echo 'Testing..'
}
}
stage('Deploy') {
steps {
echo 'Deploying....'
}
}
}
}
并不是所有的Pipeline都有这三个阶段,但对于大部分项目来说,构建,测试,部署都是必不可少的。
上述示例中,agent指令是必须的,用于指示Jenkins为Pipeline分配一个执行器和工作空间。
stages指令和steps指令也是必须的,表示需要执行的操作。
agent阶段(或者node阶段)可以指定代理节点的标签或名称,如
agent {label 'master'}
表示在标记为master
的代理节点上执行构建。
部分插件会在node阶段绑定默认的stage,比如git插件会默认执行"Declarative: Checkout SCM",如果想跳过该步骤,可以在options中添加skipDefaultCheckout()
来禁用该行为。
在配置pipeline时有一个lightweight checkout选项,勾选该选项表示只拉取需要的文件,而非全量checkout完整的仓库代码。但是需要注意的是,该选项
不会在node阶段生效,也就是说不影响git插件默认的代码拉取行为。
Build
Build通常是必不可少的一个stage,用于构建项目。
当然,Jenkinsfile并不能取代构建工具(如GNU/Make, Maven, Gradle等),它可以被看作一个粘合层,将开发生命周期(如构建,测试,部署)的各个阶段绑定在一起。
Jenkins有很多插件可以调用不同的构建工具,下面以make构建工具为例来展示Build阶段:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make' // 调用make命令,如果返回码为0,则继续执行,否则中断Pipeline
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true // 对指定模式的文件进行归档,以便后续使用
}
}
}
}
注意,归档组件并不能用作nexus等外部仓库的替代品,它只用于基本的报告或文件归档
Test
自动化测试是任何成功的持续交付过程中必不可少的组成部分。Jenkins提供了一系列插件,用于测试录制,报告和可视化。
正常情况下,当有测试用例执行失败时,通过Jenkins记录、报告并可视化失败详情是很有帮助的。
下面的示例中使用了JUnit插件来执行测试用例:
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'make check || true'
junit '**/target/*.xml'
}
}
}
}
sh 'make check || true'
确保sh这一步总是返回错误码0,可以继续往下执行。
Deploy
下面是一个示例:
pipeline {
agent any
stages {
stage('Deploy') {
when {
expression {
currentBuild.result == null || currentBuild.result == 'SUCCESS'
}
}
steps {
sh 'make publish'
}
}
}
}
Jenkins Pipeline通过env关键字暴露了很多环境变量,在Jenkinsfile中可以使用这些环境变量。具体的环境变量可以在${YOUR_JENKINS_URL}/pipeline-syntax/globals#env
下面看到。下面列举了一些常用的:
变量名 | 描述 |
---|---|
BUILD_ID | 构建号,如"153", 同BUILD_NUMBER |
BUILD_NUMBER | 构建号,同BUILD_ID |
BUILD_TAG | 值为:jenkins-${JOB_NAME}-${BUILD_NUMBER} |
BUILD_URL | 构建url,如http://buildserver/jenkins/job/MyJobName/17/ |
EXECUTOR_NUMBER | 执行本次构建的执行器标识号 |
JAVA_HOME | JDK目录 |
JENKINS_URL | Jenkins地址,如https://example.com:port/jenkins/ |
JOB_NAME | Job的名称 |
NODE_NAME | 运行当前构建的节点名称 |
WORKSPACE | 工作空间的绝对路径 |
除了Jenkins内置的环境变量外,还可以自定义环境变量。声明式Jenkinsfile中通过environment关键字来定义环境变量:
pipeline {
agent any
environment {
CC = 'clang'
}
stages {
stage('Example') {
environment {
DEBUG_FLAGS = '-g' // stage中定义的环境变量只能在当前scope使用
}
steps {
sh 'printenv'
}
}
}
}
除了静态环境变量之外,还能设置动态环境变量。如下所示:
pipeline {
agent any
environment {
// 使用标准输出返回值
CC = """${sh(
returnStdout: true,
script: 'echo "clang"'
)}"""
// 使用返回状态码
EXIT_STATUS = """${sh(
returnStatus: true,
script: 'exit 1'
)}"""
}
stages {
stage('Example') {
environment {
DEBUG_FLAGS = '-g'
}
steps {
sh 'printenv'
}
}
}
}
密码或密钥之类的变量是不能直接写到Jenkinsfile文件中的,但可以配置到Jenkins的Credential里,在Jenkinsfile文件里读取出来使用。
Credential有几种类型,如secret text
, username and password
, secret file
等。
这几种类型的变量在声明式Pipeline中可以使用credentials()
方法来获取。
Secret text
下面演示了如何获取Secret text类型的credentials:
// Jenkinsfile (声明式Pipeline)
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 {
// 可以使用$AWS_ACCESS_KEY_ID, $AWS_SECRET_ACCESS_KEY来使用credential值
}
}
stage('Example stage 2') {
steps {
//
}
}
}
}
Username and password
下面示例展示了如何获取次类型的credentials:
environment {
BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds')
}
其中,jenkins-bitbucket-common-creds
是配置的credential的ID,取到的值是包含用户名和密码的,格式为username:password
。在此基础上,可以直接使用BITBUCKET_COMMON_CREDS_USR
和BITBUCKET_COMMON_CREDS_PSW
来获取用户名和密码。
Secret file
该类型凭证被存储到文件中并上传到Jenkins。
以下示例展示了将Kubernetes配置文件以Secret file方式存储到credential来使用:
pipeline {
agent {
// Define agent details here
}
environment {
// MY_KUBECONFIG 是一个文件,如:/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")
}
}
}
}
其他类型
如果你想要在pipeline中使用其他类型的credential,比如 SSH keys 或者 certificates,可以使用Jenkins的Snippet Generator功能。
点开你的Pipeline,点击左侧菜单Pipeline Syntax,在Sample Step中选择:withCredentials:Bind credentials to variables
,然后选择将Credentials和自定义变量绑定,点击生成按钮,就能生成样例代码了:
Jenkins Pipeline使用和Groovy相同的规则来实现字符串插值。这对新手来说可能有点迷惑,因为声明字符串时即可以用单引号也可以用双引号:
def singlyQuoted = 'Hello'
def doublyQuoted = "World"
但是只有用双引号时,字符串插值才能生效。如下所示:
def username = 'Jenkins'
echo 'Hello Mr. ${username}' // 输出:Hello Mr. ${username}
echo "I said, Hello Mr. ${username}" // 输出:I said, Hello Mr. Jenkins
在单引号字符串中,${username}
会被看作是字符串的一部分,不会进行插值。所以要确保字符串插值生效,最佳做法是使用双引号声明字符串。
Groovy的字符串插值可能会导致敏感的环境变量信息泄漏。如下所示:
pipeline {
agent any
environment {
EXAMPLE_CREDS = credentials('example-credentials-id')
}
stages {
stage('Example') {
steps {
/* WRONG! */
sh("curl -u ${EXAMPLE_CREDS_USR}:${EXAMPLE_CREDS_PSW} https://example.com/")
/* CORRECT */
// sh('curl -u $EXAMPLE_CREDS_USR:$EXAMPLE_CREDS_PSW https://example.com/')
}
}
}
}
在这个示例中,敏感信息会直接注入到sh
脚本的参数中,从而导致在操作系统的进程列表中含有敏感信息的环境变量值将作为sh
进程参数明文可见。
正确的做法是sh
脚本用单引号引用。这样敏感信息在字符串中就不会被直接插入了,而是被当成原始的字符串,只有在脚本真正执行的时候才会从系统环境变量中取到实际值。
先插值再执行脚本和执行脚本时取值效果是不一样的。假设变量为
STATEMENT='hello; ls /'
,然后执行echo $STATEMENT
指令输出该变量值。先插值再执行sh脚本,等价于echo hello; ls /
,结果会输出根目录;而执行脚本时取值等价于echo 'hello; ls /'
, 结果才符合预期。
[1]. https://www.jenkins.io/doc/book/pipeline/getting-started/