网站 CI/CD 流水线搭建指南:自动化构建、测试与部署
"本地能跑,线上就崩"是每个开发团队都经历过的痛。手动把代码传上服务器,缺一个依赖、漏一个环境变量、忘了重新打包,都是事故现场。CI/CD(持续集成/持续部署)的价值,就是把"构建、测试、部署"这三件事从人肉流程变成代码提交后的自动反应:每次 push 到主干,机器自动帮你跑一遍完整流程,出问题当场报警,而不是等用户来反馈。
CI/CD 的核心价值
| 环节 | 传统方式 | CI/CD |
|---|---|---|
| 构建 | 本地手动构建,版本不一 | 干净环境自动触发 |
| 测试 | 可能遗漏、靠自觉 | 每次必跑,不过不放行 |
| 部署 | SSH 手动上传 | 流水线自动化部署 |
| 回滚 | 翻聊天记录找旧包 | 一键回滚上一个版本 |
最大的隐性收益是"干净环境":CI 每次都在全新的容器里跑,能暴露"我本地装过这个包所以能跑"这类环境差异问题。
流水线各阶段怎么划分
一条典型的网站流水线通常按下面顺序推进,每步都可以独立失败并快速定位:
代码提交 → 依赖安装 → 静态检查 → 单元测试 → 构建打包 → 产物上传 → 部署 → 健康检查
其中"构建打包"和"部署"之间建议显式传递产物(artifact),而不是让服务器重新构建。这样线上跑的一定是测试通过的那个包,避免"服务器环境不同导致行为不一致"。
测试金字塔的落地
网站项目不用一上来就追求 100% 覆盖率,按金字塔分配更现实:底层是大量廉价快速的单元测试,中间是少量集成测试,顶层才是少数端到端测试。对大多数官网和业务站,跑通"lint + 关键模块单测 + 一条冒烟测试"就已经能拦住绝大多数回归问题,剩下的交给人工验收。
GitHub Actions 工作流
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to server
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
SOURCE: "dist/"
REMOTE_HOST: ${{ secrets.DEPLOY_HOST }}
REMOTE_USER: ${{ secrets.DEPLOY_USER }}
TARGET: /var/www/example.com/
注意 deploy 这个 job 用 needs: build-and-test 串起依赖、用 if 限定只在 main 分支执行:这样 PR 合并前只跑测试不部署,主干合并且全绿后才会真正发布。
前端项目 CI/CD 要点
构建缓存
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
缓存 key 用 package-lock.json 的哈希,依赖没变就命中缓存,构建时间能从每次五分钟压到几十秒。
环境变量管理
使用 GitHub Secrets 管理敏感信息,不同环境(dev/staging/prod)配置不同变量。常见的做法是仓库里只放 .env.example,真实值一律走 Secrets。以部署为例,需要在仓库 Settings → Secrets and variables 里预先配置:
SSH_PRIVATE_KEY # 服务器登录私钥
DEPLOY_HOST # 部署服务器地址
DEPLOY_USER # 部署用户
前端项目通常会按环境生成不同配置,例如在构建命令里传入环境名:
- name: Build for production
run: npm run build -- --mode production
env:
VITE_API_BASE: ${{ secrets.API_BASE_URL }}
分支保护
CI 只有和分支保护配合才有意义。在 GitHub 仓库 Settings → Branches 里开启 main 分支保护,勾选 "Require status checks to pass before merging" 并指定 build-and-test job。这样任何没有通过测试的 PR 都无法合并,"测试没跑就上线"在流程层面就不可能发生。
部署策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 直接部署 | 构建后直接上传服务器 | 小型网站 |
| 蓝绿部署 | 新旧版本同时运行,切换流量 | 高可用要求 |
| 滚动更新 | 逐步替换实例 | 集群环境 |
| 金丝雀发布 | 先让部分用户体验新版 | 大型产品 |
对静态站或小业务,直接部署 + 保留上一份发布包的目录就够用,出问题一条命令切回去。比如把发布包部署到 releases/20260713/ 这类带时间戳的目录,再用符号链接指向当前版本:
ln -sfn /var/www/example.com/releases/20260713 /var/www/example.com/current
回滚就是把符号链接指回上一个目录,一条命令完成。
常见问题排查
| 问题 | 原因 | 处理 |
|---|---|---|
| 本地能跑、CI 报错 | 依赖版本不一致 | 用 npm ci 固定 lockfile,避免环境差异 |
| 部署后 502 | 服务器进程没起来 | 检查进程管理与日志,必要时在部署后加健康检查 |
| 密钥泄露 | 私钥进了仓库 | 立即作废并在 Secrets 里替换,回滚历史中的明文 |
| 构建太慢 | 未用缓存 | 配置 actions/cache,命中后能节省大半时间 |
一个落地案例
一个用 Nuxt 搭建的企业官网,团队三人,之前每次发版都是某个人手动 scp 上传再重启。引入 GitHub Actions 后,每天合并 PR 都会自动跑 lint、单测和构建,构建产物直接部署到服务器,回滚只需点一次。半年下来发布频率从每周一次提高到每天两三次,而线上事故从每月两三起降到零——因为问题在合并前就被拦住了,不再依赖某个人的细心。
16IDC 观察
CI/CD 在小项目上的收益刚开始不明显,但流程一旦建立,每次发布的信心和质量都会上一个台阶。建议从简单的 GitHub Actions 工作流开始,先跑通"构建 + 部署",再逐步加入测试、静态分析和自动回滚,别一开始就追求复杂。
参考:GitHub Actions 官方文档 https://docs.github.com/actions