GitHub Actions の構造と変数を整理して、AWSのデプロイ先を分岐させてみよう

記事タイトルとURLをコピーする

こんにちは。
アプリケーションサービス部、DevOps担当の兼安です。

今回は、GitHub Actionsのお話をします。
GitHub Actionsは、GitHub上でCI/CDパイプラインを構築するためのツールです。
シンプルで使いやすいツールですが、その構造を理解すると、より複雑なことができるようになります。
特に、複数のAWSアカウントにデプロイを分岐する際には、構造の理解が重要になると思うので、今回はそのお話をしたいと思います。

本記事のターゲット

GitHub Actionsを使い始めたばかりの方を対象にしています。

GitHub Actionsの構造

GitHub Actionsは、以下のような階層構造になっています。

  • Workflow
    • Job
      • Step

Workflowは、GitHub Actionsの最上位の単位です。
.github/workflowsディレクトリにYAMLファイルとして定義されます。
1つのYAMLファイルが1つのWorkflowを定義します。
Workflow同士は、デフォルトでは互いに独立して実行されます。

Workflowは、複数のJobを持つことができます。
Jobは、Workflow内で実行されるタスクの単位です。
Jobもデフォルトではそれぞれ独立して実行されます。

Jobの中には、複数のStepを定義することができます。
Stepは、実際に実行されるコマンドやアクションの単位です。
Jobの中のStepは、直列で実行されます。

ワークフローでジョブを使用する - GitHub Docs

ワークフロー実行は、既定で並列実行される 1 つ以上の jobs で構成されます。

graph TD
    R["Repository<br>.github/workflows"] --> A["01_basic_workflow_1.yml"]
    R --> D["02_basic_workflow_2.yml"]

    A --> B[Job 1]
    A --> C[Job 2]

    D --> E[Job 1]
    D --> F[Job 2]

    B --> G[Step 1]
    G --> H[Step 2]
    H --> I[Step 3]

    C --> J[Step 1]
    J --> K[Step 2]

    E --> L[Step 1]
    L --> M[Step 2]

    F --> N[Step 1]
    N --> O[Step 2]
    O --> P[Step 3]

    style R fill:#f9f9f9
    style A fill:#e1f5fe
    style D fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#f3e5f5
    style E fill:#f3e5f5
    style F fill:#f3e5f5
    style G fill:#fff3e0
    style H fill:#fff3e0
    style I fill:#fff3e0
    style J fill:#fff3e0
    style K fill:#fff3e0
    style L fill:#fff3e0
    style M fill:#fff3e0
    style N fill:#fff3e0
    style O fill:#fff3e0
    style P fill:#fff3e0

この場合、ワークフローのYAMLファイルは以下のように配置します。

.github/workflows/
  ├── 01_basic_workflow_1.yml
  └── 02_basic_workflow_2.yml

それぞれのYAMLファイルは、以下のような内容になります。

01_basic_workflow_1.yml

name: Basic Workflow 1
on: [push]
jobs:
  job1:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        run: |
          echo "Job1-Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Job1-Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Step 2
        run: |
          echo "Job1-Step2 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Job1-Step2 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Step 3
        run: |
          echo "Job1-Step3 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Job1-Step3 completed: $(date '+%Y-%m-%d %H:%M:%S')"
  job2:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        run: |
          echo "Job2-Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Job2-Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Step 2
        run: |
          echo "Job2-Step2 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Job2-Step2 completed: $(date '+%Y-%m-%d %H:%M:%S')"

02_basic_workflow_2.yml

name: Basic Workflow 2
on: [push]
jobs:
  job1:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        run: |
          echo "Workflow2 Job1 Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Workflow2 Job1 Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Step 2
        run: |
          echo "Workflow2 Job1 Step2 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Workflow2 Job1 Step2 completed: $(date '+%Y-%m-%d %H:%M:%S')"
  job2:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        run: |
          echo "Workflow2 Job2 Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Workflow2 Job2 Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Step 2
        run: |
          echo "Workflow2 Job2 Step2 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Workflow2 Job2 Step2 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Step 3
        run: |
          echo "Workflow2 Job2 Step3 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Workflow2 Job2 Step3 completed: $(date '+%Y-%m-%d %H:%M:%S')"

