GitHub Actions はWHENとWHATで分割しよう
GitHub Actions でワークフローを作成し運用していると, いつの間にかワークフローファイルが大量に増えてしまったり, ワークフローが肥大化してしまったりということが多々あります. また大抵の場合はワークフローの中で actions/checkout
のようなアクションを利用しているでしょうから, それらの依存関係の管理といったメンテナンスも必要です. そのためワークフローファイルもプロダクトコードと同様に「CLEAN」であるべきです.
どうすればワークフローファイルを「CLEAN」にできるでしょうか. 実はワークフローファイルには必ず2つの要素があります. それがイベント (WHEN) とジョブ (WHAT) です. つまりワークフローが実行される起点となるイベントを管理するワークフローファイルと実際にワークフローとして実行されるジョブを管理するワークフローファイルを分けるということです.
具体例
実際にどのように管理していくか見ていきましょう.
このリポジトリではワークフローファイルを以下のように分けています. テストは Google の test size に従って分けています[2].
/.github
├── dependabot.yml
└── workflows
├── deploy-pages.yml # GitHub Pages へのデプロイ
├── pullreq-actions.yml # dependabot が作成した GitHub Actions への Pull Request
├── pullreq-npm.yml # dependabot が作成した npm パッケージの Pull Request
├── test-large.yml # large size のテスト(E2Eテスト)
├── test-medium.yml # medium size のテスト
├── test-small.yml # small size のテスト
└── test-smoke.yml # アプリケーションのビルド検証
このように Pull Request やデプロイといった起点となるイベントとテストのような実際にワークフローとして実行したいジョブを分けています.
見ての通り, ワークフローファイルのファイル名も GitHub Actions のダッシュボード上でも整然としています[3].
これを実現しているのは再利用可能なワークフローです[4][5].
イベントを管理するワークフローはどのイベントで発火するかに注目して書くようにし, 可能な限りジョブは再利用可能なワークフローを使って実行します.
name: Pull Request(NPM)
on:
pull_request:
branches:
- master
paths:
- "package.json"
permissions:
pull-requests: write
contents: write
jobs:
test-smoke:
if : ${{ github.actor == 'dependabot[bot]' }}
uses: ./.github/workflows/test-smoke.yml
secrets: inherit
test-small:
if: ${{ github.actor == 'dependabot[bot]' }}
uses: ./.github/workflows/test-small.yml
secrets: inherit
test-medium:
if: ${{ github.actor == 'dependabot[bot]' }}
uses: ./.github/workflows/test-medium.yml
secrets: inherit
auto-merge:
if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ubuntu-latest
needs:
- test-smoke
- test-small
- test-medium
steps:
- name: Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@v2.3.0
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'
- name: Enable auto-merge for Dependabot PRs
if: steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' || steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor'
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
再利用可能なワークフローでは一つのことに集中できるように書きます. ワークフローごと分けてしまうことで, 前後のジョブに起因する不具合も減らすことができます.workflow_call
だけでなく workflow_dispatch
もつけてやると開発やデバッグに便利です.
name: Test of small size
on:
workflow_call:
jobs:
test-small:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
status: ${{ steps.test.outputs.status }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Run test
id: test
run: bun run test:small
env:
CONFIG_ID: ${{ secrets.CONFIG_ID }}
PAT: ${{ secrets.PAT }}
各ワークフローでのオーバーヘッド
ワークフローを分けたことによって、依存関係のダウンロードやコンパイルによるオーバーヘッドが生じます. GitHub Actions の時間短縮については他でも多く解説されていることもあり, ここでは立ち入りません. ただ再利用可能なワークフローを使って細かく分けることで Actions Performance Metrics などを使った解析もやりやすいはずです. あとは公式にある通り依存関係やビルドのキャッシュを利用するといったことを地道にやっていくしかないでしょう(金で殴るのが一番).
依存関係をキャッシュしてワークフローのスピードを上げる - GitHub Docs
この記事ではワークフローが実行される起点となるイベントを管理するワークフローファイルと実際にワークフローとして実行されるジョブを管理するワークフローファイルを分けることによってワークフローをより「CLEAN」に扱う方法を解説しました. 実際にワークフローを運用していくとメンテナンスやワークフローの変更で苦労する機会が少なくないです. ワークフローファイルはプロダクションコードとは異なり, テストもなく品質を保つ義務もさほどありません. ワークフローが作りっぱなしにならないためにも, 「CLEAN」なワークフローファイルを作りましょう.
-
レガシーコードからの脱却―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス, David Scott Bernstein, 吉羽 龍太郎, 永瀬 美穂, 原田 騎郎, 有野 雅士, オライリー・ジャパン, 2019 https://www.oreilly.co.jp/books/9784873118864/ ↩︎
-
Google Testing Blog: Test Sizes https://testing.googleblog.com/2010/12/test-sizes.html ↩︎
-
on-pullreq-***.yml
やjob-test-***.yml
のように接頭辞を使うとさらに整然とします. ↩︎ -
ワークフローの再利用 - GitHub Docs https://docs.github.com/ja/actions/sharing-automations/reusing-workflows ↩︎
-
複合アクションでも同様のことができますが, 私は複合アクションはより粒度が小さく具体的な内容の場合に利用します. 例えば特定のツールのインストールなどです. ↩︎
Discussion