【入門編】GitHub Actionsの実行制限を回避!「Conditional Execution」と「Continue-on-error」を組み合わせた堅牢なワークフロー設計 – バージョン管理・CI/CD活用バイブル

GitHub Actionsの実行制限を回避!「Conditional Execution」と「Continue-on-error」を組み合わせた堅牢なワークフロー設計

皆さん、こんにちは!GitHub Actionsを使いこなしていますか?

CI/CDの自動化は、開発チームの生産性を劇的に向上させる強力な武器ですが、ワークフローが複雑になると、予期せぬジョブの失敗が全体のパイプラインを停止させてしまうことも少なくありません。特に、外部サービスへの依存や、一時的なネットワークの問題などでジョブが失敗した場合、その度に手動で再実行するのは非常に手間がかかりますよね。

でも、安心してください!GitHub Actionsには、そんな悩みを解決してくれる強力な機能があるんです。今回は、「Conditional Execution(条件付き実行)」と「Continue-on-error」を組み合わせることで、特定のジョブが失敗しても後続処理を止めずに、CI/CDパイプラインの安定性を飛躍的に向上させる、現場で震えるほど役立つ極限のテクニックを、初心者の方にも分かりやすく、そして丁寧に解説していきます。

これをマスターすれば、毎日の作業が劇的に楽になること間違いなしですよ!

1. GitHub Actionsとは? CI/CD自動化の心臓部を理解しよう

まず、GitHub Actionsが一体何者なのか、その役割からおさらいしておきましょう。

GitHub Actionsは、GitHub上でコードのビルド、テスト、デプロイといった一連のソフトウェア開発ワークフローを自動化するためのプラットフォームです。

  • イベント駆動型: 特定のGitHubイベント(例: コードのプッシュ、プルリクエストの作成、Issueの作成など)をトリガーとして、定義されたワークフローが自動的に実行されます。
  • ワークフロー: YAMLファイルで定義され、一連のジョブ(Job)から構成されます。
  • ジョブ: 複数のステップ(Step)から構成され、それぞれが特定のタスク(コマンド実行、スクリプト実行など)を実行します。
  • ランナー: ジョブを実行する仮想マシンやコンテナです。GitHubがホストするマネージドランナー(Ubuntu, Windows, macOS)や、自前でホストするセルフホストランナーを選択できます。

つまり、GitHub Actionsは、開発者がコードをプッシュするたびに、自動的にテストを実行し、問題がなければデプロイまで行ってくれる、そんな夢のような環境を実現してくれるツールなんです。

2. 基礎セットアップ:初めてのワークフローを作成してみよう

では、早速GitHub Actionsのワークフローを作成してみましょう。

2.1. ワークフローファイルの配置

GitHub Actionsのワークフローは、リポジトリの`.github/workflows/`ディレクトリ内にYAMLファイルとして配置します。もしこのディレクトリが存在しない場合は、新しく作成してください。

例えば、`main.yml`という名前でファイルを作成してみましょう。

.github/workflows/main.yml
name: My First CI Workflow # ワークフローの名前

on: # ワークフローをトリガーするイベント
push: # mainブランチへのプッシュをトリガーにする
branches: [ main ]
pull_request: # mainブランチへのプルリクエストをトリガーにする
branches: [ main ]

jobs: # 実行されるジョブの集合
build: # ジョブID (任意)
runs-on: ubuntu-latest # ジョブを実行するランナー環境

steps: # ジョブ内のステップ (処理の順番)

  • name: Checkout code # コードをチェックアウトするステップ

uses: actions/checkout@v3 # GitHubが提供する公式アクションを使用

  • name: Run a simple script # 簡単なスクリプトを実行するステップ

run: echo “Hello, GitHub Actions!” # 実行したいコマンド

  • name: Another step # もう一つのステップ

run: echo “This is another step.”

