CI/CD 流水线搭建

手动部署是技术债务的温床——"只有我知道怎么部署"、"上次部署忘了跑测试"、"生产环境的环境变量和测试不一样"。CI/CD(持续集成 / 持续部署)流水线把构建、测试、部署自动化,让每次代码变更都经过一致的质量门禁,最终自动或半自动地到达生产环境。本章以 GitHub Actions 为主,构建一条完整的 Nuxt4 CI/CD 流水线。

1. CI/CD 的核心概念

1.1 持续集成 vs 持续部署

概念含义触发时机
CI(持续集成)每次提交自动运行 lint + 类型检查 + 测试Push / PR
CD(持续交付)CI 通过后自动构建部署包,等待手动审批发布Merge to main
CD(持续部署)CI 通过后自动部署到生产环境,无需手动干预Merge to main

大多数团队选择持续交付——自动化到"部署包准备就绪",发布到生产由人工确认。完全的持续部署需要非常成熟的测试覆盖和监控体系。

1.2 流水线设计原则

  • 快速反馈:lint 和类型检查最先执行(最快),测试其次,构建最后
  • 并行执行:独立的检查项并行跑,缩短总时长
  • 失败即停:任何步骤失败都阻止后续步骤,避免浪费资源
  • 幂等性:同一份代码多次构建结果相同

2. GitHub Actions 工作流

2.1 基础流水线

# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
 
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
 
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: npx nuxi typecheck
 
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: npx vitest run --coverage
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage
          path: coverage/
 
  build:
    needs: [lint, typecheck, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: .output/

关键设计:

  • lint / typecheck / test 并行执行——互不依赖,总时长取最慢的一个
  • build 依赖前三个 job——needs: [lint, typecheck, test],任何一个失败都不会构建
  • pnpm cache——actions/setup-node 自动缓存 pnpm store,第二次运行安装依赖只需几秒
  • artifact 上传——构建产物和覆盖率报告保存为 artifact,后续 job 或手动下载使用

2.2 优化:减少重复安装

上面的配置中,每个 job 都要安装依赖。可以用 reusable workflowcache 减少重复:

# 共享的 setup 步骤
setup: &setup
  - uses: actions/checkout@v4
  - uses: pnpm/action-setup@v4
  - uses: actions/setup-node@v4
    with:
      node-version: 20
      cache: 'pnpm'
  - run: pnpm install --frozen-lockfile

或者使用 actions/cache 缓存 node_modules

- uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}

3. 环境变量安全管理

3.1 环境变量的分类

类型示例存储位置
公开配置API 基础 URL、功能开关nuxt.config.ts / .env
构建时密钥Sentry DSN、Analytics IDGitHub Secrets → 构建时注入
运行时密钥数据库密码、JWT Secret部署平台的环境变量

3.2 GitHub Secrets

敏感信息存储在 GitHub Repository Secrets 中,CI 中通过 ${{ secrets.XXX }} 访问:

- run: pnpm build
  env:
    NUXT_PUBLIC_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
    NUXT_SESSION_SECRET: ${{ secrets.SESSION_SECRET }}
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

安全原则

  • Secrets 在日志中自动遮蔽(显示为 ***
  • Fork 的 PR 无法访问 Secrets(防止恶意 PR 窃取)
  • 定期轮转密钥

3.3 多环境配置

# GitHub Environments 配置
jobs:
  deploy-staging:
    environment: staging
    env:
      DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
 
  deploy-production:
    environment: production
    env:
      DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}

GitHub Environments 支持:

  • 不同环境有独立的 Secrets
  • Production 环境可以配置审批人——部署前需要指定人员批准
  • 部署保护规则——等待时间、分支限制

4. 部署流水线

4.1 Vercel 自动部署

Vercel 与 GitHub 深度集成,Push 到 main 自动部署:

# 不需要额外的 GitHub Actions 配置
# Vercel 的 GitHub App 自动检测 push 并部署
# PR 自动创建 Preview Deployment

