【GitHubActions】単一ワークフローファイルでトリガー元に応じてデプロイ先のAWSアカウントを切り替えてみる

トリガー元とデプロイ先のAWSアカウントごとにワークフローファイルをコピペしたくない人にオススメです
2021.06.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CX事業本部@大阪の岩田です。AWSを利用した開発のCI/CDでは

  • xxxブランチにマージされたらdev用AWSアカウントに自動デプロイする
  • yyyブランチにマージされたらstg用AWSアカウントに自動デプロイする
  • リリースタグが付与されたらprd用AWSアカウントに自動デプロイする

のようなデプロイ戦略は良くある話だと思います。実際にはprd環境へのデプロイには別途承認アクションを挟みたいとか、そういった要件も想定されますが、一旦そういうのは無視してワークフローの構成は全環境で共通とした場合、トリガーイベントに応じて変更したいのはAWSアカウントIDのみです。この場合、環境毎にワークフローファイルを作成すると重複が多くなってしまうので、できれば1つのワークフローファイルで全環境のデプロイに対応したいところです。どうすれば単一のワークフローファイルで複数AWSアカウントへのデプロイに対応できるかを調べてみたので内容をご紹介します。

実際にはAWSアカウントID以外にもデプロイに関連する各種パラメータや環境変数も調整したいかもしれませんが、今回は割愛します。

前提条件

本ブログでは以下のシナリオを想定します

  • devブランチのプッシュをトリガーにdev用AWSアカウントに自動デプロイする
  • mainブランチのプッシュをトリガーにstg用AWSアカウントに自動デプロイする
  • v0.0.0というフォーマットのタグのプッシュをトリガーにprd用AWSアカウントに自動デプロイする
  • prd用AWSアカウント上にGitHub Actions用のIAMユーザーが作成されている
    • 上記IAMユーザーのアクセスキーIDをシークレットアクセスキーがGitHub ActionsのEncrypted secretsに保存されており、GitHub Actionsから利用可能な状態になっている
  • 各AWSアカウント上にはデプロイ用のIAMロールが作成されており、上記GitHub Actions用のIAMユーザーからAssume Role可能な状態に設定されている
    • 各AWSアカウントのデプロイ用ロールのARNが ROLE_TO_ASSUME_DEVROLE_TO_ASSUME_STGROLE_TO_ASSUME_PRDという名前でGitHub ActionsのEncrypted secretsに保存されている
  • 自動デプロイ処理は aws sts get-caller-identityで代替する ※実際にはsam deploysls deployを利用することが多いと思います

トリガー元ごとにワークフローファイルファイルを用意する場合

まずは愚直にトリガー元ごとにワークフローファイルファイルを用意してみます。

最初はdev環境向けのワークフローファイルです。devブランチのプッシュをトリガーにデプロイを実行します

on:
  push:
    branches:
      - dev
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME_DEV }}
          role-duration-seconds: 3600
      - name: Deploy
        run: |
          aws sts get-caller-identity

本来はデプロイ用ロールの信頼ポリシーを外部IDで絞りつつrole-external-idも指定する方がベターですが、サンプルなので割愛しています。

続いてstg環境向けのワークフローファイルです。mainブランチのプッシュをトリガーにデプロイを実行します

on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME_STG }}
          role-duration-seconds: 3600
      - name: Deploy
        run: |
          aws sts get-caller-identity

最後にprd環境向けのワークフローファイルです。タグのプッシュをトリガーにデプロイを実行します

on:
  push:
    tags:
      - 'v*.*.*'
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME_PRD }}
          role-duration-seconds: 3600
      - name: Deploy
        run: |
          aws sts get-caller-identity

これで一応は各環境の自動デプロイ環境が完成しました。が、各環境のワークフローファイルの記述は非常に重複が多くなっています。

