中文官网:Jenkins
参考书籍:《Jenkins 2.x实践指南》 翟志军
构建伟大,无所不能
Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。
在软件工程领域,生产力三要素又分别指的是什么呢?
笑话一则
程序员笑话一则:程序员在椅子上打斗,经理叫他们回去,其中一位说:正在编译呢!经理回答:哦,那你们继续。
参考文章:值得推荐的13个 Jenkins 替代方案
总结一句话,适合自己的才是最好的
这里采用Jar安装,安装完毕之后再设置中设置开机自启就行了,当然还有其他方式主流的是Linux上采用Docker安装
这里的插件可以自己选择安装,在进入系统之前可以默认选择给你推荐的的插件
主界面
系统环境变量,在我们的编码中可能会存在编码问题这时候就需要设置系统编码,否则可能会乱码
钉钉的机器人配置参考网上的文章吧,有很多自行百度吧,这里主要是预警或者消息配置,当然也可以邮件通知
新建项目信息
这里我们需要记到是Svn地址或者Git地址,然后添加密码凭证
注意这个文件的名称我们需要再Svn仓库或者在Git仓库获取到该脚本文件
好了,我们的项目配置就完毕,接下里我们在项目中配置脚本文件,下面我们来看那可能配置文件的编写
Idea 配置
编写Jenkins文件,我阿里梳理一下Jenkins脚本文件中作的事情
stage('Build') {
steps {
echo 'begin build'
bat 'mvn clean install'
}
}
stage('begin upload') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'ComPany Server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '.', remoteDirectorySDF: false, removePrefix: 'target', sourceFiles: 'target/FeedbackSystem-0.0.1-SNAPSHOT.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('begin run') {
steps {
script {
def currentDate = new Date().format('yyyy-MM-dd-HH-mm-ss')
def remoteBackupsFilePath = "/jar/feedback/backups/FeedbackSystem-0.0.1-SNAPSHOT_${currentDate}.jar"
def logFilePath = "/jar/feedback/logs/feedback_${currentDate}.log"
def remoteFilePath = "/jar/feedback/FeedbackSystem-0.0.1-SNAPSHOT.jar"
sshCommand remote: remote, command: '''
if netstat -tln | grep ":9020\\s" &> /dev/null; then
process_id=$(lsof -t -i:9020)
kill ${process_id}
fi
'''
sshCommand remote: remote, command: "cp ${remoteFilePath} ${remoteBackupsFilePath}"
sshCommand remote: remote, command: "nohup /usr/java/jdk1.8.0_131/bin/java -jar ${remoteFilePath} > ${logFilePath} 2>&1 &"
}
}
}
post {
success {
echo 'success'
wrap([$class: 'BuildUser']) {
dingtalk(
robot: 'FeedBackSystem',
type: 'MARKDOWN',
title: "success: ${JOB_NAME}",
text: ["### 项目信息",
'---',
"- 项目名称: XXXXXXX",
'---',
"### 构建信息",
'---',
"- 构建编号: ${BUILD_ID}",
"- 构建状态: **${currentBuild.result}**",
'---',
"### 执行人",
'---',
"- ${BUILD_USER}",
"",
'---',
"### 持续时间",
'---',
"- ${currentBuild.durationString}",
"",
'---'
]
)
}
}
failure {
echo 'failure'
wrap([$class: 'BuildUser']) {
dingtalk(
robot: 'FeedBackSystem',
type: 'MARKDOWN',
title: "failure: ${JOB_NAME}",
text: ["### 项目信息",
'---',
"- 项目主体: XXXXXXX",
'---',
"### 构建信息",
'---',
"- 构建状态: **${currentBuild.result}**",
'---',
"### 执行人",
'---',
"- ${BUILD_USER}",
"",
'---',
"### 持续时间",
'---',
"- ${currentBuild.durationString}",
"",
'---'
]
)
}
}
}
完整案例
def remote = [:]
remote.name = 'saltm'
remote.host = ''
remote.user = ''
remote.password = ''
remote.allowAnyHosts = true
pipeline {
agent any
stages {
stage('Maven') {
steps {
echo 'begin Maven'
bat 'mvn -version'
}
}
stage('Build') {
steps {
echo 'begin build'
bat 'mvn clean install'
}
}
stage('begin upload') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'ComPany Server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '.', remoteDirectorySDF: false, removePrefix: 'target', sourceFiles: 'target/FeedbackSystem-0.0.1-SNAPSHOT.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('begin run') {
steps {
script {
def currentDate = new Date().format('yyyy-MM-dd-HH-mm-ss')
def remoteBackupsFilePath = "/jar/feedback/backups/FeedbackSystem-0.0.1-SNAPSHOT_${currentDate}.jar"
def logFilePath = "/jar/feedback/logs/feedback_${currentDate}.log"
def remoteFilePath = "/jar/feedback/FeedbackSystem-0.0.1-SNAPSHOT.jar"
sshCommand remote: remote, command: '''
if netstat -tln | grep ":9020\\s" &> /dev/null; then
process_id=$(lsof -t -i:9020)
kill ${process_id}
fi
'''
sshCommand remote: remote, command: "cp ${remoteFilePath} ${remoteBackupsFilePath}"
sshCommand remote: remote, command: "nohup /usr/java/jdk1.8.0_131/bin/java -jar ${remoteFilePath} > ${logFilePath} 2>&1 &"
}
}
}
}
post {
success {
echo 'success'
wrap([$class: 'BuildUser']) {
dingtalk(
robot: 'FeedBackSystem',
type: 'MARKDOWN',
title: "success: ${JOB_NAME}",
text: ["### 项目信息",
'---',
"- 项目名称: XXXXXX",
'---',
"### 构建信息",
'---',
"- 构建编号: ${BUILD_ID}",
"- 构建状态: **${currentBuild.result}**",
'---',
"### 执行人",
'---',
"- ${BUILD_USER}",
"",
'---',
"### 持续时间",
'---',
"- ${currentBuild.durationString}",
"",
'---'
]
)
}
}
failure {
echo 'failure'
wrap([$class: 'BuildUser']) {
dingtalk(
robot: 'FeedBackSystem',
type: 'MARKDOWN',
title: "failure: ${JOB_NAME}",
text: ["### 项目信息",
'---',
"- 项目主体: XXXXXX",
'---',
"### 构建信息",
'---',
"- 构建状态: **${currentBuild.result}**",
'---',
"### 执行人",
'---',
"- ${BUILD_USER}",
"",
'---',
"### 持续时间",
'---',
"- ${currentBuild.durationString}",
"",
'---'
]
)
}
}
}
}
前面的新建项目的步骤我就不多说了,与上面一样,我们来看看脚本文件的编写
def remote = [:]
remote.name = 'saltm'
remote.host = ''
remote.user = ''
remote.password = ''
remote.allowAnyHosts = true
pipeline {
agent any
stages {
stage('Npm Install') {
steps {
bat 'node -v'
bat 'npm install'
}
}
stage('Npm Build') {
steps {
bat 'npm run build:prod'
}
}
stage ("Linux Clear Dist") {
steps {
script {
sshCommand remote: remote, command: 'rm -rf /jar/html/dist/'
}
}
}
stage ("Upload Dist") {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'FeedBackWeb', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'dist', remoteDirectorySDF: false, removePrefix: 'dist', sourceFiles: 'dist/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
}
post {
success {
echo 'success'
wrap([$class: 'BuildUser']) {
dingtalk(
robot: 'FeedBackSystem',
type: 'MARKDOWN',
title: "success: ${JOB_NAME}",
text: ["### 项目信息",
'---',
"- 项目名称: xxxxxx",
'---',
"### 构建信息",
'---',
"- 构建编号: ${BUILD_ID}",
"- 构建状态: **${currentBuild.result}**",
'---',
"### 执行人",
'---',
"- ${BUILD_USER}",
"",
'---',
"### 持续时间",
'---',
"- ${currentBuild.durationString}",
"",
'---'
]
)
}
}
failure {
echo 'failure'
wrap([$class: 'BuildUser']) {
dingtalk(
robot: 'FeedBackSystem',
type: 'MARKDOWN',
title: "failure: ${JOB_NAME}",
text: ["### 项目信息",
'---',
"- 项目主体: xxxxx",
'---',
"### 构建信息",
'---',
"- 构建状态: **${currentBuild.result}**",
'---',
"### 执行人",
'---',
"- ${BUILD_USER}",
"",
'---',
"### 持续时间",
'---',
"- ${currentBuild.durationString}",
"",
'---'
]
)
}
}
}
}
当然上面只是一种方式,实现的方式还有多种,下面我们来介绍一些基本的概念
参考网站:初探Groovy——无缝兼容Java的脚本语言
Jenkins pipeline其实就是基于Groovy语言实现的一种DSL(领域特定语言),用于描述整条流水线是如何进行的,流水线的内容包括执行编译、打包、测试、输出测试报告等步骤。
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package' // 使用 Maven 构建项目
}
}
stage('Test') {
steps {
sh 'mvn test' // 运行单元测试
}
}
stage('Deploy') {
steps {
sh 'mvn deploy' // 部署构件到远程仓库或服务器
}
}
}
}
post部分包含的是在整个pipeline或阶段完成后一些附加的步骤,post部分是可选的,所以并不包含在pipeline最简结构中。但这并不代表它作用不大,根据pipeline或阶段的完成状态,post部分分成多种条件块,包括:
pipeline {
agent any
stages {
// 定义流水线的阶段
}
post {
success {
// 当流水线成功完成时执行的操作
// 例如发送邮件通知或触发其他流水线
}
failure {
// 当流水线失败时执行的操作
// 例如发送错误报告或执行回滚操作
}
always {
// 无论流水线成功还是失败,都会执行的操作
// 例如清理临时文件或更新报告
}
}
}
在使用指令时,需要注意的是每个指令都有自己的“作用域”。如果指令使用的位置不正确,Jenkins将会报错。
环境变量可以被看作是pipeline与Jenkins交互的媒介,比如,可以在pipeline中通过BUILD_NUMBER变量知道构建任务的当前构建次数。环境变量可以分为Jenkins内置变量和自定义变量,比如在上面我们发送邮件的时候就用到了jenkins的环境变量
自动化是指pipeline按照一定的规则自动执行,而这些规则被称为pipeline触发条件。
时间触发
时间触发是指定义一个时间,时间到了就触发pipeline执行。在Jenkins pipeline中使用trigger指令来定义时间触发。tigger指令只能被定义在pipeline块下,Jenkins内置支持cron、pollSCM,upstream三种方式
Jenkins trigger cron语法
轮询代码仓库:pollSCM