GitHub Actions 講座 #10 運用パターン — paths フィルタ、concurrency、デバッグ、コスト点検

読了 5分

文法とパイプラインは揃いました。最終回は、それを毎日回すときに必要な運用の習慣です。ひとつひとつは小さな設定ですが、なければ「ドキュメントしか直していないのに CI が全部走る」「デプロイが 2 つ重なって絡まった」「止まったジョブに 6 時間課金された」といった小さな事故が続きます。シリーズの各所で軽く触れてきた断片を、運用の視点で集めて整理します。

paths フィルタ — 変わった部分だけ起こす #

モノレポ(あるいはドキュメントとコードが 1 つのリポジトリにある普通のプロジェクト)で、すべてのプッシュがすべてのワークフローを起こすと、時間とコストが無駄になります。トリガーにパス条件を付けます。

.github/workflows/backend.yml
on:
  push:
    branches: [main]
    paths:
      - "backend/**"
      - ".github/workflows/backend.yml"   # ワークフロー自身の変更も含める

paths-ignore で逆方向(ドキュメント変更の除外など)も可能です。おすすめの慣例を 1 つ挙げると、ワークフロー自身のパスを paths に含めることです。ワークフローを直したプッシュがそのワークフローを起こさず、「直したのに検証されないまま」マージされる空振りを防いでくれます。注意すべきは必須チェックとの組み合わせです。ブランチ保護で「このチェックは必ずパス」を設定したワークフローが paths に引っかかってまったく実行されないと、PR は永遠に待ち続けます。この場合は、該当チェックを条件付きにしない、あるいはパスごとの条件をジョブレベルの if に移す、といった調整が必要です。Terraform 運用講座 #1 でインフラのパスにだけ plan を仕掛けたのが、このフィルタの実戦例です。

concurrency — 重なりをポリシーにする #

同じ対象に実行が重なったときのポリシーが concurrency です。グループキーが同じ実行は同時に 1 つだけ走り、待機中のものは最新の 1 つだけが残ります。

concurrencyの設定
# CI: 同じ PR の以前の実行はキャンセル(最新コミットだけに意味がある)
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

# デプロイ: 進行中のデプロイは最後まで(中断の方が危険)
concurrency:
  group: deploy-prod
  cancel-in-progress: false

判断基準は中断してもよい作業かです。PR の CI は古いコミットの結果に意味がないのでキャンセルが得ですし、デプロイ、マイグレーション、Terraform の apply は途中で切れると中途半端な状態が残るので、最後まで走らせて次を待たせます。グループキーの設計も同じ論理です。PR ごとに分ける(github.ref)のか、グローバルに 1 つ(deploy-prod)で直列化するのかは、「何が重なってはいけないのか」への答えと一致している必要があります。

timeout — デフォルトの 6 時間を信じない #

ジョブのデフォルトのタイムアウトは 6 時間(360 分)です。止まったテストや、応答のない外部 API を待つステップがあると、6 時間ランナーを占有して課金されます。すべてのジョブに上限を明示することを習慣にします。

.github/workflows/test.yml
jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15

値は「正常な実行の 2〜3 倍」程度にすれば十分です。タイムアウトで死ぬジョブはどのみち異常であり、早く死ぬほど早く発見できます。

デバッグ — ログを増やす方法 #

失敗ログが足りないときに使う道具を、順番に整理します。

  1. 再実行: 実行画面の Re-run メニューには、失敗したジョブだけを再実行するオプションがあります。一時的なネットワーク障害かどうかを見分ける最初の手段です。
  2. デバッグログ: リポジトリの secrets(または variables)に ACTIONS_STEP_DEBUG を true で入れるとステップ内部の詳細ログが、ACTIONS_RUNNER_DEBUG はランナーレベルのログが有効になります。再実行ダイアログの「Enable debug logging」チェックで一回だけ有効にすることもでき、こちらの方が手軽です。
  3. コンテキストのダンプ: #2 の toJSON() 出力は、「なぜ if が期待と違う評価になったのか」系のデバッグへの最短ルートです。
  4. ローカル実行ツール: act のようなローカル実行ツールで、プッシュなしにワークフローを回してみることができます。ランナー環境と完全に同じではないため最終確認には実際の実行が必要ですが、YAML 文法の試行錯誤の往復時間を大きく縮めてくれます。

コスト点検 — どこから漏れているかを見る画面 #

組織・アカウントの Settings にある Billing で、Actions の使用量をリポジトリ別、ワークフロー別、ランナー OS 別に確認できます。点検の手順は単純です。使用量上位のワークフローを開いて、#4 の道具が適用されているかを見ます。キャッシュのない依存関係のインストール、不要なマトリックスの軸(特に macOS)、paths フィルタなしの全量実行、タイムアウトのない長時間ジョブが、定番の漏れポイントです。プライベートリポジトリの実行量が増え続けるなら、#8 のセルフホステッドの損益分岐の検討が次のステップです。

シリーズを終えて #

10 本を整理するとこうなります。構造と文法をつかみ(#1〜#2)、シークレットとゲートを整え(#3)、実行を増やす・減らす方法を学び(#4)、部品化し(#5)、イメージビルドと AWS デプロイのパイプラインを組み立て(#6〜#7)、ランナーとセキュリティの原則を立て(#8〜#9)、今日の運用習慣で締めくくりました。このシリーズのワークフローが実際のインフラとつながる事例は Terraform トラック にあり、GitHub 自体を資格として整理したいなら GitHub Foundations が隣のシリーズです。

今回の記事で扱った内容です。

  • paths フィルタで変わった部分だけを起こしつつ、ワークフロー自身のパスを含め、必須チェックとの組み合わせに注意します
  • concurrency の基準は「中断してもよいか」です。CI はキャンセル、デプロイは完走後に直列化します
  • デフォルトのタイムアウト 6 時間を信じず、すべてのジョブに timeout-minutes を明示します
  • デバッグは再実行、デバッグログの切り替え、コンテキストのダンプ、ローカル実行ツールの順に使います
  • コストは Billing 画面で上位のワークフローから点検します。キャッシュの不在、macOS のマトリックス、フィルタなしの実行が定番の漏れです
X