GitHub ActionsとCloudFormationでVPCをデプロイ

近年、インフラのコード化(IaC)が標準となる中で、GitHub Actionsを利用したAWSリソースの自動デプロイは一般的な構成になってきています。
今回は、Self-hosted Runnerを利用し、GitHub ActionsからCloudFormationを通じてVPCリソースをデプロイする一連の流れを解説します。

 

アーキテクチャ

Self-hosted Runnerを利用するメリット

通常のGitHub-hosted runnerではなく、自前の環境(EC2など)で実行するSelf-hosted Runnerを利用することで、特定のネットワーク要件(VPC内リソースへのアクセス)や、実行環境のカスタマイズが容易になります。
また、プライベートリポジトリであっても、Self-hosted Runnerであれば無料で利用が可能です。
GitHub Actionsの課金の詳細についてはGitHubのドキュメントをご参照ください。
GitHub Acttionsの課金

Self-hosted Runnerのセットアップは本稿の趣旨とは逸れますので、記載はしておりませんが、参考として以下の記事をご参照ください。
GitHub Actionsのセルフホステッドランナーをセットアップ

IDプロバイダーの設定(GitHub連携)

AWSマネジメントコンソールにログインして、IAMプロバイダを設定します。

プロバイダのタイプ OpenID Connect
プロバイダのURL https://token.actions.githubusercontent.com
対象者 sts.amazonaws.com

 

IAMロールの作成(GitHub連携)

IAMロールを作成します。

IAMロール名 GitHubActionsRole
許可ポリシー AmazonVPCFullAccess
AWSCloudFormationFullAccess
※今回はFullAccessにしましたが、本番環境ではスコープを絞った権限を付与することを推奨します。

信頼ポリシーは以下を設定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::{YOUR_AWS_ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:{YOUR_GITHUB_USER_NAME}/{YOUR_GITHUB_REPOSITORY}:*"
                }
            }
        }
    ]
}

VPCデプロイ用のCloudFormationテンプレート

まずは、ベースとなるVPCリソースを定義したYAMLファイルを作成します。
環境名(Environment)などをパラメータ化し、再利用性を高めています。

ファイルパス:cloudformation/vpc-stack.yaml

Description: Provision VPC

Parameters:
  Environment:
    Type: String
  Subsystem:
    Type: String
    Default: common
  Usage:
    Type: String
    Default: vpc

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: !Sub ${Environment}-${Subsystem}-${Usage}
      - Key: Environment
        Value: !Ref Environment
Outputs:
  VpcId: 
    Description: The ID of the VPC
    Value: !Ref VPC

  VpcCidr:
    Description: The CIDR block of the VPC
    Value: !GetAtt VPC.CidrBlock

作成したYAMLファイルはGitHubのリポジトリのデフォルトブランチにマージしておきましょう。

GitHub Actionsのワークフロー定義

次に、GitHub ActionsのWorkflowを作成します。
今回はセキュリティを考慮し、OIDC(OpenID Connect)を利用してAWSの認証情報を取得する構成です。
cfn-lintによる構文チェックとバリデーション取得、VPCデプロイを行います。
また、cfn-lintはSelf-hosted-Runnerにすでにインストールされていることが前提になります。

ワークフローのポイント

  • runs-on: self-hosted: 実行環境にSelf-hosted Runnerを指定します。
  • OIDC認証: IAMロールのARNを環境変数にセットし、一時的な認証情報を取得します。
  • cfn-lintがインストールされていない場合は Self-hosted RunnerとなるOSにcfn-lintをインストールしておきます。
    • (例)pip install cfn-lint

ファイルパス:.github/workflows/provision-vpc.yaml

name: Provision VPC Stack
on:
  workflow_dispatch:
    inputs:
      environment: 
        type: choice
        description: Provision target
        options: 
        - poc
        required: true
        default: poc

env:
  ENVIRONMENT: ${{ github.event.inputs.environment }}
  AWS_REGION: ap-northeast-1
  AWS_ROLE_ARN: {YOUR_GITHUB_ACTTIONS_ROLE_ARN}}

