T-CREATOR

GitHub Actions の実行順序を完全図解:イベント → フィルタ → ジョブ → ステップの流れ

GitHub Actions の実行順序を完全図解:イベント → フィルタ → ジョブ → ステップの流れ

GitHub Actions を使用していて、「なぜこのワークフローが実行されないのだろう?」「どのタイミングでジョブが動くのかわからない」といった疑問を抱えたことはありませんか。GitHub Actions の実行順序は、見た目以上に複雑で奥深いものです。

特に複数のジョブが並行実行される場合や、条件分岐が含まれるワークフローでは、その実行フローを正確に理解することが重要になります。本記事では、GitHub Actions の実行順序を図解を交えて詳しく解説し、皆さんの理解を深めていきます。

図解による理解は、抽象的な概念を具体的にイメージできるため、記憶に残りやすく実践的な知識として身につけることができるでしょう。

背景

GitHub Actions の基本概念

GitHub Actions は、GitHub が提供する CI/CD プラットフォームです。リポジトリ内のコードの変更をトリガーとして、自動的にテスト、ビルド、デプロイなどの処理を実行できます。

GitHub Actions の主要な構成要素について理解しておきましょう。

mermaidflowchart TD
  Repository[リポジトリ] -->|contains| Workflows[ワークフロー]
  Workflows -->|consists of| Jobs[ジョブ]
  Jobs -->|contains| Steps[ステップ]
  Steps -->|executes| Actions[アクション]

  Repository -->|triggers| Events[イベント]
  Events -->|starts| Workflows

GitHub Actions は階層構造で動作しており、最上位のリポジトリから最下位のアクションまで、それぞれが明確な役割を持っています。

ワークフロー実行の全体像

GitHub Actions のワークフローは、特定のイベントが発生した際に起動される一連の自動処理です。この処理は以下の順序で実行されます。

以下の図で、イベント発生からワークフロー完了までの全体的な流れを確認してみましょう。

mermaidflowchart LR
  Event[イベント発生] -->|トリガー| Filter{フィルタ条件}
  Filter -->|条件一致| Jobs[ジョブ実行]
  Filter -->|条件不一致| Skip[スキップ]
  Jobs -->|並行/順次| Step1[ステップ1]
  Jobs --> Step2[ステップ2]
  Jobs --> Step3[ステップ3]
  Step1 --> Complete[完了]
  Step2 --> Complete
  Step3 --> Complete

このフローにおいて重要なのは、各段階で異なる制御ロジックが働いていることです。イベントからフィルタ、ジョブからステップへと、段階的に詳細な制御が行われます。

実行順序が重要な理由

GitHub Actions において実行順序の理解が重要な理由は複数あります。

第一に、依存関係の管理です。例えば、テストが成功した後にのみデプロイを実行したい場合、適切な順序制御が必要になります。

第二に、リソースの効率的な使用です。並行実行できるジョブと順次実行すべきジョブを適切に設計することで、実行時間の短縮とコスト削減が可能です。

第三に、トラブルシューティングの観点です。問題が発生した際に、どの段階で何が起こったかを正確に把握できれば、迅速な問題解決につながります。

課題

複雑な実行フローの理解の難しさ

GitHub Actions のワークフローが複雑になると、その実行順序を把握することが困難になります。特に以下のような状況では、実行フローの理解が困難になりがちです。

以下の図は、複雑なワークフローの一例を示しています。

mermaidflowchart TB
  Push[Push Event] --> Test1[Unit Test]
  Push --> Test2[Integration Test]
  Push --> Lint[Linting]

  Test1 --> Build{Build Job}
  Test2 --> Build
  Lint --> Build

  Build -->|success| Deploy1[Deploy to Staging]
  Build -->|success| Deploy2[Deploy to Production]
  Build -->|failure| Notify[Notify Team]

  Deploy1 --> E2E[E2E Testing]
  E2E -->|success| Promote[Promote to Production]
  E2E -->|failure| Rollback[Rollback]

このような複雑な依存関係を持つワークフローでは、どのジョブがいつ実行されるのか、失敗時にはどのような処理が行われるのかを正確に把握することが困難です。

複数のブランチで異なる条件を設定している場合や、手動トリガーと自動トリガーが混在している場合には、さらに複雑性が増します。

デバッグ時の問題特定の困難さ

