正在加载,请稍候…

高级 CI/CD 与 GitHub Actions:密钥、环境、矩阵构建和可复用工作流

掌握 GitHub Actions 高级用法:环境密钥、部署门控、矩阵策略、可复用工作流、缓存、OIDC 认证,构建生产级 CI/CD 流水线。

高级 CI/CD 与 GitHub Actions:密钥、环境、矩阵构建和可复用工作流

超越基础 GitHub Actions

大多数 GitHub Actions 教程都覆盖相同的内容:一个在推送时运行测试的工作流,可能还会构建 Docker 镜像。但生产流水线需要更多:基于环境的部署门控、安全的密钥管理、高效的缓存以及可维护的工作流组织。

本指南涵盖了使 GitHub Actions 能够大规模运行的模式。

高级 CI/CD 与 GitHub Actions:密钥、环境、矩阵构建和可复用工作流 插图

环境和部署门控

环境为部署添加保护规则:

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - run: npm test

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    environment: staging    # 使用 staging 环境密钥
    steps:
    - name: Deploy to staging
      run: ./deploy.sh staging
      env:
        DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}   # 来自 staging 环境

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production  # 可以要求手动审批
    # 生产环境可以配置:
    # - 必需审阅者(必须在作业运行前批准)
    # - 等待计时器(部署前延迟)
    # - 分支限制(仅来自 main)
    steps:
    - name: Deploy to production
      run: ./deploy.sh production
      env:
        DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}   # 来自 production 环境

在 GitHub Settings → Environments 中,配置:

  • 必需审阅者:必须批准的有名人员
  • 等待计时器:延迟(可用于金丝雀监控期)
  • 部署分支:只有特定分支可以部署到此环境

OIDC 认证(无需长期密钥)

无需将云凭证存储为 GitHub 密钥,而是使用 OIDC 获取临时令牌:

name: Deploy with OIDC

on:
  push:
    branches: [main]

permissions:
  id-token: write   # OIDC 必需
  contents: read

jobs:
  deploy-aws:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    # 通过 OIDC 获取临时 AWS 凭证(无需存储 AWS 密钥!)
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
        aws-region: us-east-1
        # 无需访问密钥或秘密密钥!
    
    - name: Deploy to ECS
      run: aws ecs update-service --cluster prod --service my-app --force-new-deployment

  deploy-gcp:
    runs-on: ubuntu-latest
    steps:
    - uses: google-github-actions/auth@v2
      with:
        workload_identity_provider: projects/123/locations/global/workloadIdentityPools/my-pool/providers/my-provider
        service_account: deploy@my-project.iam.gserviceaccount.com
    
    - run: gcloud run deploy my-service --image gcr.io/my-project/app:${{ github.sha }}

高级 CI/CD 与 GitHub Actions:密钥、环境、矩阵构建和可复用工作流 插图

矩阵策略

跨多个组合运行作业:

jobs:
  test:
    strategy:
      fail-fast: false       # 失败时不取消其他矩阵作业
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node: [18, 20, 22]
        exclude:
        - os: macos-latest
          node: 18            # 不在 macOS 上测试 Node 18
        include:
        - os: ubuntu-latest
          node: 20
          experimental: true  # 为特定组合添加额外变量
    
    runs-on: ${{ matrix.os }}
    
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node }}
    - run: npm test

  build:
    strategy:
      matrix:
        platform: [linux/amd64, linux/arm64]  # 多架构 Docker 构建
    steps:
    - uses: docker/setup-buildx-action@v3
    - uses: docker/build-push-action@v5
      with:
        platforms: ${{ matrix.platform }}
        tags: my-app:${{ github.sha }}-${{ matrix.platform }}

缓存:让工作流变快

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    # Node.js — 缓存 node_modules
    - uses: actions/setup-node@v4
      with:
        node-version: '20'
        cache: 'npm'          # npm/yarn/pnpm 的内置缓存
    
    - run: npm ci             # 后续运行使用缓存
    
    # 显式缓存以获得更多控制
    - name: Cache Next.js build
      uses: actions/cache@v4
      with:
        path: |
          .next/cache
          ~/.npm
        key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
        restore-keys: |
          ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
          ${{ runner.os }}-nextjs-
    
    # 使用 GitHub Actions 缓存的 Docker 层缓存
    - uses: docker/setup-buildx-action@v3
    - uses: docker/build-push-action@v5
      with:
        context: .
        cache-from: type=gha          # 从 GitHub Actions 缓存读取
        cache-to: type=gha,mode=max   # 写入 GitHub Actions 缓存
        push: true
        tags: my-app:${{ github.sha }}

