前言

GitHub Actions 是 GitHub 原生的 CI/CD 平台,自 2019 年正式发布以来,已成为 DevOps 领域最受欢迎的自动化工具之一。它直接集成在代码仓库中,无需额外搭建 Jenkins 或 GitLab CI,就能实现从代码提交到生产部署的全流程自动化。

本篇笔记系统梳理 GitHub Actions 的核心概念、语法细节、常用场景和最佳实践,涵盖从入门到进阶的完整知识体系。所有配置示例均来自实际生产环境,可直接复用。


一、GitHub Actions 基础

1.1 核心概念

理解 GitHub Actions,先搞清楚这几个核心概念:

  • Workflow(工作流):一个自动化流程的定义文件,存放在 .github/workflows/ 目录下,使用 YAML 格式。

  • Event(事件):触发 Workflow 的动作,比如 pushpull_requestscheduleworkflow_dispatch 等。

  • Job(作业):Workflow 中的一个独立执行单元,由多个 Step 组成。多个 Job 默认并行执行,也可以用 needs 依赖关系控制串行。

  • Step(步骤):Job 中的单个任务,可以是运行一条 Shell 命令(run),也可以是调用一个 Action(uses)。

  • Action(动作):可复用的步骤单元,可以来自 GitHub Marketplace,也可以自己编写。

  • Runner(运行器):执行 Job 的机器。GitHub 提供托管 Runner(ubuntu-latest、windows-latest 等),也支持自托管 Runner。

  • Artifact(制品):Job 运行过程中产生的文件,可以在 Job 之间传递,也可以下载保存。

它们的关系是:一个 Workflow 文件定义了完整的流程 → 响应某个 Event 触发 → 包含一个或多个 Job → 每个 Job 由多个 Step 组成 → Step 在 Runner 上执行。

1.2 Workflow 文件结构

一个标准的 Workflow 文件长这样:

# .github/workflows/ci.yml
name: CI Pipeline          # Workflow 名称,显示在 GitHub Actions 页面

on:                         # 触发条件
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:                        # 全局环境变量
  NODE_VERSION: '20'
  REGISTRY: ghcr.io

jobs:                       # 作业定义
  build:
    name: Build & Test
    runs-on: ubuntu-latest  # 运行环境
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

1.3 触发机制详解

on 字段定义了 Workflow 何时被触发,支持多种事件类型:

on:
  # 1. Push 事件 —— 推送代码到指定分支时触发
  push:
    branches: [main, 'release/**']
    tags: ['v*']
    paths: ['src/**', 'package.json']        # 只有这些路径的文件变更才触发
    paths-ignore: ['docs/**', '*.md']         # 排除这些路径

  # 2. Pull Request 事件
  pull_request:
    types: [opened, synchronize, reopened]    # PR 状态变更时触发
    branches: [main]

  # 3. 定时任务(Cron 表达式,UTC 时间)
  schedule:
    - cron: '0 2 * * 1'  # 每周一 UTC 02:00(北京时间 10:00)

  # 4. 手动触发
  workflow_dispatch:
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'staging'
        type: choice
        options: [dev, staging, prod]
      dry_run:
        description: '模拟运行'
        required: false
        type: boolean
        default: false

  # 5. 其他 Workflow 完成后触发
  workflow_run:
    workflows: ["Build"]
    types: [completed]

  # 6. 当收到 issue comment 时触发(常用于 ChatOps)
  issue_comment:
    types: [created]

实用技巧paths 过滤在 monorepo 中特别有用,可以避免不相关的代码变更触发不必要的 CI。


二、Workflow 语法详解

2.1 Jobs 与依赖关系

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Linting..."

  test:
    runs-on: ubuntu-latest
    needs: lint              # lint 完成后才执行
    steps:
      - run: echo "Testing..."

  deploy:
    runs-on: ubuntu-latest
    needs: [lint, test]      # 两个都完成后才执行
    if: github.ref == 'refs/heads/main'  # 仅 main 分支部署
    steps:
      - run: echo "Deploying..."

条件执行if 表达式支持 GitHub Actions 的上下文变量和函数:

steps:
  - name: Only on main
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    run: echo "Deploy to production"

  - name: Skip for bot PRs
    if: "!contains(github.event.pull_request.title, '[bot]')"
    run: echo "Run tests"

  - name: Continue on failure
    if: failure()           # 前一步失败时执行
    run: echo "Handling failure..."

  - name: Always run
    if: always()            # 无论前面成功失败都执行
    run: echo "Cleanup"