ワークフローが期待通りに動作しない場合、問題の原因を特定することは簡単ではありません。

一般的なデバッグの課題として以下があります:

課題説明影響度
ログの分散複数のジョブ・ステップにまたがるログの確認
タイミング問題並行実行による競合状態の発生
環境差異ローカル環境と GitHub Actions 環境の違い
権限問題シークレットやトークンのアクセス権限

特に並行実行されるジョブ間での問題は、実行のたびに異なる結果になる可能性があり、再現性の確保が困難になります。

並行処理と依存関係の混乱

GitHub Actions では、デフォルトでジョブが並行実行されます。これは実行時間の短縮につながる一方で、依存関係の理解を複雑にします。

以下のような混乱が生じやすいです:

  • 実行順序の誤解: 並行実行されるジョブの完了順序は保証されない
  • 共有リソースへの競合: 同じファイルやサービスに複数のジョブが同時アクセス
  • 状態の不整合: あるジョブの結果に依存する処理が、期待したタイミングで実行されない

これらの問題を回避するには、適切な依存関係の設定と、並行実行の制御が必要になります。

解決策

イベント発生の仕組み

GitHub Actions のワークフローは、リポジトリで発生する様々なイベントをトリガーとして起動されます。イベントには手動トリガーと自動トリガーがあり、それぞれ異なる特徴を持っています。

主要なイベントタイプを以下の図で確認しましょう。

mermaidflowchart LR
  subgraph "Git Events"
    Push[push]
    PR[pull_request]
    Release[release]
  end

  subgraph "Schedule Events"
    Cron[schedule]
  end

  subgraph "Manual Events"
    Manual[workflow_dispatch]
    API[repository_dispatch]
  end

  Push --> Workflow[ワークフロー実行]
  PR --> Workflow
  Release --> Workflow
  Cron --> Workflow
  Manual --> Workflow
  API --> Workflow

各イベントタイプの特徴を詳しく見ていきましょう。

Push イベントは、ブランチへのコミットプッシュ時に発生します。最も基本的なトリガーで、コードの変更を検知して CI/CD パイプラインを開始する際に使用されます。

yamlon:
  push:
    branches: [main, develop]
    paths: ['src/**', '!docs/**']

このように、特定のブランチやファイルパスの変更のみをトリガーとするよう設定できます。

Pull Request イベントは、プルリクエストの作成、更新、マージ時に発生します。コードレビュープロセスと連携させる際に重要です。

yamlon:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [main]

フィルタリング条件の適用

イベントが発生した後、ワークフローが実際に実行されるかはフィルタリング条件によって決定されます。

フィルタリングは複数の層で行われます:

mermaidflowchart TD
  Event[イベント発生] --> BranchFilter{ブランチフィルタ}
  BranchFilter -->|一致| PathFilter{パスフィルタ}
  BranchFilter -->|不一致| Skip1[スキップ]
  PathFilter -->|一致| ConditionFilter{条件フィルタ}
  PathFilter -->|不一致| Skip2[スキップ]
  ConditionFilter -->|一致| Execute[ワークフロー実行]
  ConditionFilter -->|不一致| Skip3[スキップ]

ブランチフィルタでは、特定のブランチでのみワークフローを実行するよう制御できます。

yamlon:
  push:
    branches:
      - main
      - 'release/**'
    branches-ignore:
      - 'feature/**'

パスフィルタでは、変更されたファイルのパスに基づいてワークフローの実行を制御します。

yamlon:
  push:
    paths:
      - 'src/**'
      - 'package.json'
    paths-ignore:
      - 'docs/**'
      - '**.md'

ジョブ間の依存関係管理

ジョブの依存関係は needs キーワードを使用して明示的に定義します。これにより、複雑なワークフローでも確実な実行順序を保証できます。

以下は典型的な CI/CD パイプラインの依存関係です:

mermaidflowchart TD
  Test[テストジョブ] --> Build[ビルドジョブ]
  Lint[リントジョブ] --> Build
  Build --> Deploy[デプロイジョブ]
  Build --> Security[セキュリティスキャン]
  Deploy --> E2E[E2Eテスト]
  Security --> E2E

この依存関係を YAML で表現すると以下のようになります:

yamljobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Run tests
        run: npm test

  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Run linter
        run: npm run lint

  build:
    runs-on: ubuntu-latest
    needs: [test, lint] # テストとリントが成功した後に実行
    steps:
      - name: Build application
        run: npm run build