2.2. コード解説

  • `name`: ワークフローの名前です。GitHubのUIに表示されます。
  • `on`: ワークフローをトリガーするイベントを指定します。ここでは、`push`イベント(`main`ブランチへのプッシュ時)と`pull_request`イベント(`main`ブランチへのプルリクエスト時)を指定しています。
  • `jobs`: ワークフロー内で実行されるジョブの定義です。複数のジョブを定義し、依存関係を設定することも可能です。
  • `build`: ジョブのIDです。任意ですが、後で参照する際に重要になります。
  • `runs-on`: ジョブを実行するランナー環境を指定します。`ubuntu-latest`は、常に最新のUbuntuランナーを使用することを意味します。
  • `steps`: ジョブ内で実行されるステップのリストです。上から順に実行されます。
  • `uses: actions/checkout@v3`: これは「アクション」と呼ばれるもので、GitHubが提供する便利な再利用可能なコードです。`actions/checkout`は、リポジトリのコードをランナーにチェックアウトするために必須のアクションです。
  • `run`: シェルコマンドを実行するためのキーです。

2.3. 動作確認:Hello, World! を実行しよう

このワークフローをリポジトリにコミットしてプッシュしてみてください。GitHubの「Actions」タブに移動すると、先ほど定義したワークフローが実行されているのが確認できるはずです。