如果需要在 CI 检查通过后才部署,可以在 Vercel 设置中关闭自动部署,改用 Vercel CLI:

deploy:
  needs: [lint, typecheck, test, build]
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}

4.2 Docker 部署流水线

deploy:
  needs: [lint, typecheck, test]
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: actions/checkout@v4
 
    - name: Login to Container Registry
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
 
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        push: true
        tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
 
    - name: Deploy to server
      uses: appleboy/ssh-action@v1
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USER }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          docker pull ghcr.io/${{ github.repository }}:${{ github.sha }}
          docker compose up -d --no-deps app

4.3 Cloudflare Pages 部署

deploy:
  needs: [lint, typecheck, test]
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: actions/checkout@v4
    - uses: pnpm/action-setup@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
        cache: 'pnpm'
    - run: pnpm install --frozen-lockfile
    - run: pnpm build
    - uses: cloudflare/wrangler-action@v3
      with:
        apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        command: pages deploy .output/public --project-name=my-app

5. 灰度发布

5.1 什么是灰度发布

灰度发布(Canary Release)是将新版本先发布给一小部分用户,观察效果后再逐步扩大范围:

v1.0(当前版本)= 90% 流量
v1.1(新版本)  = 10% 流量
    ↓ 观察指标正常
v1.0 = 50% | v1.1 = 50%
    ↓ 确认无问题
v1.1 = 100%

5.2 实现方式

Cloudflare:通过 Traffic Splitting 实现流量分配。

Vercel:支持 Skew Protection,可以在新旧版本之间平滑过渡。

自建 Nginx

upstream canary {
    server 127.0.0.1:3000 weight=9;   # 旧版本 90%
    server 127.0.0.1:3001 weight=1;   # 新版本 10%
}

5.3 灰度监控

灰度期间必须监控关键指标,任何异常立即回滚:

  • 错误率:新版本的 5xx 错误率是否高于旧版本
  • 响应时间:P95 延迟是否增加
  • 业务指标:转化率、页面停留时间是否下降

6. 多环境管理

6.1 环境分层

环境用途数据部署触发
local开发调试本地/Mock手动
previewPR 预览测试数据PR 创建
staging预发布验证生产数据副本Merge to develop
production正式环境真实数据Merge to main + 审批

6.2 分支策略

feature/xxx → PR → develop(自动部署到 staging)
                      ↓
                main(手动审批部署到 production)

简化方案(适合小团队):

feature/xxx → PR → main(自动部署到 production)

小团队可以跳过 staging 环境,依赖 Vercel 的 Preview Deployments 作为预发布验证。

7. 流水线性能优化

7.1 缓存策略

缓存对象缓存键失效时机
pnpm storepnpm-lock.yaml 哈希依赖变化
Nuxt 构建缓存.nuxt/ 目录配置或依赖变化
Docker 层Dockerfile 指令指令或 COPY 内容变化
Playwright 浏览器Playwright 版本版本更新

7.2 时间预算

合理的 CI 时间预算:

步骤目标时长
安装依赖(缓存命中)< 30s
ESLint< 60s
TypeScript 检查< 60s
单元测试< 120s
构建< 120s
总计< 5 分钟

超过 10 分钟的 CI 流水线会显著影响开发效率——开发者会等待 CI 结果才能合并 PR。

本章小结

  • CI 三层检查并行执行:lint + typecheck + test → build,总时长控制在 5 分钟内
  • 环境变量:GitHub Secrets 存储敏感信息,Environments 隔离多环境配置,审批人保护生产部署
  • 部署集成:Vercel 零配置自动部署,Docker 推送镜像 + SSH 部署,Cloudflare Wrangler CLI
  • 灰度发布:10% → 50% → 100% 渐进式发布,监控错误率和延迟
  • 多环境:local → preview → staging → production,分支策略匹配部署流程
  • 性能优化:pnpm cache / Docker 层缓存 / 并行 job,总时长 < 5 分钟