🛠️

GitHub Actions で Environmentsを使いマルチアカウントでのCICDコード量を減らす書き方

2024/03/25に公開

概要

マルチアカウントのAWS環境に対してS3にファイルアップロードするCICDをGitHubActionsで作成します

記事を書こうと思ったきっかけ

AWSマルチアカウント化の作業を進めているときに工夫しないとCICDのコードが複雑になると思い、この書き方ができることが知られていないと感じたため

この記事でのポイント

  • CICDのコードでIf文を使わずに環境毎にデプロイできる設定を行う
  • Environments, Repository rulesets 機能を使用
  • PullRequestの作成, Merge をトリガーにしたCICDの例を紹介

動作環境

  • GitHub

    • Environments, Repository rulesets 機能を使えるのは パブリックリポジトリ or Team Plan 以上のプライベートリポジトリ
  • GitHubActions

  • AWS

  • DevContainers(Github ActionsのVSCode拡張機能など入れてます)

  • サンプルコード

    • 今回使用するCICDのコードはこのリポジトリにあります。S3などAWSリソースをTerraform で作成するコードも含まれています

今回作成するCICDの構成図

PushをトリガーにしてS3にファイルをアップロードするCICDを作成します。
今回はマルチアカウント構成のためPRODSTGの2つの環境を作成しています

Before

普通に実装するとこのような冗長なコードになります

.github/workflows/s3-upload.yml
name: S3 upload file to Multi Account non Environments

