把 Java 项目从手动 SCP 升级到 Gitee Go 自动部署:一份踩坑实录

把 Java 项目从手动 SCP 升级到 Gitee Go 自动部署:一份踩坑实录

手里有一个跑在腾讯云轻量服务器上的小程序后端(Spring Boot,体重记录类的小项目),之前一直是本地改完代码 → 打包 → scp 上传 → SSH 上去 kill 老进程 → java -jar 起新进程。麻烦倒不算麻烦,主要是每次都觉得「这流程明明可以全自动的,为什么要手动搞」。

于是花了大概一个周末,把这套流程用 Gitee Go 跑通了。从开始折腾到生产可用,中间踩了不下六个坑,每一个都值得单独拎出来讲一讲。这篇不是教程,是踩坑日志------如果你也在走这条路,希望少走几步弯路。

选型:为什么是 Gitee Go 不是 GitHub Actions

一开始想用 GitHub Actions,毕竟最熟悉。但服务器在国内,拉 GitHub 的代码、跑 runner 都有不稳定感。最后选了 Gitee Go,三个原因:

  1. 国内访问快。代码在 Gitee,runner 在 Gitee,部署到腾讯云 CVM------全链路都在国内,零抖动。
  2. 免费额度够用。个人项目每月 500 分钟 + 单仓库 200 分钟永久免费,比 Actions 的 2000 分钟少,但单机部署一次构建也就 1 分钟。
  3. 自带主机部署插件deploy@agent 装一台 Agent 到目标服务器,就能直接 deploy,不用再单独搞 SSH 公私钥那套。

唯一坑爹的地方:官方文档是过时的。比如「服务 → Gitee Go」的入口早就改名了。后面我会逐个说哪些坑是文档锅、哪些是真的设计问题。

顺带一提,Gitee 顶部导航里现在已经没有「服务」了,叫「流水线 」(在 代码 / Issues / Pull Requests / Wiki / 统计 / 流水线 / 服务 / 管理 这一排里)。而且你在 GUI 里建好的流水线,会生成一个形如 流水线-202609041137.yml 的文件被提交到仓库------这名字一看就是自动生成的,后面讲「流水线被 push 冲掉」那坑就跟它有关。

第一坑:先别急着配流水线,先把 systemd 跑通

很多人(包括之前的我)一上来就建流水线、配脚本,结果部署到一半发现应用根本起不来,然后分不清是流水线的问题还是代码的问题。

正确顺序:先把应用在目标服务器上用 systemd 手动跑通,再接 CI/CD。

ExecStart 里有个非常容易踩的坑:

复制代码
java -jar -Xmx512M -Xms256M xxx.jar   ← 错
java -Xmx512M -Xms256M -jar xxx.jar   ← 对

JVM 参数必须放在 -jar 前面 ,否则 Java 会把 -Xmx 当成 jar 文件名。报错长这样:

复制代码
my-app.service: Main process exited, code=exited, status=1/FAILURE

我当时看到 status=1/FAILURE 以为是 JDK 路径错了,折腾了半天才反应过来是参数顺序。systemd 对参数顺序的容忍度是零,错一个字符就拒绝启动。

SuccessExitStatus=143 这行也很关键------systemctl stop 发 SIGTERM 给 Java 进程,Java 会返回 143(128+15),不加这行 systemd 会把它当成「异常退出」然后立刻重启,导致 systemctl stop 永远停不下来。

第二坑:Maven 找不到 POM

代码结构是这样的:

复制代码
my-app/
└── backend/
    └── pom.xml

不是单模块项目,所以仓库根目录里没有 pom.xml。Gitee Go 默认 Maven 任务的工作目录是仓库根目录,所以一上来就报错:

复制代码
[ERROR] The goal you specified requires a project to execute but there is no POM in this directory

修法:在构建命令前加一行 cd backend。小事,但定位起来要看日志才知道是路径问题。

第三坑:制品名带点号被拒

构建成功了,制品上传这一步炸了:

复制代码
key "my-app-1.2.3" is invalid: character '.' at position 16 is not allowed

我用了 my-app-1.2.3 作为制品名(项目名+版本号),但 Gitee Go 的制品命名规则只允许字母、数字、下划线、连字符,点号不行

去掉版本号,改成 my-app,构建阶段就完全绿了。

第四坑------也是最离谱的一个:制品名带连字符

构建成功了,制品下载的时候炸了,错误长这样:

复制代码
+ curl -kL -o /root/gitee_go/deploy/my-app.tar.gz app
curl: (6) Could not resolve host: app

我盯了半小时才反应过来------app 这个诡异的「下载地址」,其实是 agent 自动生成的下载命令被 bash 截断了

真相是这样的:Gitee Go 的主机部署插件会自动跑一条 curl 把制品下载到目标主机,命令大概长这样:

bash 复制代码
curl -kL -o /root/gitee_go/deploy/${ARTIFACT_NAME}.tar.gz "${URL}"

问题就出在 ${ARTIFACT_NAME}。我的制品名是 my-app。在 bash 里,${var-default} 是「变量未设时用默认值」的语法------my-app 这个名字被解析成了「取变量 my 的值,找不到就用默认值 -app」,于是下载命令就变成了:

复制代码
curl ... /root/gitee_go/deploy/-app.tar.gz "${URL}"

不对,连字符后面的内容被吃掉了,最终就成了 app

这个 bug 的铁证是:每次把制品名改成什么,curl 那个莫名其妙的地址就精准地丢掉 - 前面的部分my-app-1.2.3 → 假 URL 是 app-1.2.3my-app → 假 URL 是 app。精度到让我怀疑 Gitee 团队是不是故意的。