実行の様子

実行履歴から、Workflow・Jobsは独立して実行され、Stepsは直列で実行されることが確認できます。

Workflowはそれぞれ独立して実行される

Jobはそれぞれ独立して実行される

Stepは直列で実行される

WorkflowとJobの依存関係

WorkflowやJobは、デフォルトでは互いに独立して実行されますが、依存関係を設定することもできます。

Workflow同士の依存関係

Workflow間の依存関係は、workflow_runイベントを使用して設定します。
これにより、あるWorkflowが完了した後に別のWorkflowを実行することができます。

ワークフローをトリガーするイベント - GitHub Docs

name: Dependent Workflow
on:
  workflow_run:
    workflows: ["Basic Workflow 1"]
    types:
      - completed
jobs:
  dependent_job:
    runs-on: ubuntu-latest
    steps:
      - name: Run after Basic Workflow 1
        run: |
          echo "Dependent workflow started: $(date '+%Y-%m-%d %H:%M:%S')"
          echo "This job runs after Basic Workflow 1 is completed"
          sleep 30
          echo "Dependent workflow processing completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Check previous workflow status
        run: |
          echo "Previous workflow status check started: $(date '+%Y-%m-%d %H:%M:%S')"
          echo "Previous workflow conclusion: ${{ github.event.workflow_run.conclusion }}"
          if [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]]; then
            echo "Previous workflow succeeded. Continuing process..."
          else
            echo "Previous workflow failed, but this dependent workflow continues"
          fi
          sleep 30
          echo "Previous workflow status check completed: $(date '+%Y-%m-%d %H:%M:%S')"

このYAMLをpushすると、Workflowの依存関係に基づいて実行されることが確認できます。

依存先のWorkflowのログ、このWorkflowが終了しないと依存元は始まらない

依存元のWorkflowのログ、依存先の後に動く

Job同士の依存関係

Job同士の依存関係は、needsキーワードを使用して設定します。

ワークフローでジョブを使用する - GitHub Docs

name: Workflow with Job Dependencies
on: [push]
jobs:
  job1:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        run: |
          echo "Job dependencies workflow Job1 Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          sleep 30
          echo "Job dependencies workflow Job1 Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Create output
        id: job1_output
        run: |
          echo "Job1 output creation started: $(date '+%Y-%m-%d %H:%M:%S')"
          echo "result=success_from_job1" >> $GITHUB_OUTPUT
          echo "Job1 completed successfully"
          sleep 30
          echo "Job1 output creation completed: $(date '+%Y-%m-%d %H:%M:%S')"
    outputs:
      job1_result: ${{ steps.job1_output.outputs.result }}
      
  job2:
    runs-on: ubuntu-latest
    needs: job1
    steps:
      - name: Step 1
        run: |
          echo "Job2 Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          echo "This job depends on job1"
          sleep 30
          echo "Job2 Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
      - name: Use output from job1
        run: |
          echo "Using output from job1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          echo "Received value from job1: ${{ needs.job1.outputs.job1_result }}"
          sleep 30
          echo "Using output from job1 completed: $(date '+%Y-%m-%d %H:%M:%S')"
        
  job3:
    runs-on: ubuntu-latest
    needs: [job1, job2]
    steps:
      - name: Step 1
        run: |
          echo "Job3 Step1 started: $(date '+%Y-%m-%d %H:%M:%S')"
          echo "This job depends on both job1 and job2"
          sleep 30
          echo "Job3 Step1 completed: $(date '+%Y-%m-%d %H:%M:%S')"

このYAMLをpushすると、Jobの依存関係に基づいて実行されることが確認できます。

Jobの依存関係

Workflowのトリガー

Workflowは、特定のイベントによってトリガーされます。

ワークフローをトリガーするイベント - GitHub Docs

代表的なトリガーはpushpull_requestです。
トリガーはさらに条件を指定することもできます。
pushであれば、以下のように、特定のブランチに対してのみWorkflowを実行することができます。

name: Push to Main Branch
on:
  push:
    branches:
      - main
