【GitHubActions】単一ワークフローファイルでトリガー元に応じてデプロイ先のAWSアカウントを切り替えてみる
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_DEV
、ROLE_TO_ASSUME_STG
、ROLE_TO_ASSUME_PRD
という名前でGitHub ActionsのEncrypted secretsに保存されている
- 各AWSアカウントのデプロイ用ロールのARNが
- 自動デプロイ処理は
aws sts get-caller-identity
で代替する ※実際にはsam deploy
やsls 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つとして利用頂ければと思います。