![GitHub Actionsタブの例](https://docs.github.com/assets/cb-13637/images/help/actions/actions-tab.png)
(※上記はイメージ画像です。実際の画面とは異なる場合があります。)

実行が成功すると、各ステップで指定した`echo`コマンドの出力が確認できます。「Hello, GitHub Actions!」というメッセージが表示されていれば、あなたの最初のGitHub Actionsワークフローは成功です!おめでとうございます!

3. 「Continue-on-error」の正しい活用法:失敗を恐れないワークフローへ

さて、ここからが本題です。CI/CDパイプラインが長くなると、あるジョブが失敗しただけで全体のパイプラインが止まってしまうのは避けたい状況が出てきます。

例えば、以下のようなシナリオを考えてみましょう。

  • シナリオ1: テスト実行
  • 単体テストは必須。失敗したらパイプラインを止めたい。
  • E2Eテストは、実行に時間がかかり、外部サービスに依存するため、時々失敗することがある。失敗しても、デプロイの判断をしたい。
  • シナリオ2: リリースノート生成
  • リリースノートの自動生成は試みだが、完璧ではない。失敗しても、他の重要なデプロイ処理は止めずに進めたい。

このような場合に役立つのが、`continue-on-error`オプションです。

3.1. `continue-on-error`とは?

`continue-on-error`は、ジョブまたはステップレベルで設定でき、そのジョブまたはステップが失敗した場合でも、後続のステップやジョブの実行を継続させるための設定です。

ジョブレベルでの設定例:

jobs:
test_job:
runs-on: ubuntu-latest
continue-on-error: true # このジョブが失敗しても、後続のジョブは実行される
steps:

  • name: Run tests

run: |
# テスト実行コマンド
exit 1 # あえて失敗させる例

ステップレベルでの設定例:

jobs:
build:
runs-on: ubuntu-latest
steps:

  • name: Checkout code

uses: actions/checkout@v3

  • name: Run critical tests

run: |
echo “Running critical tests…”
# 必須のテストコマンド
exit 0 # 成功を想定

  • name: Run optional tests

continue-on-error: true # このステップが失敗しても、次のステップに進む
run: |
echo “Running optional tests…”
# 失敗する可能性のあるテストコマンド
exit 1 # あえて失敗させる例

  • name: Deploy to staging # このステップは、上のステップが失敗しても実行される

run: echo “Deploying to staging…”

3.2. 「正しい活用法」の落とし穴

`continue-on-error: true`は非常に便利ですが、 indiscriminately(無差別に)設定すると、本来検知すべき重要なエラーを見逃してしまう可能性があります。

落とし穴:

  • 必須のテストが失敗しても気づかない: `continue-on-error: true`をジョブ全体に設定してしまうと、単体テストや静的解析などの必須チェックが失敗しても、パイプラインは「成功」として進行してしまいます。
  • エラーの原因特定が困難になる: 複数のステップで`continue-on-error: true`が設定されていると、どのステップで何が問題だったのかを追跡するのが難しくなります。

目指すべきは「必要な失敗は止め、許容できる失敗は続行させる」という、賢い失敗のハンドリングです。

4. 「Conditional Execution (if)」との組み合わせ:賢い失敗ハンドリングの実現

ここで、`continue-on-error`だけでは難しかった「必要な失敗は止め、許容できる失敗は続行させる」という部分を、`if`条件式と組み合わせることで実現します。

`if`条件式を使うと、特定の条件が満たされた場合にのみ、ステップやジョブを実行することができます。GitHub Actionsでは、`github`コンテキスト、`runner`コンテキスト、`steps`コンテキストなど、様々なコンテキストの情報を使って条件を記述できます。

4.1. リトライが必要なタスクと必須タスクの分離

例えば、E2Eテストが時々失敗するが、リトライすることで成功する可能性がある、という状況を考えます。

jobs:
e2e_tests:
runs-on: ubuntu-latest
steps:

  • name: Checkout code

uses: actions/checkout@v3

  • name: Run E2E tests (Attempt 1)

id: run_e2e_attempt1 # このステップのIDを定義
run: |
echo “Running E2E tests – Attempt 1…”
# ここにE2Eテスト実行コマンド。失敗する可能性を考慮。
# 例: ./run_e2e_tests.sh
# 失敗した場合は、exit 1 を返す
exit 1 # ここでは意図的に失敗させる

  • name: Run E2E tests (Attempt 2)

id: run_e2e_attempt2
# 前のステップ (run_e2e_attempt1) が失敗した場合のみ実行
if: steps.run_e2e_attempt1.outcome == ‘failure’
run: |
echo “Running E2E tests – Attempt 2 (Retry)…”
# 再度E2Eテスト実行コマンド
# 例: ./run_e2e_tests.sh
# 成功したら exit 0 を返す
exit 0 # ここでは意図的に成功させる

  • name: E2E Test Status Check

# どちらかのE2Eテストステップが失敗していたら、ここでパイプラインを停止させる
if: steps.run_e2e_attempt1.outcome == ‘failure’ && steps.run_e2e_attempt2.outcome == ‘failure’
run: |
echo “E2E tests failed after retries. Stopping the workflow.”
exit 1 # ここで明示的に失敗させることで、ジョブ全体を失敗させる

解説:

1. `Run E2E tests (Attempt 1)`: 最初のE2Eテストを実行します。ここでは、意図的に失敗させています。`id`を`run_e2e_attempt1`と付けて、後で結果を参照できるようにします。
2. `Run E2E tests (Attempt 2)`: このステップは、`if: steps.run_e2e_attempt1.outcome == ‘failure’`という条件で実行されます。つまり、最初の試行が失敗した場合にのみ実行されます。ここで、リトライとして再度テストを実行し、成功するシナリオを想定しています。`id`を`run_e2e_attempt2`と付けます。
3. `E2E Test Status Check`: このステップは、最初の試行 (`run_e2e_attempt1`) も、リトライ (`run_e2e_attempt2`) も両方失敗した場合にのみ実行されます。このステップで`exit 1`を実行することで、ジョブ全体を失敗させることができます。

このように、`if`条件式と`steps`コンテキストの`outcome`プロパティ(`success`, `failure`, `cancelled`, `skipped`などを返します)を組み合わせることで、失敗時の挙動を細かく制御できます。

4.2. `continue-on-error`との相乗効果

さらに、`continue-on-error`を「許容できる失敗」のステップに適用することで、より洗練されたワークフローが実現できます。

例えば、リリースノート生成のような、失敗しても後続のデプロイに影響を与えたくないタスクに`continue-on-error: true`を設定します。

jobs:
build_and_test: # 必須のビルドとテストジョブ
runs-on: ubuntu-latest
steps:

  • name: Checkout code

uses: actions/checkout@v3

  • name: Build

run: echo “Building…”

  • name: Test

run: echo “Testing…”

generate_release_notes: # リリースノート生成ジョブ (失敗してもOK)
runs-on: ubuntu-latest
needs: build_and_test # build_and_test ジョブが成功した後に実行
continue-on-error: true # このジョブが失敗しても、後続のジョブは実行される
steps:

  • name: Generate Release Notes

run: |
echo “Generating release notes…”
# リリースノート生成コマンド (失敗する可能性あり)
exit 1 # あえて失敗させる例

deploy_to_production: # 本番デプロイジョブ (必須)
runs-on: ubuntu-latest
needs: build_and_test # build_and_test ジョブが成功した後に実行
# release_notes ジョブが失敗しても、このジョブは実行される (continue-on-error: true のおかげ)
steps:

  • name: Deploy to Production

run: echo “Deploying to production…”

この例では、`generate_release_notes`ジョブに`continue-on-error: true`を設定しています。これにより、リリースノート生成が失敗しても、`deploy_to_production`ジョブは実行されます。

もし、リリースノート生成が成功した場合のみデプロイに進みたい、という要件であれば、`deploy_to_production`ジョブの`needs`に`generate_release_notes`を追加し、`generate_release_notes`ジョブ自体には`continue-on-error: false`(デフォルト)を設定します。

このように、`continue-on-error`と`needs`、そして`if`条件式を組み合わせることで、ジョブ間の依存関係や失敗時の挙動を柔軟かつ精密に制御することが可能になります。

4.3. 現場で役立つ!実践的なシナリオ

  • 外部API依存のテスト:
  • 外部APIに依存するテストは、`continue-on-error: true`を設定したステップで実行。
  • テスト結果をサマリーに記録し、失敗した場合は通知する(`if`条件式でチェック)。
  • 必須の単体テストは、`continue-on-error: false`で実行し、失敗したらパイプラインを停止させる。
  • コードカバレッジレポート生成:
  • カバレッジレポート生成は、失敗してもビルド自体は成功させたい場合がある。
  • `continue-on-error: true`を設定し、レポート生成の失敗を後続のデプロイに影響させない。
  • レポートの閾値を下回った場合は、`if`条件式で警告を出す、あるいは次のジョブへのトリガーを無効にする、といった制御を行う。
  • 複数環境へのデプロイ:
  • ステージング環境へのデプロイは失敗しても、本番環境へのデプロイは実行したい場合。
  • ステージングデプロイのジョブに`continue-on-error: true`を設定。
  • 本番デプロイのジョブは、ステージングデプロイの成功・失敗に関わらず実行できるように、`needs`を調整する。

5. まとめ:堅牢なCI/CDワークフロー設計への道

今回は、GitHub Actionsの「Conditional Execution(if)」と「Continue-on-error」を組み合わせることで、CI/CDパイプラインの実行制限を回避し、堅牢なワークフローを設計するテクニックを解説しました。

  • `continue-on-error`: ジョブやステップの失敗を許容し、後続処理の実行を継続させます。
  • `if`条件式: 特定の条件が満たされた場合にのみ、ステップやジョブを実行します。
  • 組み合わせ: `if`条件式と`steps`コンテキストの`outcome`などを活用することで、「必要な失敗は止め、許容できる失敗は続行させる」という賢い失敗ハンドリングを実現できます。

これらの機能を理解し、適切に組み合わせることで、

  • CI/CDパイプラインの安定性向上: 一時的な問題によるパイプライン全体の停止を防ぎます。
  • 開発サイクルの高速化: 失敗したジョブの再実行の手間を省き、開発者はより迅速にフィードバックを得られます。
  • デバッグの効率化: 重要なエラーと、許容できるエラーを区別しやすくなります。

GitHub Actionsは非常に強力なツールですが、その真価を発揮するのは、今回ご紹介したような、現場のニーズに合わせた「賢い」ワークフロー設計があってこそです。

ぜひ、皆さんのプロジェクトでもこれらのテクニックを試して、CI/CDパイプラインをより安定で、より効率的なものに進化させてみてください。毎日の作業が、きっと劇的に楽になりますよ!

ご質問や、さらに深掘りしたい点があれば、お気軽にお尋ねくださいね。

タイトルとURLをコピーしました