Gitee Go 多模块 Java 项目自动部署全流程指南
从服务器绑定到 Docker Compose 重启,一份可落地的实践记录
前言
手里有一个 RuoYi-Cloud 微服务项目,包含网关、认证、系统、文件、定时任务等多个模块。每次改完代码,流程都是:本地mvn package→ 手动把 jar 包 SCP 到服务器 → SSH 上去docker compose down && up。麻烦不说,还容易漏传文件。
于是决定用 Gitee Go 把这套流程自动化。选它的原因很简单:代码在 Gitee,Runner 在国内,部署到自己的腾讯云服务器,全链路零抖动。免费额度对个人项目也够用。
这篇文章不讲“Hello World”级别的示例,而是完整记录多模块 Maven 项目 + Docker Compose 部署到自有服务器的实践过程,包括踩过的坑和最终可用的配置。
一、整体思路
Gitee Go 的部署流程很直观:
- 绑定服务器:在 Gitee 主机管理里创建主机组,安装 Agent
- 构建阶段:在 Gitee 的容器里执行
mvn package,产出 jar 包 - 制品上传:把 jar 打包成
tar.gz,上传到 Gitee 的暂存区 - 部署阶段:Agent 把制品下载到目标服务器,执行你定义的 Shell 脚本
- 变量控制:触发时传入变量,控制构建哪些模块、从哪个分支构建
核心配置是仓库根目录下的.workflow/里的 YAML 文件(Gitee Go 会自动生成一个类似流水线-202609281008.yml的文件)。
二、绑定服务器:先让 Agent 跑起来
Gitee Go 的部署不是通过 SSH 推送代码,而是通过在目标服务器上安装一个Agent 程序,由 Agent 接收流水线下发的指令来执行部署脚本。
2.1 创建主机组
登录 Gitee,进入右上角头像 →设置→Gitee Go→主机管理。
创建一个主机组,比如命名me-host(这个名字后面在流水线 YAML 里要引用)。创建时需要选择:
- 主机组名称:自定义,建议有辨识度
- 主机组标识:英文标识,用于 YAML 引用
- 仓库作用域:务必选中你的目标仓库,否则流水线无法调用这个主机组
2.2 添加主机并安装 Agent
在主机组里点击“添加主机”,Gitee 会生成一段Agent 安装命令,大致长这样:
bash
curl -k -L https://gitee-agent-shell.gz.bcebos.com/agent/gitee-go-runner-linux-amd64 \ -o gitee-go-runner && chmod +x gitee-go-runner && \ ./gitee-go-runner -u https://server-agent.gitee.com/gitee_sa_server \ -t "labelId=xxx&token=xxx&sign=xxx×tamp=xxx" \ manager -g --first-install -c "10"把这段命令复制到目标服务器上执行。执行后会看到类似输出:
text
agent registered successfully with uuid: 893bb0e8-4b9a-4299-a16c-41bde86289e1 Agent started看到Agent started就说明安装成功。回到 Gitee 主机管理页面刷新,会看到这台服务器状态变为“在线”,勾选并添加即可。
⚠️两个关键细节:
- Agent 进程需要保持运行。建议用
nohup或 systemd 托管,否则 SSH 断开后进程可能退出。- 安装日志里会生成一个UUID(如
893bb0e8-...),这个 UUID 在流水线 YAML 的hostID字段里要引用,先记下来。
2.3 在流水线中绑定主机
在deploy@agent任务中,通过hostGroupID字段绑定:
yaml
- step: deploy@agent hostGroupID: ID: me-host # 主机组标识 hostID: - "" # Agent 的 UUIDhostID可以填多个,支持多主机部署。
三、准备工作:先让应用在服务器上手动跑通
这是最重要的一步,没有之一。
很多人在这一步跳过,结果部署脚本写完,流水线跑通了,但应用起不来,分不清是 CI 的问题还是应用的问题。
正确顺序是:
- 在服务器上手动把 jar 传到正确目录
- 用 Docker Compose 手动启动所有容器
- 确认
docker compose ps显示所有服务Up - 确认应用能正常访问
只有手动流程跑通了,才值得把它写进部署脚本。
踩坑提示:
java -jar启动时,JVM 参数必须放在-jar前面。java -jar -Xmx512M app.jar会被 Java 当成在运行一个叫-Xmx512M的文件,报错Unable to access jarfile。正确写法是java -Xmx512M -Xms256M -jar app.jar。
四、构建阶段:多模块项目的配置
4.1 指定 JDK 和 Maven 版本
在build@maven任务中,必须在 step 的直属属性里指定jdkVersion和mavenVersion,不能嵌套在inputs下:
yaml
- step: build@maven jdkVersion: "17" mavenVersion: 3.9.8 commands: - mvn clean package -Dmaven.test.skip=true -U -B踩坑记录:我一开始放了两个build@maven步骤,第一个没指定 JDK 版本,结果用了默认的 JDK 8 编译,报invalid target release: 17。只保留一个构建步骤,把 JDK 版本写对。
4.2 制品收集:让 Gitee Go 找到你的 jar
artifacts.path指定的是相对于仓库根目录的路径。对于 RuoYi 这种多模块项目,jar 分散在各模块的target/或自定义输出目录下。
最终可用的配置:
yaml
artifacts: - name: BUILD_ARTIFACT path: - ./pipeline-artifacts type: .tar.gz配合构建命令里的收集逻辑:
bash
rm -rf ./pipeline-artifacts mkdir -p ./pipeline-artifacts find . -name "*.jar" -type f ! -name "*sources*" ! -name "*javadoc*" ! -name "*.jar.original" -exec cp {} ./pipeline-artifacts/ \; find ./pipeline-artifacts -maxdepth 1 -type f -name '*.jar' -print先加一句find确认 jar 的实际位置,再配置artifacts.path。盲目猜测路径会导致Error: don't exist xxx.jar。
4.3 按模块构建:用变量控制
如果想通过流水线变量控制构建哪些模块,可以用 Maven 的-pl和-am参数:
yaml
variables: global: - BUILD_MODULE - BRANCH steps: - step: build@maven jdkVersion: "17" mavenVersion: 3.9.8 commands: - |- SELECTED_BRANCH="${BRANCH:-master}" case "${SELECTED_BRANCH}" in master|dev) ;; *) echo "不支持的分支:${SELECTED_BRANCH}"; exit 1 ;; esac SELECTED_MODULES=$(printf '%s' "${BUILD_MODULE:-all}" | tr -d '[]" ' | tr ';' ',') echo "构建分支:${SELECTED_BRANCH}" echo "构建模块:${SELECTED_MODULES}" if [ -z "${SELECTED_MODULES}" ] || [ "${SELECTED_MODULES}" = "all" ] || printf '%s' ",${SELECTED_MODULES}," | grep -q ',all,'; then mvn clean package -Dmaven.test.skip=true -U -B else mvn clean package -pl "${SELECTED_MODULES}" -am -Dmaven.test.skip=true -U -B fi触发流水线时填入ruoyi-auth,ruoyi-gateway就只构建这两个模块及其依赖。不填时默认all,全量构建。
变量引用语法:${BRANCH:-master}表示如果BRANCH未定义或为空,就用master兜底。
4.4 敏感信息:用环境变量管理
如果需要在流水线中使用密码、密钥等敏感信息,不要直接写在 YAML 里。Gitee Go 提供了环境变量管理功能,在仓库「管理」→「环境变量管理」中添加。
添加后在流水线中用$变量名引用:
yaml
commands: - echo "$APP_SECRET"环境变量只在流水线配置文件中生效,对用户自定义的其他脚本文件不起作用。
五、部署阶段:主机部署脚本
5.1 制品下载配置
Gitee Go 会把制品下载到~/gitee_go/deploy/output.tar.gz,配置如下:
yaml
- step: deploy@agent displayName: 主机部署 deployArtifact: - name: output target: "~/gitee_go/deploy" source: build dependArtifact: BUILD_ARTIFACT artifactRepository: default artifactName: output artifactVersion: latest5.2 部署脚本:核心逻辑
这是整个流程中最关键的部分。脚本需要:
- 解压制品
- 把 jar 分发到正确的目录
- 备份旧 jar(保留最近 N 个)
- 停止旧容器
- 用 Docker Compose 重启
- 清理临时目录
以下是我最终可用的脚本(注意:目标服务器的/bin/sh可能是 dash,不能用 bash 的关联数组语法):
bash
#!/bin/bash set -eu DEPLOY_DIR="/opt/dailai" ARTIFACT_DIR="${HOME}/gitee_go/deploy" TIMESTAMP=$(date +%Y%m%d%H%M%S) MAX_BACKUPS=10 mkdir -p "${ARTIFACT_DIR}" "${DEPLOY_DIR}" # 用空格分隔的列表,避免 bash 关联数组(dash 不支持) JARS="ruoyi-auth.jar ruoyi-gateway.jar ruoyi-modules-system.jar ruoyi-modules-gen.jar ruoyi-modules-job.jar ruoyi-modules-file.jar ruoyi-visual-monitor.jar" DIRS="/opt/dailai/auth/jar /opt/dailai/gateway/jar /opt/dailai/modules/system/jar /opt/dailai/modules/gen/jar /opt/dailai/modules/job/jar /opt/dailai/modules/file/jar /opt/dailai/visual/monitor/jar" CONTAINERS="ruoyi-auth ruoyi-gateway ruoyi-modules-system ruoyi-modules-gen ruoyi-modules-job ruoyi-modules-file ruoyi-visual-monitor" cd "${ARTIFACT_DIR}" tar zxvf output.tar.gz INDEX=0 for JAR in ${JARS}; do TARGET_DIR=$(echo "${DIRS}" | cut -d' ' -f$((INDEX + 1))) CONTAINER=$(echo "${CONTAINERS}" | cut -d' ' -f$((INDEX + 1))) TARGET_FILE="${TARGET_DIR}/${JAR}" SOURCE_FILE=$(find . -type f -name "${JAR}" -print -quit) if [ -z "${SOURCE_FILE}" ]; then echo "跳过未构建模块:${JAR}" INDEX=$((INDEX + 1)) continue fi mkdir -p "${TARGET_DIR}" if [ -f "${TARGET_FILE}" ]; then docker stop "${CONTAINER}" 2>/dev/null || true mv "${TARGET_FILE}" "${TARGET_FILE}.${TIMESTAMP}" fi cp "${SOURCE_FILE}" "${TARGET_FILE}" # 清理历史备份,保留最近 MAX_BACKUPS 个 cd "${TARGET_DIR}" ls -1t ${JAR}.* 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | while read -r OLD; do rm -f "${OLD}" done cd "${ARTIFACT_DIR}" INDEX=$((INDEX + 1)) done cd "${DEPLOY_DIR}" docker compose down docker compose up -d --build docker compose ps rm -rf "${ARTIFACT_DIR}"/*5.3 踩坑记录
踩坑 1:Shell 解释器不是 bash。
目标服务器的/bin/sh链接到dash,而dash不支持declare -A(关联数组)。报错Syntax error: "(" unexpected。解决方案是改用普通字符串列表 +cut提取。
踩坑 2:Docker Compose 的context路径要对。
如果 compose 文件在/opt/docker-compose.yml,context: ./dailai/gateway会指向/opt/dailai/gateway。如果 compose 文件在/opt/dailai/,则要写context: ./gateway。
踩坑 3:基础镜像可能已经不存在。openjdk:17这个 tag 在 Docker Hub 上已经废弃,拉取会报not found。改用eclipse-temurin:17-jdk或amazoncorretto:17。
踩坑 4:Docker 镜像拉取网络问题。
国内服务器拉取 Docker Hub 镜像可能超时或 403。如果配置了代理,不要同时配置加速器,否则 Docker 会优先走代理去访问加速器,链路冲突。二选一即可。
六、完整流水线结构
把绑定服务器、变量配置、构建阶段、部署阶段全部串起来:
yaml
version: "1.0" name: 构建、部署到自有主机 displayName: 构建、部署到自有主机 variables: global: - BUILD_MODULE - BRANCH stages: - name: 构建 displayName: 构建 strategy: naturally trigger: auto steps: - step: build@maven name: build_maven displayName: Maven 构建 jdkVersion: "17" mavenVersion: 3.9.8 commands: - |- set -eu SELECTED_BRANCH="${BRANCH:-master}" case "${SELECTED_BRANCH}" in master|dev) ;; *) echo "不支持的分支:${SELECTED_BRANCH}"; exit 1 ;; esac SELECTED_MODULES=$(printf '%s' "${BUILD_MODULE:-all}" | tr -d '[]" ' | tr ';' ',') echo "构建分支:${SELECTED_BRANCH}" echo "构建模块:${SELECTED_MODULES}" if [ -z "${SELECTED_MODULES}" ] || [ "${SELECTED_MODULES}" = "all" ] || printf '%s' ",${SELECTED_MODULES}," | grep -q ',all,'; then mvn clean package -Dmaven.test.skip=true -U -B else mvn clean package -pl "${SELECTED_MODULES}" -am -Dmaven.test.skip=true -U -B fi rm -rf ./pipeline-artifacts mkdir -p ./pipeline-artifacts find . -name "*.jar" -type f ! -name "*sources*" ! -name "*javadoc*" ! -name "*.jar.original" -exec cp {} ./pipeline-artifacts/ \; echo "=========== 本次构建 JAR ===========" find ./pipeline-artifacts -maxdepth 1 -type f -name '*.jar' -print artifacts: - name: BUILD_ARTIFACT path: - ./pipeline-artifacts type: .tar.gz settings: [] caches: - "~/.m2" strategy: retry: "0" timeout: 4 resource: cpu: "2" memory: "4" - name: 部署 displayName: 部署 strategy: naturally trigger: auto steps: - step: deploy@agent name: _1 displayName: 主机部署 deployArtifact: - name: output target: "~/gitee_go/deploy" source: build dependArtifact: BUILD_ARTIFACT artifactRepository: default artifactName: output artifactVersion: latest script: |- #!/bin/bash set -eu DEPLOY_DIR="/opt/dailai" ARTIFACT_DIR="${HOME}/gitee_go/deploy" TIMESTAMP=$(date +%Y%m%d%H%M%S) MAX_BACKUPS=10 mkdir -p "${ARTIFACT_DIR}" "${DEPLOY_DIR}" JARS="ruoyi-auth.jar ruoyi-gateway.jar ruoyi-modules-system.jar ruoyi-modules-gen.jar ruoyi-modules-job.jar ruoyi-modules-file.jar ruoyi-visual-monitor.jar" DIRS="/opt/dailai/auth/jar /opt/dailai/gateway/jar /opt/dailai/modules/system/jar /opt/dailai/modules/gen/jar /opt/dailai/modules/job/jar /opt/dailai/modules/file/jar /opt/dailai/visual/monitor/jar" CONTAINERS="ruoyi-auth ruoyi-gateway ruoyi-modules-system ruoyi-modules-gen ruoyi-modules-job ruoyi-modules-file ruoyi-visual-monitor" cd "${ARTIFACT_DIR}" tar zxvf output.tar.gz INDEX=0 for JAR in ${JARS}; do TARGET_DIR=$(echo "${DIRS}" | cut -d' ' -f$((INDEX + 1))) CONTAINER=$(echo "${CONTAINERS}" | cut -d' ' -f$((INDEX + 1))) TARGET_FILE="${TARGET_DIR}/${JAR}" SOURCE_FILE=$(find . -type f -name "${JAR}" -print -quit) if [ -z "${SOURCE_FILE}" ]; then echo "跳过未构建模块:${JAR}" INDEX=$((INDEX + 1)) continue fi mkdir -p "${TARGET_DIR}" if [ -f "${TARGET_FILE}" ]; then docker stop "${CONTAINER}" 2>/dev/null || true mv "${TARGET_FILE}" "${TARGET_FILE}.${TIMESTAMP}" fi cp "${SOURCE_FILE}" "${TARGET_FILE}" cd "${TARGET_DIR}" ls -1t ${JAR}.* 2>/dev/null | tail -n +$((MAX_BACKUPS + 1)) | while read -r OLD; do rm -f "${OLD}" done cd "${ARTIFACT_DIR}" INDEX=$((INDEX + 1)) done cd "${DEPLOY_DIR}" docker compose down docker compose up -d --build docker compose ps rm -rf "${ARTIFACT_DIR}"/* hostGroupID: ID: me-host hostID: - 893bb0e8-4b9a-4299-a16c-41bde86289e1 notify: [] strategy: retry: "0" timeout: 5 triggers: trigger: manual notify: [] strategy: blocking: true七、触发方式:手动还是自动
你目前配置的是trigger: manual,需要手动点击运行。Gitee Go 还支持Push 触发,代码提交后自动运行:
yaml
triggers: push: branches: include: - master - dev或者通过提交信息关键字触发:
yaml
triggers: push: commitMessage: "^build:"这样只有 commit message 以build:开头的提交才会触发流水线,避免每次提交都跑一遍。
八、小结
| 环节 | 关键操作 | 常见坑 |
|---|---|---|
| 绑定服务器 | 创建主机组 → 安装 Agent → 获取 UUID | Agent 进程要托管,UUID 要记录 |
| 定义变量 | variables.global声明,触发时传入 | 变量为空要用:-默认值兜底 |
| 敏感信息 | 环境变量管理,用$VAR引用 | 不要写死在 YAML 里 |
| 构建阶段 | 指定 JDK/Maven 版本,收集 jar | 两个构建步骤会覆盖,路径要确认 |
| 部署阶段 | 解压、分发、备份、重启 | dash 不支持关联数组,Compose context 要对 |
| 触发方式 | manual手动 /push自动 | Push 触发要配分支或 commit 匹配 |
先确保服务器 Agent 在线、主机组正确关联仓库,再配置变量和触发方式,流水线才能真正跑通。一旦跑通,后续每次 push 代码,流水线自动构建、部署、重启容器,真正做到了“只关注开发”。
这套配置也适用于其他多模块 Java 项目 + Docker Compose 部署的场景,只需要调整模块名、路径和容器名即可。