我前阵子帮一个前端团队重构交付流程,React 项目每次发版都靠人肉上线:本地npm run build,再scp把整个dist目录传到云主机,登录服务器后手动解压、覆盖、重启 Nginx。听起来能跑,实际上项目越来越大以后,一次发布要碰七八个文件,漏一个就是线上事故。我这次直接用 Arbess + GitLab 把“代码提交 → 自动构建 → 主机部署”整条链路打通,跑了三个多月基本没出过岔子。这篇文章就把这套方案从选型到落地、再到排障的完整过程写出来,适合正在做 DevOps 改造、手上又有一批传统主机要维护的团队参考。
1. 为什么不是 Jenkins,也不是纯 GitLab CI:Arbess 的定位
1.1 从一次凌晨上线的惨痛经历说起
那次事故让我下定决心改造流程。当时一个 React 中台项目要在凌晨发版,开发在本地构建一切正常,但打包出来的文件传到服务器后,页面直接白屏。查了半天发现是构建时用了本地的.env.production,里面配的后端地址是内网 IP,服务器上的 Nginx 转发根本访问不到。更离谱的是,这个错误在第二天白天又复现了一次,因为另一个同事用的是他自己的环境变量文件。
这类问题的根源不是“谁操作失误”,而是整套发布流程没有标准化:构建在哪台机器、用什么 Node 版本、读取哪份环境配置、产物怎么上传、部署到哪个目录,全靠个人记忆。人肉流程只要执行两次以上,一定会出偏差。
所以我定了一个目标:所有构建必须发生在统一的 CI 平台上,开发只负责把代码推到 GitLab 指定分支,剩下的构建、打包、上传、部署全部自动化。
1.2 Arbess、Jenkins、GitLab CI 三者的取舍
很多团队一提到 CI/CD 就想到 Jenkins,或者直接用 GitLab 自带的 CI Runner。这两种方案我都经历过,这次选 Arbess 是经过一轮对比的。
先看 Jenkins。它的插件生态确实庞大,但对前端项目来说,很多插件是用不上的,而且 Jenkins 的主界面和权限模型都偏重,配一个简单的“构建 + 部署”任务要绕不少路。再加上团队里并不是每个人都有精力维护 Jenkins 的 Master 节点、插件升级和备份,Jenkins 对我来说有点重了。
再看 GitLab CI。它和 GitLab 集成度最高,.gitlab-ci.yml写起来也灵活,但有一个现实问题:我们这次要部署的目标是一批存量主机,不是 Kubernetes,也不是云原生环境。GitLab Runner 的部署方式通常需要每个执行节点都有构建环境,而我只想在一个集中平台上管理“构建产物要送到哪台主机”,Arbess 在这点上更直观。
我把三者的特点整理成了一张表,方便你们按团队情况判断:
| 对比项 | Jenkins | GitLab CI | Arbess |
|---|---|---|---|
| 上手成本 | 较高,插件多但配置繁琐 | 中,熟悉 YAML 即可 | 低,任务流可视化,配置直接 |
| 与 GitLab 集成 | 需要额外开发 Webhook | 原生集成 | Webhook 对接,支持 Secret Token |
| 主机部署能力 | 依赖 Publish Over SSH 等插件 | 需要自己写 runner 脚本 | 内置 SSH/制品推送,天然面向主机 |
| 资源占用 | Master + Slave,较重 | Runner 按项目分布,资源可控 | 轻量,单节点可跑 |
| 适合场景 | 复杂企业级流水线 | 云原生/容器化程度高的团队 | 传统主机部署为主的前后端项目 |
最终选择 Arbess,核心原因就一句:它把“构建”和“部署”两件事拆得足够清楚。构建归构建,部署归部署,每一步的执行状态、日志、产物都留得明明白白,不用去翻一堆插件日志才能定位问题。
1.3 这套组合的整体数据流
整套链路的数据流,用文字描述大概是这样的:
- React 开发分支或 Tag 更新。
- GitLab 仓库触发 Webhook,把事件通知到 Arbess 服务端。
- Arbess 检测到对应的项目与分支策略,启动一个新的构建任务。
- 构建节点拉取代码,执行依赖安装、单元测试、产物打包。
- Arbess 将
dist目录作为构建产物归档,并记录构建编号。 - 部署阶段通过 SSH 连接到目标主机,用 rsync 将产物增量同步到 Web 目录。
- 部署完成后执行远程命令检查 Nginx 配置并 reload,最后做一次 HTTP 探活。
这里的“构建节点”未必是独立机器,Arbess 自身就能充当执行节点,对于前端项目来说,构建环境只需要 Node、npm 和 Linux 基础工具,负担非常小。当然,如果你希望构建和生产环境严格隔离,也可以让 Arbess 把任务分发到独立的构建机上,后面我会讲到配置思路。
2. 环境准备:GitLab 社区版与 Arbess 服务端搭建
这一章节是整个方案的底座,很多人第一步就卡在环境搭不上。我按实际部署过程把关键步骤写出来,包含 Docker 部署 GitLab 社区版、Arbess 安装初始化、以及 GitLab 侧的 Access Token 准备。这三块搞定以后,后面接入 React 项目就是水到渠成的事。
2.1 Docker 快速部署 GitLab 社区版
我们用的是 GitLab 最新社区版,部署方式选了 Docker,因为最省心,升级也方便。服务器建议至少 4GB 内存,2 核起,否则 GitLab 跑起来会比较吃力。这里给一份我实际使用的docker-compose.yml参考:
version: '3.6' services: gitlab: image: gitlab/gitlab-ce:latest container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url 'https://gitlab.example.com' gitlab_rails['gitlab_shell_ssh_port'] = 2222 prometheus_monitoring['enable'] = false ports: - '443:443' - '80:80' - '2222:22' volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/logs:/var/log/gitlab - /srv/gitlab/data:/var/opt/gitlab几个细节说明一下。SSH 端口我映射成了 2222,避免和宿主机已有的 22 端口冲突,GITLAB_OMNIBUS_CONFIG里的gitlab_shell_ssh_port也要同步改成 2222,否则克隆仓库时给的地址不对。如果你的 GitLab 需要公网访问,记得把域名 DNS 解析做好,并用 Nginx 在外层做 HTTPS 终止,external_url保持和你最终访问地址一致,否则 Webhook 回调地址会生成错。
启动完成后,第一次访问会要求设置 root 密码。之后在 Admin Area 里可以关闭用户注册,避免公网环境被乱注册账号。这个操作很关键,尤其是你的 GitLab 暴露在公网时。
2.2 安装并初始化 Arbess 服务
Arbess 的安装我没有用 Docker,直接下载二进制包部署在单独的目录,因为要长期跑,放到systemd里管理比较稳。官网下载对应平台的压缩包后,解压到/opt/arbess,执行./arbess-server start就会默认监听8888端口。
初始化阶段要做三件事:
- 创建管理员账号,首次访问网页会引导你完成。
- 配置构建环境,Arbess 执行节点上要提前装好 Node.js。我建议直接用 nvm 安装 Node 20 LTS,并把 npm 镜像源切到国内源,否则拉依赖的速度会让你抓狂。
- 添加 GitLab 集成,在 Arbess 的管理后台找到“代码源 / 集成”配置项,填入 GitLab 地址和 Access Token。
这块看起来简单,但我必须要提醒:Arbess 默认监听端口和回调地址要在防火墙和安全组里放行。很多团队卡在“Webhook 显示成功但任务没触发”,十有八九是 Arbess 的 8888 端口没对 GitLab 服务器开放。
2.3 在 GitLab 侧创建 Access Token 和项目仓库
GitLab 的 Access Token 是让 Arbess 能读取仓库信息、拉代码的关键凭证。路径是:用户头像 → Edit Profile → Access Tokens。
我通常会建一个专门的机器人账号,不要用 root 的个人 Token。给这个账号一个最小权限,然后创建项目 Access Token,Scope 选择:
read_repository:读取代码,触发构建必选。write_repository:如果需要自动打 Tag 或写构建产物回仓库才需要。api:部分 Webhook 自动注册场景需要,如果只是手动配置 Webhook,可以不勾。
生成后的 Token 只显示一次,务必复制保存到 Arbess 的凭据库中。这个 Token 的权限控制一定要收紧,避免泄露后被恶意拉取仓库代码或篡改内容。
仓库方面,建议把 React 项目的代码结构做一次梳理。我的习惯是至少分出三个分支环境:develop对应测试主机、main对应预发、Git Tag(v*)对应生产。这样 Arbess 在配置分支策略时就会非常清晰。
3. React.js 项目构建任务:从 package.json 到流水线脚本
环境就绪以后,最核心的部分就是把 React 项目的构建流程写进 Arbess。这一章我会从“统一 Node 版本和依赖锁定”开始,然后给出构建和部署两个阶段的配置参考。
3.1 先治本:统一 Node 版本和依赖锁定
如果你在自动化构建之前没有先统一 Node 版本,后面一定会被奇奇怪怪的问题折磨。比如本地用 Node 18 开发一切正常,CI 节点上装的是 Node 16,某个依赖的engines字段直接报错,或者构建出的产物出现了语法兼容问题。
最稳妥的做法有两件事。第一,在项目根目录加.nvmrc文件,内容写20,这样无论是开发机还是 CI 节点,都能通过 nvm 自动切换版本。第二,使用package-lock.json锁住依赖,CI 上安装依赖时使用npm ci而不是npm install。npm ci会完全按照 lock 文件安装,不会自动升级任何依赖,构建可重复性大幅提升。
还有一个细节容易忽略:确保构建环境里配置了正确的 npm 镜像源。可以在流水线脚本里显式执行:
npm config set registry https://registry.npmmirror.com这样做的好处是构建环境不受个人.npmrc文件影响。
3.2 流水线第一阶段:依赖安装与构建
在 Arbess 里新建一个项目任务,流水线我使用了 Pipeline 模式,整体分两个阶段:build和deploy。构建阶段的参考配置如下:
stages: - build - deploy build: stage: build image: node:20-alpine script: - node -v - npm -v - npm ci - npm run build:prod artifacts: path: - dist/ expire_in: 7 days variables: NODE_ENV: production这里有两个点值得展开。npm run build:prod对应的是package.json里我规范好的脚本,一般会在内部执行react-scripts build或 Vite 对应的vite build。构建前必须保证.env.production这一类环境变量文件是提交进仓库的,或者由 Arbess 在构建前动态注入。如果某个环境变量只存在于某个同事的本地机器上,那这个同事一旦忘记配置,自动化构建就会原地爆炸。
artifacts配置把dist目录设为构建产物,并设置 7 天保留期。这样做有两点好处:一是部署阶段只需要取这份产物,不需要重新打包;二是万一构建日志丢失,还能从产物目录手动下载上次的包排查问题。
3.3 流水线第二阶段:SSH + rsync 完成主机部署
构建完成之后,部署阶段做的事情是把dist里的文件传到每台目标主机。这里我选择 rsync 而不是 scp,因为 rsync 支持增量同步,上传速度更快,还能在同步时排除掉不需要的文件。
部署任务的脚本类似这样:
# 先备份上一次的发布版本 ssh -i /opt/arbess/keys/deploy_key -p 22 deploy@web01 "cp -r /var/www/app /var/www/app_backup_$(date +%Y%m%d%H%M%S)" # 同步构建产物到目标目录 rsync -avz --delete --exclude='*.map' \ -e "ssh -i /opt/arbess/keys/deploy_key -p 22" \ dist/ deploy@web01:/var/www/app # 远程检查 Nginx 配置并 reload ssh -i /opt/arbess/keys/deploy_key -p 22 deploy@web01 \ "nginx -t && systemctl reload nginx"这里说几个关键点。--delete的作用是让目标目录和构建产物严格保持一致,避免旧的 JS/CSS 文件残留在服务器上,造成“改了半天线上还是老代码”的假象。--exclude='*.map'是排除 sourcemap 文件,如果你不想让线上源码映射泄露,这个排除项最好保留。
SSH Key 的管理上,我强烈建议为部署单独生成一对 Key,公钥放到目标主机的deploy用户authorized_keys中,私钥存放在 Arbess 的凭据库或者本地密钥目录,权限设为600。不要使用 root 账号直接部署,因为一旦私钥泄露,相当于服务器完全暴露。后面我会单独讲安全这部分。
3.4 手工执行任务与产物校验
自动化流水线配好以后,建议先不要直接走 Webhook 触发。我习惯在 Arbess 的任务详情里先手动执行一次,把整条链路跑通。手工执行时,要人工检查这几个指标:
- 构建阶段日志里 Node 版本是否为预期版本。
npm ci是否无报错,依赖安装时间是否在合理范围。- 构建产物的大小、关键文件(如
index.html、static/js/main-*.js)是否生成。 - 部署完成后,通过 curl 请求目标站点,确认首页返回
200,并且 HTML 引用的静态资源能正常加载。
这一步通过以后,再接 Webhook 自动触发,问题排查范围会小很多。
4. 自动化触发与版本回滚:让每一次发布都可追溯
自动构建跑通只是第一步,真正让这套系统在团队里推广起来,需要解决两个问题:一是代码一推就自动触发,二是出问题时能快速回滚。这章专门聊这两件事。
4.1 Webhook 触发配置
GitLab 项目里进入Settings → Webhooks,配置一个指向 Arbess 的 Webhook:
- URL:
http://<Arbess-IP>:8888/api/webhook/gitlab - Secret Token:自定义一个随机字符串,并填写到 Arbess 代码源配置中,避免别的仓库恶意触发任务。
- 触发事件:我勾选了
Push events和Tag push events。
配置完成后,Webhook 列表会有一条测试记录。GitLab 的“Test”按钮会发送一条测试事件,这时去 Arbess 看任务列表,如果出现了对应任务,说明链路已经通了。
这里有个经验:Webhook URL 不要用 localhost,用其他机器能访问到的实际地址或域名。之前我见过有人把 URL 填成http://127.0.0.1:8888,结果 GitLab 服务器上访问的当然是 GitLab 自己,Arbess 压根没收到任何请求。
4.2 分支与标签策略控制
生产环境不能被随便一个 push 就触发发布,所以我给 Arbess 的项目配置了分支/标签过滤规则。大致逻辑如下:
- push 到
develop分支时,自动执行测试环境构建与部署。 - push 到
main分支时,只做构建和产物归档,不自动部署生产,改为人工确认后部署。 - 创建
v*格式的 Tag 时,自动构建并部署生产主机。
在 Arbess 里,这个规则通常体现为“分支条件”配置,比如:
deploy: stage: deploy only: - main - /^v.*$/这里的only条件就保证了只有符合策略的事件才会走到部署阶段。如果团队里有喜欢直接在main上随手 push 的人,这套策略能拦住大部分误发布。
顺带提一句,如果你想在合并请求(MR)时也做自动构建验证,可以在 GitLab 的 Webhook 里增加Merge Request Events,并在 Arbess 流水线里增加一个test阶段,只跑单元测试和构建,不部署。这样既能在 CR 阶段发现构建问题,又不会频繁触发主机更新。
4.3 版本归档与一键回滚
发布最担心的就是出新版本后出现严重问题,需要立刻回滚。人肉回滚的经典操作是去服务器上翻备份目录,找到上一个版本再手动覆盖回来。这个过程慢且容易出错。
我在部署脚本里加了两个机制。第一个是备份保留机制:每次部署前,把当前线上目录复制到/var/www/app_backup_时间戳,并且只保留最近 5 个备份,脚本如下:
ssh deploy@web01 "cd /var/www && \ ls -dt app_backup_* | tail -n +6 | xargs -r rm -rf; \ cp -r app app_backup_$(date +%Y%m%d%H%M%S)"第二个是回滚任务:在 Arbess 里单独建一个项目“React应用回滚”,输入要回滚到哪个备份目录,执行将对应备份目录整体覆盖回app目录,再 reload Nginx。整个操作在 Arbess 页面上点一下即可,不需要登录服务器敲命令。
有了这两个机制,即使某个版本上线后 1 小时才被反馈出问题,也能在 2 分钟内回到上一个稳定版本。
5. 实战中的坑:Webhook 超时、构建缓存、rsync 假更新、SSH 权限
这套方案我已经跑了挺久,中间也踩过不少坑。下面这几个问题比较典型,我把排查链路写出来,供参考。
5.1 GitLab Webhook 超时导致“没触发”
现象:开发 push 代码后,Arbess 没有任何反应,但是 GitLab 里 Webhook 列表显示请求成功。
排查思路:
- 在 GitLab Webhook 列表点击“Test”,看系统是否返回
200。 - 如果返回超时,检查 Arbess 所在服务器的防火墙和安全组,尤其确认 8888 端口是否对 GitLab 服务器开放。
- 如果返回 200 但任务没触发,检查 Arbess 的日志,确认请求是否到达。
- 如果请求到了但没匹配到任务,检查 Webhook 的 Secret Token 是否与 Arbess 配置一致。
- 有些版本的 GitLab 对出站请求默认有超时限制,需要在 Admin Area 里调整出站请求的超时时间。
我之前遇到过一次诡异情况:测试事件返回 200,但实际 push 事件从未触发。最后定位到是 Arbess 的 Webhook 路径区分大小写,GitLab 配置的 URL 里多了一个大写字母,事件被 404 丢弃。所以配置时最好直接复制官方文档里的路径,不要手敲。
5.2 node_modules 缓存引发的“灵异构建失败”
现象:构建任务第一次跑成功,之后某一天突然在npm ci阶段报错,提示某个包的 hash 不匹配,重新执行一次又好了。
这个问题的本质是 CI 平台对node_modules或 npm 缓存做了持久化,而 lock 文件更新后,旧的缓存里残留了过期数据。Arbess 的构建节点如果配置了缓存目录,一定要记得给缓存设置“key”。我用的是以项目分支和 lock 文件 hash 组合的 key:
cache: key: "$CI_PROJECT_ID-$CI_COMMIT_REF_SLUG-$(sha256sum package-lock.json)" paths: - node_modules/这样只要 lock 文件不变,缓存命中;lock 文件一变,缓存自动失效,不会出现脏缓存混入构建的问题。如果你不想折腾缓存,直接把node_modules缓存关掉,改用 npm 的全局缓存目录,配合npm ci一样能稳定跑。
5.3 rsync 同步之后线上还是旧页面
现象:部署任务显示成功,rsync 日志显示文件都传了,浏览器访问还是旧页面。
这个问题的坑通常有三个来源:
- 浏览器或 CDN 缓存。静态资源文件名没变(比如
index.html被缓存),页面没有请求新资源。解决方法是让 Nginx 对 HTML 文件禁用强缓存,对带 hash 的 JS/CSS 开启长缓存。 - rsync 没有真正覆盖文件。如果你在 rsync 命令里漏了
--delete,旧的资源文件会残留,但一般不影响使用;可如果是内容没更新,检查是否 rsync 了错误的目录层级。dist/后面没有正确加斜杠,会把dist本身同步进去,造成路径多一层。 - Nginx 没有 reload。很多线上服务器跑的是旧 worker 进程,尤其是 Nginx 配置里用了 open_file_cache 时,文件更新后不能立即体现。部署后必须执行
nginx -t && systemctl reload nginx,而不是简单kill -HUP。
这个问题的排查链路很通用:先在服务器上直接cat /var/www/app/index.html,确认服务器文件确实更新了;如果文件更新了但页面还是旧,就按缓存链路继续查。
5.4 SSH 部署用户权限与免密登录配置
现象:部署任务报Permission denied (publickey)。
绝大多数原因不是 Key 格式问题,而是目标主机的用户目录权限不对。authorized_keys所属用户、.ssh目录权限、deploy用户能否写/var/www/app,这三处任何一环不对都会失败。
我通常这样配置:
# 在目标主机上创建部署用户 useradd -m deploy # 配置 .ssh 目录权限 mkdir -p /home/deploy/.ssh chmod 700 /home/deploy/.ssh touch /home/deploy/.ssh/authorized_keys chmod 600 /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh # 把公钥写入 authorized_keys echo "ssh-rsa AAAA... deploy@arbess" >> /home/deploy/.ssh/authorized_keys # 使用 sudo 或 setfacl 赋予部署目录写权限 chown -R deploy:deploy /var/www/app这里特别强调:不要图省事直接用 root 用户部署。自动化部署的私钥一旦泄露,root 权限意味着整台服务器直接沦陷。用低权限的deploy用户,仅授予需要的目录写权限,是最低限度的安全底线。
6. 安全维护与版本升级:从凭据管理到高危漏洞修复
自动化系统跑起来以后,维护的重点从“怎么构建”变成了“怎么不出事”。这一章聊凭据管理、GitLab 版本升级和日常巡检。
6.1 凭据与密钥的安全存放
整个链条里涉及的密钥太多了:GitLab Access Token、Arbess 访问密码、SSH 私钥、服务器账号密码。如果这些信息直接写死在脚本里,一旦泄露就是连锁反应。
我的做法是:
- Access Token 放入 Arbess 的凭据管理模块,流水线里使用变量引用,不在日志中明文打印。
- SSH 私钥放到独立目录,属主设为运行 Arbess 的用户,权限设为
600。 - GitLab 的 Access Token 设置过期时间,长期不用的 Token 定期清理。
- GitLab 管理员账号启用两步验证,root 密码不要和服务器 root 密码复用。
还有一点容易被忽略:流水线日志里不要打印敏感信息。比如npm install时如果使用了私有仓库的认证地址,日志会完整输出这条 URL,等于把 Token 暴露给了所有能看到日志的人。我在流水线脚本里对这类命令加了set +x或者用--silent参数。
6.2 GitLab 社区版升级与安全补丁
GitLab 是安全漏洞的高发区,尤其是公网部署的实例。我再怎么强调升级都不为过。社区版虽然没有企业版的专属支持,但官方会持续发布安全修复版本,你需要做的就是在 Release 发布后及时跟进。
升级时我的顺序是:
- 先备份 GitLab 的
/etc/gitlab配置目录和数据库,用自带的gitlab-backup create命令打完整备份。 - 拉取新的镜像或包,执行升级。
- 升级完成后执行
gitlab-ctl reconfigure和gitlab-ctl restart。 - 用管理员账号跑一遍核心功能:登录、仓库克隆、Webhook 推送。
前面说的高危漏洞修复方案,本质上就是“及时升级 + 最小暴露”。如果你的 GitLab 不需要公网注册,第一时间关闭注册功能,并且对后台管理地址加访问控制,比等补丁更有效。
6.3 日常巡检清单
每天我会在固定时间看一遍这套系统的核心指标,不需要很复杂,一个脚本加一条定时任务就够了:
- GitLab 服务存活状态和磁盘使用率。
- Arbess 进程状态,以及最近 24 小时构建任务的成功率。
- 部署主机的磁盘空间是否充足,
/var/www/app目录备份是否堆积。 - Nginx 错误日志中是否有大量
404/502。 - 证书是否快要过期,尤其是 HTTPS 证书,这个最容易忘。
这些指标可以汇总成一个简单的巡检脚本,借助 cron 每天跑一次,异常时发送告警通知。自动化系统最怕的不是它出故障,而是出故障后没人第一时间知道。
我从那次凌晨白屏事故里学到的教训是:DevOps 改造的收益并不只是“发布变快”,更是“发布可预测”。团队里任何人、在任何时间点触发构建,得到的产物和部署行为都应该是同一套标准。Arbess + GitLab 这套组合让我们把前端项目的托管、构建、部署彻底拉到了同一套流水线上,后续我还在计划把后端服务的编译发布也接进来,用同一套执行节点和凭据体系管理,减少团队内部各搞一套工具带来的维护成本。如果你也在梳理自己的主机部署流程,希望这篇文章的选型思路和坑位记录能帮你少走几步弯路。