依存関係の設定により、並行実行可能なジョブと順次実行が必要なジョブを適切に制御できます。

yamldeploy:
  runs-on: ubuntu-latest
  needs: build # ビルドジョブの成功が必須
  if: github.ref == 'refs/heads/main' # mainブランチのみ
  steps:
    - name: Deploy to production
      run: npm run deploy

ステップの順次実行

ジョブ内のステップは常に順次実行されます。前のステップが成功した場合のみ、次のステップが実行される仕組みです。

mermaidflowchart TD
  Step1[Checkout Code] --> Step2[Setup Node.js]
  Step2 --> Step3[Install Dependencies]
  Step3 --> Step4[Run Tests]
  Step4 --> Step5[Build Application]
  Step5 --> Step6[Deploy]

  Step1 -.->|失敗| End1[ジョブ終了]
  Step2 -.->|失敗| End2[ジョブ終了]
  Step3 -.->|失敗| End3[ジョブ終了]
  Step4 -.->|失敗| End4[ジョブ終了]
  Step5 -.->|失敗| End5[ジョブ終了]

ステップレベルでの制御には、条件分岐やエラーハンドリングを組み込むことができます:

yamlsteps:
  - name: Checkout code
    uses: actions/checkout@v3

  - name: Setup Node.js
    uses: actions/setup-node@v3
    with:
      node-version: '18'

  - name: Install dependencies
    run: npm install

  - name: Run tests
    run: npm test
    continue-on-error: true # エラーでも次のステップを実行

  - name: Upload test results
    if: always() # 前のステップの結果に関わらず実行
    uses: actions/upload-artifact@v3
    with:
      name: test-results
      path: test-results.xml

具体例

基本的なワークフロー例

まず、シンプルな Node.js アプリケーションの CI/CD ワークフローを見てみましょう。この例では、テスト、ビルド、デプロイの基本的な流れを図解します。

基本的なワークフローの実行順序は以下のようになります:

mermaidsequenceDiagram
  participant Dev as 開発者
  participant GitHub as GitHub
  participant Runner as GitHub Runner
  participant Server as デプロイ先

  Dev->>GitHub: git push origin main
  GitHub->>Runner: ワークフロー開始
  Runner->>Runner: コードをチェックアウト
  Runner->>Runner: Node.js環境をセットアップ
  Runner->>Runner: 依存関係をインストール
  Runner->>Runner: テスト実行
  Runner->>Runner: ビルド実行
  Runner->>Server: アプリケーションをデプロイ
  Runner->>GitHub: 実行結果を報告

このワークフローの YAML 設定は以下のようになります:

yamlname: Basic CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

続いて、環境のセットアップを行います:

yaml- name: Setup Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '18'
    cache: 'npm'

依存関係のインストールとテストの実行:

yaml- name: Install dependencies
  run: npm ci

- name: Run tests
  run: npm run test

- name: Run linting
  run: npm run lint

ビルドとデプロイの処理:

yaml- name: Build application
  run: npm run build

- name: Deploy to production
  if: github.ref == 'refs/heads/main'
  run: |
    echo "Deploying to production server"
    # 実際のデプロイコマンドをここに記述

この基本例では、全てのステップが順次実行され、どれか一つでも失敗すると後続の処理は実行されません。

複雑な依存関係を持つワークフロー例

実際のプロジェクトでは、複数のジョブが並行実行され、複雑な依存関係を持つことが一般的です。以下は、フロントエンドとバックエンドを持つ Web アプリケーションのワークフロー例です。

複雑な依存関係の実行フローを図で確認しましょう:

mermaidflowchart TD
  Push[Push Event] --> FE_Test[Frontend Test]
  Push --> BE_Test[Backend Test]
  Push --> Lint[Linting]
  Push --> Security[Security Scan]

  FE_Test --> FE_Build[Frontend Build]
  BE_Test --> BE_Build[Backend Build]
  Lint --> FE_Build
  Lint --> BE_Build

  FE_Build --> Deploy_Staging[Deploy to Staging]
  BE_Build --> Deploy_Staging
  Security --> Deploy_Staging

  Deploy_Staging --> E2E[E2E Testing]
  E2E --> Deploy_Prod[Deploy to Production]

このワークフローでは、テストとリントが並行実行され、全てが成功した後にビルドが実行されます。さらに、ステージング環境での E2E テスト後に本番デプロイが行われます。

