GitHub Actions Composite Actions:构建可复用的工作流组件

系统讲解 GitHub Actions Composite Actions 的设计模式与工程实践,涵盖输入输出定义、嵌套调用、跨仓库复用、内部市场建设以及 Composite Action 与 Reusable Workflow 的选型对比,帮助团队将重复的 CI/CD 逻辑封装为标准化可复用组件。

当你第 10 次在不同仓库中复制粘贴「安装 Node.js → 安装依赖 → 运行 Lint → 运行测试」这组步骤时,就该考虑将重复逻辑提取为可复用组件了。GitHub Actions 提供了两种复用机制:Composite Action(动作组合)和 Reusable Workflow(可复用工作流)。本文聚焦于 Composite Action,从语法基础到组织级市场建设,系统讲解如何构建高质量的 CI/CD 组件化体系。


一、Composite Action vs Reusable Workflow:选型决策

在深入 Composite Action 之前,必须先澄清它与 Reusable Workflow 的根本区别:

维度Composite ActionReusable Workflow
定义位置仓库内 .github/actions/my-action/action.yml仓库内 .github/workflows/reusable.yml
调用方式uses: ./.github/actions/my-actionuses: owner/repo/.github/workflows/reusable.yml@main
输出能力支持 outputs支持 outputs + secrets 传递
嵌套调用✅ 内部可调用其他 action❌ 不能嵌套调用 workflow
job 定义❌ 不能定义 job,只能封装 steps✅ 可以定义完整的 jobs 矩阵
运行器控制继承调用方的 runner可指定自己的 runs-on
权限传播继承调用方 permissions独立设置 permissions
适用场景步骤级复用(安装依赖、部署脚本)工作流级复用(完整 CI/CD 流水线)

选择决策树

需要复用的是?
├── 一组 steps(如安装+测试+上传报告)
│   └── Composite Action
├── 完整 workflow(包含多个 jobs)
│   └── Reusable Workflow
└── 需要跨仓库 secrets 传递?
    ├── 是 → Reusable Workflow(secrets: inherit)
    └── 否 → Composite Action(更轻量)

二、Composite Action 基础语法

2.1 最小可运行示例

创建文件 .github/actions/setup-node-deps/action.yml

name: 'Setup Node.js and Dependencies'
description: 'Setup Node.js, cache npm, install dependencies'

inputs:
  node-version:
    description: 'Node.js version to use'
    required: false
    default: '20'
  working-directory:
    description: 'Directory containing package.json'
    required: false
    default: '.'

outputs:
  cache-hit:
    description: 'Whether npm cache was hit'
    value: ${{ steps.cache.outputs.cache-hit }}

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

    - name: Cache dependencies
      id: cache
      uses: actions/cache@v4
      with:
        path: ${{ inputs.working-directory }}/node_modules
        key: ${{ runner.os }}-node-${{ inputs.node-version }}-${{ hashFiles(format('{0}/package-lock.json', inputs.working-directory)) }}
        restore-keys: |
          ${{ runner.os }}-node-${{ inputs.node-version }}-

    - name: Install dependencies
      run: npm ci
      shell: bash
      working-directory: ${{ inputs.working-directory }}

在工作流中使用:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-node-deps
        with:
          node-version: '18'
          working-directory: './frontend'

      - run: npm test
        working-directory: './frontend'

2.2 输入与输出定义规范

输入类型

属性说明示例
description人类可读的说明必需
required是否必需true / false
default默认值'20''./'
type数据类型(v2.0+)stringbooleannumber

输出定义

Composite Action 中 outputs 的值不能直接写,必须通过 value: ${{ steps.step-id.outputs.some-output }} 引用内部 step 的输出。


三、高级模式:嵌套调用与条件逻辑

3.1 嵌套调用其他 Action

Composite Action 内部可以调用其他 Composite Action,形成组件层级

# .github/actions/setup-and-test/action.yml
name: 'Setup, Build and Test'
description: 'Full CI pipeline for a Node.js package'

inputs:
  package-dir:
    required: true