高级 CI/CD 与 GitHub Actions:密钥、环境、矩阵构建和可复用工作流 插图

可复用工作流

工作流的 DRY 原则——定义一次,从多个工作流调用:

# .github/workflows/reusable-deploy.yml
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      DEPLOY_TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
    - name: Deploy ${{ inputs.image-tag }} to ${{ inputs.environment }}
      run: |
        curl -X POST https://deploy.example.com/deploy           -H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}"           -d '{"image": "${{ inputs.image-tag }}", "env": "${{ inputs.environment }}"}'
# .github/workflows/main.yml
name: Main Pipeline

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
    - id: meta
      uses: docker/metadata-action@v5
      with:
        images: my-app
        tags: type=sha
    
    - uses: docker/build-push-action@v5
      with:
        tags: ${{ steps.meta.outputs.tags }}
        push: true
  
  deploy-staging:
    needs: build
    uses: ./.github/workflows/reusable-deploy.yml    # 调用可复用工作流
    with:
      environment: staging
      image-tag: ${{ needs.build.outputs.image-tag }}
    secrets: inherit    # 传递所有密钥
  
  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      image-tag: ${{ needs.build.outputs.image-tag }}
    secrets: inherit

复合操作

用于工作流内可复用的步骤序列:

# .github/actions/setup-app/action.yml
name: Setup Application
description: Install dependencies and build the app

inputs:
  node-version:
    description: Node.js version
    default: '20'
  environment:
    description: Build environment
    required: true

runs:
  using: composite
  steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ inputs.node-version }}
      cache: npm
  
  - run: npm ci
    shell: bash
  
  - run: npm run build
    shell: bash
    env:
      NODE_ENV: ${{ inputs.environment }}
  
  - run: npm test -- --coverage
    shell: bash
# 使用复合操作
- uses: ./.github/actions/setup-app
  with:
    node-version: '20'
    environment: production

高级作业依赖和条件

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
    - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
    - run: npm test

  build:
    needs: [lint, test]    # 等待两者都通过
    if: github.ref == 'refs/heads/main'   # 仅在 main 分支上
    runs-on: ubuntu-latest
    steps:
    - run: npm run build

  notify-failure:
    needs: [build]
    if: failure()          # 如果任何先前作业失败则运行
    runs-on: ubuntu-latest
    steps:
    - name: Notify team on Slack
      uses: slackapi/slack-github-action@v1
      with:
        payload: |
          {
            "text": "🚨 Build failed: ${{ github.repository }}@${{ github.sha }}"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

  cleanup:
    needs: [build]
    if: always()           # 无论成功/失败都运行
    runs-on: ubuntu-latest
    steps:
    - run: ./cleanup.sh

工作流安全最佳实践

# 最小权限原则
permissions:
  contents: read           # 默认——在作业中进一步限制
  packages: write          # 仅当推送到 ghcr.io 时
  id-token: write          # 仅当使用 OIDC 时
  # 除非明确需要,否则不要授予 pull-requests: write

# 将操作版本固定到精确 SHA(而不是 @main 或 @v1)
# ❌ 易受供应链攻击:
- uses: some-action/action@main

# ✅ 固定到特定提交:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1

# 验证 PR 触发工作流的输入(不受信任的代码)
- name: Validate PR title
  run: |
    TITLE="${{ github.event.pull_request.title }}"
    # 清理——不要在 shell 命令中直接使用 PR 内容
    echo "PR title length: ${#TITLE}"

# 使用最小范围的 GITHUB_TOKEN
env:
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

一个设计良好的 CI/CD 流水线在日常开发中是不可见的——它只是工作、运行快速、可靠部署。在 OIDC 认证、可复用工作流和适当缓存等模式上的投资,会在项目的数月和数年内带来回报。

→ 使用 UUID 生成器 为工作流中的唯一标识符生成 UUID。