フロントエンドのテストジョブ:

yamljobs:
  frontend-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci --prefix frontend
      - run: npm run test --prefix frontend

バックエンドのテストジョブ:

yamlbackend-test:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: '18'
    - run: npm ci --prefix backend
    - run: npm run test --prefix backend

リントジョブ(全体をチェック):

yamllint:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: '18'
    - run: npm ci
    - run: npm run lint

依存関係を持つビルドジョブ:

yamlbuild:
  runs-on: ubuntu-latest
  needs: [frontend-test, backend-test, lint]
  steps:
    - uses: actions/checkout@v3
    - name: Build frontend
      run: |
        npm ci --prefix frontend
        npm run build --prefix frontend
    - name: Build backend
      run: |
        npm ci --prefix backend
        npm run build --prefix backend

条件分岐を含むワークフロー例

実際の開発現場では、ブランチやタグに応じて異なる処理を実行することが重要です。以下は、ブランチ戦略と連携した条件分岐の例です。

条件分岐を含むワークフローの実行パターンを図で表現します:

mermaidflowchart TD
  Event[Git Event] --> Branch{ブランチ判定}

  Branch -->|main| Prod_Flow[本番デプロイフロー]
  Branch -->|develop| Stage_Flow[ステージングデプロイフロー]
  Branch -->|feature/*| Test_Flow[テストのみフロー]
  Branch -->|hotfix/*| Hotfix_Flow[ホットフィックスフロー]

  Prod_Flow --> Full_Test[全テスト実行]
  Stage_Flow --> Basic_Test[基本テスト実行]
  Test_Flow --> Unit_Test[ユニットテストのみ]
  Hotfix_Flow --> Quick_Test[クイックテスト]

  Full_Test --> Prod_Deploy[本番デプロイ]
  Basic_Test --> Stage_Deploy[ステージングデプロイ]
  Quick_Test --> Hotfix_Deploy[ホットフィックスデプロイ]

ブランチに応じた条件分岐の実装:

yamlname: Conditional Workflow

on:
  push:
    branches: [main, develop, 'feature/**', 'hotfix/**']

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm test

本番環境へのデプロイ(main ブランチのみ):

yamldeploy-production:
  runs-on: ubuntu-latest
  needs: test
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: actions/checkout@v3
    - name: Deploy to production
      run: |
        echo "Deploying to production environment"
        # 本番デプロイのコマンド

ステージング環境へのデプロイ(develop ブランチのみ):

yamldeploy-staging:
  runs-on: ubuntu-latest
  needs: test
  if: github.ref == 'refs/heads/develop'
  steps:
    - uses: actions/checkout@v3
    - name: Deploy to staging
      run: |
        echo "Deploying to staging environment"
        # ステージングデプロイのコマンド

フィーチャーブランチでの軽量テスト:

yamlfeature-test:
  runs-on: ubuntu-latest
  if: startsWith(github.ref, 'refs/heads/feature/')
  steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: '18'
    - run: npm ci
    - run: npm run test:unit
    - run: npm run lint

このような条件分岐を活用することで、ブランチの性質に応じた適切な処理を自動化できます。main ブランチでは厳格なテストと本番デプロイ、feature ブランチでは軽量なテストのみ実行するといった使い分けが可能です。

まとめ

GitHub Actions の実行順序は、イベント → フィルタ → ジョブ → ステップ という明確な階層構造で制御されています。この流れを正確に理解することで、効率的で信頼性の高い CI/CD パイプラインを構築できます。

特に重要なポイントをまとめますと、まずイベントトリガーの適切な設定により、必要なタイミングでのみワークフローを実行することでリソースの無駄遣いを防げます。次に、フィルタリング条件の活用により、変更内容に応じた柔軟な制御が可能になります。

ジョブ間の依存関係管理では、needs キーワードを使用して明示的な順序制御を行い、ステップの順次実行では条件分岐やエラーハンドリングを組み込むことで、堅牢なワークフローを実現できるでしょう。

図解を活用した理解により、複雑なワークフローの動作も直感的に把握できるようになります。トラブルシューティングの際も、どの段階で問題が発生しているかを素早く特定し、効率的な解決につなげることができるはずです。

皆さんの GitHub Actions 活用の一助となれば幸いです。

関連リンク