GitHub Actions 高级工作流:CI/CD 最佳实践
GitHub Actions 早已不只是 on: push → run: npm test 那么简单。矩阵构建、可复用工作流、OIDC 免密钥认证、自定义 Action 开发——当你需要构建企业级 CI/CD 管道时,这些高级特性才是生产力倍增器。
本文从核心概念出发,逐步深入到生产环境中的最佳实践,帮你写出既安全又高效的 GitHub Actions 工作流。
一、基础回顾与核心概念
在深入高级特性之前,先快速梳理 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]
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
五、环境管理与部署环境
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 能访问 |
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
八、工作流安全性
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 }}
${{ }} 表达式在 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
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 | ⚠️ 公共仓库勿用 |