1. pipeline概述
Jenkins 流水线是jenkins的一套插件,需要在 jenkins 2.x 上安装pipeline插件之后才能使用。在《环境安装》中已经安装成功。Jenkins的流水线式DSL(domain-specific language)语言,即领域专用语言。Jenkins创建流水线的方式有三种:
- 经典UI: 【新建任务】—> 输入名称,选择流水线,进入流水线创建流程—>填写Pipeline脚本到文本框。Pipeline管理比较复杂
- Blue Ocean:根据提示创建仓库,编辑和配置Pipeline。配置完毕后的Jenkinsfile会提交到仓库,对仓库有侵入性,由开发者自行维护,方便管理
- SCM:从git仓库拉取pipeline,并运行指定的pipeline。方便统一管理所有的pipeline
1.1. 经典UI
管理台下点击【新建任务】—>填写任务名称,选择【流水线】,点击【确定】—>在流水线文本框填写 pipeline 脚本—>点击【保存】
pipeline {
agent any
stages {
stage("Build") {
steps {
echo "测试 经典UI 的Pipeline"
echo "hello world"
}
}
}
}
- 运行测试
1.2. SCM
和经典UI操作步骤一致,仅在定义Pipeline脚本时不同,Pipeline 脚本从代码仓库拉取,不过需要先配置pipeline拉取的密钥
- 配置gitee中拉取pipeline的密钥(如果当前仓库为非公开时需要操作)
【系统管理】—>【Manage Credentials】—>【添加凭据】—>添加pipeline仓库的部署密钥即可
- 配置从git仓库拉取pipeline脚本
基本步骤和经典UI一致,仅在定义流水线时使用从SCM获取,配置方法如下:
- 运行Pipeline
2. pipeline
2.1. Pipeline分类
Jenkins的pipeline分为两类:
- 声明式流水线:Jenkins 2.5 版本引入了声明式的流水线语法,它较为简洁,入门简单,对pipeline的编码有着较为严格的限制
- 脚本式流水线:是由 Groovy构建的通用 DSL,功能更加丰富,灵活度更高,但是需要更多的学习成本
当前章节会详细描述Jenkins的语法,内容较为枯燥,可以先大概看完之后,再根据Pipeline案例进行学习。
2.2. Groovy语法
2.3. 声明式pipeline
声明式语法是Jenkins比较推荐的一种形式,简单清晰,能完成绝大部分项目的需求,本人笔记就是围绕声明式语法进行学习,而不是脚本式语法!
2.3.1. 基本结构
pipeline {
agent any
stages {
stage ('Build') {
steps {
echo "start Building ..."
}
}
stage ('Test') {
steps {
echo "Start Testing ..."
}
}
stage ('Deploy') {
steps {
echo "Deploying Application ..."
}
}
}
}
最简单的pipeline结构如上述代码所示,这些block是必不可少的字段。
pipeline
是最顶级的block- agent 表示使用什么方式去执行pipeline,在分布式构建中会详细描述
- stages 表示所有的构建阶段,至少包含一个stage
- stage 表示某一个构建阶段,至少包含一个steps,Build,Test,Deploy 是最常用的几个阶段
-
2.3.2. Post
Post 相当于一个结果回调和处理的模块,可以在
pipeline { }
也可以在stage ('xx') { }
中指定,post 会根据运行结果执行不同的指令!post支持的条件如下: always:无论当前完成的状态是啥,都要执行
- faileure:失败时执行
- success:成功时执行
- changed:上次执行状态和当前不同时才执行
- fixed:上次执行状态为失败或者不稳定(unstable),当前执行成功时才执行
- regression:上次执行成功,当前执行失败、不稳定、终止时才执行
- aborted:终止时执行
- unstable:不稳定时执行
cleanup:清理条件块
post { always { // 清理workspace,如果公司项目很多,可以选择清理 cleanWs() } failure { mail to: 'duduniao@qq.com', subject: "${env.JOB_NAME}-#${env.BUILD_ID} failed", body: """ branch: ${env.GIT_BRANCH} commit: ${env.GIT_PREVIOUS_COMMIT} jenkins url: ${env.JENKINS_URL} build url: ${env.BUILD_URL} node name: ${env.NODE_NAME} """ } }
2.3.3. environment
参考 https://www.yuque.com/duduniao/linux/gsovr9#nYFTy
2.3.4. options
options 用于定义Jenkins属性,可以用在 pipeline 和 stage 块中,相对来说,放到 pipeline 中的场景较多,支持的指令如下:
retry:重试次数,根据场景选择放置位置。重试三次:
options { etry(3) }
- timeout:pipeline超时时间,单位
SECONDS
MINUTES
HOURS
,如定义超时时间10分钟:optons { timeout(time:20,unit:'MINUTES') }
- disableConcurrentBuilds:仅在并发运行pipeline。
options { disableConcurrentBuilds() }
- newContainerPerStage:每个stage 启动一个新的容器,仅在agent为docker或者dockerfile时生效:
optons { newContainerPerStage() }
- skipStagesAfterUnstable:一旦构建状态变得UNSTABLE,跳过该阶段:
options { skipStagesAfterUnstable() }
buildDiscarder:保存最近的流水线记录数量:
options { buildDiscarder(logRotator(numToKeepStr: '10')) }
// 全局配置 options { // 历史构建记录保存时间 buildDiscarder(logRotator(numToKeepStr: '10')) // 总共重试的次数 retry(2) // 超时时间 timeout(time:10,unit:'MINUTES') // 当前pipeline禁止并发运行 disableConcurrentBuilds() }
2.3.5. tools
指定构建的工具,指定之后,在构建时会将该工具目录放入到PATH中以供使用,当
agent none
时忽略。常常用于配置多版本的JDK、多版本的Maven。
参考文档:2.6. 构建工具2.3.6. when
只有当when条件为true时,对应的stage才会执行。如果是多个条件,默认以 and 连接。when 支持以下几种表达式:
branch:当前构建分支与指定的相符,用于多分支流水线,如:
when { branch 'master' }
- environment:判断环境变量是否是给定的值,如:
when { environment name: "TYPE", value: 'prod' }
- expression:当给定的Groovy表达式是否为true,如:
when { expresson { return params.DEBUG_BULD } }
- not:嵌套条件为false时执行,如:
when { not { branch 'feature' } }
- allOf:嵌套的条件全部为true时执行,如:
when { allOf { branch 'master';environment name:"TAG",value:'v.1.0.0'} }
anyOf:满足一个嵌套条件即可执行,如:
when { anyOf { branch 'master'; branch 'devops' } }
stage('deploy') { when { expression { params.deploy } environment name: 'GIT_BRANCH', value: 'origin/master' } steps { sh "make deploy" } }
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' } }
2.3.7. triggers
参考 触发器 章节
2.3.8. script
在声明式流水线中使用groovy脚本,可以使用
script { }
,提供了更加灵活的机制。但是大部分情况下,使用script 时没有必要的,对于复杂的 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" } } } } } }
2.4. 脚本式pipeline
与声明式流水线不同,脚本式流水线是由 Groovy 构建通用的DSL,大部分groovy功能在脚本式流水线中都能使用,其灵活性和功能性比较强大,比如回调、异常捕获、条件判断等等
node { stage('Example') { if (env.BRANCH_NAME == 'master') { echo 'I only execute on the master branch' } else { echo 'I execute elsewhere' } } }
node { stage('Example') { try { sh 'exit 1' } catch (exc) { echo 'Something failed, I should sound the klaxons!' throw } } }
2.5. Jenkins的内置变量和参数
Jenkins 内置了一些环境变量,同时安装的插件也会提供一些变量,在pipeline中会经常使用到,比如当前仓库的分支、commit id,当前流水线的名称、ID等等。Jenkins实例中提供了查看方式
${jenkins_url}/pipeline-syntax/globals#env
。这里面分四类:2.5.1. 环境变量
环境变量可以使用
sh 'printenv'
将当前环境变量打印出来,调用环境变量的格式是${env.xxx}
,在引用过程中,需要使用双引号而不是单引号,常见的内置环境变量有:JENKINS_URL:当前实例的URL地址,需要在系统设置中配置
- BUILD_URL:当前构建的URL地址,常常在post块中用于通知,方便快速定位
- JOB_NAME:当前项目的名称,常常在post块中用于通知,也可以和BUILD_ID拼接后用于识别构建记录
- BUILD_ID:当前项目构建的ID序号
- NODE_NAME:当前节点名称
插件提供的环境变量,需要在插件的详情页面查看,比如常用的GIT的内置变量:
- GIT_PREVIOUS_COMMIT:commit标识
- GIT_BRANCH:分支名称
在声明式pipeline中,经常会自定义环境变量,需要在 environment { }
定义, environment { }
可以定义在pipeline下,也可以定义在 stage内,作用域范围不同而已。以下是定义golang编译的环境变量:
// golang ENV
environment {
GO111MODULE='on'
GOPROXY='https://goproxy.cn,direct'
CGO_ENABLED=0
GOOS='linux'
GOARCH='amd64'
GOPATH='/opt/release/golang/path'
}
// pipeline失败发送邮件,此处会大量使用环境变量,需要使用 mail 还得先参考 3.1 配置邮件服务
post {
failure {
mail to: 'duduniao@qq.com', subject: "${env.JOB_NAME}-#${env.BUILD_ID} failed", body: """
branch: ${env.GIT_BRANCH}
commit: ${env.GIT_PREVIOUS_COMMIT}
jenkins url: ${env.JENKINS_URL}
build url: ${env.BUILD_URL}
node name: ${env.NODE_NAME}
"""
}
}
2.5.2. 参数
Jenkins中常用参数化构建,即指定一些参数,然后再pipeline中调用参数完成构建,比如让用户选择构建时jdk的版本,选择部署的机器等等。
Jenkins 指定参数有两种方式:
- 在Jenkins UI界面中,选择项目,构建【参数化构建】,并更加需要添加构建参数
如果想通过Jenkinsfile定义,则需要在
pipeline { }
下使用parameters { }
定义,JenkinsfIle定义参数格式parameters { // 单选框 choice(name:"version", choices:['v1.13.15', 'v1.14.14', 'v1.15.10', 'v1.16.2'], description:"choose golang version") // 字符串 string(name:"db_address", defaultValue:"127.0.0.1", description:"数据库地址") // 布尔值 booleanParam(name:"delete_data", defaultValue:false, description:"是否删除数据") // 文本 text(name:"notice", defaultValue:"", description:"部署完毕后提示信息") // 密码 password(name:"db_password",defaultValue:"123456",description:"数据库连接密码") }
调用参数和调用变量类似,使用
${params.option_name}
调用,方式如下:stages { stage('Test') { steps { echo "${params.version}" } } }
2.6. 构建工具
构建工具是指在CI/CD流程中经常需要使用的二进制命令或者环境,比如 git, jdk, maven, go 等等。在虚拟机上部署Jenins Agent,并用虚拟机环境进行构建时,需要配置使用的编译环境或者命令。在容器环境下编译基本不使用构建工具。操作步骤如下:
在全局工具定义中,配置工具
- 在Jenkinsfile中使用
tools { }
引用工具,引用的时候,会自动将工具填入PATH中
tools {}
可以作用在 pipeline {}
,也可以作用于某个 stage {}
,使用工具类型 名称引用工具: tools { jdk 'jdk-1.8' }
2.6.1. 多jdk环境演示
2.6.1.1. 手动安装jdk环境
因为不可抗力,采用jenkins自动安装Jdk基本都是下载失败,因此推荐使用手动安装jdk。本实验采用 openjdk 两个版本作为演示:
下载地址:https://jdk.java.net/archive/
在需要的jenkins node上安装即可,对存在java环境的node,可以配置相关标签,方便调度。这里在 jenkins master 上安装 openjdk进行演示
# 二进制安装过程省略
# jdk安装位置
[root@centos-80 java]# realpath *
/opt/release/java/openjdk-15-ga
/opt/release/java/openjdk-16-ga
2.6.1.2. 添加工具
2.6.1.3. 使用jdk环境
pipeline {
agent {
label 'master'
}
stages {
stage('Test jdk16') {
tools {
jdk 'openjdk-16-ga'
}
steps {
sh "java -version"
}
}
stage('Test jdk15') {
tools {
jdk 'openjdk-15-ga'
}
steps {
sh "java -version"
}
}
}
}
2.6.2. 多golang环境演示
2.6.2.1. 安装golang
因为网络问题,采用手动安装,在所有需要golang环境的地方安装需要的golang版本。golang其它变量,如GOPROXY,可以在pipeline中配置
# 安装过程跳过,以下是安装目录
[root@centos-80 golang]# realpath v1.1*
/opt/release/golang/v1.12.17
/opt/release/golang/v1.13.15
/opt/release/golang/v1.14.14
/opt/release/golang/v1.15.10
/opt/release/golang/v1.16.2
2.6.2.2. 添加golang环境
- 安装golang插件:插件管理,安装 Go Plugin ,重启jenkins实例
- 配置golang环境:【系统管理】—>【全局工具配置】—>【新增Go】
2.6.2.3. 使用golang工具
配置golang的tools
pipeline { agent { label 'master' } parameters { // 单选框 choice(name:"version", choices:['v1.13.15', 'v1.14.14', 'v1.15.10', 'v1.16.2'], description:"choose golang version") } tools { // 指定 golang 版本 go "${params.version}" } environment { GOOS='linux' GOARCH='amd64' GOPROXY='https://goproxy.cn,direct' CGO_ENABLED=0 GO111MODULE='on' GOPATH='/opt/release/golang/path' } stages { stage('Test go version') { steps { sh "go version" sh "printenv | grep GO" } } } }
2.7. 多分支构建
在实际开发中,一个项目往往有多个分支,如 master 分支、feature分支、release分支、fix分支等等,这些分类命名取决于公司采用什么样的开发流程。jenkins针对多分支项目提供了多分支的构建功能。
在项目中定义仓库地址和需要关注的分支信息(如feature、release等),通过不同分支下定义不同的Jenkinfile或者相同的Jenkins加上when条件的方式执行不同的操作。2.8. 制品管理
所谓制品,可以是Docker镜像、二进制文件、部署需要的tar包等等,一种软件代码交付的产物。市面有很多类型的制品库,如Harbor,DockerHub,Nexus等。甚至在初期,可以将tar包这种简单的制品放到文件服务器上。这部分较为简单,在Jenkins实践中有演示使用 DockerHub 存储Docker镜像。
2.9. 凭据管理
在Jenkins中,为了安全起见,会把密钥、用户密码等敏感信息文件存储到Jenkins凭据中,在需要的时候选择和调用。如API的token、ssh私钥、账号密码等等。
官方文档:credentials ; jenkins doc;2.9.1. 添加凭据
凭据添加非常简单,后续的节点管理中会有较多的案例:07-4-3-Jenkins实践
【系统管理】—>【Manager Credentails】—>【添加凭据】
下拉选择类型—>下拉选择范围—>填写信息:全局表示在节点和pipeline中可以使用
- 系统表示仅在jenkins系统内部和node之间能使用,通常用于添加slave节点
注意点: 描述和ID一定要填写清楚,避免随着凭据数量增加后混乱
2.9.2. 使用凭据
在系统中使用凭据添加agent节点是非常简单的,重点在于如何在 pipeline 中通过id获取到需要的凭据!
https://www.jenkins.io/doc/pipeline/steps/credentials-binding/
2.9.2.1. username/password
有两种方式可以调用,推荐使用第一种,更加优雅简洁
pipeline {
agent any
stages {
stage('Test user/password cred 1') {
environment {
USER_PASSWD_CRED = credentials('password-duduniao-ssh')
}
steps {
// USER_PASSWD_CRED username:password
// USER_PASSWD_CRED_USR username
// USER_PASSWD_CRED_PSW password
// 不安全的命令
// sh "sshpass -p ${USER_PASSWD_CRED_PSW} ssh -o StrictHostKeyChecking=no ${USER_PASSWD_CRED_USR}@10.4.7.82 "hostname""
// 安全的命令
sh 'sshpass -p $USER_PASSWD_CRED_PSW ssh -o StrictHostKeyChecking=no $USER_PASSWD_CRED_USR@10.4.7.82 "hostname" '
}
}
stage('Test user/password cred 2') {
steps {
withCredentials([usernamePassword(credentialsId:'password-duduniao-ssh', usernameVariable:'username', passwordVariable:'passwd')]) {
sh "sshpass -p ${passwd} ssh -o StrictHostKeyChecking=no ${username}@10.4.7.82 'hostname' "
}
}
}
}
}
2.9.2.2. ssh key
ssh key 不支持从 credentials 中直接获取密钥用来登陆,而需要使用 SSH Agent 插件调用ssh key。
需要注意:sshagent 代码块内执行ssh命令并不会使用凭据中的用户名
pipeline {
agent any
stages {
stage('Test ssh key cred 1') {
environment {
USER_SSH_CRED = credentials('password-duduniao-ssh')
}
steps {
sshagent (credentials: ['key-duduniao-ssh']) {
sh 'ssh -o StrictHostKeyChecking=no -l cloudbees $USER_SSH_CRED_USR@10.4.7.82 "id ; hostname"'
}
}
}
stage('Test user/password cred 2') {
steps {
withCredentials([sshUserPrivateKey(credentialsId:'key-duduniao-ssh', usernameVariable:'username', keyFileVariable:'key_file')]) {
sh "ssh -o StrictHostKeyChecking=no -i ${key_file} ${username}@10.4.7.82 'id ; hostname' "
}
}
}
}
}
2.9.2.3. secret text
secret text 类型经常用于存放token,本次模拟远程使用API触发 jenkins 任务,仅作测试使用:
- 生成Token
【用户】—>【设置】—>【添加新token】
- 添加 secret text 类型和用户名密码的凭据
- 创建测试项目,并配置远程触发器
- 测试远程触发
pipeline { agent any environment { // usernane:password USER_PASSWD_CRED = credentials('password-duduniao-jenkins') API = 'http://jenkins.ddn.com/job/upstream-1/build' } stages { stage('Test secret text cred 1') { environment { PIPELINE_TOKEN = credentials('jenkins-pipeline-token') } steps { sh 'curl -v -u $USER_PASSWD_CRED $API?token=$PIPELINE_TOKEN' } } stage('Test secret text cred 2') { steps { withCredentials([string(credentialsId:'jenkins-pipeline-token', variable:'TOKEN')]) { sh 'curl -v -u $USER_PASSWD_CRED $API?token=$TOKEN' } } } } }
2.9.2.3. secret file
在凭据管理中添加 secret file ,以 k8s 集群的kubeconfig文件为例:pipeline { agent any stages { stage('Test secret file cred 1') { environment { CONFIG = credentials('kubeconfig-aliyun') } steps { sh 'kubectl --kubeconfig $CONFIG get node' } } stage('Test secret file cred 2') { steps { withCredentials([file(credentialsId:'kubeconfig-aliyun', variable:'CONFIG')]) { sh 'kubectl --kubeconfig $CONFIG get node' } } } } }
3. 补充
3.1. 通知
pipeline 中的通知分为两种:
- 各个执行阶段进行通知(一般用于回调给上层触发程序)
pipeline 结束后通知(一般用于通知相关任务当前任务是否成功)
3.1.1. 使用邮件通知
3.1.1.1. 使用内置邮件功能
配置系统管理员邮箱地址
【系统管理】—>【系统配置】—>填写 系统管理员邮件地址
- 配置发件人信息
【系统管理】—>【系统配置】—>填写 邮件通知
- 测试pipeline
可以参考 2.3.2. Post 章节
pipeline {
agent any
post {
always {
// 清理workspace,如果公司项目很多,可以选择清理
cleanWs()
}
failure {
mail to: 'duduniao@qq.com', subject: "${env.JOB_NAME}-#${env.BUILD_ID} failed", body: """
branch: ${env.GIT_BRANCH}
commit: ${env.GIT_PREVIOUS_COMMIT}
jenkins url: ${env.JENKINS_URL}
build url: ${env.BUILD_URL}
node name: ${env.NODE_NAME}
"""
}
}
stages {
stage('Test') {
steps {
sh "false"
}
}
}
}
3.1.1.2. 使用 Email Extension 发送邮件
- 安装 Email Extension插件
- 配置 Email Extension
【系统管理】—>【系统配置】—>填写 Extended E-mail Notification
以下为邮件的默认正文,是从互联网摘抄的,生产中建议根据需求调整下,只需要提取需要的信息,组成html文档即可
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
offset="0">
<table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
<tr>
本邮件由系统自动发出,无需回复!<br/>
各位同事,大家好,以下为${PROJECT_NAME }项目构建信息</br>
<td><font color="#CC0000">构建结果 - ${BUILD_STATUS}</font></td>
</tr>
<tr>
<td><br />
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建状态: ${BUILD_STATUS}</li>
<li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
<h4><font color="#0B610B">失败用例</font></h4>
<hr size="2" width="100%" />
$FAILED_TESTS<br/>
<hr size="2" width="100%" />
<ul>
${CHANGES_SINCE_LAST_SUCCESS, reverse=true, format="%c", changesFormat="<li>%d [%a] %m</li>"}
</ul>
详细提交: <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a><br/>
</td>
</tr>
</table>
</body>
</html>
在 pipeline 中,可以自定义邮件内容,也可以选择使用默认的模板:
emailext ( subject: "title", // 邮件标题,调用系统默认标题: ${DEFAUTL_SUBJECT} recipientProviders: [buildUser(), requestor()], // 收件人类型,可选 body: "xxx", // 邮件正文, 调用默认正文: ${DEFAULT_SUBJECT} to: "xx", // 自定义收件人,可选 attachLog: true, // 添加日志到附件,可选 compressLog: true // 是否压缩日志,可选 }
// 直接调用 Email Extension插件发送邮件 pipeline { agent any post { always { // 清理workspace,如果公司项目很多,可以选择清理 cleanWs() } failure { script{ emailext ( body: '${DEFAULT_CONTENT}', subject: '${DEFAULT_SUBJECT}', to: 'duduniao@ddn.com', compressLog: true, attachLog: true, recipientProviders: [buildUser(), requestor()] ) } } } stages { stage('Test') { steps { sh "false" } } } }
``` 收件人主要有以下几类:
Build User:
Developers: 最后一次提交代码的开发者,将提交者ID和默认用户邮箱后缀拼接成邮箱
Requestor:手动发起构建请求的用户
Culprits:从最近一次构建成功以后的所以构建失败者
Upstream Committers: 上游Job变更提交者列表
Recipient List: Jenkins项目中定义的收件人列表
<a name="4l2tD"></a> ### 3.1.2. 对接IM 对接IM机器人,一般是都是使用聊天机器人的Hook URL实现,比如钉钉机器人提供一个URL地址,Jenkins通过调用该URL地址,并传递消息内容完成调用。当然部分IM在Jenkins中存在相关插件,可以简化这个操作。[钉钉消息插件](https://plugins.jenkins.io/dingding-notifications/),[官方文档](https://jenkinsci.github.io/dingtalk-plugin/guide/getting-started.html#%E5%AE%89%E8%A3%85%E6%8F%92%E4%BB%B6) <a name="5GC0p"></a> #### 3.1.2.1. 在钉钉群配置机器人 [文档](https://developers.dingtalk.com/document/app/custom-robot-access?spm=ding_open_doc.document.0.0.6d9d28e1zi7hv6#topic-2026027) <a name="rMf5V"></a> #### 3.1.2.2. 配置钉钉机器人 【系统配置】-->【钉钉】<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/378176/1621086690609-0ff21812-19b2-4dc7-9b3c-f202fd460018.png#height=781&id=M6MUe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=781&originWidth=983&originalType=binary&size=55815&status=done&style=stroke&width=983) <a name="SSeIN"></a> #### 3.1.2.3. 测试 ```groovy pipeline { agent any post { always { dingtalk ( robot: 'devops-52773fd28593e81c01094c3e8ac3348a', type: "MARKDOWN", title: "${env.JOB_NAME}#${env.BUILD_ID} ${currentBuild.result}", text: [ "# ${env.JOB_NAME}#${env.BUILD_ID} ${currentBuild.result}", "---", "- 项目名称:${env.JOB_NAME}", "- 构建ID:${env.BUILD_ID}", "- 状态:${currentBuild.result}", "- 节点:${env.NODE_NAME}", "- [详情](${env.BUILD_URL})" ] ) cleanWs() } } stages { stage('Test') { steps { sh "true" } } } }
3.1.3. HTTP回调
如果是其它服务调用Jenkins API触发任务,往往Jenkins Pipeline 需要回调执行结果给调用者,简单的golang代码模拟callback接口,并在172.25.117.28运行
go run cmd/main.go
```go // cmd/main.go package main
import ( “encoding/json” “fmt” “io/ioutil” “net/http” )
type CallbackMsg struct {
ProjectName string json:"project_name"
// 项目名称
BuildID string json:"build_id"
// 构建ID
Agent string json:"agent"
// 执行任务的 jenkins agent
JenkinsUrl string json:"jenkins_url"
// jenkins 地址
BuildUrl string json:"build_url"
// 当前构建的任务的 url
Status string json:"status"
// 构建状态
}
func callback(w http.ResponseWriter, r *http.Request) { method := r.Method if method != http.MethodPost { , = w.Write([]byte(“Invalid Request Method”)) w.WriteHeader(400) return } body, err := ioutil.ReadAll(r.Body) if err != nil { , = w.Write([]byte(fmt.Sprintf(“Invalid Body, err:%s”, err.Error()))) w.WriteHeader(400) return } result := new(CallbackMsg) err = json.Unmarshal(body, result) if err != nil { , = w.Write([]byte(fmt.Sprintf(“Invalid Body, err:%s”, err.Error()))) w.WriteHeader(400) return } fmt.Println(string(body)) , = w.Write([]byte(fmt.Sprintf(“recv message:%s”, string(body)))) return }
func main() { http.HandleFunc(“/callback”, callback) if err := http.ListenAndServe(“0.0.0.0:9001”, nil); err != nil { panic(err) } }
```groovy
pipeline {
agent any
post {
always {
// 实际生产环境下,可能需要retry
// 更多更丰富的信息需要自己进行拼接
httpRequest contentType: 'APPLICATION_JSON',
httpMode: 'POST',
requestBody: """{"project_name":"${env.JOB_NAME}","build_id":"${env.BUILD_ID}","agent":"${env.NODE_NAME}","jenkins_url":"${env.JENKINS_URL}","build_url":"${env.BUILD_URL}","status":"${currentBuild.result}"}""",
responseHandle: 'NONE',
timeout: 5,
url: 'http://172.25.117.28:9001/callback'
cleanWs()
}
}
stages {
stage('Test') {
steps {
sh "false"
}
}
}
}
3.2. 触发器
Jenkins 任务除了用户手动点击操作之外,还可以使用制动计划任务进行周期性触发,或者又其它任务触发,最常用的是其它的服务(如gitlab)通过API进行触发。
所有配置触发的任务都是可以手动触发的!
3.2.1. 计划任务
Jenkins 支持cron格式的计划任务,同时也支持更多类型的写法,一般情况cron格式能满足要求,使用方式如下:
pipeline {
agent any
triggers {
cron('* * * * *')
}
stages {
stage('Builder') {
steps {
echo "Builder"
}
}
stage('Test') {
steps {
echo "Test"
}
}
}
}
3.2.2. 关联任务
部分场景下,任务A执行成功后,需要触发任务B的执行,则成为A为B的上游任务!使用 upstream 可以定义!
其中如果关注多个上游 project,需要使用逗号分隔,只要任意一个上游 project满足条件,则触发当前 project
pipeline {
agent any
triggers {
upstream(
upstreamProjects: "upstream-1,upstream-2",
threshold: hudson.model.Result.SUCCESS
)
}
stages {
stage('Builder') {
steps {
echo "Builder"
}
}
stage('Test') {
steps {
echo "Test"
}
}
}
}
3.2.4. API触发
API 触发在自动化运维开发中使用非常多,直接调用 Jenkins 的API,并传递构建参数:
3.2.4.1. 配置Jenkins
点击右上角用户名—>【设置】—>【添加新token】
在Project中配置远程触发的令牌,可以用户自定义
3.2.4.2. 远程调用
- 简单调用
如果pipeline是有参数的,只能使用 buildWithParameters 方式调用,否则报错 HTTP ERROR 400 This page expects a form submission
.
项目地址: JENKINS_URL/job/JOBNAME
# curl 测试无参数
[root@duduniao go-simple]# curl -X POST -u duduniao:11566de004ec7f7695905bba51f89d23d4 http://jenkins.ddn.com/view/%E5%85%A5%E9%97%A8/job/triggers/build?token=triggers-a49eee6c5494b1aa3f185693885f78b6
# curl 有参数. 传递 busybox和 tag参数
curl -X POST -u duduniao:11566de004ec7f7695905bba51f89d23d4 "http://jenkins.ddn.com/view/%E5%85%A5%E9%97%A8/job/triggers/buildWithParameters?token=triggers-a49eee6c5494b1aa3f185693885f78b6&image='busybox'&tag='v0.0.1'"
- json请求处理
想通过json 字符串传参,但是没有找到好的处理方式。后续慢慢补充
3.2.5. 使用Generic Webhook Triggers
官方文档:https://plugins.jenkins.io/generic-webhook-trigger/
3.3. 并行步骤和并发构建
3.3.1. 并行步骤
之前的所以Jenkinsfile都是从上到下逐步执行的,但是部分场景中为了节约时间,可以将多个步骤同时进行,被称为并行步骤。比如同时将服务部署到Docker和虚拟机中,或者同时对手机和平台设备进行测试等,通过并行步骤可以降低很多的等待时间。Jenkins支持 stage 和 step 两个语句块的并行执行!一般将并行的步骤作为一个整体,只要有一个失败了,其它并行步骤就需要停下来,这个功能可以通过 failFast true
指定。
并行的 stage 可以指定在不同的 agent 上操作,而并行的 step 只能在同一个 agent 上,因此并行的 step 局限性很大!
3.3.1.1. stage并行
pipeline {
agent none
stages {
stage('git clone code') {
agent { label "VM" }
steps {
echo "git clone xxxx"
echo "git checkout branch"
}
}
stage('deploy application') {
// 只要有一个失败了,就立刻停止所有并行任务
failFast true
// 并行的任务块
parallel {
stage('deploy to vm') {
agent { label "docker" }
steps {
echo "start deploy to vm cluster"
sh "date +%T ; sleep 20 ; date +%T"
}
}
stage('deploy to docker-compose') {
agent { label "docker" }
steps {
echo "start deploy to docker-compose"
sh "date +%T ; sleep 10 ; date +%T"
}
}
stage('deploy to kubernetes ') {
agent { label "docker" }
steps {
echo "start deploy to kubernetes"
sh "date +%T ; sleep 15 ; date +%T"
}
}
}
}
}
}
3.3.1.2. step并行
pipeline {
agent none
stages {
stage('git clone code') {
agent { label "VM" }
steps {
echo "git clone xxxx"
echo "git checkout branch"
}
}
stage('compile apps') {
agent { label 'docker && golang' }
steps {
parallel (
apiserver: {
echo "go build kube-apiserver"
sh "date +%T ; sleep 20 ; date +%T"
},
kube-proxy: {
echo "go build kube-proxy"
sh "date +%T ; sleep 10 ; date +%T"
},
controller-manager: {
echo "go build controller-manager"
sh "date +%T ; sleep 15 ; date +%T"
}
)
}
}
}
}
3.3.2. 并发构建
默认的Pipeline 是允许并发构建的,即同一个流水线可以执行多个任务,但是部分场景中,基于机器资源和性能,或者任务本身可能存在冲突,不适合并发构建。
通过 option { disableConcurrentBuilds() }
可以指定不允许并发构建
pipeline {
agent any
options {
disableConcurrentBuilds()
buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '10', numToKeepStr: '20')
timeout(activity: true, time: 20)
}
stages {
stage('Clone') {
steps {
sh "sleep 20"
}
}
stage('Build') {
steps {
sh "sleep 30"
}
}
}
}
3.3.3. 资源锁
资源锁只针对共享资源进行加锁,避免步骤之间争抢共享资源导致异常,lock指令只能在 options 和 steps 块内使用,例如以下场景:
测试机器数量有限,不允许多个pipeline操作一台测试机
pipeline { agent any parameters { string(name:"test_server", defaultValue:"172.21.21.22", description:"input test host for linux env ") string(name:"branch", defaultValue:"master", description:"select branch name to checkout") } stages { stage('clone') { steps { echo "clone code from gitee ..." } } stage('build') { steps { echo "checkout to ${params.branch} ..." echo "make build ..." } } stage('test') { steps { // lock {} 只能放在steps内 lock (resource: "${params.test_server}") { echo "start testing ..." sh "sleep 60" echo "stop testing ..." echo "clear testing ..." } } } stage('push to nexus') { steps { sh "sleep 20" echo "push finish" } } } }
测试中会发现,在 Lockable Resources 中会发现当前资源对哪个 Job 锁定,如果其它Job想要执行必须等待当前的锁释放
代码仓库太大,选择在公共目录下创建分支目录,每次构建进入对应分支拉取最新代码,就需要避免多个任务同时操作一个分支
执行的任务分为编译任务和非编译任务,编译任务不能在一台机器上并行,而非编译任务可以
pipeline {
agent { label "centos" }
parameters {
// branch名称
string(name:"branch_name", defaultValue:"master", description:"branch name")
// 是否涉及编译,非编译的包仅拷贝配置文件,生成config包
booleanParam(name:"compile", defaultValue:true, description:"compile binary file")
}
options { timestamps() }
stages {
stage ('run compile job') {
when {
// 做条件判断,如果是编译任务走该分支
expression { return params.compile }
}
steps {
// 编译任务锁住当前节点和当前分支,避免多个编译任务同时在一台机器上运行
// 之所以不能把 lock 放在 stage 下的 options 内,是因为optionse内无法获取到 env.NODE_NAME 变量
// 而且自定义的 env 变量也无法在 options 内获取,params 可以在options内正常使用
lock(extra: [[resource: "${env.NODE_NAME}-${params.branch_name}"]], resource: "${env.NODE_NAME}") {
script {
stage('git pull all') {
echo "${env.AGENT}"
echo "git pull"
}
stage('build') {
echo "make build "
sh "sleep 60"
}
stage('compress all') {
echo "make compress"
sh "sleep 20"
}
}
}
}
}
stage ('run config job') {
when {
not { expression { return params.compile } }
}
steps {
lock(resource: "${env.NODE_NAME}-${params.branch_name}") {
script {
stage('git pull config') {
echo "git pull"
}
stage('compress all') {
echo "make compress"
sh "sleep 30"
}
}
}
}
}
stage ('push to nexus') {
steps {
echo "push ..."
sh "sleep 30"
}
}
}
}