绕开的办法:制品名只用字母、数字、下划线 。我后来改成 myapp,立刻就过了。官方示例也都老老实实用 outputBUILD_ARTIFACT 这类名字,不是没道理的。

这个坑踩了我一个多小时。后来想明白是 bash 变量语法之后觉得又好气又好笑------估计 Gitee 团队从来没遇到过有人用带连字符的制品名,所以没考虑到这个边界情况。

第五坑:部署脚本里不该有的 curl

制品下载成功后,我的部署脚本一开始是这样的(参考的官方示例):

bash 复制代码
curl -kL -o /opt/my-app/my-app.tar.gz app
bash /opt/my-app/deploy.sh

看起来很合理对吧?但 agent 在执行脚本之前,已经把制品下载到 /root/gitee_go/deploy/。我在脚本里又自己 curl 一次,等于重复下载,还引用了一个不存在的「制品名」当 URL。

正确的脚本应该是:

bash 复制代码
set -e
ls -la /root/gitee_go/deploy/   # 看看 agent 给我们下载了什么
rm -rf /tmp/wt-deploy && mkdir -p /tmp/wt-deploy
tar zxvf /root/gitee_go/deploy/myapp.tar.gz -C /tmp/wt-deploy
JAR=$(find /tmp/wt-deploy -name 'my-app-*.jar' ! -name '*.original' | head -1)
echo "找到 jar: $JAR"
[ -f /opt/my-app/my-app.jar ] && cp /opt/my-app/my-app.jar /opt/my-app/my-app.jar.bak
cp "$JAR" /opt/my-app/my-app.jar
systemctl restart my-app
sleep 5
systemctl status my-app --no-pager

整个脚本永远不要再写 curl------agent 已经帮你下好了,你只管用。

第六坑:流水线被 push 冲掉,差点前功尽弃

最让我哭笑不得的一坑。某次改了点东西 git push 之后,发现流水线又跑不动了。打开一看,我之前在 GUI 里精心调整的部署脚本、制品名、主机组 ID,全部被打回了最初的版本

原因后来搞清楚了:Gitee Go 的「流水线」本质就是仓库里 .workflow/*.yml 这个文件的镜像 。你在 GUI 里改完保存,它会自动 commit 一个 yaml 到仓库的 .workflow/ 目录;反过来,你 push 代码时如果这个目录里有内容(哪怕是别的文件),仓库版本就会覆盖 GUI 状态。

我当时在 GUI 改了之后没有把这个 yaml 拉回本地,下次 push 一个不同状态的 .workflow/ 就把我覆盖了。

修法是两步:

  1. 重新用 GUI 编辑器把流水线配回去(或者用代码视图粘贴一段完整的 yaml)
  2. .workflow/ 加进 .gitignore
bash 复制代码
echo '.workflow/' >> .gitignore
git add .gitignore
git commit -m "chore: 不再跟踪 .workflow 目录"
git push

从此以后流水线归 GUI 管,代码 push 不会再影响它。


现在的日常

复制代码
本地 IDE 改代码
  ↓ git push
Gitee Go 自动触发 Maven 构建(40 秒)
  ↓
Agent 把制品下载到 /root/gitee_go/deploy/
  ↓ 主机部署任务
解压 → 替换 jar → systemctl restart → 状态校验
  ↓ 10 秒后
新版本已在生产跑

改完代码 push 一下,喝口水回来就是新版本。从最初手动 scp + kill + 启动的 5 分钟,到现在全流程 1 分钟不到。

下面是这条流水线真实跑通的一次截图(构建 49 秒 + 部署 15 秒,一共 1 分 13 秒,全绿):

注意左边那栏的信息------「触发方式:手动触发」是因为我还没把它接到推送上(这条流水线是我手动点的「执行」);右边两个「成功」分别是 Maven 构建(49s)主机部署(15s)。等我把触发方式改成 push 自动触发,连这个手动点都不用了。


给同样要走这条路的人几句忠告

  1. 先跑通 systemd 再配流水线------别让两个问题混在一起排查
  2. 制品名不要带连字符 ------这玩意会被 bash 当 ${var-default} 截断(这个 bug 应该写在文档最显眼的位置,但 Gitee 没写)
  3. .workflow/ 加进 .gitignore------否则每次 push 都是和流水线赌命

希望这篇能帮你少踩几个。折腾完你会发现,其实没那么复杂,只是细节多。

附,项目小程序码,欢迎体验指正

相关推荐
lmy_loveF1 小时前
go 切换go version 版本
开发语言·后端·golang
用户467179578361 小时前
【Java全栈教程】第18课:异常处理
java
她说..1 小时前
MySQL 与 Java 的 JSON 数据处理
java·mysql·json·springboot
咖啡八杯1 小时前
实体基类设计:BaseEntity 公共字段抽取与 TreeEntity 树形继承
java·架构·代码规范
她的男孩1 小时前
AI 写完 100 万行代码没人 Review,我做了一件事:把 AI 写的代码管起来了
java·人工智能·程序员
Gl�ria1 小时前
Linux 查看日志常用命令
java·linux·servlet
大模型丫丫2 小时前
LangGraph + MCP(Model Context Protocol)完整讲解
java·开发语言·数据库
lemon_sjdk2 小时前
JavaFX 源码深度剖析:揭开 StringBinding 的神秘面纱
java·javafx·源码解析·绑定框架
砚底藏山河2 小时前
拉数管道设计:从一次性脚本到可重启的数据流水线(魔码量化实战 #01)
java·数据库·python·金融·maven