前言
本文提供了一份全面的Jenkins学习指南,旨在帮助你理解和掌握这一强大的持续集成/持续部署(CI/CD)工具。文章详细介绍了Jenkins的安装、配置过程,以及如何与Maven和Git等工具集成,帮助你从零开始搭建自己的CI/CD环境。
在学习过程中,你将逐步了解如何设置Jenkins、安装必要的插件,通过示例项目,最终实现应用的自动化部署。特别是通过Jenkins Pipeline,你将学会如何定义构建、部署等各个阶段。
快速使用:只看配置相关内容+最终版Pipeline+脚本解释即可
安装部署
Jenkins+Maven+Git
英文官网:https://www.jenkins.io
中文官网:https://www.jenkins.io/zh/
安装JDK
Linux检查是否安装了JDK
java -version
如果没找到就安装一个
查询所有可以安装版本
yum search java|grep jdk
下载安装JDK11(Jenkins支持一件安装插件的JDK版本最低是11,所以这里安装了11)
yum install -y java-11-openjdk
修改默认JDK版本
alternatives --config java
这里指定为11,避免后面一系列的问题
安装Jenkins
下载不了点这里
https://updates.jenkins.io/download/war/
下载Jenkins
下载2.387.3(目前最新插件最低支持的Jenkins版本是2.387.3)
运行Jenkins
/usr/lib/jvm/java-11-openjdk-11.0.23.0.9-2.el7_9.x86_64/bin/java -jar jenkins_2.387.3.war --httpPort=8080
访问页面
http://xxx.xxx.xx.xxx:8080/
填写管理员密码
这个密码存在于启动时的控制台界面上,也可以去下方路径的文件中查找
安装推荐的插件
Maven安装
下载
下载链接:https://maven.apache.org/download.cgi
上传到Linux
解压
mkdir -p /opt/maven-3.9.8
tar zxvf apache-maven-3.9.8-bin.tar.gz -C /opt/maven-3.9.8
配置镜像
<mirrors>
<!-- 优先使用阿里云镜像 -->
<mirror>
<id>aliyun</id>
<name>Aliyun Maven Mirror</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
<!-- 如果阿里云镜像不可用,再使用 Maven Central -->
<mirror>
<id>central</id>
<name>Maven Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
运行
/opt/maven-3.9.8/apache-maven-3.9.8/bin/mvn
查看Maven使用的JDK版本
/opt/maven-3.9.8/apache-maven-3.9.8/bin/mvn -v
Jenkins安装Maven插件
创建Demo项目
示例代码仓库:https://github.com/ChenJiahao0205/Jenkins-Learning
创建Jenkins任务
填写代码仓库地址
Linux安装GIT解决报错
yum install git
验证
配置Maven版本
配置刚才安装Maven的路径即可
保存完回到配置项目页面即可,忽略这个警告即可
设置POM位置
这里的POM指的是,项目中的POM位置
测试构建
构建&手动运行Jar包
构建
查看Jar包
安装JDK
yum install -y java-devel
sudo yum install -y java-11-openjdk-devel
手动运行项目
java -jar Jenkins-Learning-0.0.1-SNAPSHOT.jar
验证正常访问项目接口
自动化发布到服务器
安装插件
配置SSH服务器(待发布服务的服务器)
任务中配置SSH以及构建后操作
Transfer Set Source files
源文件路径
由构建日志可以得出构建后的Jar的存储目录
Remove prefix
去生成的远端目录中忽略源文件目录的哪些目录
Remote directory
保存的远端地址
Exec command
构建完成后执行的命令
Jar包保存位置
构建完成后执行的命令
nohup java -jar /root/jenkins/workspace/Jenkins-Learning*.jar &
查看构建结果
日志输出
查看项目是否启动
ps aux | grep java
SSH puslishers超时机制
命令优化
控制台执行下面命令的时候会卡顿
nohup java -jar /root/jenkins/workspace/Jenkins-Learning*.jar &
优化为
nohup java -jar /root/jenkins/workspace/Jenkins-Learning*.jar >mylog.log 2>&1 &
该命令可被替换为
nohup java -jar /root/jenkins/workspace/Jenkins-Learning*.jar &>mylog.log &
>覆盖写入
>>追加写入
2代表 标准错误输出
1代表 标准输出
2>&1 将正常输出和错误输出全部写入
替换配置
配置超时时间
构建前执行的命令
清理之前打包的Jar包、Kill正在执行的Jar包之类的操作
Pre Steps
编写shell脚本,批量执行操作
-
vi getPidAndKillShell.sh
-
#!/bin/bash echo "">/root/getPidAndKillShell.txt
-
-
修改权限
-
chmod 777 getPidAndKillShell.sh
-
-
添加配置
-
测试构建项目看能否正常执行脚本生成getPidAndKillShell.txt
构建前脚本开发
清理Jar包和杀死进程
修改getPidAndKillShell.sh内容
#!/bin/bash
# 删除历史数据
rm -rf jenkins
# 筛选正在执行的Jar包信息 并取出第二个字符(PID)
pid=`ps -ef | grep Jenkins-Learning | grep 'java -jar' | awk '{printf $2}'`
# 杀掉当前Jar包进程
kill -9 $pid
增加逻辑判断和参数传入
没找到PID时不进行KILL
#!/bin/bash
appname=$1
# 删除历史数据
rm -rf jenkins
# 筛选正在执行的Jar包信息 并取出第二个字符(PID)
pid=`ps -ef | grep $appname | grep 'java -jar' | awk '{printf $2}'`
# 如果PID为空(使用-z做空值判断),提示一下,否则执行KILL命令
if [ -z $pid ];
then
echo "$appname not start"
else
# 杀掉当前Jar包进程
kill -9 $pid
echo "$appname stoping......"
fi
运行./getPidAndKillShell.sh Jenkins-Learning
验证未有Jar包运行时
验证有Jar包运行时
优化脚本
#!/bin/bash
appname=$1
# 检查是否提供了应用名称参数
if [ -z "$appname" ]; then
echo "Error: Application name is required."
exit 1
fi
# 删除历史数据
rm -rf jenkins
# 筛选正在执行的Jar包信息 并取出第二个字符(PID)
pid=$(ps -ef | grep "$appname" | grep 'java -jar' | awk '{print $2}')
# 如果PID为空(使用-z做空值判断),提示一下,否则执行KILL命令
if [ -z "$pid" ]; then
echo "$appname not start"
else
# 杀掉当前Jar包进程
kill -9 $pid
echo "$appname stopping......"
check=$(ps -ef | grep -w "$pid" | grep java)
if [ -z "$check" ]; then
echo "$appname pid:$pid is stopped"
else
echo "$appname stop failed"
fi
fi
测试
Pre Steps增加参数
提交代码测试
旧版本
提交新版本代码
运行Jenkins
发现问题
两个Jar包都过来了
分析原因,Post Step进行源文件匹配时,匹配到了这两个文件,所以都传过来了
删除jenkins工作目录中旧的jar包
修改Pre Steps配置的脚本
getPidAndKillShell.sh
#!/bin/bash
appname=$1
# 检查是否提供了应用名称参数
if [ -z "$appname" ]; then
echo "Error: Application name is required."
exit 1
fi
# 筛选正在执行的Jar包信息 并取出第二个字符(PID)
pid=$(ps -ef | grep "$appname" | grep 'java -jar' | awk '{print $2}')
# 如果PID为空(使用-z做空值判断),提示一下,否则执行KILL命令
if [ -z "$pid" ]; then
echo "$appname not start"
else
# 杀掉当前Jar包进程
kill -9 $pid
echo "$appname stopping......"
check=$(ps -ef | grep -w "$pid" | grep java)
if [ -z "$check" ]; then
echo "$appname pid:$pid is stopped"
else
echo "$appname stop failed"
fi
fi
# 删除历史数据工作路径数据
rm -rf /root/.jenkins/workspace/learning-jenkins-demo-01/target/Jenkins-Learning*.jar
修改Post Steps配置
修改前
修改后
创建postStepsShell脚本
#!/bin/bash
# 找到最新的JAR包
latest_jar=$(ls -t /root/jenkins/workspace/Jenkins-Learning*.jar | head -n 1)
nohup java -jar "$latest_jar" mylog.log 2>&1 &
重新测试
目前正在运行版本号为03
现在新增加了04版本
使用Pipeline
安装插件
pipeline插件和SSH Pipeline Steps插件
创建流水线
创建任务
配置Pipeline
创建Pipeline脚本
pipeline {
agent any
environment {
WORKSPACE_DIR = "/root/.jenkins/workspace"
TARGET_DIR = "/root/jenkins/workspace"
JAR_NAME = ""
APP_NAME = "Jenkins-Learning"
REPO_URL = "https://github.com/ChenJiahao0205/Jenkins-Learning.git"
JENKINS_TASK_NAME = "learning-jenkins-demo-02-pipeline"
}
// 定义 Maven 工具,确保它在 Jenkins 全局工具配置中已定义
tools {
maven "maven-3.9.8"
}
stages {
stage('Checkout') {
steps {
script {
// 从 Git 仓库检出代码
checkout([$class: 'GitSCM',
branches: [[name: '*/master']], // 替换为你的分支名
userRemoteConfigs: [[url: "${REPO_URL}"]]
])
}
}
}
stage('Clean History Jar') {
steps {
script {
// 删除历史数据工作路径数据
sh "rm -rf ${WORKSPACE_DIR}/${JENKINS_TASK_NAME}/target/Jenkins-Learning*.jar"
}
}
}
stage('Build') {
steps {
script {
// 使用 Maven 构建项目
sh 'mvn clean package'
}
}
}
stage('Find Latest Jar') {
steps {
script {
// 获取最新的 Jar 包名称
JAR_NAME = sh(script: "ls -t ${WORKSPACE_DIR}/${JENKINS_TASK_NAME}/target/*.jar | head -n 1", returnStdout: true).trim()
echo "Latest Jar: ${JAR_NAME}"
}
}
}
stage('Move Jar to Target Directory') {
steps {
script {
// 将最新的 Jar 包移动到目标目录
sh "mv ${JAR_NAME} ${TARGET_DIR}/"
echo "Moved Jar to Target Directory: ${TARGET_DIR}"
// 使用 SSH 将最新的 Jar 包传输到目标服务器
// sshPublisher(publishers: [sshPublisherDesc(configName: 'my-server',
// transfers: [sshTransfer(sourceFiles: "${JAR_NAME}", remoteDirectory: "${TARGET_DIR}")],
// usePromotionTimestamp: false, alwaysPublishFromMaster: true, paramPublish: true, failOnError: true)])
}
}
}
stage('Stop Running Application') {
steps {
script {
// 停止正在运行的 Jar 包
sh '''
pid=$(ps -ef | grep "${APP_NAME}" | grep 'java -jar' | awk '{print $2}')
if [ -z "$pid" ]; then
echo "${APP_NAME} not start"
else
kill -9 $pid
echo "${APP_NAME} stopping......"
check=$(ps -ef | grep -w "$pid" | grep java)
if [ -z "$check" ]; then
echo "${APP_NAME} pid:$pid is stopped"
else
echo "${APP_NAME} stop failed"
fi
fi
'''
}
}
}
stage('Deploy Application') {
steps {
script {
// 获取 JAR 文件的名称
def jarName = sh(script: "basename ${JAR_NAME}", returnStdout: true).trim()
// 启动新的 Jar 包
sh "nohup java -jar ${TARGET_DIR}/${jarName} > ${TARGET_DIR}/mylog.log 2>&1 &"
echo "Started Application with Jar: ${TARGET_DIR}/${jarName}"
}
}
}
}
post {
always {
echo 'Pipeline completed.'
}
success {
echo 'Deployment succeeded.'
}
failure {
echo 'Deployment failed.'
}
}
}
脚本解释
-
Agent Any: 表示此 Pipeline 可以在任何可用的 Jenkins agent 上运行。
-
Environment: 定义了一些环境变量,比如工作目录和目标目录。
-
tools
- maven:配置使用maven,在全局工具配置,maven安装中进行配置
-
Stages: 定义了多个阶段。
- Clean History Jar: 清理历史Jar包
- Build: 构建项目,使用 Maven 作为示例。如果你的项目不是使用 Maven 构建的,请根据实际情况修改。
- Find Latest Jar: 使用 shell 命令查找生成的最新 Jar 包。
- Move Jar to Target Directory: 模拟使用 sshPublisher 插件将最新的 Jar 包传输到目标服务器。这里使用MV代替了
- Stop Running Application: 停止正在运行的Jar包
- Deploy Application: 在目标服务器上通过 SSH 执行部署命令,启动 Jar 包。
- Post Steps: 在 Pipeline 结束后执行的步骤。
-
Always: 无论 Pipeline 成功或失败,总是会执行这一步。
-
Success: 只有当 Pipeline 成功时,才会执行。
-
Failure: 只有当 Pipeline 失败时,才会执行。
执行流水线
报错
Started by user chenjiahao
org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException: script not yet approved for use
at org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval.using(ScriptApproval.java:668)
at org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition.create(CpsFlowDefinition.java:123)
at org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition.create(CpsFlowDefinition.java:69)
at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:312)
at hudson.model.ResourceController.execute(ResourceController.java:101)
at hudson.model.Executor.run(Executor.java:442)
Finished: FAILURE
解决方案
批准脚本运行即可
再次执行流水线
Java项目无法正常运行
现象
流水线虽然执行成功了,但是程序没有成功启动
通过查看jps发现,程序启动了一瞬间就结束了
[root@jh-centos7 workspace]# jps
8823 war
9945 Jps
[root@jh-centos7 workspace]# jps
8823 war
9979 Jenkins-Learning-0.0.4-SNAPSHOT.jar
9966 Jps
[root@jh-centos7 workspace]# jps
8823 war
9979 -- process information unavailable
9997 Jps
查看日志
发现执行的命令是
nohup java -jar /root/jenkins/workspace/Jenkins-Learning-0.0.4-SNAPSHOT.jar
而非配置中的
"nohup java -jar ${TARGET_DIR}/${jarName} > ${TARGET_DIR}/mylog.log 2>&1 &"
解决:在Pipeline中需要额外配置
参考文章
https://blog.csdn.net/weixin_43767360/article/details/131980955
替换执行Java程序的命令为
JENKINS_NODE_COOKIE=dontKillMe nohup java -jar ${TARGET_DIR}/${jarName} > ${TARGET_DIR}/mylog.log 2>&1 &
最终版Pipeline
pipeline {
agent any
environment {
WORKSPACE_DIR = "/root/.jenkins/workspace"
TARGET_DIR = "/root/jenkins/workspace"
JAR_NAME = ""
APP_NAME = "Jenkins-Learning"
REPO_URL = "https://github.com/ChenJiahao0205/Jenkins-Learning.git"
JENKINS_TASK_NAME = "learning-jenkins-demo-02-pipeline"
}
// 定义 Maven 工具,确保它在 Jenkins 全局工具配置中已定义
tools {
maven "maven-3.9.8"
}
stages {
stage('Checkout') {
steps {
script {
// 从 Git 仓库检出代码
checkout([$class: 'GitSCM',
branches: [[name: '*/master']], // 替换为你的分支名
userRemoteConfigs: [[url: "${REPO_URL}"]]
])
}
}
}
stage('Clean History Jar') {
steps {
script {
// 删除历史数据工作路径数据
sh "rm -rf ${WORKSPACE_DIR}/${JENKINS_TASK_NAME}/target/Jenkins-Learning*.jar"
}
}
}
stage('Build') {
steps {
script {
// 使用 Maven 构建项目
sh 'mvn clean package'
}
}
}
stage('Find Latest Jar') {
steps {
script {
// 获取最新的 Jar 包名称
JAR_NAME = sh(script: "ls -t ${WORKSPACE_DIR}/${JENKINS_TASK_NAME}/target/*.jar | head -n 1", returnStdout: true).trim()
echo "Latest Jar: ${JAR_NAME}"
}
}
}
stage('Move Jar to Target Directory') {
steps {
script {
// 将最新的 Jar 包移动到目标目录
sh "mv ${JAR_NAME} ${TARGET_DIR}/"
echo "Moved Jar to Target Directory: ${TARGET_DIR}"
// 使用 SSH 将最新的 Jar 包传输到目标服务器
// sshPublisher(publishers: [sshPublisherDesc(configName: 'my-server',
// transfers: [sshTransfer(sourceFiles: "${JAR_NAME}", remoteDirectory: "${TARGET_DIR}")],
// usePromotionTimestamp: false, alwaysPublishFromMaster: true, paramPublish: true, failOnError: true)])
}
}
}
stage('Stop Running Application') {
steps {
script {
// Define variables
def pid = sh(script: "pgrep -f 'java -jar ${TARGET_DIR}/${APP_NAME}'", returnStdout: true).trim()
// Check if the PID exists
if (pid) {
echo "${APP_NAME} is running with PID: $pid. Stopping it..."
sh "kill -9 $pid"
// Verify if the application has stopped
def check = sh(script: "ps -p $pid", returnStatus: true)
if (check != 0) {
echo "${APP_NAME} pid:$pid has stopped successfully."
} else {
echo "${APP_NAME} stop failed. Process with PID $pid is still running."
}
} else {
echo "${APP_NAME} is not running."
}
}
}
}
stage('Deploy Application') {
steps {
script {
// 获取 JAR 文件的名称
def jarName = sh(script: "basename ${JAR_NAME}", returnStdout: true).trim()
// 启动新的 Jar 包
sh "JENKINS_NODE_COOKIE=dontKillMe nohup java -jar ${TARGET_DIR}/${jarName} > ${TARGET_DIR}/mylog.log 2>&1 &"
echo "Started Application with Jar: ${TARGET_DIR}/${jarName}"
}
}
}
}
post {
always {
echo 'Pipeline completed.'
}
success {
echo 'Deployment succeeded.'
}
failure {
echo 'Deployment failed.'
}
}
}
脚本解释
-
Agent Any: 表示此 Pipeline 可以在任何可用的 Jenkins agent 上运行。
-
Environment: 定义了一些环境变量,比如工作目录和目标目录。
-
tools
- maven:配置使用maven,在全局工具配置,maven安装中进行配置
-
Stages: 定义了多个阶段。
- Clean History Jar: 清理历史Jar包
- Build: 构建项目,使用 Maven 作为示例。如果你的项目不是使用 Maven 构建的,请根据实际情况修改。
- Find Latest Jar: 使用 shell 命令查找生成的最新 Jar 包。
- Move Jar to Target Directory: 模拟使用 sshPublisher 插件将最新的 Jar 包传输到目标服务器。这里使用MV代替了
- Stop Running Application: 停止正在运行的Jar包
- Deploy Application: 在目标服务器上通过 SSH 执行部署命令,启动 Jar 包。
- Post Steps: 在 Pipeline 结束后执行的步骤。
-
Always: 无论 Pipeline 成功或失败,总是会执行这一步。
-
Success: 只有当 Pipeline 成功时,才会执行。
-
Failure: 只有当 Pipeline 失败时,才会执行。
本文示例项目地址
https://github.com/ChenJiahao0205/Jenkins-Learning.git
最后
本文部分内容结合B站视频学习总结得到,参考视频:https://www.bilibili.com/video/BV1bS4y1471A
感谢大家看到这里,文章如有不足,欢迎大家指出;彦祖点个赞吧彦祖点个赞吧彦祖点个赞吧,欢迎关注程序员五条!