我前阵子帮一个前端团队重构交付流程React 项目每次发版都靠人肉上线本地npm run build再scp把整个dist目录传到云主机登录服务器后手动解压、覆盖、重启 Nginx。听起来能跑实际上项目越来越大以后一次发布要碰七八个文件漏一个就是线上事故。我这次直接用 Arbess GitLab 把“代码提交 → 自动构建 → 主机部署”整条链路打通跑了三个多月基本没出过岔子。这篇文章就把这套方案从选型到落地、再到排障的完整过程写出来适合正在做 DevOps 改造、手上又有一批传统主机要维护的团队参考。1. 为什么不是 Jenkins也不是纯 GitLab CIArbess 的定位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 在这点上更直观。我把三者的特点整理成了一张表方便你们按团队情况判断对比项JenkinsGitLab CIArbess上手成本较高插件多但配置繁琐中熟悉 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 TokenScope 选择read_repository读取代码触发构建必选。write_repository如果需要自动打 Tag 或写构建产物回仓库才需要。api部分 Webhook 自动注册场景需要如果只是手动配置 Webhook可以不勾。生成后的 Token 只显示一次务必复制保存到 Arbess 的凭据库中。这个 Token 的权限控制一定要收紧避免泄露后被恶意拉取仓库代码或篡改内容。仓库方面建议把 React 项目的代码结构做一次梳理。我的习惯是至少分出三个分支环境develop对应测试主机、main对应预发、Git Tagv*对应生产。这样 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 deployweb01 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/ deployweb01:/var/www/app # 远程检查 Nginx 配置并 reload ssh -i /opt/arbess/keys/deploy_key -p 22 deployweb01 \ 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 的 WebhookURLhttp://Arbess-IP:8888/api/webhook/gitlabSecret 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 deployweb01 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 组合的 keycache: 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... deployarbess /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 这套组合让我们把前端项目的托管、构建、部署彻底拉到了同一套流水线上后续我还在计划把后端服务的编译发布也接进来用同一套执行节点和凭据体系管理减少团队内部各搞一套工具带来的维护成本。如果你也在梳理自己的主机部署流程希望这篇文章的选型思路和坑位记录能帮你少走几步弯路。 SEO 优化官网定制响应式建站教育培训建站