2.2 Steps 详解

每个 Step 可以有以下属性:

steps:
  - name: Step name              # 显示名称
    id: my_step                  # 唯一 ID,用于后续引用输出
    if: success()                # 条件执行
    working-directory: ./src     # 工作目录
    env:                         # Step 级别的环境变量
      MY_VAR: 'hello'
    run: |                       # Shell 命令
      echo "Value: $MY_VAR"
      echo "output=value" >> $GITHUB_OUTPUT  # 设置输出
    continue-on-error: true      # 允许失败而不中断

  - name: Use previous output
    run: echo "Got ${{ steps.my_step.outputs.output }}"

2.3 Matrix 策略

Matrix 让你用一份配置跑多组参数,非常适合多版本、多平台测试:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        exclude:                    # 排除特定组合
          - os: windows-latest
            node-version: 18
        include:                    # 追加额外组合
          - os: ubuntu-latest
            node-version: 22
            experimental: true
      fail-fast: false              # 一个失败不取消其他
      max-parallel: 3               # 最多并行 3 个
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci && npm test
      - name: Notify experimental failure
        if: failure() && matrix.experimental
        run: echo "Experimental build failed, not blocking"

2.4 环境变量与 Secrets

env:
  GLOBAL_VAR: 'available to all jobs'

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      JOB_VAR: 'available to all steps in this job'
    steps:
      - name: Use env
        env:
          STEP_VAR: 'available to this step only'
        run: |
          echo "Global: $GLOBAL_VAR"
          echo "Job: $JOB_VAR"
          echo "Step: $STEP_VAR"
          echo "Secret: ${{ secrets.MY_SECRET }}"
          echo "Env from context: ${{ vars.MY_CONFIG }}"

注意:Secrets 在日志中会被自动遮蔽为 ***,但要小心不要通过 echo 或其他方式间接泄露。使用 env 传入环境变量比直接在 run 中引用更安全。


三、常用 Action

3.1 actions/checkout

几乎所有 Workflow 都需要的第一步:

- uses: actions/checkout@v4
  with:
    repository: owner/repo       # 检出其他仓库
    ref: develop                 # 指定分支/标签
    token: ${{ secrets.GH_PAT }} # 使用 PAT 访问私有仓库
    submodules: true             # 递归检出子模块
    fetch-depth: 0               # 完整历史(用于 changelog 生成)
    path: my-repo                # 检出到指定目录

3.2 actions/setup-node

- uses: actions/setup-node@v4
  with:
    node-version: '20'           # 支持 semver,如 '20.x'、'^20'
    cache: 'npm'                 # 自动缓存 npm/pnpm/yarn 依赖
    registry-url: 'https://registry.npmjs.org'
    scope: '@my-org'             # npm scope

3.3 actions/setup-java

- uses: actions/setup-java@v4
  with:
    distribution: 'temurin'      # temurin/zulu/adopt/microsoft/liberica/corretto
    java-version: '21'
    cache: 'gradle'              # 缓存 Gradle 或 Maven 依赖
    # cache: 'maven'
    server-id: github            # Maven settings.xml 配置
    settings-path: ${{ github.workspace }}

3.4 actions/setup-python

- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'
    # cache: 'pipenv'
    # cache: 'poetry'

3.5 docker/build-push-action

构建并推送 Docker 镜像:

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Login to GHCR
  uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: |
      ghcr.io/${{ github.repository }}:latest
      ghcr.io/${{ github.repository }}:${{ github.sha }}
    cache-from: type=gha
    cache-to: type=gha,mode=max
    platforms: linux/amd64,linux/arm64  # 多架构构建

3.6 其他常用 Action

# 缓存任意目录
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/go-build
      ~/go/pkg/mod
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    restore-keys: ${{ runner.os }}-go-

# 上传制品
- uses: actions/upload-artifact@v4
  with:
    name: build-output
    path: dist/
    retention-days: 7

# 下载制品
- uses: actions/download-artifact@v4
  with:
    name: build-output
    path: dist/

# 发送 Slack 通知
- uses: slackapi/slack-github-action@v2
  with:
    webhook: ${{ secrets.SLACK_WEBHOOK }}
    webhook-type: incoming-webhook
    payload: |
      {"text": "Build ${{ job.status }}: ${{ github.repository }}"}

四、自托管 Runner

