一、项目准备
1.微服务拆分
- Ruoyi-Cloud在设计上有7个微服务,分别负责不同功能,以下信息将在后续配置用到
| 模块 | 微服务 | 代码根目录 | dockerfile路径 |
|---|---|---|---|
| 网关模块 | RuoYi-Gateway | ruoyi-gateway/ | docker/ruoyi/gateway |
| 认证模块 | RuoYi-Auth | ruoyi-auth/ | docker/ruoyi/auth |
| 系统模块 | RuoYi-System | ruoyi-modules/ruoyi-system/ | docker/ruoyi/modules/system |
| 代码生成 | RuoYi-Gen | ruoyi-modules/ruoyi-gen | docker/ruoyi/modules/gen |
| 定时任务 | RuoYi-Job | ruoyi-modules/ruoyi-job | docker/ruoyi/modules/job |
| 文件服务 | RuoYi-File | ruoyi-modules/ruoyi-file | docker/ruoyi/modules/file |
| 监控中心 | RuoYi-Monitor | ruoyi-visual/ruoyi-monitor/ | docker/ruoyi/visual/monitor |
- 针对这些微服务,我们需要创建7条Jenkins多分支流水线来分别对这些微服务进行CICD
2.数据库文件导入
在若依根目录sql/路径中,有预设的数据库脚本,需要创建对应数据库导入
在数据库服务器
10.0.0.138中,将sql/路径下的脚本全部传输到服务器,进入数据库
bash
create database `ry-cloud`;
create database `ry-config`;
# 编写导入脚本
vim ry-data.sh
------------------------------------------------------------------------
echo $(date)
mysql -uroot -pPangle@123 -f ry-cloud < ry_20250523.sql
echo $(date)
echo $(date)
mysql -uroot -pPangle@123 -f ry-cloud < quartz.sql
echo $(date)
echo $(date)
mysql -uroot -pPangle@123 -f ry-config < ry_config_20260311.sql
echo $(date)
------------------------------------------------------------------------
# 执行脚本
nohup sh ry-data.sh > ry-data.log &
3.nacos安装
我们的操作基于master分支,而该分支代码已升级至SpringBoot4 对应nacos版本为3.X.X
在
10.0.0.140服务器上部署nacos,前往nacos官网下载压缩版:https://nacos.io/
bash
# 下载解压
cd /usr/local
wget https://download.nacos.io/nacos-server/nacos-server-3.1.1.zip
unzip nacos-server-3.1.1.zip
cd nacos/
# 配置文件编辑数据库路径
vim conf/application.properties
------------------------------------------------------------------------
spring.sql.init.platform=mysql
db.num=1
db.url.0=jdbc:mysql://{数据库服务器ip}:3306/ry-config?characterEncoding=utf8&connectTimeout=5000&socketTimeout=10000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
db.user=root
db.password={root用户密码}
------------------------------------------------------------------------
# 单机模式启动
sh bin/startup.sh -m standalone
4.配置地址
- 配置微服务注册地址
若依微服务依赖nacos进行服务注册和读取配置,首先要为每个微服务配置nacos地址
在每个微服务
代码根目录下src/main/resources/bootstrap.yml文件中修改:修改每个配置文件中,每个
server-addr:后的内容,将127.0.0.1改为nacos服务器ip10.0.0.140
- nacos配置数据库地址
nocos将从数据库的
ry-config库读取配置文件登录nacos主页 > 配置管理 > 配置列表
修改 ruoyi-gateway-dev.yml 、ruoyi-auth-dev.yml 、ruoyi-system-dev.yml 、ruoyi-gen-dev.yml 、ruoyi-job-dev.yml
redis的配置地址 host: {数据库服务器ip}
datasource中的 url: 和 password: 设置成自己数据库相关配置
- 配置后端API地址
前端将部署到
10.0.0.135,后端将以微服务容器部署到10.0.0.137因此在前端代码文件需要配置后端服务器地址和访问端口
在前端目录
ruoyi-ui中,编辑配置文件vue.config.js
- 找到
target: http://127.0.0.0:8080,将其改为自定义的target: http://10.0.0.137:8080
二、编写配置文件
1.Jenkinsfile
-
在对项目完成准备工作后,将其上传到Gitlab仓库
Ruoyi-Cloud,然后新建分支prod-peizhi,将其作为生产Jenkins需要发布的分支 -
在
prod-peizhi分支中,在每个微服务代码根目录新建文件Jenkinsfile并编辑内容:-
针对不同微服务,只需要配置
SERVICE_TYPE、BASE_NAME、HOST_PORT和CONTAINER_PORT就可复用该Jenkinsfile
SERVICE_TYPE BASE_NAME HOST_PORT & CONTAINER_PORT basic gateway 8080 basic auth 9200 modules system 9201 modules gen 9202 modules job 9203 modules file 9300 visual monitor 9100 - 在Jenkins全局配置中,添加前后端目标服务器信息:命名为
若依微服务-前端和若依微服务-后端,供Jenkinsfile调用
-
Groovy
pipeline {
agent any
tools {
// 全局工具已配置
maven 'Maven-3.8.9'
jdk 'JDK-17'
}
environment {
// 不同微服务需修改此处:gateway/auth为 basic、job/file/gen/system为 modules、monitor为 visual
SERVICE_TYPE = "basic"
BASE_NAME = "gateway" // 基础服务名称
HOST_PORT=8080 // 宿主机端口
CONTAINER_PORT=8080 // 容器内端口
// --------------------------------------------------------以下信息自动拼接----
// 根据微服务类型和基础名称动态生成应用名称,基于RuoYi的项目结构
// APPNAME 根据生成的jar包名,将作为镜像名称
APPNAME = "ruoyi-${SERVICE_TYPE == 'basic' ? BASE_NAME : (SERVICE_TYPE == 'modules' ? "modules-${BASE_NAME}" : "visual-${BASE_NAME}")}"
APP_DIR = "${SERVICE_TYPE == 'basic' ? "./ruoyi-${BASE_NAME}" : (SERVICE_TYPE == 'modules' ? "./ruoyi-modules/ruoyi-${BASE_NAME}" : "./ruoyi-visual/ruoyi-${BASE_NAME}")}"
// 拼接jar包完整路径,供Dockerfile使用
JAR_PATH = "${APP_DIR}/target/${APPNAME}.jar"
// Dockerfile路径,根据微服务类型动态选择
DOCKERFILE_DIR = "${SERVICE_TYPE == 'basic' ? "./docker/ruoyi/${BASE_NAME}" : (SERVICE_TYPE == 'modules' ? "./docker/ruoyi/modules/${BASE_NAME}" : "./docker/ruoyi/visual/${BASE_NAME}")}"
// --------------------------------------------------------以下信息无需修改----
VERSION="V1.0.${env.BUILD_NUMBER}"
// 拼接为符合Harbor镜像规范的完整镜像名
HARBOR_URL="121.4.90.98:80"
IMAGE_NAME="${HARBOR_URL}/ruoyi/${APPNAME}:${VERSION}"
// Harbor用户名密码
HARBOR_USER="admin"
HARBOR_PASS="Harbor12345"
// 前后端服务器地址,在Jenkins系统配置中添加,命名
NGINX_SERVER="若依微服务-前端"
APP_SERVER="若依微服务-后端"
}
options {
disableConcurrentBuilds() // 禁止并行构建
timestamps() // 日志添加时间戳
}
stages {
stage('Npm打包前端') {
when {
// 仅微服务名为ruoyi-gateway的流水线执行
expression { return env.APPNAME == 'ruoyi-gateway' }
}
steps {
script {
dir('ruoyi-ui') {
sh """
rm -rf dist*.zip
npm install --registry=https://registry.npmmirror.com
npm run build:prod
zip -r dist-${VERSION}.zip ./dist/*
"""
}
}
}
}
stage('Maven打包后端') {
steps {
script {
sh """
# 将jar包构建后复制到Docker构建目录,供Dockerfile使用
mvn clean package -Dmaven.test.skip=true -pl ${APP_DIR} -am -f ./pom.xml
cp ${JAR_PATH} ${DOCKERFILE_DIR}/jar
"""
}
}
}
stage('本地构建镜像') {
steps {
script {
sh """
cd ${DOCKERFILE_DIR}
docker build -t ${APPNAME}:${VERSION} .
"""
}
}
}
stage('镜像推送Harbor') {
steps {
script {
sh """
docker login ${HARBOR_URL} -u ${HARBOR_USER} -p ${HARBOR_PASS}
docker tag ${APPNAME}:${VERSION} ${IMAGE_NAME}
docker push ${IMAGE_NAME}
"""
}
}
}
stage('部署前端') {
when {
expression { return env.APPNAME == 'ruoyi-gateway' }
}
steps {
script {
echo "=== 部署前端 ==="
// sshPublisher:Jenkins SSH插件,实现远程服务器操作
sshPublisher(publishers: [
sshPublisherDesc(
configName: NGINX_SERVER, // 指定前端部署的SSH服务器
transfers: [
sshTransfer(
sourceFiles: "ruoyi-ui/dist-${VERSION}.zip", // 要推送的前端压缩包
removePrefix: '', // 不删除压缩包的前缀路径
remoteDirectory: '', // 推送至远程服务器的当前目录
// 远程服务器执行的命令:解压压缩包并删除源文件
execCommand: """
# 切换到前端部署目录-->解压并覆盖旧文件-->删除压缩包
cd /usr/local/RuoYi-Cloud/ruoyi-ui && unzip -o dist-${VERSION}.zip && rm -rf dist-${VERSION}.zip
"""
)
],
verbose: true // 输出详细SSH操作日志
)
])
}
}
}
stage('部署后端') {
options {
timeout(time: 30, unit: 'MINUTES') // 超时时间10分钟
}
steps {
script {
echo "=== 部署后端 ==="
sshPublisher(publishers: [
sshPublisherDesc(
configName: APP_SERVER, // 指定后端部署的SSH服务器
transfers: [
sshTransfer(
sourceFiles: '', // 无需推送文件
removePrefix: '',
remoteDirectory: '',
// 远程执行部署脚本:标准镜像名、端口等参数
execCommand: """
sh /usr/bin/ry-cloud.sh ${IMAGE_NAME} ${HOST_PORT} ${CONTAINER_PORT}
"""
)
],
verbose: true
)
])
}
}
}
}
}
2.脚本文件
部署后端阶段是调用后端服务器的/usr/bin/ry-cloud.sh脚本文件,需在后端服务器添加改文件
bash
vim /usr/bin/ry-cloud.sh
------------------------------------------------------------------------
#!/bin/bash
FULL_IMAGE_NAME=$1
HOST_PORT=$2
CONTAINER_PORT=$3
# ===== 根据Jenkins传入参数,解析镜像名 =====
# 1:按最后一个:分割 "地址/项目名/镜像名" 和 "版本号"
IMAGE_PREFIX=${FULL_IMAGE_NAME%:*} # 取:左侧部分 → 121.4.90.98:80/ruoyi/ruoyi-gateway
VERSION=${FULL_IMAGE_NAME##*:} # 取:右侧部分 → V1.0.11
# 2:按最后一个/分割 "地址/项目名" 和 "镜像名"
APP_NAME=${IMAGE_PREFIX##*/} # 取最后一个/右侧 → ruoyi-gateway
HARBOR_ADDR=${IMAGE_PREFIX%/*} # 取最后一个/左侧 → 121.4.90.98:80/ruoyi
# ===== 打印解析结果 =====
echo "===== 部署参数解析结果 ====="
echo "完整镜像名: ${FULL_IMAGE_NAME}"
echo "Harbor地址+项目名: ${HARBOR_ADDR}"
echo "微服务名: ${APP_NAME}"
echo "版本号: ${VERSION}"
echo "主机端口: ${HOST_PORT}"
echo "容器端口: ${CONTAINER_PORT}"
echo "============================"
# ===== 停止并删除旧容器 =====
echo "===== 停止并删除旧容器 ====="
OLD_CONTAINER=$(docker ps -aq --filter "name=${APP_NAME}")
if [ -n "${OLD_CONTAINER}" ]; then
echo "发现运行中的旧容器,ID: ${OLD_CONTAINER},正在停止"
docker stop ${OLD_CONTAINER} || echo "警告:容器可能已停止"
echo "删除旧容器: ${OLD_CONTAINER}"
docker rm ${OLD_CONTAINER} || echo "警告:容器可能已删除"
else
echo "未发现运行中的旧容器,跳过"
fi
# ===== 删除旧镜像 =====
echo "===== 删除旧镜像 ====="
# 检查旧镜像是否存在
if docker images -q ${FULL_IMAGE_NAME} > /dev/null 2>&1; then
echo "发现旧镜像 ${FULL_IMAGE_NAME},正在删除"
docker rmi ${FULL_IMAGE_NAME} || echo "警告:镜像可能已删除"
else
echo "未发现旧镜像 ${FULL_IMAGE_NAME},跳过"
fi
# ===== 登录Harbor并拉取新镜像 =====
echo "===== 登录Harbor并拉取新镜像 ====="
# Harbor用户名密码,也可由Jenkins传入
HARBOR_USER="admin"
HARBOR_PASS="Harbor12345"
docker login ${HARBOR_ADDR%/*} -u ${HARBOR_USER} -p ${HARBOR_PASS} || echo "警告:Harbor登录失败"
docker pull ${FULL_IMAGE_NAME} || { echo "ERROR:拉取新镜像失败!"; exit 1; }
# ===== 启动新容器 =====
echo "===== 启动新容器 ====="
docker run -d \
--name ${APP_NAME} \
--restart=always \
-p ${HOST_PORT}:${CONTAINER_PORT} \
${FULL_IMAGE_NAME}
# 验证容器是否启动成功
if docker ps --filter "name=${APP_NAME}" | grep -q "${APP_NAME}"; then
echo "===== 部署成功 ====="
echo "容器名: ${APP_NAME}"
echo "映射端口: ${HOST_PORT}:${CONTAINER_PORT}"
exit 0
else
echo "ERROR:容器启动失败 "
docker logs ${APP_NAME} # 打印日志便于排查
exit 1
fi
------------------------------------------------------------------------
三、配置Jenkins任务
按
微服务名在Jenkins新建任务并命名,选择多分支流水线类型
分支源选择Git,项目仓库填写Ruoyi-Cloud的Gitlab仓库地址,并配置Gitlab凭据行为:
发现分支后添加根据名称过滤(支持通配符),选择包含prod*,匹配生产分支prod-peizhi
Build Configuration的脚本路径填写对应微服务的代码根目录➕/Jenkinsfile,如:ruoyi-gateway/Jenkinsfile点击应用后保存,Jenkins就会自动扫描对应分支,并根据其Jenkinsfile进行构建
其他微服务同理建立Jenkins多分支流水线任务并做好配置
总结
-
文档从自由风格的项目到多分支流水线的任务搭建配置,我们可以发现,在篇幅上自由风格项目的Jenkins配置较多,而多分支流水线的Jenkins配置非常少,只需要编写好Jenkinsfile,然后告诉Jenkins去哪里找Jenkinsfile文件即可,这也就体现出流水线任务的优势,不用过多去关注Jenkins的插件、系统设置、配置等,只需要根据需求编写Jenkinsfile即可,后续修改也很好找修改点。
-
在内容上按 单前、后端的简单项目、前后端分离项目 最后到 微服务项目的项目设计,由浅入深通过Jenkins完成不同项目的CICD,已适配较多互联网公司的自动化发布任务。
-
在自动化发布上:后续将设计编写K8s集群整合Jenkins、Gitlab CI/CD、Gitlab Runner以及Argo CD等更多CICD工具完成不同项目的自动化发布。
-
在制品保存、链路追踪上:后续将搭建加入nexus的maven私有仓库、skywalking全链路追踪,进一步为项目高并发能力的提升作支持
-
在日志收集分析、监控告警上:将搭建ELK日志处理、Prometheus+kibana监控告警一体化平台,保障项目高可用与问题的告警及时处理
-
对于文档内容、设计以及后续文档有任何建议或意见欢迎交流:
- 裴东青:956143827@qq.com