GitHub Actions 高级工作流:CI/CD 最佳实践

GitHub Actions 早已不只是 on: push → run: npm test 那么简单。矩阵构建、可复用工作流、OIDC 免密钥认证、自定义 Action 开发——当你需要构建企业级 CI/CD 管道时,这些高级特性才是生产力倍增器。

本文从核心概念出发,逐步深入到生产环境中的最佳实践,帮你写出既安全又高效的 GitHub Actions 工作流。

前置知识:本文假设你已经会用 GitHub Actions 编写基本的 YAML 工作流。如果还没有,建议先看 官方入门文档

一、基础回顾与核心概念

在深入高级特性之前,先快速梳理 GitHub Actions 的核心抽象:

概念 说明 类比
Workflow 一个 YAML 文件定义的自动化流程 一整条流水线
Event 触发 Workflow 的事件(push、PR、schedule…) 流水线的启动信号
Job Workflow 中的一组 Steps,同一 Job 内共享 Runner 流水线上的一个工位
Step Job 中的一个任务单元(Action 或 shell 命令) 工位上的一个操作步骤
Runner 执行 Job 的服务器(GitHub-hosted 或 Self-hosted) 实际干活的机器
Action 可复用的任务单元(社区或自定义) 可插拔的工具组件
name: Basic CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test

这是最基本的形态。但当项目变大、团队变多,你需要更多武器。

二、矩阵构建:多版本、多平台并行测试

你的库需要同时支持 Node 18/20/22、在 Ubuntu/macOS/Windows 上跑测试吗?手动写 9 个 Job 太蠢了。strategy.matrix 让你声明维度,GitHub 自动展开组合并行执行。

基本矩阵

jobs:
  test:
    strategy:
      matrix:
        node-version: [18, 20, 22]
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

这会生成 3 × 3 = 9 个并行 Job。GitHub 会自动分配 Runner,所有 Job 同时执行。

排除特定组合

某些组合可能没意义,比如 Node 18 不支持 macOS ARM:

strategy:
  matrix:
    node-version: [18, 20, 22]
    os: [ubuntu-latest, macos-latest, windows-latest]
    exclude:
      - node-version: 18
        os: macos-latest   # Node 18 在 macOS ARM 上有兼容问题

包含额外组合

在矩阵基础上追加特殊组合,比如加一个 Canary 版本的测试:

strategy:
  matrix:
    node-version: [18, 20, 22]
    include:
      - node-version: 23
        os: ubuntu-latest
        experimental: true  # 自定义变量

失败策略

strategy:
  fail-fast: false  # 某个组合失败不取消其他 Job
  matrix:
    node-version: [18, 20, 22]
推荐:CI 管道中始终设置 fail-fast: false。否则一个 Node 版本失败就会取消其他版本的测试,你无法一次运行看到所有失败。

三、可复用工作流:Caller / Callee 模式

当多个仓库的 CI 流程相似,复制粘贴 YAML 不是长久之计。可复用工作流(Reusable Workflows)让你定义一次,到处调用。

Callee:定义可复用工作流

# .github/workflows/ci-template.yml
name: Reusable CI

on:
  workflow_call:
    inputs:
      node-version:
        required: true
        type: string
      run-lint:
        required: false
        type: boolean
        default: true
    secrets:
      npm-token:
        required: true

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          registry-url: https://registry.npmjs.org
      - run: npm ci
        env:
          NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
      - if: ${{ inputs.run-lint }}
        run: npm run lint
      - run: npm test

Caller:调用可复用工作流

# 仓库 A 的 .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  call-ci:
    uses: my-org/ci-templates/.github/workflows/ci-template.yml@v1
    with:
      node-version: 20
      run-lint: true
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

可复用工作流的关键限制