on:
  push:
    branches:
      - production
      - staging
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: "set variables"
        id: variables
        run: |
          # Pushされたブランチ名を取得してIf文の条件分岐で環境名を定義している
          BRANCH_NAME="$(echo ${GITHUB_REF#refs/heads/})"
          if [ "$BRANCH_NAME" = "staging" ]; then \
            _env="stg"; \
            echo "AWS_IAM_ROLE_ARN=${{ secrets.AWS_IAM_ROLE_ARN_STG }}" >> $GITHUB_ENV; \
          elif [ "$BRANCH_NAME" = "production" ]; then \
            _env="prod"; \
            echo "AWS_IAM_ROLE_ARN=${{ secrets.AWS_IAM_ROLE_ARN_PRD }}" >> $GITHUB_ENV; \
          else \
            exit 1; \
          fi
          # 環境名をActions内部の環境変数として定義
          echo "ENV=${_env}" >> $GITHUB_ENV
      - uses: actions/checkout@v4.1.1
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4.0.2
        with:
          role-to-assume: ${{ env.AWS_IAM_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
      - name: Check Bucket Name
        run: |
          aws s3 ls
      - name: Upload file to S3
        env:
          S3_UPLOAD_BUCKET: ${{ env.ENV }}-${{ secrets.BUCKET_NAME }}
        run: |
          aws s3 cp upload-test.txt s3://$S3_UPLOAD_BUCKET/

set variables で定義しているIf文の条件分岐の部分が冗長に感じますね。
マルチアカウントで環境毎に stg-bucket-name, prod-bucket-name のような名称のバケットにデプロイします。
またこの部分はAWSの認証を伴うworkflowで繰り返し定義する必要があるので、コードの可読性はどんどん下がります

After

今回紹介するコードがこちらです。先ほどのコードとやっていることは同じですが短く書けていることがわかります

.github/workflows/s3-upload.yml
name: S3 upload file to Multi Account

on:
  push:    
    branches: # production, stagingブランチにPush, Mergeされた時のみ実行
      - production
      - staging

jobs:
  build:
    runs-on: ubuntu-latest
    permissions: # OIDCで使用するTokenの権限
      id-token: write
      contents: read
    environment: ${{ github.ref_name }} # Pushされたブランチ名を取得してenvironmentに設定
    steps:
      - uses: actions/checkout@v4.1.1
      - name: Configure AWS credentials # AWSの認証をOIDCで行う設定
        uses: aws-actions/configure-aws-credentials@v4.0.2
        with:
          role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
      - name: Check Bucket Name # S3のバケット名を確認
        run: |
          aws s3 ls
      - name: Copy files to S3 # S3にファイルをアップロード
        run: |
          aws s3 cp upload-test.txt s3://${{vars.AWS_S3_BUCKET}}/

Environments, Repository rulesets を使うことでこのように短く書くことができます。
${{ github.ref_name }} はGithub Actionsに標準で用意されている環境変数で、Pushされたブランチ名を取得できます。

Environments

EnvironmentsはGithub の機能で、 パブリックリポジトリ or Team Plan 以上のプライベートリポジトリ で使用できます

Environmentsを使わない場合のAWSの認証情報管理

AWS_IAM_ROLE_ARN_PRDAWS_IAM_ROLE_ARN_STG がGithubのSecretsに保存されています。
なのでCICDでこれらの値を取得するには secrets.AWS_IAM_ROLE_ARN_PRD,secrets.AWS_IAM_ROLE_ARN_STG のように別々の名称で取得する必要があるため、どうしてもコード内部に条件分岐が必要になります。

Beforeのコードではブランチ名によってIf文で分岐させて環境のコードを取得していました

Environmentsを使った場合

PROD, STGのどちらの環境も AWS_IAM_ROLE_ARNという名称で定義されていることがわかると思います。

CICDでこれらの値を取得する際も secrets.AWS_IAM_ROLE_ARN という名称で取得できるため、コード内部に条件分岐を書かなくても環境毎の設定を取得できます。

ではどの部分で環境を判断しているかというと、environment: ${{ github.ref_name }} の部分です。

Afterのコードでは environment: ${{ github.ref_name }} となっていますが、この部分は environment: productionenvironment: staging のように環境名をベタ書きするのが一般的です

Environmentsに関する詳細解説が書かれている記事

ブランチ戦略 の部分で詳細を書きますが、今回のCICDのポイントはenvironmentsの名前とデプロイ先のブランチ名を一致させることです

Environmentsの何がいいのか?

サンプルのようにSecretやVariablesが3個だと実感がわきにくいですが、実際のプロジェクトではSecretやVariablesが20個, 30個と増えていくことが多いです

その時、Environmentsを使わないと secrets.AWS_IAM_ROLE_ARN_PRD, secrets.AWS_IAM_ROLE_ARN_STG のような条件分岐で取得する部分も20個, 30個書く必要がでてきます。

そこでEnvironmentsを使えばSecretやVariablesがいくら増えてもCICDの最初に environment: productionenvironment: staging のように一回書くだけで済むのでとても簡潔なコードになります

Repository Rules

Repository RulesもGithubの機能で、 パブリックリポジトリ or Team Plan 以上のプライベートリポジトリ で使用できます。

今回のCICDでの役割は、PullRequestのMerge時にHEADブランチが削除されないように保護する設定や、PROD環境のブランチにPullRequestなしの直接Pushを禁止する設定で使用しています

以前からGithub に存在する Branch Protection Rules でも同じことが実現できます。Github 的に Repository RulesBranch Protection Rules の進化系らしく Repository Rules の方が Github Organization でのルール使い回しや柔軟な制御設定などが可能なようです

Repository Rulesの詳細な解説記事

今回はこのように production , staging ブランチに対して PullRequestのレビュー必須化、ブランチの直Push禁止、ブランチ削除保護設定を有効にしました

ブランチ戦略

  • 今回のCICDは基本的にこのフローでしか動作しません
    • デフォルトブランチをproductionとしています
      • EnvironmentsのproductionにはPRODの環境変数が設定されているためブランチ名も同じ方がわかりやすいため(mainなどでも運用は可能です)
    • topicブランチ以外はGithub のRepository Rules を使用してMerge時にブランチが削除されないように保護を行っております
    • サンプルコードに development ブランチ, AWS環境がないのは予算の都合なので実際はDEV, STG, PRODの3つの環境を作成する想定です

おまけ

PullRequest作成時に環境毎のCICDを実行

PullRequestのMerge時だけでなくPullRequest作成時にCICDを実行したい場合はこのように書きます

name: S3 upload file to Multi Account

on:
  pull_request:
    branches:
      - production
      - staging

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    environment: ${{ github.base_ref }} # PullRequest作成時のベースブランチ名を取得
    steps:
      - uses: actions/checkout@v4.1.1
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4.0.2
        with:
          role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
      - name: Check Bucket Name
        run: |
          aws s3 ls
      - name: Copy files to S3
        run: |
          aws s3 cp upload-test.txt s3://${{vars.AWS_S3_BUCKET}}/

Merge時トリガーとの違いは ${{ github.base_ref }} の部分でPullRequestのBaseブランチ名を取得するようになっております。
用途としてはその環境に対してCIによるCheckなどを行いたい場合を想定しています

このようにstagingに対するPullRequest作成時にActionsが実行されています

この部分でstagingのenvironmentsの設定でCICDが成功したことが確認できます

まとめ

  • Environments, Repository rulesets を使うことでマルチアカウントのCICDのコードがシンプルになる
  • ブランチ名と環境名を一致させることで更にコードがシンプルになる

参考資料

SocialPLUS Tech Blog

Discussion