📁

GitHub Actions でディレクトリ構造にもとづいて並列実行する

2024/03/06に公開

Leaner 開発チームの黒曜(@kokuyouwind)です。

今年 5 月に開催される RubyKaigi 2024 で CfP が採択されました!

昨年の RubyKaigi 2023 LT では「RBS meets LLMs - 大規模言語モデルを用いた型推論を試してみた」というタイトルで登壇しましたが、この発表で Future Work として挙げた「ツール化と各種 LLM, プロンプトでの精度検証の話」をする予定です。

https://zenn.dev/leaner_dev/articles/20230519-rubykaigi-2023-lt

以上近況でした。記事は Ruby と全く関係のない GitHub Actions の話です。

TL;DR

GitHub Actions の matrix 指定には ${{ fromJSON(needs.setup.outputs.matrix) }} のように動的な値を指定できる。

先行する job で ls 結果を出力しておくことで、ディレクトリごとに 1 つずつジョブを立ち上げて並列実行できる。

ディレクトリ構造に基づいて GitHub Actions を並列実行させたい

Terraform でリソースを管理する際、環境ごとに管理を分離するため environments 以下に環境ごとのディレクトリを作成するのがベストプラクティスになっています。

https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja#subdirectories

弊社でもこの構造を取っており、運用している 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]

.github/workflows/terraform_plan.yml
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 に使える形に整形してしまいましょう。

以下が書き換えたバージョンのワークフロー定義です。

.github/workflows/terraform_plan.yml
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 に動的な値を渡せると初めて知ったときに衝撃を受けました。めちゃくちゃ便利なのでぜひ活用してみてください!

脚注
  1. 内部コード名を利用していてそのままだと分かりづらいため、ここでは serviceA, serviceB としています。 ↩︎

  2. 認証設定など細かいところは省いています。 ↩︎

  3. 画像は 1 つのサービスしか管理していない時期なので、並列数が少なくなっています。 ↩︎

  4. これも 2 つめのサービスは develop しか管理していないときなので並列数がやや少ないです。また setupworkflow_call で別ファイルに切り出しているため、ジョブ名表示がやや異なっています。 ↩︎

リーナーテックブログ

Discussion