限制 说明
最多 4 层嵌套 可复用工作流调用可复用工作流,最多嵌套 4 层
不能使用 workflow_dispatch 触发 Caller 只能用 workflow_call 触发 Callee
Secrets 必须显式传递 Callee 不能直接访问 Caller 的 secrets
矩阵不能在 Caller 传入 矩阵策略定义在 Callee 内部,Caller 无法动态指定
组织级模板:在组织内创建一个 .github 仓库,把可复用工作流放在 .github/workflows/ 下,所有仓库都能用 uses: my-org/.github/.github/workflows/ci.yml@v1 引用。

四、自定义 Action 开发

社区 Action 不能覆盖所有场景?自己写。GitHub Actions 支持三种类型的自定义 Action:

三种类型对比

类型 语言 执行方式 适用场景
JavaScript JS/TS 直接在 Runner 上运行 通用、速度最快
Docker 任意语言 在容器中运行 需要特定环境/依赖
Composite YAML + Shell 组合多个 Step 流程编排、无需编译

JavaScript Action 示例

# action.yml
name: 'PR Size Label'
description: 'Label PRs based on changed lines count'
inputs:
  max-small-size:
    description: 'Maximum lines for "small" label'
    required: false
    default: '50'
outputs:
  label:
    description: 'The applied label'
    value: ${{ steps.label.outputs.result }}
runs:
  using: 'node20'
  main: 'dist/index.js'
// src/index.ts
import * as core from '@actions/core';
import * as github from '@actions/core';

async function run() {
  const maxSize = Number(core.getInput('max-small-size'));
  const pr = github.context.payload.pull_request;
  const changedLines = pr.additions + pr.deletions;

  let label: string;
  if (changedLines <= maxSize) {
    label = 'size/small';
  } else if (changedLines <= maxSize * 4) {
    label = 'size/medium';
  } else {
    label = 'size/large';
  }

  core.setOutput('label', label);
  console.log(`PR has ${changedLines} changed lines → ${label}`);
}

run().catch(err => core.setFailed(err.message));

Docker Action 示例

# action.yml
name: 'Lint Python'
description: 'Run Python linters in a controlled environment'
inputs:
  path:
    description: 'Path to lint'
    required: false
    default: '.'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.path }}
# Dockerfile
FROM python:3.12-slim
RUN pip install ruff mypy black
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Composite Action 示例

# action.yml
name: 'Setup Node Project'
description: 'Checkout + Setup Node + Install Dependencies'
inputs:
  node-version:
    required: false
    default: '20'
runs:
  using: 'composite'
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
    - run: npm ci
      shell: bash
选择建议:90% 场景用 Composite Action(零编译、零依赖、最简单)。需要复杂逻辑用 JavaScript Action。需要特殊系统环境(如 Go 编译工具链)用 Docker Action。

五、环境管理与部署环境

GitHub Actions 的 Environments 功能让你为不同部署阶段(staging、production)配置保护规则、审批流程和专属 Secrets。

定义环境

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging    # 关联 staging 环境
    steps:
      - run: echo "Deploying to staging..."

  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment:            # 关联 production 环境
      name: production
      url: https://myapp.example.com   # 部署链接,显示在 PR 页面
    steps:
      - run: echo "Deploying to production..."

保护规则

在仓库 Settings → Environments 中配置:

保护规则 说明
Required reviewers 指定审批人,必须有人点 Approve 才能继续
Wait timer 审批后等待指定时间才执行(冷静期)
Deployment branch 限制只有特定分支能部署到该环境
Environment secrets 环境专属 Secret,只有该环境的 Job 能访问
环境 Secrets 的隔离production 环境的 Secret 无法在 staging Job 中访问。这比把所有 Secret 放在仓库级别更安全——即使 staging 被入侵,生产密钥也不会泄露。

六、工件与缓存

跨 Job 传递文件用 Artifact,加速构建用 Cache。两者目的不同,别搞混。

Artifact:跨 Job 传递文件

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 5   # 保留 5 天

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - run: aws s3 sync dist/ s3://my-bucket/

Cache:加速依赖安装

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: 20
      cache: npm          # 自动缓存 node_modules
  - run: npm ci           # 命中缓存时秒级完成

手动缓存控制

