GitHub Actions 講座 #5 再利用 — composite action と reusable workflow

読了 4分

ワークフローをいくつか作っていると、同じステップの塊がコピーされ始めます。checkout と言語のセットアップとキャッシュ設定の 3 ステップがすべてのワークフローの冒頭に付いていて、リポジトリが複数あればリポジトリの数だけ複製されます。キャッシュキーを 1 つ直すのに全部を探し回らなければならない状況は、コードなら関数に抽出していたはずのサインです。GitHub Actions における関数が、今回の 2 つの主役、composite action と reusable workflow です。

composite action — ステップのまとまりをアクションにする #

#1 から使ってきた actions/checkout のようなアクションを自作する一番簡単な方法が composite action です。ステップ群を action.yml ファイルに包む方式です。まずはリポジトリの中に置くローカルアクションから見ます。

.github/actions/setup-project/action.yml
# .github/actions/setup-project/action.yml
name: setup-project
description: チェックアウト + Python + 依存関係のインストールを一度に

inputs:
  python-version:
    description: Python のバージョン
    default: "3.13"

runs:
  using: composite
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: ${{ inputs.python-version }}
        cache: "pip"
    - run: pip install -r requirements.txt
      shell: bash

使う側はパスで参照します。

.github/workflows/ci.yml
    steps:
      - uses: actions/checkout@v4          # ローカルアクションを読むには先にチェックアウト
      - uses: ./.github/actions/setup-project
        with:
          python-version: "3.12"

文法で引っかかる 2 点を挙げておきます。composite の run ステップには shell: bash の明示が必要で(通常のワークフローと違って省略できません)、ローカルアクションはリポジトリのファイルなので checkout が先に来ないと読めません。入力は inputs で宣言して ${{ inputs.名前 }} で使い、outputs も宣言できるので、#2 のステップ出力と同じ方式で値を返せます。

reusable workflow — ジョブ全体を呼び出す #

composite がステップのまとまりだとすれば、reusable workflow はジョブ構成全体を再利用します。ワークフローに workflow_call トリガーを付けると、他のワークフローから呼び出せる部品になります。

.github/workflows/deploy-template.yml
# .github/workflows/deploy-template.yml (共有リポジトリ)
on:
  workflow_call:
    inputs:
      environment:
        type: string
        required: true
    secrets:
      deploy-role:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh
        env:
          ROLE: ${{ secrets.deploy-role }}

呼び出す側は、ジョブの位置に uses を書きます。

.github/workflows/deploy.yml
# 各サービスリポジトリのワークフロー
jobs:
  deploy-prod:
    uses: my-org/workflows/.github/workflows/deploy-template.yml@v1
    with:
      environment: production
    secrets:
      deploy-role: ${{ secrets.PROD_DEPLOY_ROLE }}

ステップの uses ではなく ジョブの uses だという位置の違いが、2 つの方式の本質的な違いをそのまま表しています。reusable workflow はランナーの指定、environment、複数ジョブの構成までまるごと持ってくるので、呼び出す側は値を入れるだけの薄い殻になります。secrets は自動では渡らず、明示的に渡す必要があります(secrets: inherit で全部渡すこともできますが、必要なものだけ渡すほうが #9 の最小権限の原則に合います)。呼び出しの深さ制限のような制約もあるので、部品を部品で何重にも包む設計は避けます。

選択基準と組織の標準化 #

2 つの選択基準は 1 行にまとまります。すなわち ジョブの中の繰り返しなら composite、ジョブ構成そのものの繰り返しなら reusable workflow です。「チェックアウトとインストール」のような準備の塊は composite、「うちの会社の標準デプロイ手順」のようなパイプラインは reusable workflow が合います。2 つは排他的ではないので、reusable workflow のステップが composite action を使う組み合わせが自然です。

組織レベルの標準化は、共有リポジトリ 1 つから始めます。my-org/workflows のようなリポジトリに reusable workflow と composite action を集め、各サービスリポジトリは @v1 のようにタグでバージョンを固定して使います。共有部品を直したらタグを上げ、各リポジトリは準備ができたときにバージョンを上げる構造なので、Terraform 基礎 #8 のモジュールのバージョン固定と完全に同じ原理です。繰り返しが実際に 2 回以上現れたときに部品化するという、導入タイミングの基準まで同じです。

まとめ #

今回扱った内容です。

  • 繰り返されるステップの塊は composite action(action.yml)に包みます。run ステップの shell の明示と、ローカルアクションの checkout 先行が文法の落とし穴です
  • ジョブ構成全体の再利用は workflow_call トリガーの reusable workflow で、呼び出しはジョブの位置の uses です
  • reusable workflow に secrets は自動では渡りません。必要なものだけ明示的に渡すのが基本です
  • 基準は「ジョブの中の繰り返しなら composite、ジョブ構成の繰り返しなら reusable」で、2 つの組み合わせが自然です
  • 組織の標準化は共有リポジトリ + タグでのバージョン固定です。Terraform モジュールの運用と同じ原理です

次回(#6 Docker ビルドと GHCR プッシュ)からは、部品を実戦のパイプラインに組み立てます。最初は、コンテナイメージをビルドして GitHub のレジストリにプッシュするパイプラインです。

X