GitHub Actions でディレクトリ構造にもとづいて並列実行する
Leaner 開発チームの黒曜(@kokuyouwind)です。
今年 5 月に開催される RubyKaigi 2024 で CfP が採択されました!
昨年の RubyKaigi 2023 LT では「RBS meets LLMs - 大規模言語モデルを用いた型推論を試してみた」というタイトルで登壇しましたが、この発表で Future Work として挙げた「ツール化と各種 LLM, プロンプトでの精度検証の話」をする予定です。
以上近況でした。記事は Ruby と全く関係のない GitHub Actions の話です。
TL;DR
GitHub Actions の matrix
指定には ${{ fromJSON(needs.setup.outputs.matrix) }}
のように動的な値を指定できる。
先行する job で ls
結果を出力しておくことで、ディレクトリごとに 1 つずつジョブを立ち上げて並列実行できる。
ディレクトリ構造に基づいて GitHub Actions を並列実行させたい
Terraform でリソースを管理する際、環境ごとに管理を分離するため environments
以下に環境ごとのディレクトリを作成するのがベストプラクティスになっています。
弊社でもこの構造を取っており、運用している 2 つのサービス[1]についてそれぞれ develop
, staging
, production
の環境ごとのディレクトリを作成しています。
❯ tree environments -L 1
environments
├── serviceA-develop
├── serviceA-production
├── serviceA-staging
├── serviceB-develop
├── serviceB-production
└── serviceB-staging
ところで、各環境のディレクトリで terraform plan
をひたすら実行するのは面倒です。とはいえ modules
を変更した場合は影響範囲がわからないため、各環境でのプラン結果を確認する必要があります。
こういった場合、 Pull Request を作成したときに各ディレクトリでの terraform plan
を GitHub Actions で確認できると便利そうです。
matrix に直接ディレクトリ名を列挙する場合
各ディレクトリでの terraform plan
は並列実行可能なので、 matrix
を利用して並列実行すれば良さそうです。
この場合、だいたい以下のようなワークフロー定義で実現できます。[2]
name: Terraform Plan
on: pull_request
jobs:
plan:
runs-on: ubuntu-latest
needs: [ setup ]
strategy:
fail-fast: false
matrix:
environment:
- serviceA-develop
- serviceA-production
- serviceA-staging
- serviceB-develop
- serviceB-production
- serviceB-staging
defaults:
run:
working-directory: environments/${{ matrix.environment }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan
これで、以下のようにディレクトリごとの terraform plan
が並列実行されます。[3]
これでも十分便利ですが、今後 environments
以下に管理対象を増やしたときには GitHub Actions ワークフローファイルも書き換える必要があります。
面倒ですし、修正を忘れる可能性も高いですね。
matrix を動的に生成する
実は matrix
に固定値ではなく動的な値を設定することもできます。
そこで、先行するジョブで environments
以下の ls
結果を取得し、 matrix
に使える形に整形してしまいましょう。
以下が書き換えたバージョンのワークフロー定義です。
name: Terraform Plan
on: pull_request
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.output_matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: output_matrix
run: |
echo "matrix=$(ls environments | jq -R -s -c '{ "environment": split("\n")[:-1] }')" > $GITHUB_OUTPUT
plan:
runs-on: ubuntu-latest
needs: [ setup ]
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
defaults:
run:
working-directory: environments/${{ matrix.environment }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan
ls environments
した値を jq
で整形して GITHUB_OUTPUT
に出力しているところがポイントです。
これを実行すると、以下のように setup
が先行ジョブとして実行されたうえで、 plan
が並列実行されています。[4]
これによって、今後 environments
を増やしてもワークフロー定義を触らずに並列数が増えるようになりました。便利!
まとめ
今回は Terraform の environments ディレクトリ以下の構造に基づいて並列実行しましたが、他にも動的な matrix 制御が役立つ場面は色々ありそうです。
ぱっと思いつくのは以下のようなケースでしょうか。
- サポートする Ruby プラットフォームをリポジトリ内ファイルで管理したい
- rspec の前回の実行時間レポートに基づいて、並列実行するファイルを振り分けたり並列数を増減させたい
自分は matrix に動的な値を渡せると初めて知ったときに衝撃を受けました。めちゃくちゃ便利なのでぜひ活用してみてください!
Discussion