10-实战:RuoYi-Cloud的自动化发布
一、项目准备
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/路径下的脚本全部传输到服务器,进入数据库
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/
# 下载解压 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 standalone4.配置地址
- 配置微服务注册地址
若依微服务依赖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就可复用该JenkinsfileSERVICE_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调用
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脚本文件,需在后端服务器添加改文件
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
