Jenkins 在项目中的使用理解为:在 Jenkins 服务器将项目打包备份,然后将包发送到远程应用服务器,然后脚本启动包,完事!
一、Jenkins 安装和配置
1. Jenkins 安装
环境:centos8
参考:https://www.jenkins.io/doc/book/installing/linux/#red-hat-centos
sudo install wget
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo yum upgrade
sudo yum install jenkins java-1.8.0-openjdk-devel
sudo systemctl daemon-reload
sudo systemctl start jenkins
sudo systemctl status jenkins
# 默认8080端口,开启端口
sudo firewall-cmd --zone=public --permanent --add-port 8080/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --query-port=8080/tcp
# 安装 maven 和 git
sudo yum install git
sudo yum install maven
默认配置文件在/etc/sysconfig/jenkins
,可以修改端口等信息
2. 启动和配置
http://localhost:8080
获取管理员密码
$ sudo cat /var/lib/jenkins/secrets/initialAdminPassword
2dd8c576a3634e2a93f0ede7c13132bf
默认安装推荐插件,安装可能会失败,重试即可。
创建一个用户
3. 安装配置插件
3.1 Publish Over SSH
将 build 好的文件上传到远程 linux 的指定目录下,在插件管理中搜索并安装改插件
3.2 Config File Provider(非必须)
maven 设置 settings.xml 使用,安装成功后配置
在 Content 中配置自己的仓库地址即可
也可以修改我们安装的 maven 的默认的 settings.xml 文件
$ whereis maven
maven: /etc/maven /usr/share/maven
$ cd /usr/share/maven/conf/
$ ls
logging settings.xml
3.3 Maven Integration(非必须)
maven 构建工具插件,可以在新建项目的时候选择构建一个maven项目
,默认是没有该选项的
3.4 Build Name and Description Setter
自定义 Build History 名称,方便不同模块构建的区分
4. 全局工具配置
配置 maven 和 jdk,剩余默认即可
注意:
如果 maven 设置 settings.xml 修改的是安装 maven 的 settings.xml,则Maven 配置
默认即可
获取 maven 路径
$ whereis maven
maven: /etc/maven /usr/share/maven
5. 系统配置
拉到最后,点击新增SSH Servers
添加远程服务器
二、构建可选模块可回滚项目
参考:https://www.jianshu.com/p/af5fecaa8357
mall 项目主要有以下模块:
mall-admin
mall-portal
mall-pay
1. 新建任务,构建一个自由风格的软件项目
2. 参数化构建过程
2.1 模块选择
新建选项参数:在构建项目时选择模块(构建单体 springboot 应用时不需要)
2.2 发版还是回滚
新建选项参数:在构建项目时选择是发版还是回滚
Deploy【发版】
Rollback【回滚】
2.3 回滚版本号
新建字符参数:回滚时指定版本号
2.4 配置文件选择(非必须)
可以通过以下指定激活的配置文件
spring:
profiles:
active: dev
也可以通过启动覆盖指定的配置文件
nohup java -jar -Dspring.profiles.active=${ACTIVE} demo.jar > demo.log 2>&1 &
新建字符参数:指定激活的配置文件
3. 从 git 克隆代码
点击添加账号密码
4. 自定义 Build History 名称
效果如下:
5. 增加构建步骤
5.1 构建 maven 项目,配置打包参数
clean package -pl ${MODULE} -am -Ppre -Dmaven.test.skip=true
- ${MODULE}:在构建选择模块后可获得模块名,从而决定构建哪个模块
- -pl:指定只构建单独的模块而不是所有模块
- -am:构建模块所需要的项目,如公共模块
- -P:选择激活的配置文件
- -Dmaven.test.skip=true:跳过测试
5.2 构建执行 shell,项目打包备份、回滚和清理备份
# 保留文件数
RESERVED_NUM=5
# 模块文件夹
FILE_DIR=${WORKSPACE}/${MODULE}
# 备份文件夹
FILE_BAK_DIR=${FILE_DIR}/bak
DATE=$(date "+%Y%m%d-%H%M%S")
case ${STATUS} in
Deploy)
echo "发版"
# 创建每次要备份的目录
path="${FILE_BAK_DIR}/${BUILD_NUMBER}"
if [ ! -d ${path} ]
then
mkdir -p ${path}
fi
# 将打包好的war包备份到相应目录,覆盖已存在的目标
cp -f ${FILE_DIR}/target/*.jar ${path}
# 进入备份目录
cd ${FILE_BAK_DIR}
# 当前有几个文件夹,即几个备份,删除多余备份
fileNum=$(ls -l | grep '^d' | wc -l)
while [ ${fileNum} -gt ${RESERVED_NUM} ]
do
# 获取最旧的那个备份文件夹
oldFile=$(ls -rt | head -1)
echo ${DATE} "删除备份文件:"${oldFile}
rm -rf ${FILE_BAK_DIR}/${oldFile}
let "fileNum--"
done
echo "备份完成"
;;
Rollback)
echo "回滚"
echo "VERSION:${VERSION}"
# 如果文件夹不存在或文件夹太小,则判断备份文件不存在,直接返回
if [ ! -d ${FILE_BAK_DIR}/${VERSION} ] || [ $(du -s ${FILE_BAK_DIR}/${VERSION} | awk '{print $1}') -lt 100 ]
then
echo "备份文件不存在"
# 抛出异常,让 Jenkins 构建失败
set -e
# 退出
exit 1
fi
# 进入备份目录
cd ${FILE_BAK_DIR}/${VERSION}
# 将备份拷贝到程序打包目录中,并覆盖之前的war包
cp -f *.jar ${FILE_DIR}/target/
;;
*)
set -e
exit 1
;;
esac
脚本执行失败让 Jenkins 构建失败
参考:jenkins ssh脚本出现错误后怎么才能让jenkins构建失败
注意: 存在一个问题,就是该步骤在远程的SSH Server
执行时,不会构建失败,只会显示Finished: UNSTABLE
5.3 构建执行 shell,项目本地打包执行(不在本地启动应用可跳过)
创建 Jenkins 家目录,否则 shell 没有执行权限
cd /home
sudo mkdir jenkins
sudo chown -R jenkins:jenkins jenkins/
添加项目启动命令
export JAVA_HOME PATH CLASSPATH
JAVA_HOME=/opt/jdk1.8.0_281
PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH
BUILD_ID=dontKillMe
# 模块文件夹
JAR_DIR=${WORKSPACE}/${MODULE}/target
FILE_DIR=/home/jenkins/apps/
JARFILE=${MODULE}-1.0-SNAPSHOT.jar
# 设置超时时间,如果达到超时时间项目还未启动则退出
TIMEOUT=60
# 根据模块使用响应端口号
PORT=0
if [ "mall-admin" == ${MODULE} ]
then
PORT=31001
elif [ "mall-portal" == ${MODULE} ]
then
PORT=8085
elif [ "mall-pay" == ${MODULE} ]
then
PORT=8086
else
echo "模块不存在"
set -e
exit 1
fi
if [ ! -d ${FILE_DIR} ]
then
mkdir -p ${FILE_DIR}
fi
# 复制编译好的包
cp -f ${JAR_DIR}/${JARFILE} ${FILE_DIR}
cd ${FILE_DIR}
PROCESS_ID=$(ps -ef | grep ${JARFILE} | grep -v grep | awk '{print $2}')
# 如果该项目正在运行,就杀死项目进程
if [ ! -z "${PROCESS_ID}" ]
then
echo "停止服务"
kill -9 ${PROCESS_ID}
else
echo "服务未启动"
fi
nohup java -jar ${JARFILE} > ${MODULE}.log 2>&1 &
# 如果该项目还未启动,则等启动后再执行下个发布任务,判断项目是否启动要用端口号占用,不能用进程,因为在项目启动的瞬间进程就有了
retryTime=0
while true
do
RESTART_PROCESS_ID=$(lsof -i:${PORT} | awk '{if (NR>1){print $2}}')
if [ -z "${RESTART_PROCESS_ID}" ]
then
# 如果超时还未启动则退出
if [ ${retryTime} -gt ${TIMEOUT} ]
then
echo "启动超时"
set -e
exit 1
fi
retryTime=$((${retryTime} + 3))
sleep 3
echo "服务启动中"
else
echo "服务启动成功"
break
fi
done
注意: BUILD_ID=dontKillMe
,Jenkins 默认会在构建完成后杀掉构建过程中 shell 命令触发的衍生进程。Jenkins 根据 BUILD_ID 识别某个进程是否为构建过程的衍生进程,修改 BUILD_ID 则此进程能在后台保留运行,否则项目启动成功后就被杀死了。
6. 增加构建后操作
构建后将包发到远程服务器并执行启动操作
注意: 如果是微服务,那么系统服务可能存在于多台服务器上,为了避免所有服务器同时启动导致的服务不可用,要等服务发布成功后再发布下一个服务,具体表现为,启动服务后轮询服务端口号,当端口号被占用后即可理解为服务启动成功,然后再继续下一个发布任务。
Source files:打包源文件路径
Remove prefix:${MODULE}/target
Remote directory:远程服务器代码存放路径,相对路径,前缀是新增SSH Server
时设置的Remote Directory
Exec command:远程服务器执行脚本
export JAVA_HOME PATH CLASSPATH
JAVA_HOME=/opt/jdk1.8.0_281
PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH
FILE_DIR=/home/jl/apps/
JARFILE=${MODULE}-1.0-SNAPSHOT.jar
# 设置超时时间,如果达到超时时间项目还未启动则退出
TIMEOUT=60
# 根据模块使用响应端口号
PORT=0
if [ "mall-admin" == ${MODULE} ]
then
PORT=31001
elif [ "mall-portal" == ${MODULE} ]
then
PORT=8085
elif [ "mall-pay" == ${MODULE} ]
then
PORT=8086
else
echo "模块不存在"
set -e
exit 1
fi
if [ ! -d ${FILE_DIR} ]
then
mkdir -p ${FILE_DIR}
fi
cd ${FILE_DIR}
PROCESS_ID=$(ps -ef | grep ${JARFILE} | grep -v grep | awk '{print $2}')
# 如果该项目正在运行,就杀死项目进程
if [ ! -z "${PROCESS_ID}" ]
then
echo "停止服务"
kill -9 ${PROCESS_ID}
else
echo "服务未启动"
fi
nohup java -jar ${JARFILE} > ${MODULE}.log 2>&1 &
# 如果该项目还未启动,则等启动后再执行下个发布任务,判断项目是否启动要用端口号占用,不能用进程,因为在项目启动的瞬间进程就有了
retryTime=0
while true
do
RESTART_PROCESS_ID=$(lsof -i:${PORT} | awk '{if (NR>1){print $2}}')
if [ -z "${RESTART_PROCESS_ID}" ]
then
# 如果超时还未启动则退出
if [ ${retryTime} -gt ${TIMEOUT} ]
then
echo "启动超时"
set -e
exit 1
fi
retryTime=$((${retryTime} + 3))
sleep 3
echo "服务启动中"
else
echo "服务启动成功"
break
fi
done
注意:nohup java -jar ${JARFILE} > ${MODULE}.log &
,默认会报超时错误
这是因为 Jenkins 默认会在构建完成后杀掉构建过程中 shell 命令触发的衍生进程。Jenkins 根据 BUILD_ID 识别某个进程是否为构建过程的衍生进程,而且远程脚本执行环境为非 tty(终端)。
解决方法:
a. nohup java -jar ${JARFILE} > ${MODULE}.log 2>&1 &
这样确实可以解决问题,但是原理不明
参考:解决linux环境下nohup: redirecting stderr to stdout问题
b. 在 SSH Server 的高级中勾选Exec in pty
,让脚本在 pty(虚拟终端)中执行,
参考:Jenkins 构建后执行 nohup 脚本,前台不退出解决
c. 修改 BUILD_ID,Jenkins 就无法识别是否为衍生进程,则此进程能在后台保留运行;
BUILD_ID=dontKillMe
nohup java -jar ${JARFILE} > ${MODULE}.log &