jobs:
  main_branch_job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Main branch specific step
        run: echo "This job runs only on push to main branch"
      - name: Show branch info
        run: |
          echo "Current branch: ${{ github.ref_name }}"
          echo "Event: ${{ github.event_name }}"
name: Push to Develop Branch
on:
  push:
    branches:
      - develop
jobs:
  develop_branch_job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Develop branch specific step
        run: echo "This job runs only on push to develop branch"
      - name: Show branch info
        run: |
          echo "Current branch: ${{ github.ref_name }}"
          echo "Event: ${{ github.event_name }}"

Jobの実行環境

Jobは、runs-onキーワードで実行環境を指定します。
実行環境はRunnerと呼ばれ、GitHubから提供されるGitHub-Hosted Runnerを使用します。

GitHub ホステッド ランナーの概要 - GitHub Docs

Runnerは、自前で用意したり、Workflowの中で選択肢を限定することもできます。
このあたりは別の機会に詳しくお話しできればと思います。

GitHub Actionsでは、Job単位でRunnerを指定することができます。
以下の例では、3つのJobを異なるRunnerで実行し、OSのバージョンを確認しています。

name: Job with Different Runners
on: [push]
jobs:
  ubuntu_job:
    runs-on: ubuntu-latest
    steps:
      - name: Check Ubuntu Version
        run: |
          echo "Running on Ubuntu"
          lsb_release -a
          echo "Current user: $(whoami)"
          echo "Home directory: $HOME"
          
  windows_job:
    runs-on: windows-latest
    steps:
      - name: Check Windows Version
        run: |
          echo "Running on Windows"
          systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
          echo "Current user: $env:USERNAME"
          echo "Home directory: $env:USERPROFILE"
          
  macos_job:
    runs-on: macos-latest
    steps:
      - name: Check macOS Version
        run: |
          echo "Running on macOS"
          sw_vers
          echo "Current user: $(whoami)"
          echo "Home directory: $HOME"

RunnerがUbuntuの実行ログ

RunnerがWindowsの実行ログ

RunnerがmacOSの実行ログ

Environmentと変数

ここで、一旦Workflowから離れて、Environmentと環境変数について述べます。
GitHubにはEnvironmentという概念があります。

デプロイに環境の使用 - GitHub Docs

Environmentごとに、VariablesとSecretsを設定することができます。
Variablesは通常の変数、Secretsは機密情報を設定する変数です。

例えば、productiondevelopmentというEnvironmentを作成します。
それぞれに変数を設定します。
この時、変数名が同じであっても、Environmentごとに異なる値を設定することができます。
なお、変数はEnvironmentなしでも設定できますが、本記事では割愛します。

graph TD

    B[Environment: production] --> D[Variable: API_URL<br/>Value: api.prod.example.com]
    B --> E[Secret: DATABASE_PASSWORD<br/>Value: ***prod_password***]

    C[Environment: development] --> F[Variable: API_URL<br/>Value: api.dev.example.com]
    C --> G[Secret: DATABASE_PASSWORD<br/>Value: ***dev_password***]

    style B fill:#e8f5e8
    style C fill:#e1f5fe
    style D fill:#f0f8ff
    style E fill:#ffe4e1
    style F fill:#f0f8ff
    style G fill:#ffe4e1

EnvironmentのVariables

EnvironmentのSecrets

EnvironmentとJobの紐付け

GitHub Actionsでは、Jobを特定のEnvironmentに紐付けることができます。

graph TD
    R[Repository] --> A["08_multiple_environments_demo.yml"]

    A --> B[Job 1]
    A --> C[Job 2]


    B --> D[Environment: production]
    C --> E[Environment: development]

    style R fill:#f9f9f9
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#f3e5f5
    style D fill:#e8f5e8
    style E fill:#e1f5fe

この場合、ワークフローのYAMLファイルは以下のように配置します。
各Jobは、特定のEnvironmentに紐付けられています。
JobごとにEnvironmentの指定を変更しつつ、Stepで同じ変数を参照すると、Environmentごとに異なる値が出力されます。

