プロジェクトの規模が拡大するにつれ、テストが増えたり静的解析など対応しなければらないこともあり、CI/CDの実行時間が増加しがちです。
基本的な並列JOB
lint
やユニットテストのように、互いに依存しないタスクは、jobs
配下に並べて定義するだけで並列実行されます。これが最も基本的な並列化の手法です。
# .github/workflows/ci.yml
jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test:unit
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
一方、「ビルド後にテストを実行する」といった依存関係の制御には、needs
キーワードを用います。これにより、先行JOBの完了を待って後続のJOBを開始させることが可能です。以下の例では、build
の完了後に2つのテストが並列で実行されます。
# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
# この後、ビルド成果物をアーティファクトとしてアップロード
test-chrome:
needs: build # buildジョブの完了を待機
runs-on: ubuntu-latest
steps:
# アーティファクトをダウンロードしてからテスト実行
- run: npm test:chrome
deploy:
needs: [test-chrome, test-firefox] # 2つのテストジョブの完了を待機
runs-on: ubuntu-latest
steps:
- run: echo "All tests passed, deploying..."
アーティファクト
GitHub Actionsの各JOBは独立した仮想環境で実行されるため、JOB間でファイルなどの成果物
に当たる物を直接共有することはできません。
そこで、JOB間でファイルを共有する仕組みとして「アーティファクト」を利用します。先行JOBでactions/upload-artifact
を用いてファイルをアップロードし、後続のJOBでactions/download-artifact
でダウンロードすることで、データの受け渡しをします。
バージョンv4
は高速化されており、利用を推奨されているようです。また、retention-days: 1
のように保持期間を短く設定することで、ストレージコストを抑える工夫も有効です。
# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist-files
path: ./dist
retention-days: 1
package:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist-files
- run: ls -l # distディレクトリが展開されている
テストの効率化
strategy.matrixを使用すると、複数のOSやNode.jsバージョンなど、様々な組み合わせのテストを効率的に実行できます。マトリックスに定義した組み合わせに基づき、JOBが自動で生成され、並列処理される仕組みのようですね。
# .github/workflows/ci.yml
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [16, 18, 20]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
ワークフローの最適化
コスト削減
concurrency
を設定することで、同一ブランチへの連続プッシュ時に、実行中のワークフローを自動でキャンセルできます。これにより、不要なリソース消費を防ぐことが可能です。
# .github/workflows/ci.yml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
ジョブ失敗時の挙動
continue-on-error: true
を設定することで、特定のJOBが失敗した場合でも、ワークフロー全体が停止するのを防ぐことが可能です。また、後続のジョブでif: always()
条件を使用すれば、先行JOBの成否にかかわらず処理を実行させることもできます。
# .github/workflows/ci.yml
jobs:
test-optional:
runs-on: ubuntu-latest
continue-on-error: true # このJOBが失敗しても後続は止まらない
steps:
- run: npm test:experimental
deploy:
needs: test-optional
if: always() # test-optional JOB の成否に関わらず実行
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
まとめ
GitHub Actionsの並列処理機能を活用することで、CI/CDの実行時間を大きく改善できるかもしれません。
- 独立タスクの並列化が基本
- needsによる依存関係の定義
- アーティファクトによるデータ共有
- **matrixによる多環境テストの効率化
- concurrencyなどによるワークフローの最適化