steps:
  - uses: actions/cache@v4
    with:
      path: |
        ~/.npm
        ~/.cache/pip
      key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json', '**/requirements.txt') }}
      restore-keys: |
        deps-${{ runner.os }}-   # 部分匹配回退

Artifact vs Cache 对比

特性 Artifact Cache
目的 跨 Job 传递构建产物 加速依赖安装
作用域 同一 Workflow Run 跨 Workflow Run、跨分支
可下载 是(UI 手动下载)
自动清理 按 retention-days 7 天未访问自动清理
典型场景 build → deploy 传递 dist/ 缓存 node_modules、pip、Maven
缓存键设计:用 hashFiles 基于锁文件生成缓存键,锁文件不变就命中缓存。同时提供 restore-keys 前缀匹配作为回退,避免锁文件微调导致完全 miss。

七、OIDC 与云服务集成

传统方式:把 AWS Access Key、GCP Service Account Key 存为 GitHub Secret——密钥轮换麻烦、泄露风险高。OIDC(OpenID Connect)让 GitHub Actions 直接向云服务商证明身份,无需存储任何长期密钥

工作原理

1. GitHub 为 Workflow Run 生成短期 JWT Token
2. Action 将 JWT 发送给云服务商的 STS 接口
3. 云服务商验证 JWT 签名和 Claims
4. 返回临时凭证(有效期通常 1 小时)
5. Action 用临时凭证操作云资源

AWS 集成示例

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write    # 必须声明,允许请求 OIDC Token
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeploy
          aws-region: ap-east-1
      - run: aws s3 sync dist/ s3://my-bucket/

AWS 侧 Trust Policy 配置

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
        }
      }
    }
  ]
}

GCP 集成示例

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/123/locations/global/workloadIdentityPools/github/providers/my-provider
          service_account: github-actions@my-project.iam.gserviceaccount.com
      - uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: my-service
          region: asia-east1

Azure 集成示例

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - run: az webapp deploy --resource-group myRG --name myApp --src-path dist.zip
OIDC 的核心优势:零长期密钥、自动过期、细粒度控制(可限制只有特定仓库/分支/环境才能获取凭证)。这是云部署的最佳实践,GitHub 官方也推荐替代传统 Secret 方式。

八、工作流安全性

CI/CD 管道是攻击面的重要一环。一次 Supply Chain 攻击可能比直接入侵服务器更致命。

1. 最小权限原则

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read        # 只读代码
      packages: write       # 写入包注册表
      # 不给 id-token: write(不需要 OIDC 就不给)
      # 不给 actions: write(不需要修改 Workflow 就不给)
默认权限问题:如果不显式声明 permissions,GitHub 默认给 write 权限。强烈建议在 Workflow 顶层设置 permissions: read-all,再按需放开。
# 顶层默认最小权限
permissions: read-all

jobs:
  deploy:
    permissions:
      contents: read
      id-token: write       # 按需放开

2. 第三方 Action 审计

# ❌ 不安全的引用:使用可变标签,可能被篡改
- uses: someone/action@main

# ✅ 安全的引用:使用 SHA 哈希,不可变
- uses: someone/action@a8c5c03e8f4c1a4e89e6e9b6e4d4a4c8b7d6e5f4

3. Pin 住 Action 版本

引用方式 安全性 可读性 推荐度
@main ❌ 可变,可能被注入恶意代码 不推荐
@v3 ⚠️ 可变,tag 可被强制更新 一般
@SHA ✅ 不可变,代码锁定 最安全
@v3 + comment SHA ✅ 兼顾可读与安全 推荐
# 推荐:tag + SHA 注释
- uses: actions/checkout@v4             # v4.2.2
  # pin: actions/checkout@a8c5c03e8f4c1a4e89e6e9b6e4d4a4c8b7d6e5f4

4. 防止 Script Injection

# ❌ 危险:直接拼接用户输入
- run: echo "Comment: ${{ github.event.issue.title }}"

# ✅ 安全:使用环境变量传递
- run: echo "Comment: $TITLE"
  env:
    TITLE: ${{ github.event.issue.title }}