name: Multiple Environments Demo
on:
  push:
    branches:
      - main
jobs:
  production_job:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Echo API URL from production environment
        run: echo "API URL is ${{ vars.API_URL }}"
      - name: Echo Database password (using fallback if not set)
        run: echo "Database password is ${{ secrets.DATABASE_PASSWORD || 'not-set' }}"
      - name: Show environment info
        run: |
          echo "Environment: production"
          echo "Branch: ${{ github.ref_name }}"
          
  development_job:
    runs-on: ubuntu-latest
    environment: development
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Echo API URL from development environment
        run: echo "API URL is ${{ vars.API_URL }}"
      - name: Echo Database password (using fallback if not set)
        run: echo "Database password is ${{ secrets.DATABASE_PASSWORD || 'not-set' }}"
      - name: Show environment info
        run: |
          echo "Environment: development"
          echo "Branch: ${{ github.ref_name }}"

Variablesの値がproductionのものになっている

Variablesの値がdevelopmentのものになっている

Environmentに条件を設ける

前項の例では、mainブランチをターゲットとしたWorkflowから、Environment developmentにも紐づけているというちょっと不自然な例になっています。
このような不自然な状態を抑制するために、Environmentには条件を設けることができます。
GitHubのSettingsから、Environmentに条件を設定します。

productionにmainブランチだけの条件を設定

これで、Environment productionは、mainブランチをトリガーとしたWorkflowからのみ使用できるようになります。

AWSアカウント認証情報の取得

GitHub ActionsでAWSにデプロイするためには、AWSの認証情報が必要です。
2025年6月現在においては、GitHub ActionsでAWSの認証情報を取得するには、OIDCを使用するのがセキュアで良いでしょう。

アマゾン ウェブ サービスでの OpenID Connect の構成 - GitHub Docs

GitHubのOIDCについてはこちらもご覧ください。

blog.serverworks.co.jp

デプロイ先のAWSアカウントに、GitHub Actionsからのアクセスを許可するIAMロールを作成します。
作成したIAMロールにデプロイに必要な権限を付与し、信頼ポリシーで特定のGitHubリポジトリからのアクセスのみを許可するようにします。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<AWSアカウントID>:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:<組織名>/<リポジトリ名>:<ブランチ(全てのブランチならアスタリスク)>",
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

EnvironmentにSecretsを作成し、作成したIAMロールのARNを設定します。

GitHub Actionsからのアクセスを許可するIAMロール

IAMロールARNのSecretsを作成

Secretsを作成したら、WorkflowのYAMLでそのSecretsを取得して利用します。

name: Deploy to AWS
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }}
        aws-region: ap-northeast-1
    - name: Display AWS credentials
      run: aws sts get-caller-identity

デプロイ先AWSアカウントの分岐

ここまでの内容を踏まえて、デプロイ先のAWSアカウントを分岐させるWorkflowを作成します。
Environmentにproductiondevelopmentを作成し、それぞれにOIDC用のIAMロールを設定します。

EnvironmentのSecrets

上述の通り。 Environment productionは、mainブランチでのみ使用できるように条件を設けます。

そして、mainブランチのWorkflowでは、production Environmentを使用し、developブランチのWorkflowでは、development Environmentを使用します。

graph TD
    A[Repository] --> B[main branch push]
    A --> C[develop branch push]

    B --> D["09_aws_deploy_production.yml"]
    C --> E["10_aws_deploy_development.yml"]

    D --> F[Job: deploy_production]
    E --> G[Job: deploy_development]

    F --> H[Environment: production]
    G --> I[Environment: development]

    E -.-x| 参照不可 | H

    H --> J[Secret: AWS_GITHUB_ACTIONS_ROLE_ARN<br/>Value: arn:aws:iam::111111111111:role/...]
    I --> K[Secret: AWS_GITHUB_ACTIONS_ROLE_ARN<br/>Value: arn:aws:iam::222222222222:role/...]

    J --> L[AWS Account: Production<br/>Account ID: 111111111111]
    K --> M[AWS Account: Development<br/>Account ID: 222222222222]

    style A fill:#f9f9f9
    style B fill:#e8f5e8
    style C fill:#e1f5fe
    style D fill:#e8f5e8
    style E fill:#e1f5fe
    style F fill:#f3e5f5
    style G fill:#f3e5f5
    style H fill:#e8f5e8
    style I fill:#e1f5fe
    style J fill:#ffe4e1
    style K fill:#ffe4e1
    style L fill:#e8f5e8
    style M fill:#e1f5fe

