网站 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