当项目需要特殊硬件、内网访问、或自定义环境时,自托管 Runner 是必要选择。

4.1 安装与配置

# 创建目录
mkdir actions-runner && cd actions-runner

# 下载最新版本(Linux x64 为例)
curl -o actions-runner-linux-x64-2.319.1.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-linux-x64-2.319.1.tar.gz

tar xzf actions-runner-linux-x64-2.319.1.tar.gz

# 配置(从 GitHub 仓库 Settings > Actions > Runners 获取 token)
./config.sh --url https://github.com/your-org/your-repo \
  --token YOUR_REGISTRATION_TOKEN \
  --labels self-hosted,linux,x64,gpu \
  --name prod-runner-01 \
  --work _work

# 作为 systemd 服务安装运行
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status

4.2 标签管理

标签决定了哪些 Job 会被分配到哪个 Runner:

jobs:
  # 使用 GitHub 托管 Runner
  lint:
    runs-on: ubuntu-latest

  # 使用自托管 Runner(必须有所有指定标签)
  build:
    runs-on: [self-hosted, linux, gpu]

  # 使用标签做更细粒度的匹配
  deploy:
    runs-on: [self-hosted, linux, production]

4.3 Runner 分组(Runner Group)

对于大型团队,可以用 Runner Group 实现权限隔离:

jobs:
  deploy:
    runs-on: [self-hosted, linux, production]
    # 在仓库/组织设置中配置 Runner Group 的访问控制

4.4 安全考虑

自托管 Runner 的安全至关重要:

# 对于公开仓库,强烈建议仅使用临时 Runner
jobs:
  build:
    runs-on: [self-hosted, linux]
    container:
      image: node:20-slim        # 在容器中运行,隔离环境
      options: --read-only        # 只读文件系统
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

安全清单

  • 不要在公开仓库使用持久化的自托管 Runner(PR 可能包含恶意代码)

  • 使用容器模式运行 Job,限制资源访问

  • 定期更新 Runner 版本

  • 使用 CODEOWNERS 控制 Workflow 文件的修改权限

  • 为 Runner 设置专用的低权限用户

4.5 自动伸缩

使用 Actions Runner Controller (ARC) 在 Kubernetes 上实现 Runner 自动伸缩:

# ARC Helm values 示例
# helm install arc --namespace arc-systems \
#   actions-runner-controller/actions-runner-controller \
#   -f arc-values.yaml

# arc-values.yaml
authSecret:
  create: true
  github_token: "ghp_xxxxxxxxxxxx"

runnerDeployment:
  name: org-runner
  replicas: 2
  spec:
    organization: your-org
    labels:
      - k8s
      - linux
    resources:
      limits:
        cpu: "2"
        memory: "4Gi"

# HorizontalRunnerAutoscaler 配置
horizontalRunnerAutoscaler:
  minReplicas: 1
  maxReplicas: 10
  scaleDownDelaySecondsAfterScaleOut: 300
  metrics:
    - type: PercentageRunnersBusy
      scaleUpThreshold: '0.75'
      scaleDownThreshold: '0.25'

五、Secrets 管理

5.1 Secrets 层级

GitHub Actions 的 Secrets 分三个层级,优先级从高到低:

  1. Environment Secrets — 绑定到特定部署环境(如 production),可以配合审批流程

  2. Repository Secrets — 仓库级别,所有 Workflow 可访问

  3. Organization Secrets — 组织级别,可共享给多个仓库

steps:
  - name: Use secrets
    env:
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}           # Repository secret
      DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}        # Environment secret
      SHARED_API_KEY: ${{ secrets.ORG_API_KEY }}        # Organization secret
    run: |
      echo "Deploying with key..."
      # 不要 echo secret 值

5.2 配置 Environment Secrets

在 GitHub 仓库的 Settings → Environments 中配置:

# 使用 Environment
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production          # 引用 environment 名称
    steps:
      - name: Deploy
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: ./deploy.sh

5.3 OIDC 联合身份认证(推荐)

OIDC 无需存储长期凭据,是目前最安全的方式:

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write              # 必须声明 id-token 权限
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: ap-southeast-1

      - name: Deploy to S3
        run: aws s3 sync ./dist s3://my-bucket

      # 同样支持 GCP、Azure
      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/github/providers/github
          service_account: github-actions@my-project.iam.gserviceaccount.com

