手工部署Java项目这事儿,干过的人都懂那套流程有多磨人:先登录堡垒机,找到目标服务器,上传新的jar包,再kill掉旧进程,nohup重启,最后还要盯着日志确认起来没有。一台机器这么搞勉强能忍,三台五台甚至更多主机的时候,脑子里那根弦基本就绷不住了——这台改了那台漏了、这台环境变量没配、那台端口被占了,每次发布就像开盲盒。
今天要聊的这套Arbess+GitLab方案,就是专门治这个毛病的。Arbess是我们内部对这套自动化流水线平台的统称,你可以把它理解为一套自建的精简CI/CD调度器,GitLab负责管代码和触发信号,Arbess负责把“拉代码、编译、打包、分发、滚动重启”这一整串动作串起来,最终把Java项目自动部署到多台主机上。它能解决的问题很明确:构建环境统一、发布流程标准化、多主机部署不再靠人肉逐台操作。适合被手工发布折磨过的小团队、想给项目搭正经CI/CD但还没动过手的后端开发,以及刚接手部署运维工作的DevOps新人。
这套东西搭完之后,日常发布就变成了一件事——把代码push到仓库,剩下的交给流水线。下面我把整个方案的架构思路、环境搭建、流水线设计和坑点排查完整拆开讲,照着做基本能落地。
1. 方案整体思路与架构设计:先想清楚再动手
1.1 为什么必须上自动化构建部署平台
先说说手工部署最隐蔽的成本。很多小团队的Java项目一开始只有一台服务器,开发自己打包自己传,出问题自己回滚,好像也没多大事。但项目一旦进入多人协作、多环境(测试、预发、线上)、多主机集群的阶段,手工部署的短板就会集中爆发。
最典型的场景是“构建环境不一致”。张三电脑上编出来的包和李四电脑上编出来的包,可能因为JDK小版本不一样、本地maven仓库里的依赖版本漂移,产生莫名其妙的行为差异。每次排查问题,最后发现是“我本地是好的啊”。这本质上不是代码问题,而是构建过程不可控。自动化构建平台把编译环境固定下来,跑出来的包就是标准化的产物,这种“在我机器上能用”的扯皮会直接消失。
另一个痛点是多主机部署的“时间错位”。集群里有五台机器,手工发布时先改1号机,再改2号机,中间隔着几分钟甚至十几分钟。这期间前两台机器跑的是新版本,后三台还在跑旧版本,如果负载均衡没有及时摘流量,用户请求一分散,就会出现“一会儿新版本一会儿旧版本”的体验,甚至因为新旧版本接口不兼容直接报错。手工操作很难做到让集群内所有节点在同一时间窗口内完成切换,而流水线把分发和重启做成标准动作,节奏可以压到很紧凑。
还有一层是“可回滚性”。手工部署时,旧包往往随手丢在某个临时目录里,真要回滚,经常连上一个版本的文件都找不到。自动化平台天然会保留历史产物,回滚就是选择版本再跑一遍部署流程,几分钟搞定。这一点在线上出故障的时候,价值怎么强调都不过分。
1.2 核心架构与流水线链路拆解
整个架构可以拆成四个角色:GitLab负责代码托管和事件触发;Arbess作为流水线调度中枢,负责拉取代码、执行构建、管理产物、下发部署指令;应用集群主机是最终的部署目标,负责运行Java进程;另外一般还会有一台负载均衡器在前面承接流量。
流水线的主链路非常直观:
- 开发把代码push到GitLab的某个分支;
- GitLab通过Webhook向Arbess发送“代码有更新”的HTTP回调;
- Arbess校验回调签名后,触发对应的构建任务;
- 构建任务先从GitLab拉代码,回到Arbess的工作目录;
- 执行Maven编译、测试、打包,生成带版本号的jar产物;
- 将jar产物分发给集群内每一台目标主机;
- 各主机按照滚动策略依次拉起新版本,做健康检查;
- 全部主机新版本正常后,流水线标记为成功;任一节点异常则触发回滚。
这里面最核心的设计思路是“控制与执行分离”。Arbess只做调度和下发指令,真正的软件安装、进程管理、健康检查脚本在目标主机上执行。这种设计和Kubernetes里控制面与节点分离的思路是相通的,好处是Arbess本身不会成为单点故障——就算它宕机了,已经在主机上运行的Java进程不受影响,最多只是不能继续发新版。
用一个生活化的类比:GitLab是仓库管理员,负责登记货物入库;Arbess是调度台,负责排计划、派车、盯进度;每台主机像是独立的装配车间,收到货后自己完成组装和自检,自检结果再反馈给调度台。调度台不亲自去拧螺丝,但每一颗螺丝拧没拧到位它都知道。
1.3 技术选型背后的取舍逻辑
这套方案里有两个关键选型决定,值得多说几句。
第一个是“为什么用GitLab而不是其他代码平台”。GitLab是自托管、开箱即用的,它对Webhook、Deploy Token、分支保护这些CI/CD配套能力的支持非常成熟,而且私有化部署时数据不出内网,对很多公司来说这是硬性要求。相比GitHub的某些功能需要付费,GitLab Community Edition已经能把代码托管和触发通道这件事干得很完整。
第二个是“为什么构建和部署要分开,而不是让每台主机各自构建”。很多人一开始会想:既然每台主机都有JDK和Maven,那我让每台主机自己去GitLab拉代码、自己编译不就行了?省掉分发这一步。这个想法在几台机器的时候确实可行,但集群规模一大就出问题:每台机器都要一份完整的maven依赖库,磁盘占用翻倍;每台机器构建出来的包理论上一致,但实际会因为依赖缓存差异产生偶发的“灵异问题”;更关键的是,分发过程本身是一个天然的“发布前检查点”——在同一个地方集中构建、统一推送,方便做产物校验和版本管理。构建一次,分发多处,是性价比最高的方式。
还有一个容易被忽略的选型点:部署脚本不用做成Agent常驻进程,而是用Arbess通过SSH远程执行。Agent方案需要每台机器装额外的客户端,还要维护Agent自身的升级和心跳,复杂度提升不少。SSH方案简单直接,OpenSSH本身在所有Linux发行版上都有,一个免密配置就能打通,故障面小很多。对小团队和中等规模的集群来说,SSH执行脚本已经足够稳。
2. 环境准备与基础搭建:把GitLab和Arbess拉通
2.1 基础环境清单与网络规划
在动手之前,先把机器规划清楚,别等到装了一半发现内网端口互不相通,那才是最浪费时间的事。我这里给一套经过实测的参考配置,按最小规模来。
| 角色 | 建议数量 | 建议配置 | 基础软件 |
|---|---|---|---|
| GitLab服务器 | 1台 | 4核8G,100G磁盘 | GitLab CE、Docker(可选) |
| Arbess调度机 | 1台 | 4核8G,200G磁盘 | JDK17、Maven 3.8+、MySQL或H2数据库 |
| 应用集群主机 | 初始3台 | 2核4G起,按业务量加 | JDK17、OpenSSH、Java应用运行环境 |
| 负载均衡器 | 1台 | 2核4G起 | Nginx或HAProxy |
网络规划上,重点关注三件事。
第一,GitLab和Arbess之间要能双向通信。Arbess要能访问GitLab的HTTP端口拉代码,GitLab要能回调Arbess暴露的Webhook端口。回调一般走HTTP 8080或者自定义端口,注意防火墙别把这个端口忘了。
第二,Arbess到各目标主机的22端口必须畅通。整个部署行为都依赖SSH,如果中间有防火墙策略,记得把Arbess这台机器的IP加白名单。
第三,目标主机之间最好也能互通,因为有些回滚脚本可能需要从别的节点拷贝产物,互不相通的话就得让Arbess中转,反而复杂。前期规划时把这些通道理清楚,后面会顺很多。
2.2 Arbess平台安装初始化
Arbess作为一套轻量的流水线调度平台,安装过程不复杂,核心是配好Java运行环境和数据库。
第一步先装JDK和Maven。JDK直接上17,这是目前Java生态里比较稳妥的长期支持版本,Maven用3.8以上。装完记得把Maven的settings.xml配好阿里云镜像或者公司内网私服地址,这一步非常关键,不然后面每次构建都去中央仓库下载,慢到你怀疑人生。
第二步把Arbess的服务端包解压到一个固定目录,比如/opt/arbess。它的启动方式就是一个Java进程:
java -jar arbess-server.jar --server.port=8080 \ --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/arbess \ --spring.datasource.username=arbess \ --spring.datasource.password=你的密码数据库可以用MySQL,也可以用轻量的H2。团队刚起步、不想单独维护数据库的话,H2更省事;如果后面要多人同时使用、保留大量历史记录,MySQL更靠谱。我建议一开始就用MySQL,免得日后迁移。
启动之后,打开Arbess的管理控制台,第一件事是配置一个“构建环境”。这个环境里指定Maven的路径、JDK的路径、默认的工作目录。Arbess执行构建时,会在这个环境里自动调用mvn命令,所以Maven和JDK版本一定要固定,不能今天17明天8,否则构建环境统一这件事就名存实亡了。
2.3 GitLab项目管理与令牌配置
GitLab这边要做的事,是把代码仓库准备好,并且给Arbess开一个“最小权限”的访问通道。
在GitLab上创建一个项目,比如叫做order-service,Java项目代码就推到这个仓库里。为了不让Arbess每次拉代码都输入账号密码,推荐用Deploy Token。在项目设置里找到Repository,新增一个Deploy Token,勾选read_repository权限即可。不要图方便去用个人Access Token,那玩意儿权限太大,而且绑定在某个人的账号下,人离职了整个流水线就没法拉代码了。Deploy Token是项目级的,权限收敛,生命周期独立,是正解。
拿到Token后,回到Arbess控制台,在“代码源管理”里把仓库地址填进去。地址格式类似http://gitlab服务器IP/group/order-service.git,认证方式选Token,把Deploy Token的用户名和密码填上。Arbess拉代码时用的就是这组凭据,平时它只在构建时访问代码,不需要任何写权限。
这个环节有一个容易踩的坑:GitLab默认配置下,请求频率限制可能比较保守,如果后面并发构建多了,偶尔会出现403。遇到这种情况,在GitLab的配置文件里把rate limit的阈值调大就行。先记着,后面排查章节再展开。
2.4 Webhook联动配置:从push到自动触发
流水线自动化的触发器是Webhook,配置时要把GitLab的“代码变化事件”和Arbess的“构建任务”绑定在一起。
登录GitLab项目,进入Settings → Webhooks,在URL里填Arbess接收回调的地址,一般长这样:
http://arbess服务器IP:8080/api/webhook/gitlabSecret Token填一串自己随机生成的字符串,后面Arbess会用这个Token校验回调的真实性,防止别人伪造请求触发构建。触发事件勾选Push events即可,如果你希望合并请求也触发构建,可以额外勾选Merge request events,但初期建议保持简单,只保留Push。
保存之后,GitLab会提供一个Test按钮。点击Test,观察Arbess那边的日志,如果出现类似“webhook received, triggering pipeline”的记录,说明链路已经通了。这里注意,测试时要确认Arbess的8080端口对GitLab服务器是开放的,很多Webhook配了半天没反应,最后发现是防火墙把端口挡了。
Webhook通了之后,在Arbess里创建一个流水线任务,把代码源、分支名称(比如main或develop)、构建命令、部署目标等信息都填进去,然后手动执行一次,验证整条流水线能跑通。这一步先不追求多主机部署,只要能把代码拉下来、构建出产物,就算成功了。
3. 核心流水线实现:构建、分发与集群部署
3.1 流水线阶段拆解:拉取、编译、打包、分发、部署
Arbess里的一个完整流水线任务,通常拆成五个阶段。每个阶段之间是串行依赖关系,前一个失败,后面的不会执行。阶段划分如下:
| 阶段 | 核心动作 | 产出 |
|---|---|---|
| 1. Pull | 从GitLab指定分支拉取最新代码 | 工作目录下的源码 |
| 2. Build | 执行Maven编译和打包 | target下的jar包 |
| 3. Test | 运行单元测试(可选但强烈建议) | 测试报告 |
| 4. Publish | 将jar包按版本号归档到产物库 | 带版本标记的产物 |
| 5. Deploy | 分发jar包到各主机并滚动重启 | 全部节点新版本就绪 |
这五个阶段里,Pull和Build是纯开发视角的,跟本地开发差不多;Publish和Deploy是运维视角的,约束了产物如何流转。把这五步串成流水线,本质上是把开发到上线之间的所有动作标准化。
阶段命名建议清晰直白,比如prepare-source、mvn-package、publish-artifact、deploy-all。Arbess的控制台里可以设置阶段超时时间,比如Pull阶段超过5分钟就失败,Deploy阶段超过10分钟就失败。超时失败是非常必要的保护措施,不然一个卡死的任务会一直占着构建节点。
3.2 Maven构建与产物管理的细节
构建这一步,命令本身不复杂,但有几个细节直接决定流水线好不好维护。
先看核心命令:
mvn clean package -DskipTests=false -Dmaven.test.failure.ignore=false-DskipTests=false的意思是跳过测试的开关,把skipTests设为false,就是强制测试不跳过;-Dmaven.test.failure.ignore=false表示测试一旦失败,构建直接失败,整个流水线中止。很多团队为了“省事”把这两项设成了跳过,结果代码里全是红线自己还不知道,等线上炸了才后悔。测试阶段是流水线里成本最低的故障拦截点,千万别省。
为了保证产物可追溯,建议在构建命令前后把版本号算出来。Arbess环境变量里可以读取Git提交号(Commit SHA)和构建时间。用它们拼出产物文件名:
ARTIFACT_NAME="order-service-$(git rev-parse --short HEAD)-$(date +%Y%m%d%H%M%S).jar" cp target/original.jar /opt/arbess/release/${ARTIFACT_NAME}这样产物库里的每个jar包都有唯一的标识,回滚时只要按名字找历史版本就行。
产物管理还要注意保留策略。Arbess的产物目录建议保留最近5~10个版本,超出后自动清理。线上出问题需要回滚时,找到上一版本或者上上版本就够了,保留太多历史包纯粹浪费磁盘。清理逻辑可以简单粗暴:保留最近N个文件,其余删除。用cron脚本定时跑一次即可。
3.3 多主机集群部署:SSH免密、分发策略与滚动发布
多主机部署是整个方案里最有技术含量、也最容易翻车的部分。先说SSH免密配置。
Arbess调度机要能以指定用户身份免密登录所有目标主机。在Arbess机器上执行:
ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa ssh-copy-id deploy_user@主机IP每一台目标主机都执行一次ssh-copy-id,完成后测试:
ssh deploy_user@主机IP "hostname"能正常返回主机名就说明通了。这里提醒一句:部署用的账号别用root,更别直接用root登录跑应用。建议每台机器创建一个专用用户,比如deploy,给予应用目录的读写权限,然后用sudo去执行受控的重启命令。权限收得越紧,线上出事故时的爆炸半径越小。
SSH通了之后,分发策略就简单了。Arbess把构建好的jar包用scp传到每台主机的固定目录,比如/app/order-service/releases/,这个目录下每个版本一个子目录,目录名就是版本号。然后执行滚动发布脚本。
滚动发布的核心思路是“逐台替换,全程不掉线”。具体步骤:
- 在负载均衡器上把当前要发布的主机从upstream里摘掉,让新流量不再进入这台机器;
- 检查这台机器上正在运行的旧版本进程,记录PID和当前启动命令;
- 停止旧进程,把旧jar包备份到/releases-backup目录;
- 把新jar包放到运行目录,用记录的启动参数启动新进程;
- 等待进程启动完成,执行健康检查(比如curl /actuator/health),确认返回UP;
- 把该主机重新挂回负载均衡;
- 重复上述动作,直到集群内所有主机都完成切换。
这里有个很容易被忽略的点:新版本启动后不要急着挂回负载均衡,一定要等健康检查过了再挂。如果新版本起不来,这台机器就一直摘着,等回滚到旧包后再挂回。这套滚动策略用一句话概括就是“先摘流量、再杀进程、换包重启、验活通过、恢复流量”。
3.4 健康检查与自动回滚机制
健康检查是整个部署链路里最不能省的一环,否则“自动化部署”就变成了“自动化制造故障”。Java项目如果是Spring Boot,最简单的健康检查就是Actuator接口:
curl -fsS http://127.0.0.1:8080/actuator/health这个接口正常时返回{"status":"UP"}。在Arbess的部署脚本里,检查逻辑不只是一次curl,而是带重试的多次探活:
for i in {1..15}; do code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/actuator/health) if [ "$code" == "200" ]; then echo "health check ok" exit 0 fi sleep 5 done echo "health check failed" exit 115次重试、每次间隔5秒,等于给新应用最多75秒的启动时间。Java进程在低配机器上启动确实可能慢,这个窗口要留足。
健康检查失败时,自动回滚的逻辑要接上。回滚的思路和发布是镜像的:把当前这台机器再次从负载均衡摘掉,停止新进程,从备份目录恢复上一个版本的jar包,用原来的启动参数启动,再次做健康检查,通过后挂回。整个过程在Arbess里可以封装成一个rollback任务,触发条件就是健康检查失败。
打个比方,这个过程就像换汽车轮胎:先把车稳稳架起来(摘流量),拆下旧胎(停旧进程),换上新胎(放新包),转动检查(健康检查),没问题了才落地放行(恢复流量)。万一新胎有问题,立刻换回旧胎,再检查一遍。发布的本质不是“换了新零件”,而是“保证行驶始终安全”。
4. 参数调优与问题排查:上线后更重要的那部分
4.1 关键配置项参考表
流水线能跑起来只是第一步,真正要长期稳定运行,有几个参数值得提前设置好。直接给一份参考值:
| 配置项 | 建议值 | 说明 |
|---|---|---|
| 流水线超时时间 | 30分钟 | 超过认为失败,避免任务卡死 |
| 构建阶段超时 | 10分钟 | Maven第一次构建依赖较多,预留宽裕窗口 |
| 部署阶段超时 | 15分钟 | 多主机+健康检查,单台预留3~5分钟 |
| SSH连接超时 | 10秒 | 防止主机失联时脚本长时间挂起 |
| 健康检查重试次数 | 15次 | 结合5秒间隔,覆盖应用启动慢的情况 |
| 日志保留天数 | 30天 | 过期日志自动清理,防磁盘满 |
| 构建产物保留版本数 | 10个 | 兼顾回滚余地和磁盘成本 |
| 并发构建数 | 2 | 避免同时多个构建把机器资源打满 |
4.2 常见坑点与排查实录
这套方案跑下来,遇到的问题是扎扎实实的,我挑几个最常见的写在这里,基本囊括了前期最可能踩的坑。
第一个坑是Webhook触发不成功。表现是push了代码,Arbess任务列表里没有任何反应。排查顺序先看GitLab的Webhook历史记录,里面有每次回调的请求和响应详情。如果显示连接超时或者拒绝连接,基本就是防火墙或者Arbess端口没开;如果显示能连上但返回非200,检查Secret Token是否一致。签名校验那步别嫌麻烦,一定要做,否则内网里随便一个请求都能触发你的构建任务,万一被恶意刷构建,机器直接被打爆。
第二个坑是Maven构建时依赖下载超时。这个在首次构建时尤其常见。解决思路有两个:一个是在maven的settings.xml里配置镜像仓库,把中央仓库的访问走国内镜像或者公司私服;另一个是在Arbess的构建环境里把本地仓库目录固定下来,比如/opt/maven-repo,并设为共享路径,这样第二次构建时大部分依赖都命中了缓存,速度能有数量级的提升。
第三个坑是SSH执行部署命令时提示Host key verification failed。这是因为Arbess机器的~/.ssh/known_hosts里没有目标主机的指纹。自动化的场景下,如果不想每次交互确认,可以给SSH配置加一行:
StrictHostKeyChecking no放在Arbess机器的~/.ssh/config里,限定只对集群网段生效。安全洁癖者可能觉得放宽不好,但内网环境里这算务实的选择,把账号权限管住就好。
第四个坑是发布后一部分机器服务异常但没被发现。很多新人会把健康检查配置成只检查端口通不通,比如nc -vz 127.0.0.1 8080,结果应用进程虽然起来了,但内部状态根本不可用,端口照样通,等于白查。健康检查一定要打到业务接口上,Spring Boot的actuator/health是底线,更严格一点可以检查一个只读的DB查询接口,确保“依赖的资源也正常”。
第五个坑是滚动发布过程中负载均衡出现5xx。前面说了要先摘流量再发布,但很多人第一次操作时容易忘记在负载均衡上摘节点,或者摘了但没有等正在处理的请求结束就直接kill进程。建议摘节点后等待30~60秒(根据你的请求平均耗时来定),再停止旧进程,这样才能最大限度避免5xx。
4.3 性能调优与日常维护建议
流水线稳定运行一段时间后,关注的焦点会从“能不能跑”变成“跑得好不好”。这里有几个日常维护的点,亲测有效。
构建缓存是最值得投入的优化点。Arbess的构建环境要尽量固定,本地maven仓库不要动不动就clean。只要代码依赖不出现大的版本变更,第二次起的构建速度基本能控制在两分钟左右。定期给仓库做个瘦身,比如每周清理一次未使用的快照依赖。
Arbess调度机上的日志要配置滚动切分。Java进程的输出、部署脚本的执行日志、Webhook的访问日志,都按天归档,保留30天就好。不然跑三五个月,磁盘被日志塞满,Arbess自己先挂掉,那就尴尬了。我给每个部署脚本开头都加上时间戳,方便后续按时间线排查问题。
还有一个小建议:每台目标主机上的旧版本进程,别急着删。保留最近两个版本,配合负载均衡摘节点的方式,一旦线上需要临时切回旧包,手动操作也能在几分钟内完成。自动回滚脚本毕竟是程序,也会遇到程序解决不了的场景(比如数据库迁移不兼容),留一手总没错。
写在最后的一点经验
这套Arbess+GitLab的自动化部署方案,前前后后我调整过很多次,最大的体会是:自动化的价值不在于省去敲那几行ssh命令,而在于把“发布”从碰运气式的操作变成可重复、可预期、可回滚的流程。流水线跑通的那天确实很爽,但真正让我睡踏实的是健康检查和回滚脚本写好之后——那意味着就算新版本上线出问题,系统也知道怎么自己“退回来”。
最后再分享一个小技巧:部署脚本里的版本号、端口、健康检查地址这些常量,建议全部抽到Arbess的任务变量里,不要硬编码在脚本中。这样以后改端口或者加主机,只需要在控制台改配置,不需要动脚本本身。别小看这一步,等你维护了五六个项目的流水线之后,就知道这有多重要了。