こんにちは!日々のCI/CDパイプラインの構築、本当にお疲れ様です。
「あっちのワークフローにも、こっちのワークフローにも、まったく同じ『Node.jsのセットアップと依存関係のインストール』を書いている気がする……」
そんなコードの重複に、もやもやしたことはありませんか?
プロジェクトが大きくなるにつれて、CIの設定ファイル(YAML)はどんどん肥大化し、修正が必要になったときにすべてのファイルを書き換える地獄が訪れます。
これを綺麗さっぱり解決し、あなたのGitHub Actionsを劇的にDRY(Don’t Repeat Yourself)にしてくれる秘密兵器が 「Composite Actions(コンポジットアクション)」 です。
今回は、初心者の方でも今日からすぐに導入できるよう、その基礎から現場で使える設計の極意まで、優しく丁寧に解説していきますね。これをマスターすれば、毎日のCI/CD作業が本当に楽になりますよ!
—
1. そもそも「Composite Actions」ってなに?
GitHub Actionsにおける「アクション(Action)」とは、再利用可能な処理のパーツのことです。これまで、他の人が作ったアクション(`actions/checkout@v4`など)を呼び出して使ったことがある方も多いでしょう。
自作のアクションを作る方法にはいくつかの種類がありますが、その中でも「Composite Actions」は、普段書いているワークフローのステップ(steps)をそのままひとまとめにして、自分専用のカスタムアクションとして切り出せる優れものです。
なぜ、普通のワークフローのコピペじゃダメなの?
「同じYAMLをコピー&ペーストすればいいじゃないか」と思うかもしれませんが、それだと以下のような問題が発生します。
- Node.jsのバージョンを上げたい時、全ワークフローのファイルを書き換える必要がある(変更漏れが発生する)。
- ワークフロー全体のファイル行数が数千行に膨れ上がり、何をやっているか分からなくなる。
Composite Actionsを使えば、「お決まりの初期化処理」を1つのファイルにカプセル化し、呼び出し側はたった1行書くだけでスッキリ綺麗に保つことができます。
—
2. 基礎セットアップ:はじめてのComposite Actionsを作ろう
百聞は一見にしかず。実際に手を動かして作ってみましょう。
リポジトリのディレクトリ構造
GitHubで自作のアクションを認識させるためには、リポジトリ内の特定の場所にファイルを配置する必要があります。基本のディレクトリ構成は以下の通りです。
.github/
└── actions/
└── setup-node-env/
└── action.yml # ← ここにアクションの定義を書きます!
└── workflows/
└── ci.yml # ← 呼び出し元のワークフロー
ポイントは `.github/actions/` のような専用ディレクトリを切り、その中にアクションごとのフォルダを作り、必ず `action.yml` という名前でファイルを作成することです。
「HelloWorld」的動作確認:Node.jsのセットアップを共通化する
それでは、よくある「リポジトリのチェックアウト + Node.jsのセットアップ + 依存関係のインストール」を一つにまとめたComposite Actionを作ってみましょう。
`.github/actions/setup-node-env/action.yml` を開き、以下のように記述します。
name: ‘Setup Node Environment’
description: ‘リポジトリをチェックアウトし、指定バージョンのNode.jsと依存関係をインストールする’
外部から受け取る引数(インプット)を定義できます
inputs:
node-version:
description: ‘使用するNode.jsのバージョン’
required: false
default: ’18.x’
runs:
using: ‘composite’ # ここでComposite Actionであることを指定します
steps:
# 1. リポジトリのコードを手元に持ってくる
- name: Checkout Repository
uses: actions/checkout@v4
# 2. 指定されたバージョンのNode.jsをセットアップ
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: ‘npm’
# 3. 依存関係をインストール
- name: Install dependencies
run: npm ci
shell: bash # Composite Actionsではshellの指定が必須です!
💡 ここが重要ポイント:
`runs.using: ‘composite’` と指定するのが最大の特徴です。また、通常のワークフローと違って、`run`コマンドを使う場合は `shell: bash` などを必ず明示する必要があります。忘れやすいので注意してくださいね!
—
3. 作ったアクションをワークフローから呼び出してみよう
先ほど作った自分専用のアクションを、実際のCIワークフロー(`.github/workflows/ci.yml`)から呼び出してみましょう。
name: CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 自作したComposite Actionsを呼び出す!
# パスには、リポジトリのルートからの相対パスを指定します
- name: Setup Environment
uses: ./.github/actions/setup-node-env
with:
node-version: ’20.x’ # 必要に応じて引数を渡せます(省略すればデフォルトの18.x)
# ここから先は、その環境を前提とした独自のテストやビルド処理を書くだけ!
- name: Run Lint
run: npm run lint
- name: Run Test
run: npm test
どうでしょう? ワークフローの記述が劇的にスッキリしましたよね!
もし将来、Node.jsのキャッシュ戦略が変わったり、インストールコマンドにオプションを追加したくなっても、`.github/actions/setup-node-env/action.yml` を1箇所直すだけで、すべてのワークフローにその変更が自動的に反映されます。これがDRYの醍醐味です。
—
4. 現場で役立つ!さらに一歩進んだ設計の極意
基本が分かったところで、シニアエンジニアが実践している「さらにメンテナンス性を高めるための設計テクニック」をいくつかこっそり伝授します。
1. アクション側から「出力(Outputs)」を返す
処理を共通化したはいいものの、「そこで生成された成果物のパス」や「判定結果」を呼び出し元のワークフローで使いたい場合もありますよね。そんなときは `outputs` を使います。
action.yml の一部
inputs:
# … 省略 …
outputs:
node-version-used:
description: ‘実際に使用されたNode.jsのバージョン’
value: ${{ steps.set-version.outputs.version }}
runs:
using: ‘composite’
steps:
- id: set-version
run: echo “version=20.x” >> $GITHUB_OUTPUT
shell: bash
呼び出し側では、`${{ steps.step-id.outputs.node-version-used }}` のように受け取ることができます。
2. プライベートリポジトリ間での共有
今回は同じリポジトリ内(`./.github/actions/…`)に置きましたが、共通化を進めると「組織(Organization)内のすべてのプロジェクトで同じCIの作法を強制したい」というニーズが出てきます。
その場合は、専用の共通リポジトリを作り、そこにComposite Actionsを配置します。
呼び出すときは、以下のように組織名とリポジトリ名を指定するだけです!
uses: your-org/shared-actions/setup-node-env@main
これで、全社的なCIの標準化が一気に進みます。
—
まとめ
今回は、GitHub Actionsの「Composite Actions」を使ってワークフローをDRYにする方法をご紹介しました。
- 重複する一連のステップは、`action.yml` にまとめてカプセル化する。
- `using: ‘composite’` と `shell: bash` の指定を忘れない。
- Inputs/Outputsを活用して、柔軟で再利用性の高いパーツを作る。
CIの記述が綺麗に整理されると、開発者としてのモチベーションもぐっと上がりますし、何よりメンテナンスのストレスから解放されます。
「毎日の作業が劇的に楽になる」この感覚を、ぜひあなたのプロジェクトでも味わってみてくださいね。それでは、快適なCI/CDライフを!