Script Injection 原理${{ }} 表达式在 YAML 解析阶段被直接替换。如果用户在 Issue 标题中写 ; rm -rf /,它会直接拼进 shell 命令执行。用 env 传递则由 shell 处理,不会执行注入代码。

5. 限制 Workflow 触发源

# 只允许特定人员触发部署
jobs:
  deploy:
    if: github.actor == 'zzdbilly' || contains(fromJSON('["maintainer1", "maintainer2"]'), github.actor)

九、性能优化

CI 跑得慢,开发体验就差。以下是系统性的优化策略。

1. 并发控制:避免重复运行

# 同一分支的新推送取消旧的运行
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

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

当你快速连续推送 3 个 commit,只有最后一次运行的 Workflow 会完整执行,前两次自动取消——节省 Runner 资源,结果也更快出来。

2. Job 并行与依赖

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

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

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

  build:
    needs: [lint, typecheck, test]   # 三个 Job 全部通过才执行
    runs-on: ubuntu-latest
    steps:
      - run: npm run build

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: npm run deploy
执行流程:

  lint ─────┐
            ├─→ build → deploy
  typecheck ┤
            │
  test ─────┘

  前三个并行,build 等全部完成,deploy 等 build 完成

3. 条件执行:跳过不必要的步骤

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

      # 路径过滤:只有 src/ 变更才跑测试
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            src:
              - 'src/**'

      - if: steps.changes.outputs.src == 'true'
        run: npm test

  # 只在 tag 推送时构建发布
  release:
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - run: npm run release

4. 缓存依赖安装

# setup-node 自带缓存
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: npm           # 自动基于 lock 文件缓存

# setup-python 自带缓存
- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: pip

# setup-go 自带缓存
- uses: actions/setup-go@v5
  with:
    go-version: '1.22'
    cache: true

5. 自托管 Runner

GitHub-hosted Runner 有局限:每次都是全新环境、网络受限、免费额度有限。自托管 Runner 适合:

  • 需要 GPU 的机器学习任务
  • 需要访问内网资源
  • 构建时间长的项目(自托管 Runner 不计费)
  • 需要特定硬件/软件环境
# 安装自托管 Runner
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.321.0.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz
tar xzf actions-runner-linux-x64-2.321.0.tar.gz
./config.sh --url https://github.com/OWNER/REPO --token TOKEN
./run.sh
自托管 Runner 安全风险:公共仓库不建议用自托管 Runner,因为任何人都可以提交 PR 触发 Workflow,在 Runner 上执行任意代码。私有仓库或配合 environment 保护规则使用更安全。

性能优化检查清单

优化项 预期提速 难度
并发控制 concurrency 避免浪费,间接提速
依赖缓存 30-60 秒
Job 并行化 线性→并行,显著 ⭐⭐
条件执行 / 路径过滤 跳过无用 Job ⭐⭐
自托管 Runner 消除冷启动、不计费 ⭐⭐⭐

十、速查表

场景 推荐方案 关键配置
多版本/多平台测试 strategy.matrix fail-fast: false
多仓库共享 CI 可复用工作流 workflow_call + uses:
流程编排无编译 Composite Action using: composite
生产部署保护 Environments 审批人 + 环境级 Secret
跨 Job 传递产物 upload/download-artifact retention-days
加速依赖安装 actions/cache 或内置缓存 hashFiles 生成键
云服务免密钥认证 OIDC permissions: id-token: write
第三方 Action 安全 Pin SHA + 注释 tag @SHA + # pin: @v3
防注入攻击 env 传递变量 避免 ${{ }} 直接拼接
避免重复运行 concurrency cancel-in-progress: true
私有仓库高性能 自托管 Runner ⚠️ 公共仓库勿用
一句话总结:矩阵构建解决多维度测试,可复用工作流消除重复,OIDC 消除密钥风险,最小权限 + SHA Pin 消除供应链攻击,并发控制 + 缓存消除等待时间。把这五件事做好,你的 CI/CD 管道就超过了 90% 的项目。