GitHub Actions:matrix build / cache / reusable workflow 实战

起因

库需要在 Python 3.10/3.11/3.12 × Linux/Mac/Windows × 9 个组合上测试。
之前老 CI 写 9 个 job 重复代码。GitHub Actions 的 matrix 一行配齐。

1. matrix build

# .github/workflows/test.yml
name: test

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        python: ['3.10', '3.11', '3.12']
        os: [ubuntu-latest, macos-latest, windows-latest]
        exclude:
          - os: windows-latest
            python: '3.10'   # 跳某些组合
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
          cache: 'pip'
      - run: pip install -e .[dev]
      - run: pytest -xvs

fail-fast: false:一个 job 失败不取消其它(看完所有再 fail)。

8 个 job 并行跑(GitHub 免费 plan 公开 repo 20 并发)。

2. cache 加速

- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/.cache/pypoetry
      .venv
    key: ${{ runner.os }}-py${{ matrix.python }}-${{ hashFiles('**/poetry.lock') }}
    restore-keys: |
      ${{ runner.os }}-py${{ matrix.python }}-

key 包含 lockfile hash → lockfile 没变 → cache 命中。
restore-keys fallback:完全不命中时拿前缀匹配最新的(部分有比没有强)。

cache 命中跳过 pip install → CI 时间从 2 分钟 → 20 秒。

Node:

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'
- run: npm ci

setup-node action 内置 npm/yarn/pnpm cache,不需要单独 actions/cache。

3. matrix + variables

strategy:
  matrix:
    include:
      - { name: 'python 3.10 / no extras', python: '3.10', extras: '' }
      - { name: 'python 3.12 / all extras', python: '3.12', extras: 'all' }
      - { name: 'python 3.12 / pgsql', python: '3.12', extras: 'postgres' }
runs-on: ubuntu-latest
steps:
  - run: pip install -e .[${{ matrix.extras }}]
  - run: pytest

includepython: × extras: 笛卡尔积更精细,只跑你 list 的组合。

4. reusable workflows

公共逻辑抽到一个 yml 给多 repo 用:

# .github/workflows/python-test.yml (reusable)
name: python-test

on:
  workflow_call:
    inputs:
      python-version:
        type: string
        default: '3.12'
      coverage:
        type: boolean
        default: false
    secrets:
      CODECOV_TOKEN:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ inputs.python-version }}
      - run: pip install -e .[dev]
      - run: pytest ${{ inputs.coverage && '--cov' || '' }}
      - if: inputs.coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

调用方:

# 某 repo .github/workflows/ci.yml
jobs:
  test:
    uses: my-org/.github/.github/workflows/python-test.yml@main
    with:
      python-version: '3.12'
      coverage: true
    secrets:
      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

更新 reusable yml → 所有调用方下次 run 自动用新版(无需逐个 repo
改)。

5. composite actions(细粒度复用)

复用几个 step 而不是整个 workflow:

# my-action/action.yml
name: 'Setup project'
description: 'Install uv + Python + sync deps'
inputs:
  python-version:
    default: '3.12'
runs:
  using: composite
  steps:
    - uses: astral-sh/setup-uv@v3
      with:
        version: 'latest'
        enable-cache: true
    - uses: actions/setup-python@v5
      with:
        python-version: ${{ inputs.python-version }}
    - run: uv sync --frozen
      shell: bash
# 调用
- uses: ./my-action
  with:
    python-version: '3.12'
- run: uv run pytest

放在仓库本地 (./.github/actions/setup) 或独立 repo
(my-org/setup-action@v1)。

6. 条件运行

- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
  run: ./scripts/deploy.sh

- if: matrix.os == 'ubuntu-latest'
  run: ./scripts/coverage.sh

- if: failure()
  run: ./scripts/notify-slack.sh

success() / failure() / always() / cancelled() 是 step 级条件。

7. 路径 / 分支过滤

on:
  push:
    paths:
      - 'backend/**'
      - '.github/workflows/backend.yml'
    branches:
      - main
      - 'release/**'

monorepo 里只改前端时不跑后端 CI。

8. concurrency 防同一分支同时多 run

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

PR 连续 push 时,老的 in-progress run 被 cancel,只跑最新。
省 CI 资源。

9. secrets / env

env:
  NODE_ENV: production
  API_BASE_URL: https://api.example.com

jobs:
  deploy:
    env:
      EXTRA: value
    steps:
      - env:
          STEP_LOCAL: x
        run: echo "$EXTRA $STEP_LOCAL ${{ secrets.AWS_KEY }}"

secrets 在 repo settings → Secrets and variables → Actions 配。
环境(environment)配的可加 approval gate:

jobs:
  deploy-prod:
    environment: production    # 需要审批员点击 approve 才跑
    steps: ...

10. 自托管 runner(cost / hardware 控制)

runs-on: [self-hosted, linux, gpu]

自己机器装 GitHub Actions runner agent → label 标记 → workflow 用 label
选择。免费层时间紧 / 需 GPU / 需大内存时省钱。

11. 经验:调试 workflow

# 本地跑 workflow(用 act)
brew install act
act -j test                    # 跑 test job
act pull_request               # 模拟 pr 事件

act 用 Docker 模拟 GitHub Actions 环境。不 100% 一致(缺一些 GitHub
service),但能本地快速迭代。

或者用 tmate 在 GitHub runner 里开 SSH 会话调试:

- name: SSH into runner if test fails
  if: failure()
  uses: mxschmitt/action-tmate@v3
  with:
    detached: true
    limit-access-to-actor: true

step 失败时 GitHub 给你 SSH URL,进 runner 看现场。强烈推荐紧急调试用。

12. cost 控制

  • GitHub 免费层公开 repo 无限 minutes;私有 repo 每月 2000 minutes
  • concurrency cancel 老 run
  • 选小机器(默认 ubuntu-latest 2-core;linux 私有可以选 ubuntu-24.04-arm
    64 cores 但贵 16x)
  • 把 release / nightly build 放 self-hosted

效果

我们一个开源库 CI:

  • matrix build 9 组合并行,总时长 4 分钟(顺序跑要 30 分钟+)
  • cache 让 build 时间 80% 来自实际跑测试,不是装依赖
  • reusable workflow 让 6 个相关 repo 共享配置,改一处生效全部
  • composite action 把"setup uv + sync"封装,每 yml 少 10 行

踩过的坑

  1. cache key 不带 lockfile hash:依赖变了 cache 还用老的,
    bug 隐蔽。永远 hashFiles('package-lock.json') 之类。

  2. secrets 在 PR 不可见:跨 fork PR 默认不传 secrets(防泄露)。
    要 dependabot / fork PR 跑需要 deploy → 用 workflow_run
    triggered workflow 在 base repo 跑。

  3. windows runner 路径分隔:脚本里 hardcode / 在 Windows 上
    失败。用 cross-platform shell(bash 在 Windows runner 也有)+
    \ / 都用 path.join

  4. timeout 默认 6 小时:偶尔 hang 的 job 把 minutes 烧光。
    timeout-minutes: 30 给每个 job 设上限。

  5. actions 升级 v3 → v4 breaking:node 16 退役大批 actions 强制
    升 node 20。看 GitHub deprecation notice + dependabot for actions
    自动 PR 升级。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。