YAMLファイルは以下の通りです。
permissionsの設定をしているのと、aws-actions/configure-aws-credentialsでAWSの認証情報を取得しているのがポイントです。

name: Deploy to AWS Production
on:
  push:
    branches:
      - main

permissions:
  id-token: write
  contents: read

jobs:
  deploy_production:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        
      - name: Configure AWS credentials for production
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }}
          aws-region: ap-northeast-1
          role-session-name: GitHubActions-Production
          
      - name: Display AWS credentials
        run: |
          echo "Attempting to get AWS caller identity..."
          aws sts get-caller-identity || echo "AWS credentials not configured or invalid"
          
      - name: List S3 buckets (example deployment check)
        run: |
          echo "Listing S3 buckets to verify permissions..."
          aws s3 ls || echo "Unable to list S3 buckets - check permissions"
          
      - name: Deploy simulation
        run: |
          echo "Simulating deployment to production environment"
          echo "Environment: production"
          echo "Branch: ${{ github.ref_name }}"
          echo "Commit SHA: ${{ github.sha }}"
name: Deploy to AWS Development
on:
  push:
    branches:
      - develop

permissions:
  id-token: write
  contents: read

jobs:
  deploy_development:
    runs-on: ubuntu-latest
    environment: development
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        
      - name: Configure AWS credentials for development
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }}
          aws-region: ap-northeast-1
          
      - name: Display AWS credentials
        run: |
          echo "Attempting to get AWS caller identity..."
          aws sts get-caller-identity || echo "AWS credentials not configured or invalid"
          
      - name: List S3 buckets (example deployment check)
        run: |
          echo "Listing S3 buckets to verify permissions..."
          aws s3 ls || echo "Unable to list S3 buckets - check permissions"
          
      - name: Deploy simulation
        run: |
          echo "Simulating deployment to development environment"
          echo "Environment: development"
          echo "Branch: ${{ github.ref_name }}"
          echo "Commit SHA: ${{ github.sha }}"

これで、それぞれのブランチに対して、異なるAWSアカウントにデプロイすることができます。
また、Environment側に制限を設けているので、誤ってdevelopブランチのWorkflowからproductionのAWSアカウントにデプロイすることを防ぐことができます。

仮に、Deploy to AWS Developmentの方に、Job: deploy_productionを追加してproductionのsecretsにアクセスさせた場合、エラーとなります。

developのWorkflowからproductionの変数にはアクセスできない

まとめ

  • GitHub ActionsはWorkflow、Job、Stepの階層構造を持ちます
  • WorkflowとJobは依存関係を設定できます
  • Environmentを使用して、変数や機密情報を管理できます
  • EnvironmentをJobに紐付けることで、Environmentごとの変数を参照できます
  • Environmentは制限を設けることができ、意図しないブランチからのアクセスを防げます
  • これらを組み合わせることで、複数のAWSアカウントにデプロイを分岐できます

余談

2025年5月にGitHub CertificationのGitHub Actionsを取得しました。
自宅周辺(広島)にはオフラインの試験会場がなかったため、オンラインで受験したのですが、試験監督の説明がすべて英語テキストだったので、かなり戸惑いました。
まったくわからない内容ではなかったためなんとかなりましたが、これからオンラインで受験される方は私と同じような状況になるかもしれないので、心構えだけしておくことをおすすめします。

www.credly.com

兼安 聡(執筆記事の一覧)

アプリケーションサービス部 DS3課所属
2025 Japan AWS Top Engineers (AI/ML Data Engineer)
2025 Japan AWS All Certifications Engineers
2025 AWS Community Builders
Certified ScrumMaster
PMP
広島在住です。今日も明日も修行中です。