$ diff deploy-dev.yml deploy-stg.yml 
4c4
<       - dev
---
>       - main
16c16
<           role-to-assume: ${{ secrets.ROLE_TO_ASSUME_DEV }}
---
>           role-to-assume: ${{ secrets.ROLE_TO_ASSUME_STG }}
$ diff deploy-stg.yml deploy-prd.yml 
3,4c3,4
<     branches:
<       - main
---
>     tags:
>       - 'v*.*.*'
16c16
<           role-to-assume: ${{ secrets.ROLE_TO_ASSUME_STG }}
---
>           role-to-assume: ${{ secrets.ROLE_TO_ASSUME_PRD }}

今回はデプロイ処理をaws sts get-caller-identityで代替しているため、あまり重複箇所が目立たないかもしれませんが、実際のデプロイ処理はもっと複雑な処理になり重複箇所がもっと多くなるはずです。例えば別のステップでnpm installを実行したり、ビルド処理を実行したり...

ということでワークフローファイルの集約に挑戦してみます。

単一のワークフローファイルに集約してみる

まず集約後のワークフローです。

on:
  push:
    branches:    
      - dev
      - main
    tags:
      - 'v*.*.*'
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Set env to dev
        if: endsWith(github.ref, '/dev')
        run: |
          echo "ROLE_TO_ASSUME=${{ secrets.ROLE_TO_ASSUME_DEV }}" >> $GITHUB_ENV
      - name: Set env to stg
        if: endsWith(github.ref, '/main')
        run: |
          echo "ROLE_TO_ASSUME=${{ secrets.ROLE_TO_ASSUME_STG }}" >> $GITHUB_ENV
      - name: Set env to prd
        if: startsWith(github.ref, 'refs/tags/v')
        run: |
          echo "ROLE_TO_ASSUME=${{ secrets.ROLE_TO_ASSUME_PRD }}" >> $GITHUB_ENV
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ env.ROLE_TO_ASSUME }}
          role-duration-seconds: 3600
      - name: Deploy
        run: |
          aws sts get-caller-identity

まず

on:
  push:
    branches:    
      - dev
      - main
    tags:
      - 'v*.*.*'

の部分は、これまで各ワークフローファイルで個別に指定していたトリガーを全て集約した形になります。ここは特に説明不要でしょう。

ポイントになるのは次の3つのステップです

      - name: Set env to dev
        if: endsWith(github.ref, '/dev')
        run: |
          echo "ROLE_TO_ASSUME=${{ secrets.ROLE_TO_ASSUME_DEV }}" >> $GITHUB_ENV
      - name: Set env to stg
        if: endsWith(github.ref, '/main')
        run: |
          echo "ROLE_TO_ASSUME=${{ secrets.ROLE_TO_ASSUME_STG }}" >> $GITHUB_ENV
      - name: Set env to prd
        if: startsWith(github.ref, 'refs/tags/v')
        run: |
          echo "ROLE_TO_ASSUME=${{ secrets.ROLE_TO_ASSUME_PRD }}" >> $GITHUB_ENV

各ステップでifを利用してトリガー元のブランチorタグをチェックし、各環境向けのデプロイ用IAMロールのARNを$GITHUB_ENVに保存しています。これで以後のステップはROLE_TO_ASSUMEという環境変数経由でデプロイ用IAMロールのARNが取得できるようになります。

環境ファイル

あとはデプロイ用IAMロールにAssume Roleする処理で、対象ロールのARNを環境変数ROLE_TO_ASSUMEから取得するように修正すればOKです

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
          role-to-assume: ${{ env.ROLE_TO_ASSUME }}
          role-duration-seconds: 3600

これで元々3つのワークフローファイルに分かれていた処理を1つに集約することができました!

まとめ

if$GITHUB_ENVを組み合わせてワークフローファイルを集約する方法をご紹介しました。もちろんなんでもかんでも集約すれば良いというものではなく、あえて重複を許容してブランチや環境毎にワークフローファイルを用意するほうがかえって保守性が高くなるというケースもあるかと思います。ワークフローの複雑度や更新頻度、メンテナンス性などを考慮しつつ選択肢の1つとして利用頂ければと思います。