Jenkins有一下几种模式:
- 一般初次接触Jenkins建议使用自由风格(freestyle),可视化的UI页面配合一些Jenkins插件再写一些简单的shell/bat命令即可实现从源码构建到项目部署的整个步骤。
- 当你对Jenkins熟悉度达到一定级别后可以尝试将自由风格的转变成Jenkins的pipeline语法编写 Jenkinsfile 形成构建模版共同类型的项目使用。
至今我们已经完成Jenkins的基础部分介绍,这里我们就开始新的篇章学习Jenkins的Pipeline。
Pipeline向Jenkins中添加了一组强大的工具, Pipeline在代码中实现的,通常会检查到源代码控制, 使团队有编辑, 审查和迭代他们的交付流水线的能力。
Jenkins Pipeline是一组插件,支持在Jenkins上实现和集成持续交付的管道。Pipeline这个单词是水管的意思。
Jenkins为了更好支持CI和CD,通过Groovy语言的DSL(动态描述语言)来开发Pipeline组件。在Jenkins中有一句话,Pipeline as code,Pipeline是Jenkins中最优雅的存在。之前Jenkins上UI操作动作,都可以在Pipeline中代码实现,主要你对Jenkins和Groovy语言有足够多掌握。
pipline介绍
使用要求
为了使用 Jenkins Pipeline,你需要:
- Jenkins 2.x 或以上版本(旧版本到 1.642.3 可能可以,但不建议)
- 安装Pipeline 插件
概念
- 流水线
pipeline
: 是用户定义的一个CD流水线模型 。流水线的代码定义了整个的构建过程, 他通常包括构建, 测试和交付应用程序的阶段 。 - 节点
node
: 是一个机器 ,它是Jenkins环境的一部分,它主要负责执行流水线,也就是我们常说的master
和slave
的node节点 - 阶段
stage
: 我们可以将构建的整个过程拆分很多阶段stage
, 每个阶段执行不同的任务比如 "执行代码检出", "执行mvn构建", "执行部署" 和 " 执行制品收集" 等阶段。 - 步骤
step
: 一个单一的任务, 每个任务都有自己的工作内容,比如我们在"执行mvn构建" 需要实行shell
脚本mvn clean install
,步骤必须包含在一个阶段内。
声明式和脚本化
在开始说声明式和脚本化的区别前我们先聊一个话题声明式编程模型 和 命令式编程模型,这两者都是为了编程而存在的,只不过声明时编程模型更倾向于 I want to do
, 而命令式编程 表现于 you must to do
, 两个模型的特点是:
- 声明式 就是告诉系统我想做什么,声明式编程限制用户使用更严格和预定义的结构,编程维度更高,实现功能更方便
- 命令式 则是一板一眼,你让程序干什么,程序就按照你的指令执行,编程维度更低,能够实现更复杂的需求
jenkins的pipeline也根据这两个模型创建出两种语法格式:声明式语法和脚本化语法
Pipeline中的两种语法格式:声明式和脚本化,这两种语法格式在书写时是不同的。
- 声明式相比脚本化的流水线语法,它提供更丰富的语法特性, 不需要Groovy语言功底支撑
- 脚本化是为了使编写和读取流水线代码更容易而设计的。
两种的根本差距是:
- 声明式是通过pipeline和jenkins的一系列插件提供的方法和函数进行编写pipeline code,Jenkins 会将声明式 Pipeline 转换为 Groovy 脚本,然后执行。
- 脚本式 Pipeline 直接使用 Groovy 语法,因此更灵活和强大。用户可以直接编写 Groovy 代码来实现复杂的逻辑。
然而,在实际的应用场景中,我们通常都是声明式和脚本化相结合的流水线。
比较两种语法的不同:
// 声明时语法
pipeline {
agent any
stages {
stage('Build') {
steps {
/* balabala小魔仙 */
}
}
}
}
// 脚本时语法
node {
stage('Build') {
/* balabala小魔仙 */
}
}
基础架构和语法
接下来我们看下一个最基本的pipeline语法, jenkins这边也提供了一个简单的pipeline示例
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World'
}
}
}
}
- pipeline 是声明式流水线的一种特定语法,他定义了包含执行整个流水线的所有内容和指令的 "block" 代码块。
- agent是声明式流水线的一种特定语法,它指示 Jenkins 为整个流水线分配一个执行器 (在节点上)和工作区。
- node 是脚本化流水线的一种特定语法,它指示 Jenkins 在任何可用的代理/节点上执行流水线 (和包含在其中的任何阶段)这实际上等效于 声明式流水线特定语法的
agent
。 - 如果期望在标签为
java
的 机器上运行构建,两种表示方式为:agent { label 'java' }
node('java') { }
- node 是脚本化流水线的一种特定语法,它指示 Jenkins 在任何可用的代理/节点上执行流水线 (和包含在其中的任何阶段)这实际上等效于 声明式流水线特定语法的
- stage 是一个描述
stages
的语法块。一个stages
** 可以包含多个stage
, 但是每个stage
后面的名字必须唯一** - steps 是声明式流水线的一种特定语法,它描述了在这个 stage 中要运行的步骤。注意看哦
steps
是个复数,所以每个stage 只能包含一个steps
- echo 是一个执行打印命令的流水线 。是声明时语法内提供的函数
语法结构
agent: 节点
stage: 阶段
steps: 动作
- 声明式流水线所有的代码包裹在pipline{}内,声明当前格式为pipline格式的job类型
- stages{}层用来包含该pipeline所有的stage子层,每一个stage都是一个模块,相互不受影响
- stage{}层用来包含具体我们需要编写任务的steps{}子层
- steps{}层用来添加我们具体需要调用的模块语句
特殊的语法区域
agent 区域
agent
部分指定了整个流水线或特定部分将在 Jenkins 环境中执行的位置,这取决于 agent
区域的位置。在 pipeline
块的顶层必须定义 agent
,而在 stage
级别的使用是可选的。
agent
定义了 Pipeline 的执行位置,可以使用 any
、none
、具体的 Jenkins 节点标签名、甚至 Docker 镜像。例如,我们需要指定在名为 “java” 的服务器上执行,可以写成:
agent { node { label 'java' } }
agent { label 'java' }
agent { docker 'maven:3-alpine' }
agent none
表示整个 Pipeline 没有默认的执行节点,因此 Pipeline 内的每个stage
可以根据需要在其内部指定不同的执行节点,否则stage
内的代码不会被执行。
environment 区域
environment
指令制定一个 键-值对序列,该序列将被定义为所有步骤的环境变量,或者是特定于阶段的步骤, 这取决于 environment
指令在流水线内的位置。
- 可以出现在
pipeline
代码块和stage
代码块- 在
pipeline
块出现表示全局变量 - 在
stage
代码块出现则该环境变量只能生效在各个步骤内
- 在
- 可以和凭据结合
- 对于账号密码类型的凭据,指定的环境变量指定为
username:password
- 对于
Secret Text
的凭证, 将确保指定的环境变量包含秘密文本内容。 - 更多关于凭据的信息,请查看官网
- 对于账号密码类型的凭据,指定的环境变量指定为
environment {
JAVA_HOME = "/usr/local/java8"
}
- 全局变量:与
agent
平级,这时环境变量应用于所有的stages
任务。 - 局部变量:在
stage
内部定义environment
,此时仅在该stage
内部任务中应用。
parameters 区域
parameters
区域主要为流水线构建添加运行时的参数选项。通过 params
来获取 parameters
设置的属性,params
获取的是 parameters
所有结果返回的 Map
。
script 区域
script
区域主要在 steps
内定义,允许编写 Groovy 脚本语言,并支持 Groovy 逻辑运算。
- 条件判断
stage('Conditional Stage') {
steps {
script {
if (env.BRANCH_NAME == 'main') {
echo 'Deploying to production'
} else {
echo 'Deploying to staging'
}
}
}
}
解释:这个示例展示了如何根据当前的分支名决定部署到哪个环境。如果分支名是 main
,将打印 Deploying to production
,否则打印 Deploying to staging
。
- **循环 **
stage('Loop Example') {
steps {
script {
for (int i = 0; i < 5; i++) {
echo "Iteration ${i}"
}
}
}
}
解释:在这个示例中,使用 for
循环输出从 0 到 4 的数字,每次迭代都会打印 Iteration <数字>
。
- **调用外部命令并处理结果 **
stage('Execute Command') {
steps {
script {
def result = sh(script: 'ls -l', returnStdout: true).trim()
echo "Command output: ${result}"
}
}
}
解释:这里我们执行了 ls -l
命令,并将输出保存到 result
变量中。然后通过 echo
打印出命令的结果。
- **自定义函数 **
stage('Custom Function') {
steps {
script {
def greet = { name -> return "Hello, ${name}" }
def message = greet('Jenkins')
echo message
}
}
}
解释:这个示例定义了一个名为 greet
的自定义函数,该函数接受一个参数并返回一个问候语。然后调用该函数并输出问候语。
关键字介绍
pipeline 声明pipline语法
node 是脚本化流水线的一种特定语法,它指示 Jenkins 在任何可用的代理/节点上执行流水线 (和包含在其中的任何阶段)这实际上等效于 声明式流水线特定语法的`agent`。
agent 指示 Jenkins 为整个流水线分配一个执行器(在 Jenkins 环境中的任何可用代理/节点上)和工作区。
stages 步骤组,包含一个或多个stages
stage 定义了在整个流水线的执行任务的概念性地不同的的子集
step 步骤,任务。
parameters 为流水线定义构建参数
params 将为流水线定义的所有参数作为 Map,例如:params.MY_PARAM_NAME。
env 可以从脚本式流水线中访问的环境变量(jenkins的环境变量),例如: env.PATH 或 env.BUILD_ID
currentBuild 可用于发现当前正在执行的流水线的信息, 比如 currentBuild.result,currentBuild.displayName 等属性。参考内置的全局变量参考页面 ${YOUR_JENKINS_URL}/pipeline-syntax/globals 以获取完整的,最新的,currentBuild 的属性列表。
when
when
指令允许流水线根据给定的条件决定是否应该执行阶段。 when
指令必须包含至少一个条件。 如果 when
指令包含多个条件, 所有的子条件必须返回True,阶段才能执行。 这与子条件在 allOf
条件下嵌套的情况相同
- 只能出现在
stage
代码块 - when有以下几个参数:
- branch: 当正在构建的分支与模式给定的分支匹配时,执行这个阶段, 例如:
when { branch 'master' }
。注意,这只适用于多分支流水线。 - environment: 当指定的环境变量是给定的值时,执行这个步骤, 例如:
when { environment name: 'DEPLOY_TO', value: 'production' }
- expression: 当指定的Groovy表达式评估为true时,执行这个阶段, 例如:
when { expression { return params.DEBUG_BUILD } }
- not: 当嵌套条件是错误时,执行这个阶段,必须包含一个条件,例如:
when { not { branch 'master' } }
- allOf: 当所有的嵌套条件都正确时,执行这个阶段,必须包含至少一个条件,例如:
when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
- anyOf: 当至少有一个嵌套条件为真时,执行这个阶段,必须包含至少一个条件,例如:
when { anyOf { branch 'master'; branch 'staging' } }
- branch: 当正在构建的分支与模式给定的分支匹配时,执行这个阶段, 例如:
- 在进入
stage
前评估when
,当满足when的条件后才会进入该stage
- 当
stage
内指定agent
时,如果设置了when
则会先进入agent
环境后在评估条件,除非你设置when
的beforeAgent
属性为true
pipeline {
agent none
stages {
stage('Example Deploy') {
agent {
label "java"
}
when {
beforeAgent true
branch 'master'
}
steps {
echo 'Deploying'
}
}
}
}
parallel
声明式流水线的parallel
块内可以包含多个stage
,这表示它们将并行执行。 但需要注意哦:一个阶段必须只有一个 steps
或 parallel
的阶段。 嵌套阶段本身不能包含进一步的 parallel
阶段。任何包含 parallel
的阶段(下面案例的stage('Parallel Stage')
阶段)不能包含 agent
或 tools
阶段, 因为他们没有相关 steps
。
通过添加 failFast true
到包含 parallel
的 stage
中, 当其中一个进程失败时,你可以强制所有的 parallel
阶段都被终止。
pipeline {
agent any
stages {
stage('Non-Parallel Stage') {
steps {
echo 'This stage will be executed first.'
}
}
stage('Parallel Stage') {
when {
branch 'master'
}
failFast true
parallel {
stage('Branch A') {
agent {
label "for-branch-a"
}
steps {
echo "On Branch A"
}
}
stage('Branch B') {
agent {
label "for-branch-b"
}
steps {
echo "On Branch B"
}
}
}
}
}
}
流程控制
if语句
脚本化流水线从 Jenkinsfile
的顶部开始向下串行执行, 就像 Groovy 或其他语言中的大多数传统脚本一样。 因此,提供流控制取决于 Groovy 表达式, 比如 if/else
条件, 例如:
node {
stage('Example') {
if (env.BRANCH_NAME == 'master') {
echo 'I only execute on the master branch'
} else {
echo 'I execute elsewhere'
}
}
}
如果是声明时语法则需要使用script
进行包裹强调这是一段脚本语法:
pipeline {
agent any
stages {
stage('Example') {
script {
if (env.BRANCH_NAME == 'master') {
echo 'I only execute on the master branch'
} else {
echo 'I execute elsewhere'
}
}
}
}
}
捕获异常
脚本化流水线语法使用Groovy的异常处理支持来管理脚本化流水线流控制。当步骤失败 ,无论什么原因,它们都会抛出一个异常。处理错误的行为必须使用Groovy中的 try/catch/finally
块 , 例如:
node {
stage('Example') {
try {
sh 'exit 1'
}
catch (exc) {
echo 'Something failed, I should sound the klaxons!'
throw
}
}
}
常用函数
sh
在 Unix/Linux 环境中执行 Shell 命令
sh 'echo Hello from Unix/Linux'
echo
输出消息到 Jenkins 控制台。
echo 'This is a message'
parallel
并行执行多个步骤。
parallel(
stage('xxx') {
steps {
echo 'Running step xxx'
}
}
stage('yyy') {
steps {
echo 'Running step yyy'
}
}
)
input
等待用户输入
input 'Do you want to proceed?'
timeout
设置步骤的超时时间
timeout(time: 10, unit: 'MINUTES') {
sh 'long-running-command'
}
retry
重试指定次数的步骤
retry(3) {
sh 'unstable-command'
}
archiveArtifacts
保存构建产生的工件
archiveArtifacts artifacts: '**/target/*.jar', allowEmptyArchive: true
currentBuild
currentBuild
是 Jenkins Pipeline 中用于访问和操作当前构建(build)状态的变量。通过 currentBuild
,你可以获取构建的各种信息,设置构建结果,添加描述等。
常见用法
- 获取当前构建的基本信息
- 设置当前构建的结果
- 添加构建描述
- 访问构建的环境变量
属性和方法
currentBuild.number
: 获取当前构建的编号。currentBuild.url
: 获取当前构建的 URL。currentBuild.durationString
: 获取当前构建的持续时间字符串。currentBuild.result
: 获取或设置当前构建的结果。currentBuild.displayName
: 获取或设置当前构建的显示名称。currentBuild.fullDisplayName
: 获取当前构建的完整显示名称。currentBuild.id
: 获取当前构建的 ID。currentBuild.startTimeInMillis
: 获取当前构建的开始时间(以毫秒为单位)。currentBuild.duration
: 获取当前构建的持续时间(以毫秒为单位)。currentBuild.buildCauses
: 获取触发当前构建的原因。
Pipeline 常用函数一览
代码检出
checkout
: 检出代码。git
: 使用 Git 检出代码。svn
: 使用 Subversion 检出代码。
构建和执行
sh
: 在 Unix/Linux 系统上执行 Shell 命令。bat
: 在 Windows 系统上执行批处理命令。powershell
: 在 Windows 系统上执行 PowerShell 脚本。docker
: 使用 Docker 进行构建和执行。maven
: 使用 Maven 进行构建。
测试
junit
: 解析 JUnit 测试结果。archiveArtifacts
: 归档构建产物。
通知
mail
: 发送电子邮件。emailext
: 发送电子邮件。需要依赖Email Extension
插件dingtalk
: 发送 钉钉 消息。需要依赖dingtalk
插件wxwork
: 发送 微信消息。需要依赖WXWork Notification
插件
流程控制
stage
: 定义流水线的一个阶段。node
: 指定在哪个节点上执行步骤。parallel
: 并行执行多个步骤。timeout
: 设置步骤的超时时间。retry
: 重试执行步骤。try {...} catch(exc) { }
: 捕获并处理错误。error
: 触发错误并终止流水线。input
: 等待用户输入。sleep
: 暂停一段时间。
sleep time: 10, unit: 'SECONDS'
文件操作
readFile
: 读取文件内容。
def content = readFile 'example.txt'
echo "File content: ${content}"
writeFile
: 写入文件内容。
writeFile file: 'example.txt', text: 'Hello, Jenkins!'
stash
: 存储文件以供后续步骤使用。
stash name: 'sourceFile', includes: '**'
unstash
: 恢复之前存储的文件。
unstash 'source'
环境变量
withEnv
: 在指定的环境变量下执行步骤。
withEnv(['VAR_NAME=value']) {
// 任务
}
env
: 访问和设置环境变量。
其他
echo
: 打印消息。pwd
: 获取当前工作目录。dir
: 在指定目录下执行步骤。类似Linux的cd
命令
dir('path/to/dir') {
// 任务
}
fileExists
: 检查文件是否存在。deleteDir
: 删除当前目录及其内容。archive
: 归档文件。fingerprint
: 生成文件的指纹。timestamps
: 为日志添加时间戳。
timestamps {
// 任务
}
wrap
: 包装步骤以添加额外的功能(如超时、重试等)。
Pipeline使用方法
什么是 JenkinsFile?
JenkinsFile 是来定义 Jenkins pipeline的文本文件。 使用 JenkinsFile 将流水线实现为代码,并且可以使用域特定语言(DSL)来定义。 JenkinsFile一般有三种方式创建:
- 使用经典 UI 创建的
Jenkinsfile
由 Jenkins 自己保存(一般保存在/var/lib/jenkins/jobs/${JOB_NAME}/config.xml
复杂的流水线很难在流水线配置页面经典 UI的脚本文本区域进行编写和维护。为简化操作,流水线的 Jenkinsfile
可以在文本编辑器或集成开发环境(IDE)中进行编写并提交到源码管理系统 (可选择性地与需要 Jenkins 构建的应用程序代码放在一起)。然后 Jenkins 从源代码管理系统中检出 Jenkinsfile
文件作为流水线项目构建过程的一部分并接着执行你的流水线。
Pipeline的工作机制
Jenkins Pipeline 底层是基于 Groovy 语言的。Groovy 是一种动态脚本语言,可以与 Java 无缝集成。Jenkins 使用 Groovy 作为其脚本语言,因为它强大且灵活。
jenkins 依赖 Pipeline 插件,Pipeline 插件 将我们书写的Pipeline 代码(无论是声明式还是脚本式) 转化为 groovy 脚本后进行执行
pipeline构建ruoyi项目
书写框架
我们来看看我们在自由风格构建的步骤
1.拉取代码
2.漏洞扫描
2.编译打包
4.推送nexus
5.部署服务
那我们可以按照这个思路进行书写pipeline框架
pipeline {
agent any
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
}
}
stage('漏洞扫描') {
steps {
echo "漏洞扫描"
}
}
stage('编译打包') {
steps {
echo "编译打包"
}
}
stage('推送nexus') {
steps {
echo "推送nexus"
}
}
stage('部署服务') {
steps {
echo "部署服务"
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
创建pipeline
这里我们还是以ruoyi-auth
为例,为了不和之前的环境ruoyi-auth
重名,首先需要创建一个文件夹,命名为测试
在测试文件夹内 新增item -->命令为ruoyi-auth
-->创建类型 pipeline -->流水线 --> pipeline Script,这里先编写我们需要做得步骤和具体内容
可以看到执行成功,那么我们再进行一些改造
设置job属性
pipeline可以为每个job设置特定的参数属性,比如是否允许并发构建、保留多少个构建历史、job构建超时时间等等,你是不是突然想起什么,这不就是UI界面内的内容嘛
pipeline通过 **options**
- buildDiscarder:设置项目构建历史保留多少个。例如:
options { buildDiscarder(logRotator(numToKeepStr: '1')) }
- disableConcurrentBuilds: 是否允许并发构建。例如:
options { disableConcurrentBuilds() }
,禁止单个项目并发构建 - timeout:设置流水线运行的超时时间, 在此之后,Jenkins将中止流水线。例如:
options { timeout(time: 1, unit: 'HOURS') }
- retry:在失败时, 重新尝试整个流水线的指定次数。 例如:
options { retry(3) }
- timestamps: 显示构建时间。 例如:
options { timestamps() }
, 需要依赖timestamps
插件
设置构建参数
虽然我们在pipeline流程图内没有标记设置构建参数,前面我们在设计ruoyi-auth
的时候是有构建参数的,那么我们这边就从构建参数开始设置。
前面已经说过,parameters
是设置构建参数的关键字,所有的构建参数必须要写在parameters
块内,针对于参数jenkins pipeline和freestyle一样,都有一下构建选项:
- 字符参数: 在pipeline内使用
string
表示 - 文本参数:在pipeline内使用
text
表示 - 选项参数: 在pipeline内使用
choice
表示 - 布尔参数: 在pipeline内使用
booleanParam
表示 List Git branches
: 在pipeline内使用listGitBranches
表示 (需要安装List Git Branches
插件)
现在我们需要实现ruoyi-auth
freestyle 时的样式
pipeline {
agent any
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PALTFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
}
}
stage('编译打包') {
steps {
echo "编译打包"
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
保存后,可以看见,构建按钮还是 立即构建 并不是参数化构建按钮 Build with Parameteres
,说明这个时候我们设置的参数还没生效。这是因为在 Jenkins 中,Pipeline 作业的配置通常是从 Jenkinsfile
中读取的。对于参数化构建,Jenkins 需要读取和解析 Jenkinsfile
中的参数配置,并将其应用到作业中。所以需要我们手动触发一下构建。
构建完成就能发现,构建的按钮已经变了对吧,如果还是没有变化,可能是你的pipeline代码没有写对哦
代码检出过程
清理工作区间
deleteDir()
是pipeline内部内置的函数,作用就删除当前工作目录的所有内容,类似shell的rm -rf *
因此请确保你在适当的上下文中使用它,以避免意外删除重要文件。
执行代码检出
在 Jenkins Pipeline 中,git
和 checkout
是两种用于检出代码库的不同方式。它们都可以用于从版本控制系统(如 Git)中检出代码,但它们的使用方式和灵活性有所不同。
我们在代码检出阶段,使用的是echo
进行模拟代码检出操作。这里进行真实的代码检出。如果你的代码仓库使用的是公开仓库(不需要账号密码即可拉取代码),那么可以直接使用 git
将代码进行检出。
git branch: BRANCH, url: 'https://gitee.com/kubesre/ruoyi-auth.git'
// 使用公开仓库不需要输入凭据即可拉取代码,如果是私有仓库,可以写成下面的方式'git-root'表示凭据ID
git branch: BRANCH, url: 'https://gitee.com/kubesre/ruoyi-auth.git', credentialsId: 'git-root'
git
是pipeline内部内置的函数BRANCH
是获取listGitBranches
参数的值,当别人写成env.BRANCH
或者params.BRANCH
的时候你不要惊讶,jenkins的pipeline插件会将构建参数的name
映射成:- groovy内部变量
BRANCH
params
MAP中的元素- 以及系统环境变量
BRANCH
- groovy内部变量
大多数情况下,我们代码仓库为了保密性都是私有仓库,所以我们需要和freestyle一样需要使用凭据将代码检出。pipeline不仅支持 git
函数检出,还支持checkout
函数检出,checkout
函数可以定制更高级的检出细节,具体内容请参考本篇内的【Pipeline内部函数】-【插件提供的步骤】-【checkout
步骤】
checkout scmGit(branches: [[name: '${BRANCH}']], extensions: [], userRemoteConfigs: [[credentialsId: 'git-root', url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
目前的全部代码如下:
pipeline {
agent any
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PALTFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
}
}
stage('编译打包') {
steps {
echo "编译打包"
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
构建过程
执行shell命令
如果我们在不期望得到命令的返回值于状态码的情况下我们执行shell
命令:
sh 'ls -l'
但是这种情况需要注意,当shell命令的退出状态码为非0时,pipeline会触发结束构建流程,所以如果我们需要获取shell的执行结果状态码,可以使用下面返回命令状态码的方式。
我们目前使用的是声明时语法,以下代码使用的是脚本时语法格式,所以以下代码内容需要放到 script {}
块中,表示强调这些代码是脚本时语法格式,让pipeline按照脚本时语法进行转换
返回命令状态码:
pipeline {
......
stages {
stage('代码检出') {
steps {
script {
def statusCode = sh returnStatus: true, script: 'command-not-found'
println statusCode
if (statusCode!=0) {
env.REASON="命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
......
}
def 在groovy中表示定义一个变量
println 和echo 意思一样,表示打印一个内容
if () {} 在groovy中表示if语句判断,后面groovy语法会讲
执行结果,可以看到捕获到错误状态码了
当然,我们也可以获取命令执行的返回结果
返回命令输出:
def stdoutMsg = sh returnStdout: true, script: 'ls'
小知识:在控制台中,隐藏正在执行的命令
问题描述
在控制台中,当使用 sh 步骤时会打印正在执行的 Shell 命令,如下所示:
...
[Pipeline] script
[Pipeline] {
[Pipeline] sh
+ ls -l -h
total 16K
drwxr-xr-x 2 jenkins jenkins 25 Mar 21 21:00 deploy
drwxr-xr-x 4 jenkins jenkins 192 Mar 21 21:00 docs
drwxr-xr-x 139 jenkins jenkins 8.0K Mar 21 21:00 notebook
-rw-r--r-- 1 jenkins jenkins 47 Mar 21 21:00 README.md
drwxr-xr-x 7 jenkins jenkins 211 Mar 21 21:00 scripts
drwxr-xr-x 2 jenkins jenkins 23 Mar 21 21:00 template
[Pipeline] }
[Pipeline] // script}
# 我们希望能够隐藏命令输出,即隐藏 + ls -l -h 输出行。
问题原因: 在 Jenkins 执行 Shell 命令时,默认启用 -x 与 -e 选项,而 -x 选项导致命令打印。
解决办法
# 关闭 -x 选项,即仅使用 -e 选项(以下两种写法皆可):
sh """#!/bin/sh -e
// do some stuff
"""
sh """\
#!/bin/sh -e
// do some stuff
"""
# 还可以使用 set +x 也可以,但是会输出 set +x 命令自身,不够“干净”:
sh """
set +x
// do some stuff
"""
下面我们就是用shell命令在pipeline中执行构建命令吧,如果不想要返回值(状态码和输出结果),可以直接使用 内置函数sh
stage('构建') {
steps {
echo '构建'
sh "
source /etc/profile
mvn clean install
"
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
echo '将制品纳入归档'
}
}
如果你想获取返回值,则必须使用脚本式声明 ,声明时语法不支持 groovy
式赋值:
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n source /etc/profile ; mvn clean install '
if (statusCode!=0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
echo '将制品纳入归档'
}
}
执行结果
前面我们使用
/etc/profile.d/maven.sh
定义 mvn 的环境变量,所以一定要source /etc/profile
,不然会提示找不到mvn 命令
如果你不想通过source /etc/profile
引用环境变量,那么可以在pipeline内使用jenkins已定义的全局工具
使用在jenkins内部已定义的工具
我们在构建过程中可以使用shell命令直接调用一些构建命令,有时候我们也可以调用在jenkins内已经定义的工具,这些工具在jenkins--> 管理jenkins --> tools内定义的我们都可以直接使用。比如我们在jenkins的工具内定义了maven
只需要在pipeline内定义tools即可,如下
pipeline {
agent any
tools {
maven "mvn3.3.3" // mvn3.3.3 就是我们在全局定义的maven工具名称,你可以定义多个,只需要指定名称即可
}
整体代码如下,发现编译打包部分不需要再去source 环境变量了
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install '
if (statusCode!=0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
echo '将制品纳入归档'
}
}
pipeline {
agent any
tools {
maven "maven3.3.3"
}
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PALTFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
sh 'pwd && ls -l'
script {
def statusCode = sh returnStatus: true, script: 'command-not-found'
println statusCode
if (statusCode!=0) {
env.REASON="命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install '
if (statusCode!=0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
echo '将制品纳入归档'
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
检查制品是否存在、纳入制品库
当我们构建完成后,需要检查我们的打包结果,这里可以使用 pipeline 内置函数 fileExists
,它的用法:
我们使用if
判断,当文件存在时将文件纳入制品库,如果不存在或者名称不规范将包构建直接报错
- 将制品纳入制品库可以使用
archiveArtifacts
函数
注意事项1:使用文件夹内构建的流水线,必须将JOB_NAME
需要变成 JOB_BASE_NAME
,生产环境建议大家也常用JOB_BASE_NAME
排除文件夹名的干扰
注意事项2:使用变量时必须用双引号,流水线语法生成的都不带双引号导致识别不到变量
if (fileExists("target/${JOB_BASE_NAME}.jar")){
// 归档构建产物
archiveArtifacts artifacts: "target/${JOB_BASE_NAME}.jar", fingerprint: true, followSymlinks: false, onlyIfSuccessful: true
} else {
error "未找到构建成品target/${JOB_NAME}.jar,请研发检查项目pom.xml的输出路径以及包名是否规范"
}
解释如下
fingerprint: true #记录所有归档成品的指纹
followSymlinks: false #归档时不跟随符号链接
onlyIfSuccessful: true #只有构建成功时归档
整体代码如下
pipeline {
agent any
tools {
maven "maven3.3.3"
}
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PALTFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
script {
def statusCode = sh returnStatus: true, script: 'pwd'
println statusCode
if (statusCode!=0) {
env.REASON="命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install '
if (statusCode!=0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
script {
if (fileExists("target/${JOB_BASE_NAME}.jar")){
// 归档构建产物
archiveArtifacts artifacts: "target/${JOB_BASE_NAME}.jar", fingerprint: true, followSymlinks: false, onlyIfSuccessful: true
} else {
error "未找到构建成品target/${JOB_BASE_NAME}.jar,请研发检查项目pom.xml的输出路径以及包名是否规范"
}
}
echo '将制品纳入归档'
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
部署过程
生成启动脚本
使用 sh
函数,生成sh脚本
sh '''
tee > start_up.sh <<EOF
#!/bin/bash
declare -A server_port_set
server_port_set=( ["ruoyi-auth"]="8090" ["ruoyi-system"]="8091" ["ruoyi-gateway"]="8080" )
server_port=\\${server_port_set["${JOB_BASE_NAME}"]}
[ -d /data/prog/project/${JOB_BASE_NAME} ] || mkdir -p /data/prog/project/${JOB_BASE_NAME}
project_jar_path=/data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
update_jar_path=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
[ -f \\${project_jar_path} ] && {
echo "检测到旧的jar包,开始替换"
backup_path=/data/backup/\\$(date +%F-%H-%M-%S)
mkdir -p \\${backup_path}
/bin/cp -f \\${project_jar_path} \\${backup_path}/
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
} || {
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
}
jps | grep ${JOB_BASE_NAME}.jar && {
kill -9 \\$(ps -ef | grep ${JOB_BASE_NAME}.jar | grep -v grep |awk '{print \\$2}')
}
[ -d /data/logs/project/${JOB_BASE_NAME} ] || mkdir -p /data/logs/project/${JOB_BASE_NAME}/
cd /data/prog/project/${JOB_BASE_NAME}/
nohup /data/prog/jdk1.8.0_271/bin/java \
-server -Xms500m -Xmx500m \
-jar /data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar \
--server.port=\\${server_port} --spring.profiles.active=dev \
--spring.cloud.nacos.config.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.config.username=nacos \
--spring.cloud.nacos.config.password=nacos \
--spring.cloud.nacos.discovery.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.discovery.username=nacos \
--spring.cloud.nacos.discovery.password=nacos >> /data/logs/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}-console.log 2>&1 &
EOF
'''
通过Over SSH部署
这里我们还是自由风格的使用over ssh配置
通过流水线语法生成配置之后去掉一些默认配置
sshPublisher
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ruoyi-server',
transfers: [
sshTransfer(
remoteDirectory: '/data/update/${JOB_BASE_NAME}/',
removePrefix: 'target/',
sourceFiles: 'target/${JOB_BASE_NAME}.jar'
),
sshTransfer(
execCommand: 'cd /data/prog/project/${JOB_BASE_NAME}/ ; sh bin/start_up.sh',
remoteDirectory: '/data/prog/project/${JOB_BASE_NAME}/bin',
sourceFiles: 'start_up.sh'
)
]
)
])
完整代码如下
pipeline {
agent any
tools {
maven "maven3.3.3"
}
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PALTFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
script {
def statusCode = sh returnStatus: true, script: 'pwd'
println statusCode
if (statusCode != 0) {
env.REASON = "命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install'
if (statusCode != 0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
script {
if (fileExists("target/${JOB_BASE_NAME}.jar")) {
// 归档构建产物
archiveArtifacts artifacts: "target/${JOB_BASE_NAME}.jar", fingerprint: true, followSymlinks: false, onlyIfSuccessful: true
} else {
error "未找到构建成品target/${JOB_BASE_NAME}.jar,请研发检查项目pom.xml的输出路径以及包名是否规范"
}
}
echo '将制品纳入归档'
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
sh '''
tee > start_up.sh <<EOF
#!/bin/bash
declare -A server_port_set
server_port_set=( ["ruoyi-auth"]="8090" ["ruoyi-system"]="8091" ["ruoyi-gateway"]="8080" )
server_port=\\${server_port_set["${JOB_BASE_NAME}"]}
[ -d /data/prog/project/${JOB_BASE_NAME} ] || mkdir -p /data/prog/project/${JOB_BASE_NAME}
project_jar_path=/data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
update_jar_path=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
[ -f \\${project_jar_path} ] && {
echo "检测到旧的jar包,开始替换"
backup_path=/data/backup/\\$(date +%F-%H-%M-%S)
mkdir -p \\${backup_path}
/bin/cp -f \\${project_jar_path} \\${backup_path}/
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
} || {
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
}
jps | grep ${JOB_BASE_NAME}.jar && {
kill -9 \\$(ps -ef | grep ${JOB_BASE_NAME}.jar | grep -v grep | awk '{print \\$2}')
}
[ -d /data/logs/project/${JOB_BASE_NAME} ] || mkdir -p /data/logs/project/${JOB_BASE_NAME}/
cd /data/prog/project/${JOB_BASE_NAME}/
nohup /data/prog/jdk1.8.0_271/bin/java \
-server -Xms500m -Xmx500m \
-jar /data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar \
--server.port=\\${server_port} --spring.profiles.active=dev \
--spring.cloud.nacos.config.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.config.username=nacos \
--spring.cloud.nacos.config.password=nacos \
--spring.cloud.nacos.discovery.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.discovery.username=nacos \
--spring.cloud.nacos.discovery.password=nacos >> /data/logs/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}-console.log 2>&1 &
EOF
'''
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ruoyi-server',
transfers: [
sshTransfer(
remoteDirectory: '/data/update/${JOB_BASE_NAME}/',
removePrefix: 'target/',
sourceFiles: 'target/${JOB_BASE_NAME}.jar'
),
sshTransfer(
execCommand: 'cd /data/prog/project/${JOB_BASE_NAME}/ ; sh bin/start_up.sh',
remoteDirectory: '/data/prog/project/${JOB_BASE_NAME}/bin',
sourceFiles: 'start_up.sh'
)
]
)
])
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
通过ansible部署
ansible 配合 sh 模块实现传包、启动
script {
// 运行 Ansible 命令,并捕获退出码
def exitCode = sh(script: """
ansible ${PLATFORM}-${JOB_BASE_NAME} -m shell -a "mkdir -p /data/update/${JOB_BASE_NAME}"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m copy -a "src=target/${JOB_BASE_NAME}.jar dest=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m script -a "start_up.sh"
""", returnStatus: true)
// 根据退出码进行处理
if (exitCode != 0) {
echo "Ansible 部署出现异常,请运维人员检查~!"
// 你可以在这里添加更多的错误处理逻辑
currentBuild.result = 'FAILURE'
} else {
echo "部署成功."
}
}
完整如下
pipeline {
agent any
tools {
maven "maven3.3.3"
}
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PALTFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
script {
def statusCode = sh returnStatus: true, script: 'pwd'
println statusCode
if (statusCode != 0) {
env.REASON = "命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install'
if (statusCode != 0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
script {
if (fileExists("target/${JOB_BASE_NAME}.jar")) {
// 归档构建产物
archiveArtifacts artifacts: "target/${JOB_BASE_NAME}.jar", fingerprint: true, followSymlinks: false, onlyIfSuccessful: true
} else {
error "未找到构建成品target/${JOB_BASE_NAME}.jar,请研发检查项目pom.xml的输出路径以及包名是否规范"
}
}
echo '将制品纳入归档'
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
sh '''
tee > start_up.sh <<EOF
#!/bin/bash
declare -A server_port_set
server_port_set=( ["ruoyi-auth"]="8090" ["ruoyi-system"]="8091" ["ruoyi-gateway"]="8080" )
server_port=\\${server_port_set["${JOB_BASE_NAME}"]}
[ -d /data/prog/project/${JOB_BASE_NAME} ] || mkdir -p /data/prog/project/${JOB_BASE_NAME}
project_jar_path=/data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
update_jar_path=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
[ -f \\${project_jar_path} ] && {
echo "检测到旧的jar包,开始替换"
backup_path=/data/backup/\\$(date +%F-%H-%M-%S)
mkdir -p \\${backup_path}
/bin/cp -f \\${project_jar_path} \\${backup_path}/
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
} || {
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
}
jps | grep ${JOB_BASE_NAME}.jar && {
kill -9 \\$(ps -ef | grep ${JOB_BASE_NAME}.jar | grep -v grep | awk '{print \\$2}')
}
[ -d /data/logs/project/${JOB_BASE_NAME} ] || mkdir -p /data/logs/project/${JOB_BASE_NAME}/
cd /data/prog/project/${JOB_BASE_NAME}/
nohup /data/prog/jdk1.8.0_271/bin/java \
-server -Xms500m -Xmx500m \
-jar /data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar \
--server.port=\\${server_port} --spring.profiles.active=dev \
--spring.cloud.nacos.config.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.config.username=nacos \
--spring.cloud.nacos.config.password=nacos \
--spring.cloud.nacos.discovery.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.discovery.username=nacos \
--spring.cloud.nacos.discovery.password=nacos >> /data/logs/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}-console.log 2>&1 &
EOF
'''
script {
// 运行 Ansible 命令,并捕获退出码
def exitCode = sh(script: """
ansible ${PLATFORM}-${JOB_BASE_NAME} -m shell -a "mkdir -p /data/update/${JOB_BASE_NAME}"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m copy -a "src=target/${JOB_BASE_NAME}.jar dest=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m script -a "start_up.sh"
""", returnStatus: true)
// 根据退出码进行处理
if (exitCode != 0) {
echo "Ansible 部署出现异常,请运维人员检查~!"
// 你可以在这里添加更多的错误处理逻辑
currentBuild.result = 'FAILURE'
} else {
echo "部署成功."
}
}
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
构建历史显示
现在我们就需要将他进行优化,修改构建历史名称和构建历史描述pipeline 给我提供了两种方式:
- 使用
Build Name and Description Setter
插件提供的函数修改
buildDescription '这是一个构建描述'
buildName '这是我自主命名的job_name'
- 使用
currentBuild
内置方法 修改
currentBuild.displayName = '这是我自主命名的job_name'
currentBuild.description = 'This is a sample build description'
增加stage
stage('增加构建信息') {
steps {
script {
// 更新构建名称和描述
currentBuild.displayName = "${BUILD_NUMBER}-${JOB_NAME}"
currentBuild.description = "提交者: ${BUILD_USER} <br> 提交者ID:${BUILD_USER_ID} <br> 提交时间:${BUILD_TIMESTAMP} <br> 构建分支:${BRANCH}"
}
}
}
完整代码
pipeline {
agent any
tools {
maven "maven3.3.3"
}
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PLATFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
script {
def statusCode = sh returnStatus: true, script: 'pwd'
println statusCode
if (statusCode != 0) {
env.REASON = "命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
stage('增加构建信息') {
steps {
script {
// 更新构建名称和描述
currentBuild.displayName = "${BUILD_NUMBER}-${JOB_NAME}"
currentBuild.description = "提交者: ${BUILD_USER} <br> 提交者ID:${BUILD_USER_ID} <br> 提交时间:${BUILD_TIMESTAMP} <br> 构建分支:${BRANCH}"
}
}
}
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install'
if (statusCode != 0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
script {
if (fileExists("target/${JOB_BASE_NAME}.jar")) {
// 归档构建产物
archiveArtifacts artifacts: "target/${JOB_BASE_NAME}.jar", fingerprint: true, followSymlinks: false, onlyIfSuccessful: true
} else {
error "未找到构建成品target/${JOB_BASE_NAME}.jar,请研发检查项目pom.xml的输出路径以及包名是否规范"
}
}
echo '将制品纳入归档'
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
sh '''
tee > start_up.sh <<EOF
#!/bin/bash
declare -A server_port_set
server_port_set=( ["ruoyi-auth"]="8090" ["ruoyi-system"]="8091" ["ruoyi-gateway"]="8080" )
server_port=\\${server_port_set["${JOB_BASE_NAME}"]}
[ -d /data/prog/project/${JOB_BASE_NAME} ] || mkdir -p /data/prog/project/${JOB_BASE_NAME}
project_jar_path=/data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
update_jar_path=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
[ -f \\${project_jar_path} ] && {
echo "检测到旧的jar包,开始替换"
backup_path=/data/backup/\\$(date +%F-%H-%M-%S)
mkdir -p \\${backup_path}
/bin/cp -f \\${project_jar_path} \\${backup_path}/
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
} || {
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
}
jps | grep ${JOB_BASE_NAME}.jar && {
kill -9 \\$(ps -ef | grep ${JOB_BASE_NAME}.jar | grep -v grep | awk '{print \\$2}')
}
[ -d /data/logs/project/${JOB_BASE_NAME} ] || mkdir -p /data/logs/project/${JOB_BASE_NAME}/
cd /data/prog/project/${JOB_BASE_NAME}/
nohup /data/prog/jdk1.8.0_271/bin/java \
-server -Xms500m -Xmx500m \
-jar /data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar \
--server.port=\\${server_port} --spring.profiles.active=dev \
--spring.cloud.nacos.config.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.config.username=nacos \
--spring.cloud.nacos.config.password=nacos \
--spring.cloud.nacos.discovery.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.discovery.username=nacos \
--spring.cloud.nacos.discovery.password=nacos >> /data/logs/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}-console.log 2>&1 &
EOF
'''
script {
// 运行 Ansible 命令,并捕获退出码
def exitCode = sh(script: """
ansible ${PLATFORM}-${JOB_BASE_NAME} -m shell -a "mkdir -p /data/update/${JOB_BASE_NAME}"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m copy -a "src=target/${JOB_BASE_NAME}.jar dest=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m script -a "start_up.sh"
""", returnStatus: true)
// 根据退出码进行处理
if (exitCode != 0) {
echo "Ansible 部署出现异常,请运维人员检查~!"
// 你可以在这里添加更多的错误处理逻辑
currentBuild.result = 'FAILURE'
} else {
echo "部署成功."
}
}
}
}
}
post {
success {
echo 'Pipeline 执行成功!'
}
failure {
echo 'Pipeline 执行失败,请检查问题。'
}
}
}
钉钉告警
我们之前在freestyle的时候在开始部分设置了钉钉的机器人,可以自定义通知时机
当然在pipeline中也有相关的关键字,它就是post
关键字。
post
部分定义一个或多个steps,这些阶段根据流水线或阶段的完成情况而 运行(取决于流水线中 post
部分的位置). post
支持以下 触发时机: always
, changed
, failure
, success
, unstable
, 和 aborted
。这些条件块允许在 post
部分的步骤的执行取决于流水线或阶段的完成状态。
- always: 无论流水线或阶段的完成状态如何,都允许在
post
部分运行该步骤。 - changed: 只有当前流水线或阶段的完成状态与它之前的运行不同时,才允许在
post
部分运行该步骤。 - failure: 只有当前流水线或阶段的完成状态为"failure",才允许在
post
部分运行该步骤, 通常web UI是红色。 - success: 只有当前流水线或阶段的完成状态为"success",才允许在
post
部分运行该步骤, 通常web UI是蓝色或绿色。 - unstable: 只有当前流水线或阶段的完成状态为"unstable",才允许在
post
部分运行该步骤, 通常由于测试失败,代码违规等造成。通常web UI是黄色。 - aborted: 只有当前流水线或阶段的完成状态为"aborted",才允许在
post
部分运行该步骤, 通常由于流水线被手动的aborted。通常web UI是灰色。
post
可以出现在pipeline{}
代码块内,也可以出现在stage
代码块内。但是对于pipeline语法来说,post
并不是一个必须得代码块,你可以根据你的需要去使用它。
位于pipeline代码块内:
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
post {
always {
echo '无论哪种构建情形(成功失败)我都会执行'
}
}
}
位于stage代码块内:
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
post {
success {
echo '只有Example步骤运行成功后,我才会执行'
}
}
}
}
post {
always {
echo '无论哪种构建情形(成功失败)我都会执行'
}
}
}
添加post
post {
always {
dingtalk (
//指定钉钉机器人名称
robot: "dev",
type: 'MARKDOWN',
title: "${JOB_BASE_NAME} 构建${currentBuild.result}",
text: [
"## [${JOB_BASE_NAME} 构建${currentBuild.result}提醒](${BUILD_URL}console)",
"---",
"- 项目名称:${JOB_BASE_NAME} ",
"- 构建编号:${BUILD_NUMBER} ",
"- 构建分支:${BRANCH} ",
"- 构建人:${BUILD_USER} ",
"- 构建URL:${BUILD_URL} ",
"- 构建结果:${currentBuild.result} ",
"- 构建环境:${env.PLATFORM} ",
"- 构建持续时间:${currentBuild.duration/1000} "
]
)
}
}
完整如下
pipeline {
agent any
tools {
maven "maven3.3.3"
}
parameters {
listGitBranches(
branchFilter: '.*',
credentialsId: 'git-root',
defaultValue: '',
description: '请选择分支或标签',
listSize: '5',
name: 'BRANCH',
quickFilterEnabled: false,
remoteURL: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git',
selectedValue: 'NONE',
sortMode: 'NONE',
tagFilter: '*',
type: 'PT_BRANCH_TAG'
)
choice(
choices: ['dev', 'qa'],
description: '请选择要部署的环境',
name: 'PLATFORM'
)
}
stages {
stage('拉取代码') {
steps {
echo "拉取代码"
checkout scmGit(
branches: [[name: '${BRANCH}']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: 'http://gitlab.jiajia.top/java-ruoyi/ruoyi-auth.git']])
script {
def statusCode = sh returnStatus: true, script: 'pwd'
println statusCode
if (statusCode != 0) {
env.REASON = "命令执行出错!!!"
}
println "${env.REASON}"
}
}
}
stage('增加构建信息') {
steps {
script {
// 更新构建名称和描述
currentBuild.displayName = "${BUILD_NUMBER}-${JOB_NAME}"
currentBuild.description = "提交者: ${BUILD_USER} <br> 提交者ID:${BUILD_USER_ID} <br> 提交时间:${BUILD_TIMESTAMP} <br> 构建分支:${BRANCH}"
}
}
}
stage('编译打包') {
steps {
echo "编译打包"
script {
def statusCode = sh returnStatus: true, script: '#!/bin/sh -e\n mvn clean install'
if (statusCode != 0) {
error "构建出现错误!!!请开发在本地打包测试后再提交构建"
}
}
echo '检查构建产物是否存在'
echo '检查产物名称是否符合规范'
script {
if (fileExists("target/${JOB_BASE_NAME}.jar")) {
// 归档构建产物
archiveArtifacts artifacts: "target/${JOB_BASE_NAME}.jar", fingerprint: true, followSymlinks: false, onlyIfSuccessful: true
} else {
error "未找到构建成品target/${JOB_BASE_NAME}.jar,请研发检查项目pom.xml的输出路径以及包名是否规范"
}
}
echo '将制品纳入归档'
}
}
stage('代码扫描') {
steps {
echo "代码扫描"
}
}
stage('部署服务') {
steps {
echo "部署服务"
sh '''
tee > start_up.sh <<EOF
#!/bin/bash
declare -A server_port_set
server_port_set=( ["ruoyi-auth"]="8090" ["ruoyi-system"]="8091" ["ruoyi-gateway"]="8080" )
server_port=\\${server_port_set["${JOB_BASE_NAME}"]}
[ -d /data/prog/project/${JOB_BASE_NAME} ] || mkdir -p /data/prog/project/${JOB_BASE_NAME}
project_jar_path=/data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
update_jar_path=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar
[ -f \\${project_jar_path} ] && {
echo "检测到旧的jar包,开始替换"
backup_path=/data/backup/\\$(date +%F-%H-%M-%S)
mkdir -p \\${backup_path}
/bin/cp -f \\${project_jar_path} \\${backup_path}/
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
} || {
/bin/cp -f \\${update_jar_path} \\${project_jar_path}
}
jps | grep ${JOB_BASE_NAME}.jar && {
kill -9 \\$(ps -ef | grep ${JOB_BASE_NAME}.jar | grep -v grep | awk '{print \\$2}')
}
[ -d /data/logs/project/${JOB_BASE_NAME} ] || mkdir -p /data/logs/project/${JOB_BASE_NAME}/
cd /data/prog/project/${JOB_BASE_NAME}/
nohup /data/prog/jdk1.8.0_271/bin/java \
-server -Xms500m -Xmx500m \
-jar /data/prog/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar \
--server.port=\\${server_port} --spring.profiles.active=dev \
--spring.cloud.nacos.config.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.config.username=nacos \
--spring.cloud.nacos.config.password=nacos \
--spring.cloud.nacos.discovery.server-addr=192.168.1.91:8848 \
--spring.cloud.nacos.discovery.username=nacos \
--spring.cloud.nacos.discovery.password=nacos >> /data/logs/project/${JOB_BASE_NAME}/${JOB_BASE_NAME}-console.log 2>&1 &
EOF
'''
script {
// 运行 Ansible 命令,并捕获退出码
def exitCode = sh(script: """
ansible ${PLATFORM}-${JOB_BASE_NAME} -m shell -a "mkdir -p /data/update/${JOB_BASE_NAME}"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m copy -a "src=target/${JOB_BASE_NAME}.jar dest=/data/update/${JOB_BASE_NAME}/${JOB_BASE_NAME}.jar"
ansible ${PLATFORM}-${JOB_BASE_NAME} -m script -a "start_up.sh"
""", returnStatus: true)
// 根据退出码进行处理
if (exitCode != 0) {
echo "Ansible 部署出现异常,请运维人员检查~!"
// 你可以在这里添加更多的错误处理逻辑
currentBuild.result = 'FAILURE'
} else {
echo "部署成功."
}
}
}
}
}
post {
always {
dingtalk (
//指定钉钉机器人名称
robot: "dev",
type: 'MARKDOWN',
title: "${JOB_BASE_NAME} 构建${currentBuild.result}",
text: [
"## [${JOB_BASE_NAME} 构建${currentBuild.result}提醒](${BUILD_URL}console)",
"---",
"- 项目名称:${JOB_BASE_NAME} ",
"- 构建编号:${BUILD_NUMBER} ",
"- 构建分支:${BRANCH} ",
"- 构建人:${BUILD_USER} ",
"- 构建URL:${BUILD_URL} ",
"- 构建结果:${currentBuild.result} ",
"- 构建环境:${env.PLATFORM} ",
"- 构建时间:${BUILD_TIMESTAMP} ",
"- 构建持续时间:${currentBuild.duration/1000}秒"
]
)
}
}
}
标签:Pipeline,NAME,echo,JOB,BASE,构建,Jenkins,stage
From: https://www.cnblogs.com/Unstoppable9527/p/18418709