runs:
  using: composite
  steps:
    # 调用底层 Composite Action
    - uses: ./.github/actions/setup-node-deps
      with:
        working-directory: ${{ inputs.package-dir }}

    - name: Lint
      run: npm run lint
      shell: bash
      working-directory: ${{ inputs.package-dir }}

    - name: Test
      run: npm test -- --coverage
      shell: bash
      working-directory: ${{ inputs.package-dir }}

    - name: Upload coverage
      uses: codecov/codecov-action@v4
      with:
        directory: ${{ inputs.package-dir }}/coverage

3.2 条件执行

在 Composite Action 中使用 if 条件:

runs:
  using: composite
  steps:
    - name: Install dependencies
      run: npm ci
      shell: bash

    - name: Run security audit
      if: inputs.run-audit == 'true'
      run: npm audit --audit-level=high
      shell: bash

注意:Composite Action 中的 if 条件不支持 ${{ }} 表达式以外的上下文,如 github.event_name 可用,但 env.MY_VAR 需要显式传递为 input。

3.3 多 Shell 支持

对于跨平台项目,Composite Action 需要显式指定 shell

runs:
  using: composite
  steps:
    - name: Run script (cross-platform)
      run: |
        if [ "${{ runner.os }}" = "Windows" ]; then
          ./scripts/build.ps1
        else
          ./scripts/build.sh
        fi
      shell: bash

四、跨仓库复用:组织级 Action 市场

4.1 发布到独立仓库

将 Composite Action 发布到独立仓库(如 my-org/actions-setup-node),供所有组织内仓库使用:

# 仓库:my-org/actions-setup-node
# 文件:action.yml(根目录)
name: 'Org Standard Node.js Setup'
description: 'Organization-standard Node.js environment setup'

inputs:
  node-version:
    default: '20'

runs:
  using: composite
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
    - run: npm ci
      shell: bash

调用方式:

- uses: my-org/actions-setup-node@v1
  with:
    node-version: '18'

4.2 版本管理策略

策略标签适用场景
语义化版本v1.2.3明确指定,可复现
浮动主版本v1自动接收向后兼容更新
分支引用@main开发阶段,不推荐生产使用
Commit SHA@abc1234最高安全性,审计友好

推荐:生产环境使用 v1 或精确版本号,安全敏感场景使用 SHA。

4.3 组织级 Action 市场目录

在组织内维护一个「官方 Action 目录」仓库:

# my-org/.github/actions-marketplace/README.md

## 认证 Actions

