【小ネタ】CDKとGitHub Actionsを使ってスタックを簡単に並列デプロイする

2022.07.07

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

はじめに

デプロイ時間を短縮するために、GitHub Actions上でCDKを用いてスタックを並列にデプロイすることを検討したことはありませんか。 CircleCIではコマンド定義が可能ですが、GitHub Actions(以降GHAとする)の場合、スタック分ジョブを定義するか、独自のAction定義をする方法があります。今回は、簡単に(=共通化しつつ、記述量の少ない形)スタックを並列にデプロイする方法を紹介します。

※ 注意
CDK自体で並列にスタックをデプロイするオプションについては、いくつか進行中のPRが存在します。本体に取り込まれた場合この方法は必要なくなることが予想されます。

GitHub Actionsの設定

方針

設定の肝は、cdk listとGHAのstrategyのmatrixを活用します。実行結果のイメージは以下のようになります。 スクリーンショット 2022-07-07 6 45 08

設定内容

例えばLambdaとAPIGatewayを使ったサーバーレスAPIをデプロイする際に、Lambdaだけ並列にデプロイしたい場合は、以下のようにします。設定のポイントは後述します。

name: Deploy
env:
  PROJECT_NAME: projectName

on:
  push:
    branches:
      - develop
      - master
jobs:
  setup:
    name: Setup
    runs-on: ubuntu-latest
    outputs:
      stageName: ${{ steps.setenv.outputs.stageName }}
      stacks: ${{ steps.setenv.outputs.stacks }}
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Use Node
        uses: actions/setup-node@v1
        with:
          node-version: "16.x"
      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: "**/node_modules"
          key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
      - name: Install
        run: yarn install --frozen-lockfile
      - name: Set environment
        id: setenv
        run: |
          stageName=""
          if ${{ github.ref == 'refs/heads/develop' }}; then
            stageName=dev
          elif ${{ github.ref == 'refs/heads/master' }}; then
            stageName=prd
          else
            exit 1
          fi

          echo "::set-output name=stageName::$stageName"
          api_lambda_stacks=$(yarn --silent \
            cdk list --context stageName=$stageName *-api-lambda-* \
            --long --json| \
            jq -c 'map(.id)')
          echo "::set-output name=stacks::$(echo $api_lambda_stacks | jq -c)"

  deploy-api-lambda:
    name: deploy-api-lambda
    needs:
      - setup
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        stack: ${{fromJson(needs.setup.outputs.stacks)}}
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Use Node
        uses: actions/setup-node@v2
        with:
          node-version: '16.x'
      - name: Restore node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: "**/node_modules"
          key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ロール名
          aws-region: リージョン名
      - name: Deploy
        shell: bash
        run: |
          yarn deploy -c stageName=${{ needs.setup.outputs.stageName }} \
            ${{ matrix.stack }} \
            --require-approval never

設定のポイント

setupというジョブで、デプロイするスタック名のリストを動的に生成し、outputの変数に設定します。後述しますが、本変数をstrategyのmatrixに指定することになります。

# zsh
yarn --silent cdk list --context stageName=dev \*-api-lambda-\* --long --json| jq -c 'map(.id)'
["dev-a-api-lambda-article","dev-b-api-lambda-auth","dev-c-api-lambda-doc","dev-d-api-lambda-git-hub-app","dev-e-api-lambda-user"]

コマンドオプションの補足

  • yarnの--silentオプションは標準出力にyarnの出力が含まれないようにするため
  • cdkの--long --json オプションはlist結果をjqでパースしやすくするため

前述のoutputの変数をstrategyのmatrixに指定して並列にデプロイすることが実現できます。

  deploy-api-lambda:
    name: deploy-api-lambda
    ...
    strategy:
      fail-fast: false
      matrix:
        stack: ${{fromJson(needs.setup.outputs.stacks)}}
    ...
      - name: Deploy
        shell: bash
        run: |
          yarn deploy -c stageName=${{ needs.setup.outputs.STAGE_NAME }} \
            ${{ matrix.stack }} \
            --require-approval never

Lambdaがデプロイされたあと、APIGatewayをデプロイしたい場合は、以下のコードで実行順の制御が可能です。

  deploy-api:
    name: deploy-api
    needs:
      - setup
      - deploy-api-lambda:
    steps:
      # APIスタックのデプロイロジックを書く

最後に

CircleCIのコマンド定義が欲しくなったら基本的にはstrategyのmatrixで代用可能か考えてみると良さそうです。 またNodejsFunctionだと、各スタックデプロイ時に全てソースに対してビルドが走ってしまうため、クレジット消費が無駄になってしまう問題があります。事前にビルド、キャッシュし、NodejsFunctionは利用しないことで防ぐことが可能です。esbuildは高速なので、気にならない程度だとは思いますがご注意ください。