jobs:
  lint:
    name: Lint CloudFormation Template
    runs-on: self-hosted

    permissions:
      id-token: write
      contents: read

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

      - name: Run cfn-lint
        run: |
          cfn-lint cloudformation/vpc.yaml --format junit --output-file cfn-lint-report.xml
        continue-on-error: true

      - name: Upload lint results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: cfn-lint-results
          path: cfn-lint-report.xml

  validate:
    name: Validate CloudFormation Template
    runs-on: self-hosted

    permissions:
      id-token: write
      contents: read

    needs: lint
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Validate CloudFormation template
        run: |
          aws cloudformation validate-template \
            --template-body file://cloudformation/vpc.yaml

      - name: Display validation result
        run: |
          echo "### CloudFormation Validation ✅" >> $GITHUB_STEP_SUMMARY
          echo "Template validation successful" >> $GITHUB_STEP_SUMMARY

  deploy:
    name: Deploy VPC Stack
    runs-on: self-hosted
    needs: validate
    
    environment:
      name: ${{ github.event.inputs.environment }}

    permissions:
      id-token: write
      contents: read

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

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Verify AWS credentials
        run: aws sts get-caller-identity

      - name: Set stack name
        id: stack-info
        run: |
          STACK_NAME="provision-${{ env.ENVIRONMENT }}-vpc"
          echo "stack-name=${STACK_NAME}" >> $GITHUB_OUTPUT
          echo "Stack name: ${STACK_NAME}"

      - name: Deploy CloudFormation Stack
        env:
          STACK_NAME: ${{ steps.stack-info.outputs.stack-name }}
        run: |
          echo "Deploying CloudFormation stack: ${STACK_NAME}"
          
          aws cloudformation deploy \
            --stack-name ${STACK_NAME} \
            --template-file cloudformation/vpc.yaml \
            --parameter-overrides \
              Environment=${{ env.ENVIRONMENT }} \
              Subsystem=common \
              Usage=vpc \
            --tags \
              Environment=${{ env.ENVIRONMENT }} \
              ManagedBy=GitHub-Actions \
              Repository=${{ github.repository }} \
              DeployedBy=${{ github.actor }} \
            --no-fail-on-empty-changeset
          
          echo "Deployment completed!"

      - name: Get Stack Outputs
        id: stack-outputs
        env:
          STACK_NAME: ${{ steps.stack-info.outputs.stack-name }}
        run: |
          VPC_ID=$(aws cloudformation describe-stacks \
            --stack-name ${STACK_NAME} \
            --query 'Stacks[0].Outputs[?OutputKey==`VpcId`].OutputValue' \
            --output text 2>/dev/null || echo "Not available")
          
          VPC_CIDR=$(aws cloudformation describe-stacks \
            --stack-name ${STACK_NAME} \
            --query 'Stacks[0].Outputs[?OutputKey==`VpcCidr`].OutputValue' \
            --output text 2>/dev/null || echo "Not available")
          
          echo "vpc-id=${VPC_ID}" >> $GITHUB_OUTPUT
          echo "vpc-cidr=${VPC_CIDR}" >> $GITHUB_OUTPUT

      - name: Create Deployment Summary
        env:
          STACK_NAME: ${{ steps.stack-info.outputs.stack-name }}
          VPC_ID: ${{ steps.stack-outputs.outputs.vpc-id }}
          VPC_CIDR: ${{ steps.stack-outputs.outputs.vpc-cidr }}
        run: |
          echo "## 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
          echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
          echo "| Stack Name | \`${STACK_NAME}\` |" >> $GITHUB_STEP_SUMMARY
          echo "| VPC ID | \`${VPC_ID}\` |" >> $GITHUB_STEP_SUMMARY
          echo "| CIDR Block | \`${VPC_CIDR}\` |" >> $GITHUB_STEP_SUMMARY
          echo "| Environment | \`${{ env.ENVIRONMENT }}\` |" >> $GITHUB_STEP_SUMMARY
          echo "| Region | \`${{ env.AWS_REGION }}\` |" >> $GITHUB_STEP_SUMMARY

      - name: Cleanup
        if: always()
        run: rm -f /tmp/awscreds

デプロイの実行と確認

  1. Self-hosted Runnerを実行します。
  2. GitHubリポジトリの Actions タブへ移動します。
  3. Provision VPC Stack を選択し、Run workflowを展開します。
  4. Use workflow fromのBranch:がデフォルトブランチであることとProvision targetがpocであることを確認し、Run workflow をクリックします。
  5. Run workflow をクリックします。
  6. しばらくするとProvision VPC Stack が正常終了するので、Provision VPC Stack をクリックします。
  7. Workflowの各ステップが正常終了していることが確認できます。Deployment SummaryでStack NameやVPC IDなどの情報が確認できます。
  8. Validate CloudFormation T… をクリックすると、バリデーションが確認できます。
  9. AWSマネジメントコンソールのCloudFormation画面で、スタックが CREATE_COMPLETE になっていることを確認します。
  10. CloudFormationのスタックからリソースを選択し、vpcがデプロイされていることを確認します。
  11. VPCがデプロイされました。

まとめ:自動化による効率化

GitHub ActionsとCloudFormationを組み合わせることで、手動操作によるミスを減らし、一貫性のあるインフラ構築が可能になります。Self-hosted Runnerを活用して、よりセキュアで柔軟なデプロイパイプラインを構築しましょう。
VPCのデプロイのみとしていますが、CloudFormationで他のAWSリソースもデプロイすることが可能です。
また、今回はcfn-lintによる構文チェックを入れていますが、より品質を高めるためにpytestによるテストや変更セットを自動で作成して差分チェックさせるなどのような、CICDパイプラインにも応用が可能です。