Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

8. GitHub Actions 实战: workflow / expression / 缓存 / 矩阵 / reusable / 自托管

TL;DR

GitHub Actions 是 CI/CD 的事实标准之一(尤其开源项目)。上一章 CI/CD 讲了通用管线模型,这一章手把手落到 GitHub Actions:语法、环境、expression、缓存、矩阵构建、可复用 workflow、自托管 runner、安全(权限/secret)。看完能自己搭一条生产级 pipeline。

读完应能:

  1. 读懂任意 .github/workflows/*.yml 并写出自己的。
  2. 用 expression、条件、矩阵、缓存优化 CI 速度和正确性。
  3. 用 reusable workflow 消除跨仓库重复。
  4. 正确配置权限和 secret,不踩常见安全坑。

一、核心概念

1.1 三要素

event (触发器) → job (任务, 独立 runner) → step (步骤, 同一 runner 顺序执行)
  • workflow:一个 .github/workflows/xxx.yml 文件。
  • job:workflow 里的一个任务,跑在独立 runner 上(可并行)。
  • step:job 里的一步(一个命令或一个 action),顺序执行、共享 shell。

1.2 关键文件路径

.github/
  workflows/
    ci.yml            # 每个文件 = 一个 workflow

1.3 最小 workflow

name: CI
on: push           # 触发:push 到任何分支

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4    # 检出代码
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go build ./...
      - run: go test ./...

二、触发器(Event)

2.1 常用触发

on:
  push:
    branches: [main]            # 只推 main
    paths:
      - 'src/**'                # 只当 src 变化时(避免无关触发)
      - 'go.mod'
      - '!docs/**'              # 排除 docs
  pull_request:
    types: [opened, synchronize, reopened]   # synchronize = 新提交
  schedule:
    - cron: '0 2 * * *'        # 每天 2 点(UTC)
  workflow_dispatch:            # 手动触发(必须加这个按钮才会出现)

2.2 触发规则要点

  • pushpull_request 常搭配(PR 阶段 + 合并后各跑一遍)。
  • paths 过滤器避免"只改 README 也跑全量 CI"。
  • workflow_dispatch + inputs 可做手动参数化触发。
on:
  workflow_dispatch:
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'staging'
        type: choice
        options: [staging, prod]

三、上下文与 Expression

3.1 关键上下文(Context)

上下文内容例子
github事件/仓库/actorgithub.sha, github.ref, github.actor
envworkflow/job/step 级 env自定义环境变量
secrets仓库 secretssecrets.GITHUB_TOKEN
vars仓库变量vars.REGION
jobjob 状态job.status
needs依赖 job 的输出needs.build.outputs.ver
matrix矩阵参数matrix.go-version

3.2 Expression 语法

# 表达式用 ${{ }} 包裹,在字符串里也可插值
- run: echo "sha is ${{ github.sha }}"
- if: ${{ github.ref == 'refs/heads/main' }}
- if: ${{ !cancelled() }}       # 前序失败也跑(清理用)
- if: ${{ success() }}          # 默认: 全成功才跑
- if: ${{ failure() }}          # 失败才跑

3.3 条件操作符

==  !=  &&  ||  !  ( )  contains()  startsWith()  endsWith()
- name: 只在 PR 且改动 src 时跑集成测试
  if: github.event_name == 'pull_request' && contains(github.event.pull_request.files.*.filename, 'src/')

四、Job 依赖与并行

4.1 needs(依赖)

jobs:
  test:
    runs-on: ubuntu-latest
  deploy:
    needs: test                  # 等 test 成功后
    runs-on: ubuntu-latest

4.2 并发控制(防止重复发布)

concurrency:
  group: deploy-${{ github.ref }}   # 同 ref 的任务互斥
  cancel-in-progress: true          # 新任务顶掉旧任务

4.3 超时与失败容错

jobs:
  test:
    timeout-minutes: 10          # job 级超时
    steps:
      - run: sleep 100000
        timeout-minutes: 2       # step 级超时
  cleanup:
    if: ${{ always() }}          # 无论成败都跑
    runs-on: ubuntu-latest
    needs: [test, deploy]

五、矩阵构建(Matrix)

5.1 多版本/多平台

jobs:
  test:
    strategy:
      matrix:
        go: ['1.21', '1.22']
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-go@v5
        with: { go-version: ${{ matrix.go }} }
      - run: go test ./...

→ 自动生成 2 × 2 = 4 个并行 job。

5.2 排除组合

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    go: ['1.21', '1.22']
    exclude:
      - os: windows-latest    # windows 只测一个版本
        go: '1.21'

5.3 include(加额外组合)

matrix:
  include:
    - os: ubuntu-latest
      go: '1.23'    # 额外加一个

5.4 矩阵产物汇总

多个 OS 的产物要上传再合并:

- name: Upload artifact
  uses: actions/upload-artifact@v4
  with:
    name: dist-${{ matrix.os }}
    path: dist/

# 汇总 job
publish:
  needs: build        # 等所有矩阵 job
  steps:
    - uses: actions/download-artifact@v4
      with: { pattern: dist-* }

六、缓存与提速

6.1 缓存依赖

- name: Cache go modules
  uses: actions/cache@v4
  with:
    path: ~/.cache/go-build
    key: go-cache-${{ runner.os }}-${{ hashFiles('go.sum') }}
    restore-keys: |
      go-cache-${{ runner.os }}-
  • key 变化(依赖变了)才重新缓存。
  • restore-keys 提供"找不到精确 key 时用相近的"。

6.2 各生态缓存

# Go
actions/cache  →  ~/.cache/go-build,  key=hashFiles('go.sum')

# Python
actions/setup-python@v5 自带 cache: pip
  with: { python-version: '3.12', cache: 'pip' }

# Node
actions/setup-node@v4
  with: { node-version: 20, cache: 'npm' }

# Rust
Swatinem/rust-cache@v2    # 自动处理 target/

6.3 加速技巧

  • actions/setup-*cache 参数(自动)。
  • 只装依赖不改就复用层(Docker layer caching)。
  • 矩阵并行 > 单 job 里并行步骤。
  • 慢测试拆分到独立 job 并行。

七、可复用 Workflow(Reusable)

7.1 为什么

多仓库重复 CI 配置 → 抽成一个可复用 workflow,改一处全生效。

7.2 定义(被复用方)

.github/workflows/test-reusable.yml(必须 workflow_call):

name: Reusable Test
on:
  workflow_call:
    inputs:
      go-version:
        required: true
        type: string
    secrets:
      token:
        required: true
    outputs:
      test-passed:
        value: ${{ jobs.test.result }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: { go-version: ${{ inputs.go-version }} }
      - run: go test ./...

7.3 调用(使用方)

jobs:
  test:
    uses: ./.github/workflows/test-reusable.yml   # 同仓库
    with:
      go-version: '1.22'
    secrets:
      token: ${{ secrets.GITHUB_TOKEN }}

note

跨仓库复用:uses: owner/repo/.github/workflows/xxx.yml@main。可复用 workflow 里的 secrets 必须显式 secrets: 传,不能用 ${{ secrets }} 全局。


八、环境与 Secrets

8.1 环境(Environment)——部署隔离

jobs:
  deploy-prod:
    environment: production        # 绑定到仓库的 environment
    runs-on: ubuntu-latest

Environment 支持:分支保护规则 + 审批者(生产部署需人工 approve)+ 独立 secrets。

environment:
  name: production
  url: https://api.example.com    # 显示在 GitHub UI

8.2 Secrets 安全要点

- name: Deploy
  env:
    DB_PASSWORD: ${{ secrets.DB_PASSWORD }}   # 从 secret 读,不硬编码
  run: |
    curl -H "Authorization: Bearer $DB_PASSWORD" ...

warning

绝不要 echo "${{ secrets.X }}" 打印 secret(会进日志/缓存)。用 env 传,运行时通过环境变量取。secret 不能在 if: 条件里比较(会泄露值到日志)。

8.3 GITHUB_TOKEN 权限最小化

permissions:
  contents: read              # 默认最小: 只读
  pull-requests: write        # 需要写 PR 时才加
  packages: write

warning

permissions: write-all 是全开——Dependabot/PWN 攻击直接拿到写权限。永远最小权限。合并 PR 的 workflow(pull_request_target)尤其危险(运行在基础分支上下文,别 checkout 攻击者代码)。

8.4 密钥扫描

- uses: gitleaks/gitleaks-action@v2   # 扫描提交里的密钥

九、自托管 Runner(Self-hosted)

9.1 什么时候需要

  • 需要特定硬件/GPU、私有网络、容器环境。
  • 比 GitHub 托管便宜(大量 build)。

9.2 配置

# 在仓库 Settings → Actions → Runners 获取 token
./config.sh --url https://github.com/owner/repo \
            --token <token> --labels my-runner
./run.sh

9.3 安全警告

warning

自托管 runner 在 public 仓库 = 远程代码执行。任何人都能开 PR 让 runner 跑代码。除非绝对信任 PR 来源,否则 public 仓库别用自托管 runner。保护办法:只在 pull_request_target + 手动触发用,或跑在隔离 VM。

9.4 Runner 分组与标签

runs-on:
  group: my-group      # 指定 runner group
  labels: [gpu, linux] # 按标签选 runner

十、完整生产级示例

name: CI/CD

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]
  workflow_dispatch:

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

permissions:
  contents: read

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: golangci/golangci-lint-action@v6

  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        go: ['1.21', '1.22']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: { go-version: ${{ matrix.go }}, cache: true }
      - run: go test ./... -race -count=1

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build & push image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

  deploy-staging:
    needs: build
    if: github.ref == 'refs/heads/main'
    environment: staging
    runs-on: ubuntu-latest
    steps:
      - run: kubectl set image deployment/app app=ghcr.io/${{ github.repository }}:${{ github.sha }}

十一、结束 + 速查表

tip

一页快速唤回:

  • 结构:event → job(独立 runner)→ step(顺序共享 shell)。
  • 触发push / pull_request / schedule / workflow_dispatch;用 paths 过滤。
  • expression${{ }}github / env / secrets / needs / matrix 上下文。
  • 条件if: success() / failure() / always() / cancelled()
  • 矩阵strategy.matrix 多版本多平台,exclude / include
  • 缓存actions/cachesetup-* 自带 cache;key 用 hashFiles
  • 可复用workflow_call / uses: owner/repo/.github/workflows/x.yml@main
  • 环境environment: 绑定审批 + 独立 secrets;生产部署加审批
  • 安全permissions 最小化;secret 用 env 传不打印;public 仓库别用自托管 runner
  • 并发concurrency.group 防重复发布。

下一篇: 9. 云原生发布与 GitOps: K8s / Helm / ArgoCD / Flux.