| Action | 用途 | 最新版本 |
|--------|------|---------|
| [actions-setup-node](https://github.com/my-org/actions-setup-node) | Node.js 环境标准化 | v2.1.0 |
| [actions-deploy-aws](https://github.com/my-org/actions-deploy-aws) | OIDC 方式部署到 AWS | v1.3.0 |
| [actions-notify-slack](https://github.com/my-org/actions-notify-slack) | CI 结果通知 | v1.0.5 |

## 使用规范

1. 优先使用认证 Actions,避免重复造轮子
2. Action 输入必须提供 `default` 值,降低使用门槛
3. 每个 Action 必须包含 `README.md` 和使用示例

五、Composite Action 的工程化最佳实践

5.1 目录结构规范

.github/
├── actions/
│   ├── setup-node-deps/
│   │   ├── action.yml          # 核心定义
│   │   ├── README.md           # 使用文档
│   │   └── scripts/
│   │       └── post-install.sh # 辅助脚本
│   ├── deploy-aws/
│   │   ├── action.yml
│   │   └── README.md
│   └── notify-slack/
│       ├── action.yml
│       └── README.md
└── workflows/
    └── main.yml                # 主工作流

5.2 输入验证与默认值

inputs:
  environment:
    description: 'Target environment'
    required: true
    # 使用 type: choice(调用方限制)
    
  timeout:
    description: 'Timeout in minutes'
    required: false
    default: '30'
    # 在 step 中做数值验证

runs:
  using: composite
  steps:
    - name: Validate inputs
      run: |
        if [[ ! "${{ inputs.environment }}" =~ ^(staging|production)$ ]]; then
          echo "Invalid environment: ${{ inputs.environment }}"
          exit 1
        fi
        if [[ "${{ inputs.timeout }}" -gt 120 ]]; then
          echo "Timeout cannot exceed 120 minutes"
          exit 1
        fi
      shell: bash

5.3 测试 Composite Action

Composite Action 本身没有官方测试框架,但可以通过「引用自身的测试 workflow」验证:

# .github/workflows/test-action.yml
name: Test Composite Action
on: [push, pull_request]

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

      - name: Test setup-node-deps
        uses: ./.github/actions/setup-node-deps
        with:
          node-version: '18'

      - name: Verify Node version
        run: node --version | grep "v18"

六、Composite Action 与 Reusable Workflow 的联合使用

在复杂项目中,两者不是互斥的,而是分层协作

┌─────────────────────────────────────────┐
│         Reusable Workflow               │
│  ┌─────────────────────────────────┐   │
│  │         Job: build              │   │
│  │  ┌─────────────────────────┐   │   │
│  │  │   Composite Action      │   │   │
│  │  │   (setup + test + lint) │   │   │
│  │  └─────────────────────────┘   │   │
│  └─────────────────────────────────┘   │
│  ┌─────────────────────────────────┐   │
│  │         Job: deploy             │   │
│  │  ┌─────────────────────────┐   │   │
│  │  │   Composite Action      │   │   │
│  │  │   (build image + push)  │   │   │
│  │  └─────────────────────────┘   │   │
│  └─────────────────────────────────┘   │
└─────────────────────────────────────────┘

示例

# reusable-ci.yml(Reusable Workflow)
name: Standard CI
on:
  workflow_call:
    inputs:
      node-version:
        default: '20'

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: my-org/actions-setup-node@v1
        with:
          node-version: ${{ inputs.node-version }}
      - uses: ./.github/actions/run-tests  # Composite Action

  deploy:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: ./.github/actions/deploy-aws  # Composite Action
        with:
          environment: staging

七、常见问题解答(FAQ)

Q1: Composite Action 可以使用 uses: docker:// 吗?

不支持。Composite Action 的 runs 只能是 composite 类型,内部 step 只能使用 runuses 调用其他 action。如果需要 Docker 执行,可以使用 Reusable Workflow 或直接在工作流中调用 docker://

Q2: Composite Action 中如何访问调用方的 secrets?

直接访问调用方 secrets 需要在调用时显式传递:

# 调用方
- uses: ./.github/actions/my-action
  with:
    api-token: ${{ secrets.API_TOKEN }}

# action.yml
inputs:
  api-token:
    required: true
runs:
  steps:
    - run: echo "${{ inputs.api-token }}"

Q3: 如何调试 Composite Action 的内部步骤?

在调用方启用 debug 日志:

env:
  ACTIONS_STEP_DEBUG: true

然后在 Actions 日志中可以看到每个内部 step 的详细执行过程。

Q4: Composite Action 可以定义环境变量吗?

可以在内部 step 中设置 env,但不能像 workflow 那样在 job 级别定义全局环境变量:

runs:
  using: composite
  steps:
    - run: npm test
      shell: bash
      env:
        NODE_ENV: test
        CI: true

总结

Composite Action 是 GitHub Actions 生态中最被低估的复用机制。它填补了「单个 step 太细」与「整个 workflow 太粗」之间的空白,让团队能够将高频重复的 CI/CD 逻辑封装为标准组件。

实施路径

阶段行动效果
识别重复统计各仓库 workflow 中的重复 steps找到复用机会
提取组件将重复步骤提取为 Composite Action首次复用
发布市场建立组织级 Action 仓库和目录跨仓库复用
版本管理引入语义化版本和变更日志可维护性
联合 Workflow高压场景组合 Reusable Workflow完整流水线复用

最终目标是让每位开发者像调用标准库函数一样调用组织的 CI/CD 组件,将精力集中在业务逻辑而非 pipeline plumbing 上。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「github-actions」更多文章

  1. GitHub Actions 通知与 ChatOps:Slack/钉钉/飞书集成与评论触发工作流
  2. GitHub Actions 自托管 Runner:架构设计、安全隔离与大规模部署
  3. GitHub Actions 缓存优化完全指南:从 actions/cache 到分层依赖管理