OIDC 配置步骤

  1. 在云平台创建身份提供商(Identity Provider),指向 GitHub 的 OIDC 端点

  2. 创建角色并配置信任策略,仅允许特定仓库/分支的 Workflow 假设角色

  3. 在 Workflow 中使用对应的 Action 获取临时凭据

5.4 变量(Variables)vs Secrets

非敏感配置建议使用 Variables 而非 Secrets:

# Repository/Environment Variables(非敏感,日志中可见)
env:
  APP_URL: ${{ vars.APP_URL }}
  FEATURE_FLAG: ${{ vars.ENABLE_NEW_UI }}

# Secrets(敏感,日志中被遮蔽)
env:
  API_KEY: ${{ secrets.API_KEY }}

六、常用场景实战

6.1 Node.js CI 完整配置

name: Node.js CI

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

permissions:
  contents: read
  pull-requests: write

jobs:
  ci:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

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

      - run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npm run type-check

      - name: Unit tests
        run: npm test -- --coverage

      - name: Upload coverage
        if: github.event_name == 'pull_request'
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

      - name: Build
        run: npm run build

      - name: Upload build
        uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/
          retention-days: 3

6.2 Java CI 完整配置(Gradle)

name: Java CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '21'
          cache: 'gradle'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build
        run: ./gradlew build -x test

      - name: Test
        run: ./gradlew test

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: build/reports/tests/

      - name: Upload JAR
        uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: build/libs/*.jar

6.3 Docker 构建与推送

name: Docker Build & Push

on:
  push:
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

6.4 自动发布 npm 包

name: Release

on:
  push:
    tags: ['v*']

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci

      - run: npm run build

      - name: Publish to npm
        run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          files: dist/*

七、多环境部署

7.1 环境定义与审批流程

name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options: [staging, production]

jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    environment: dev
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to dev
        run: |
          echo "Deploying to dev..."
          # 实际部署命令

  deploy-staging:
    runs-on: ubuntu-latest
    needs: deploy-dev
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to staging
        run: echo "Deploying to staging..."

  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment:
      name: production
      url: https://example.com
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to production
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: echo "Deploying to production..."

7.2 环境保护规则

在 GitHub 仓库 Settings → Environments 中配置:

  • Required reviewers:指定审批人,部署前必须有人批准

  • Wait timer:部署前等待指定分钟数(可用于"冷静期")

  • Deployment branches:限制只有特定分支可以部署到该环境

  • Environment Secrets / Variables:环境级别的配置和凭据

7.3 分支策略与自动部署

name: Multi-env Deploy

on:
  push:
    branches:
      - develop       # → dev 环境
      - 'release/*'   # → staging 环境
      - main          # → production 环境

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ github.ref_name == 'develop' && 'dev' || startsWith(github.ref_name, 'release/') && 'staging' || 'production' }}
    steps:
      - uses: actions/checkout@v4
      - name: Determine env
        id: env
        run: |
          if [[ "${{ github.ref_name }}" == "develop" ]]; then
            echo "target=dev" >> $GITHUB_OUTPUT
          elif [[ "${{ github.ref_name }}" == release/* ]]; then
            echo "target=staging" >> $GITHUB_OUTPUT
          else
            echo "target=production" >> $GITHUB_OUTPUT
          fi
      - name: Deploy to ${{ steps.env.outputs.target }}
        run: echo "Deploying to ${{ steps.env.outputs.target }}"

八、复合 Action 与可重用 Workflow

8.1 复合 Action(Composite Action)

复合 Action 将多个步骤打包成一个可复用的单元,存放在仓库中:

# .github/actions/setup-project/action.yml
name: 'Setup Project'
description: 'Install dependencies and setup environment'

inputs:
  node-version:
    description: 'Node.js version'
    required: false
    default: '20'
  install-command:
    description: 'Install command'
    required: false
    default: 'npm ci'

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'

    - name: Install dependencies
      shell: bash
      run: ${{ inputs.install-command }}

    - name: Verify setup
      shell: bash
      run: |
        echo "Node: $(node -v)"
        echo "npm: $(npm -v)"

使用复合 Action:

steps:
  - uses: actions/checkout@v4
  - uses: ./.github/actions/setup-project
    with:
      node-version: '22'
  - run: npm test

8.2 可重用 Workflow(Reusable Workflow)

可重用 Workflow 通过 workflow_call 触发,适合跨仓库或跨项目的标准化流程:

# .github/workflows/reusable-deploy.yml(可重用 Workflow)
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
      replicas:
        required: false
        type: number
        default: 2
    secrets:
      DEPLOY_TOKEN:
        required: true
    outputs:
      deploy-url:
        description: "Deployment URL"
        value: ${{ jobs.deploy.outputs.url }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    outputs:
      url: ${{ steps.deploy.outputs.url }}
    steps:
      - name: Deploy
        id: deploy
        env:
          TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying ${{ inputs.image-tag }} to ${{ inputs.environment }}"
          echo "Replicas: ${{ inputs.replicas }}"
          echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUT

调用可重用 Workflow:

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

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
      image-tag: ${{ github.sha }}
      replicas: 2
    secrets:
      DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}

  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      image-tag: ${{ github.sha }}
      replicas: 5
    secrets:
      DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }}

跨仓库调用

jobs:
  ci:
    uses: your-org/shared-workflows/.github/workflows/node-ci.yml@main
    with:
      node-version: '20'
    secrets: inherit    # 继承调用方的所有 secrets

九、缓存优化

9.1 actions/cache 基础

- name: Cache Go modules
  uses: actions/cache@v4
  with:
    path: |
      ~/.cache/go-build
      ~/go/pkg/mod
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    restore-keys: |
      ${{ runner.os }}-go-

9.2 内置缓存(推荐)

很多 setup Action 已内置缓存支持,无需手动配置 actions/cache

# Node.js —— 缓存 npm/pnpm/yarn
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'           # 自动检测 lockfile 并缓存

# Java —— 缓存 Gradle/Maven
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: 21
    cache: 'gradle'

# Python —— 缓存 pip/poetry/pipenv
- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'

# Go —— 使用 actions/cache
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/go-build
      ~/go/pkg/mod
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

# Rust —— 缓存 cargo
- uses: Swatinem/rust-cache@v2

9.3 Docker 构建缓存

# 方式一:使用 GitHub Actions 缓存后端(推荐)
- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: my-app:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

# 方式二:使用 Registry 缓存
- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: my-app:latest
    cache-from: type=registry,ref=ghcr.io/my-app:buildcache
    cache-to: type=registry,ref=ghcr.io/my-app:buildcache,mode=max

# 方式三:使用本地缓存
- uses: actions/cache@v4
  with:
    path: /tmp/.buildx-cache
    key: ${{ runner.os }}-buildx-${{ github.sha }}
    restore-keys: ${{ runner.os }}-buildx-

- uses: docker/build-push-action@v6
  with:
    context: .
    cache-from: type=local,src=/tmp/.buildx-cache
    cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

# 防止缓存无限增长
- run: rm -rf /tmp/.buildx-cache && mv /tmp/.buildx-cache-new /tmp/.buildx-cache

9.4 缓存最佳实践

  • Key 设计:使用 hashFiles() 基于 lockfile 生成 key,确保依赖变化时缓存失效

  • restore-keys:设置回退 key,即使精确匹配失败也能用到近似的旧缓存

  • 缓存大小:GitHub 托管 Runner 每个仓库有 10GB 缓存上限,超出会自动清理最旧的

  • 跨分支:缓存在同一仓库的分支间共享,main 分支的缓存可以被 PR 分支读取


十、最佳实践

10.1 安全

# 1. 最小权限原则
permissions:
  contents: read          # 仅读取代码
  # 不要写 permissions: write-all

# 2. Pin Action 版本(避免供应链攻击)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1
  # 或至少用主版本号
  # uses: actions/checkout@v4

# 3. 不要在日志中暴露敏感信息
- name: Safe logging
  run: |
    echo "::add-mask::$MY_SECRET"     # 手动遮蔽
    echo "Token length: ${#MY_SECRET}" # 只打印长度

# 4. 使用 GITHUB_TOKEN 而非 PAT(自动过期,权限有限)
- uses: actions/checkout@v4
  with:
    token: ${{ secrets.GITHUB_TOKEN }}

# 5. 验证第三方 Action 的来源
# 优先使用 GitHub 官方 Action 和知名组织的 Action

10.2 性能优化

# 1. 使用 Job 级别的 timeout
jobs:
  build:
    timeout-minutes: 15

# 2. 合理使用 needs 做并行
jobs:
  lint:
    runs-on: ubuntu-latest
    steps: [...]
  test:
    runs-on: ubuntu-latest      # 和 lint 并行
    steps: [...]
  build:
    needs: [lint, test]         # lint 和 test 都完成后
    steps: [...]

# 3. 条件跳过不必要的 Job
jobs:
  deploy:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps: [...]

# 4. 合并多个小 Step 减少启动开销
- name: Setup and test
  run: |
    npm ci
    npm run lint
    npm test
    npm run build

# 5. 使用 paths 过滤避免不必要的触发
on:
  push:
    paths: ['src/**', 'tests/**', 'package.json', 'package-lock.json']

10.3 调试技巧

# 1. 启用调试日志(在仓库 Settings > Secrets 中设置)
# ACTIONS_STEP_DEBUG = true
# ACTIONS_RUNNER_DEBUG = true

# 2. 使用 act 在本地测试(https://github.com/nektos/act)
# act -j build

# 3. 打印上下文信息
- name: Debug context
  env:
    GITHUB_CONTEXT: ${{ toJson(github) }}
    JOB_CONTEXT: ${{ toJson(job) }}
    STEPS_CONTEXT: ${{ toJson(steps) }}
    RUNNER_CONTEXT: ${{ toJson(runner) }}
  run: |
    echo "Event: ${{ github.event_name }}"
    echo "Ref: ${{ github.ref }}"
    echo "SHA: ${{ github.sha }}"
    echo "Actor: ${{ github.actor }}"
    echo "Runner OS: ${{ runner.os }}"

# 4. tmate 远程调试(暂停 Workflow 并提供 SSH 访问)
- name: Setup tmate session
  if: failure()                        # 仅在失败时启动
  uses: mxschmitt/action-tmate@v3
  # 或者用 workflow_dispatch 手动触发调试

10.4 成本控制

GitHub Actions 定价要点

  • 公开仓库免费

  • 私有仓库每月有免费额度(GitHub Free: 2000 分钟/月,Pro: 3000 分钟/月)

  • 不同 Runner 的系数不同:Linux = 1x,macOS = 10x,Windows = 2x

成本优化策略

# 1. 使用 paths 过滤
on:
  push:
    paths: ['src/**']    # 只有源码变更才触发

# 2. 及时取消过时的 Workflow
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true     # 同一分支的新提交会取消旧的运行

# 3. 使用小型 Runner(按需选择)
runs-on: ubuntu-latest          # Linux 最便宜
# runs-on: macos-latest        # macOS 贵 10 倍

# 4. 缓存依赖,减少安装时间
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'

# 5. 精简 CI 步骤
# - 跳过 lint 如果有本地 pre-commit hook
# - 只在 PR 时跑完整测试,push 时只跑核心测试

# 6. 合并多个小 Workflow
# - 避免同一个事件触发多个 Workflow
# - 使用 reusable workflow 减少重复配置

10.5 维护性

# 1. 使用 YAML 锚点减少重复(GitHub Actions 不原生支持,但可以用 reusable workflow 替代)

# 2. 统一 Action 版本管理(使用 Dependabot)
# .github/dependabot.yml
# version: 2
# updates:
#   - package-ecosystem: "github-actions"
#     directory: "/"
#     schedule:
#       interval: "weekly"

# 3. 使用 Matrix 测试多版本
strategy:
  matrix:
    node: [18, 20, 22]

# 4. 为每个 Job 设置有意义的 name
jobs:
  test:
    name: "Test (Node ${{ matrix.node }})"

# 5. 使用 Workflow 链接相关 PR
- name: Add PR comment
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: `✅ Build passed! [View logs](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
      })

附录:常见问题速查

Q: Workflow 文件在哪里? A: .github/workflows/ 目录下,YAML 格式,文件名任意。

Q: 如何手动触发 Workflow? A: 在 on 中添加 workflow_dispatch,然后在 GitHub Actions 页面点击 "Run workflow"。

Q: Secrets 有大小限制吗? A: 单个 Secret 最大 64KB,每个仓库最多 100 个,组织最多 1000 个。

Q: 如何在 Job 之间传递文件? A: 使用 actions/upload-artifactactions/download-artifact

Q: Cron 表达式是哪个时区? A: UTC。北京时间要减 8 小时,比如北京时间 10:00 = UTC 02:00。

Q: 如何复用同一仓库的 Workflow? A: 使用 workflow_call 定义可重用 Workflow,然后用 uses: ./.github/workflows/xxx.yml 调用。

Q: 自托管 Runner 如何清理工作目录? A: Runner 默认在每次 Job 结束后清理工作目录。如果需要保留,可以在 Step 中使用 actions/cache