文章目录
前言
本文所使用的软件及版本如下:
Gitlab:14.6.1;Gitlab-Runner:16.3.1
本文使用Gitlab CI/CD + Gitlab Runner实现自动打包部署springboot项目的功能
使用自动CI/CD可以减轻维护的负担,也可以避免人员操作失误等问题
- Gitlab与Gitlab-Runner均使用Docker部署,Runner也使用Docker为执行者(executor)
- 本文中Runner仅负责打包并输出至指定目录,springboot项目由定时脚本进行备份&部署
- 本文中包含笔者的CI配置、备份&部署的定时脚本
- 有关Gitlab的部署,本文不作赘述
一、Gitlab Runner部署
1、获取Runner注册令牌
使用Gitlab的管理员账号(默认为root)按下图顺序打开Gitlab-Runner管理界面
复制Runner的注册令牌,在后续部署Runner时会用到
2、注册Runner
使用以下命令拉取Runner镜像,注意:本文使用的不是官方镜像
docker pull bitnami/gitlab-runner
使用以下命令创建并运行Runner
Runner注册时会生成config.toml至/etc/gitlab-runner/config.toml,而实际运行时使用的是/home/gitlab-runner/.gitlab-runner/config.toml,所以将其映射为同一文件方便修改
-d后台运行,-name命名为[Gitlab_Runner],--restart always自动启动,-v挂载宿主机目录
[宿主机目录]根据实际情况修改,docker.sock路径一般不需要修改
docker run -d --name Gitlab_Runner --restart always \
-v [宿主机目录]/.gitlab-runner/config.toml:/etc/gitlab-runner/config.toml \
-v [宿主机目录]:/home/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
bitnami/gitlab-runner:latest
接下来以root进入容器内部进行操作([Gitlab_Runner]需替换为实际容器名)
docker exec -it -u root Gitlab_Runner bash
如下图所示,输入命令开始注册Runner:
因笔者Gitlab与Runner位于同一Docker网络下,所以此处Gitlab地址为内网地址,请根据实际情况修改
三个可以为空的配置,可以随时在Gitlab中修改
gitlab-runner register
3、配置Runner
Runner注册后,查看宿主机内对应文件如下:
docker in docker挂载路径需要根据实际情况修改,语法与docker指令一致:宿主机路径:容器内路径
请注意,此处的前者路径为宿主机的路径,而不是Runner容器的路径
在[runners.docker]下,添加:pull_policy = “if-not-present”,可以使Runner运行时,仅当镜像不存在时进行拉取,如下:
pull_policy = "if-not-present" # 默认always,可选: always, if-not-present, never
推荐将maven及其仓库路径映射出来,这样可以复用本地仓库,配置如下:
volumes = ["/cache", "[maven路径]:/root/.m2", "[jar包存放路径]:/app/build"]
# 注:maven仓库路径为:[maven路径]/repository
笔者配置了多种缓存,如下:
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
重启Runner容器后,在Gitlab内可以看到刚刚注册的Runner
至此,Gitlab Runner已部署、配置完成。
二、配置GitLab CI
如下图,进入CI配置文件编辑界面,随后修改预置的CI配置文件:
Gitlab已提供大量模板供参考,包含maven;官方文档中也有详细的语法说明。因此本文不作赘述,仅介绍笔者CI配置思路
因实际需求考虑,笔者没有使用Runner将项目封装为Docker镜像运行,而是仅输出jar包。备份、部署运行等动作由定时任务执行
所以CI配置仅执行mvn package,并将jar包移动至指定目录,因此仅需要build阶段
笔者CI配置示例如下:
# 脚本变量
variables:
# 项目端口号
PORT_NUM: "[端口号]"
# 构建阶段打印的信息
BUILD_PREFIX_INFO: "$CI_COMMIT_BRANCH 构建开始\n构建创建者:$GITLAB_USER_NAME\n分支提交者:$CI_COMMIT_AUTHOR\n打包开始时间:$CI_PIPELINE_CREATED_AT"
BUILD_SUFFIX_INFO: "$CI_COMMIT_BRANCH 构建结束"
# 项目文件根目录
# Runner容器中的"[jar包存放路径]:/app/build"],指定了/app/build映射至宿主机
BASE_PATH: "/app/build/$CI_PROJECT_NAMESPACE/$CI_PROJECT_TITLE"
# 新包路径
PACKAGE_PATH: "$BASE_PATH/new"
# 新包名:项目名-端口号.jar
PACKAGE_NAME: "$CI_PROJECT_TITLE-$PORT_NUM.jar"
# 备份路径
BACKUP_PACKAGE_PATH: "$BASE_PATH/backup"
# maven打包参数
MAVEN_PACKAGE_OPTS: "-Djansi.passthrough=true -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8
-DskipTests=true -Dmaven.test.skip=true"
# 构建阶段,打包项目并放至对应路径下,由服务器自动部署脚本进行备份与部署
# jar包命名规则:项目名-端口号.jar
# 最终产物文件结构:
# / 项目文件根目录,也即/app/build/
# ╚═══╦══群组或用户名/
# ╚═══╦══项目名/
# ╠═══╦══new/
# ║ ╚══════new.jar 输出的jar包
# ╠═══╦══backup/
# ║ ╚══════backup.jar
# ╚══════old.jar
build: # 阶段名,可以任意修改
stage: build
# 仅master分支执行
only:
- master
before_script:
- echo -e "$BUILD_PREFIX_INFO"
script:
- mvn $MAVEN_PACKAGE_OPTS clean package
- if [ ! -d $PACKAGE_PATH ];
then echo -e "创建项目新包路径"; mkdir -p $PACKAGE_PATH;
fi
- if [ ! -d $BACKUP_PACKAGE_PATH ];
then echo -e "创建项目备份路径"; mkdir -p $BACKUP_PACKAGE_PATH;
fi
- if [ -f target/*.jar ];
then cp target/*.jar $PACKAGE_PATH/$PACKAGE_NAME; # 输出jar包至[jar包存放路径]
else echo -e "未找到项目新包,不进行迁移"; exit 1;
fi
# 若不需要将jar包输出至Gitlab流水线产物中,则不需要以下步骤
- cp target/*.jar $CI_PROJECT_DIR/$PACKAGE_NAME # 输出至Gitlab工件目录下
# 输出打包后的jar包至Gitlab流水线产物
artifacts:
name: $CI_PROJECT_NAMESPACE-$CI_PROJECT_TITLE-$CI_COMMIT_BRANCH
paths:
- $CI_PROJECT_DIR/$PACKAGE_NAME
expire_in: 1 week
after_script:
- echo -e "$BUILD_SUFFIX_INFO"
三、运行流水线
当项目git根目录下存在.gitlab-ci.yml文件时,如果再对相关分支进行提交或合并操作,Gitlab就会自动创建并执行流水线及相关作业
如下图为一次成功的作业,在宿主机的[jar包存放路径]文件夹内,也找到了生成的jar包:
四、使用定时脚本进行备份&部署
笔者服务器的文件结构示例如下:
因此定时脚本需要有如下功能:
笔者的定时备份&部署脚本如下:
BaseDir="[群组文件夹的父目录]"
BackupDirName="backup"
NewPackageDirName="new"
LogDirName="[日志]" # 日志文件夹名,遍历时跳过
RunScriptName="[启停脚本].sh"
StopCommand=" stop" # 停止服务指令
StartCommand=" start" # 启动服务指令
cd $BaseDir
echo "**********开始部署**********"
if (ls -A $BaseDir/ | grep -q ".")
then
for groupName in $(ls -A $BaseDir/)
do
echo -----开始检查群组:$groupName-----
for projectName in $(ls -A $BaseDir/$groupName)
do
thisBaseDir=$BaseDir/$groupName/$projectName
projectPath=$groupName/$projectName
if [ $projectName == $LogDirName ]
then continue
fi
if [ ! -d $thisBaseDir/$BackupDirName ]
then echo $projectPath未创建备份文件夹; mkdir -p $thisBaseDir/$BackupDirName;
fi
if [ ! -d $thisBaseDir/$NewPackageDirName ]
then echo $projectPath未创建新包文件夹; mkdir -p $thisBaseDir/$NewPackageDirName;
fi
if [ -f $thisBaseDir/$NewPackageDirName/* ]
then
echo $projectPath中已发现新文件,开始进行部署;
cd $thisBaseDir;
if [ -f $thisBaseDir/$RunScriptName ]
then $thisBaseDir/$RunScriptName$StopCommand;
else echo $projectPath未找到停止脚本!;
fi
if [ -f $thisBaseDir/*.jar ]
then
for file in $thisBaseDir/*.jar
do
# 备份文件名格式:old_YYYYmmdd-HHMM.jar
mv $file $thisBaseDir/$BackupDirName/$(basename ${file%.*})_$(date +%Y%m%d-%H%M).jar;
done
echo "旧包备份完成";
else echo $projectPath未找到旧包,无需备份;
fi
mv $thisBaseDir/$NewPackageDirName/*.jar $thisBaseDir/; #新包不存在的情况已排除
echo "新包迁移完成";
if [ -f $thisBaseDir/$RunScriptName ]
then $thisBaseDir/$RunScriptName$StartCommand;
else echo $projectPath未找到启动脚本!;
fi
echo $projectPath部署完毕;
else echo $projectPath中未发现新文件;
fi
done
echo -----结束检查群组:$groupName-----
done
else
echo "未发现新文件"
fi
echo "**********部署结束**********"
exit 0
笔者java服务启停脚本如下:
#!/bin/bash
# jar包文件名
PROJECT_NAME="[jar包文件名].jar"
# jdk路径,笔者未安装java环境,而是直接使用jdk文件夹内的java
JavaBaseDir="[jdk路径]/jdk/bin"
function check() {
PID=$(ps aux | grep $1 | grep -v grep | awk '{print $2}')
if [[ "${PID[@]}" != "" ]];then
echo "服务已存在:$1"
exit 1
fi
}
function check_start() {
PID=$(ps aux | grep $1 | grep -v grep | awk '{print $2}')
if [[ "${PID[@]}" != "" ]];then
echo "服务启动成功: $1"
else
echo "服务启动失败: $1"
fi
}
function stop_service() {
PID=$(ps aux | grep $1 | grep -v grep | awk '{print $2}')
if [[ "${PID[@]}" != "" ]];then
ps -ef | grep $1 | grep -v grep | cut -c 9-15 | xargs kill -9
test $? -eq 0 && echo "服务关闭成功: $1"
else
echo "未找到对应服务: $1"
fi
}
function start_service() {
check ${PROJECT_NAME}
# 运行服务,若已配置java环境,可以直接使用java指令
nohup $JavaBaseDir/java -Xms1000m -Xmx1000m -XX:-UseGCOverheadLimit -Dfile.encoding=utf-8 -jar ${PROJECT_NAME} >/dev/null 2>&1 &
check_start ${PROJECT_NAME}
}
case $1 in
start)
start_service
;;
stop)
stop_service ${PROJECT_NAME}
;;
restart)
stop_service ${PROJECT_NAME}
sleep 1
start_service
;;
*)
echo "Usage: bash $0 start | stop | restart"
;;
esac
笔者某次定时脚本的输出如下:
参考网址
官方文档
Gitlab之CICD环境变量
GitLab CI/CD + GitLab Runner in Docker 全自动部署服务器网页
自动化构建:gitlab gitlab-run ,maven的缓存 和 gitea drone drone-run
总结
至此,Gitlab自动部署springboot项目的功能已实现,各位请根据自己实际需求选择采取何种Runner执行者、CI/CD内容等
感谢各位阅览。