KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

AWS CDKによるデプロイ作業を高速にするには?

1日に飲むコーヒーはカップ3杯までと決めています。 プラットフォームチームのさだです。

デプロイが遅い

私たちのチームは、弊社の各サービスから利用される共通の認証基盤を開発し、またそのアカウント管理機能をユーザー向けに提供しています。本番環境をリリースするときは次のような手順でやっています。

  1. リリース作業開始の社内アナウンス
  2. バックエンドをデプロイ
  3. フロントエンドをデプロイ
  4. 主に手作業でリグレッションテスト
  5. リリース作業完了の社内アナウンス

リリース手順

バックエンドはサーバーレスアーキテクチャを採用していて、AWS CDKを使ってインフラを管理・デプロイしています。ただデプロイが終わるまでに、まあまあ長い時間がかかっていました。

リグレッションテストをするため、デプロイ中はチーム全員でスタンバイしています。 その間「デプロイまだ終わんないかな〜」などと進捗率をチラチラ見ながらチームで歓談しています。

それはそれでチームビルディング的に有意義な時間になっているとは言え、やっぱり待ち時間は短くしたいですよね。

CloudFormation変更セットの作成と実行

さて、デプロイが終わるのを待っている間、AWS CDKはどんなことを行っているのでしょうか? cdk deploy の標準出力を見ると、こんな感じです(一部省略)。

cdk deploy MyBackendStack

MyBackendStack
MyBackendStack: deploying...
...
MyBackendStack: creating CloudFormation changeset...
...
 ✅  MyBackendStack

途中でCloudFormationの変更セットを作成しているようですね。

さらに cdk deploy のヘルプを見ると --execute というオプションがあります。

--execute    Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)

なるほど --no-execute を付ければ変更セットは実行されなくなるということですね。

どうやら cdk deploy では、CloudFormationの変更セットを作成し実行するようです。

それなら、変更セットを作成するところまでは事前に終わらせておいて、リリース作業では変更セットの実行だけやるようにしたら、リリース作業の待ち時間を短くできるんじゃないかしら?

やってみた

それでは変更セットの作成と実行を分離してみましょう。

まず、変更セットの作成をリリース作業開始前に終わらせておきます。次のようなスクリプトを用意しました。

#!/bin/bash
# 変更セットの作成

stack="MyBackendStack"
change_set="cdk-deploy-change-set"

cdk deploy "${stack}" \
  --no-execute \
  --change-set-name "${change_set}" \
  --require-approval never

# 作成した変更セットの Status を取得
status=$(aws cloudformation describe-change-set \
  --stack-name "${stack}" \
  --change-set-name "${change_set}" \
  --query 'Status' \
  --output text)

# Status が FAILED なら変更セットを削除
if [[ "${status}" == "FAILED" ]]; then
  aws cloudformation delete-change-set \
    --stack-name "${stack}" \
    --change-set-name "${change_set}"
fi

私たちの環境ではこのスクリプトをCIの中に組み込み、メインブランチにpushされると変更セットが作成されるようにしました。

次に、変更セットの実行だけをリリース作業時に行います。 以下のようなスクリプトを用意しました。

#!/bin/bash
# 変更セットの実行

stack="MyBackendStack"
change_set="cdk-deploy-change-set"

# 変更セットの ExecutionStatus を取得
execution_status=$(aws cloudformation list-change-sets \
  --stack-name "${stack}" \
  --query "Summaries[?ChangeSetName=='${change_set}'].ExecutionStatus" \
  --output text)

# ExecutionStatus が AVAILABLE なら変更セットを実行
if [[ "${execution_status}" == "AVAILABLE" ]]; then
  aws cloudformation execute-change-set \
    --stack-name "${stack}" \
    --change-set-name "${change_set}"
fi

⚠️ 変更がないのに変更セットが作成される

変更セット作成スクリプトでは、そのステータスをチェックして FAILED なら削除するようにしています。

cdk deploy --no-execute を実行するだけでは、スタックに変更がまったくなくても変更セットが作成されてしまいます。そのような変更セットのstatusを見てみると FAILED になっていて、当然それを実行してもエラーになります。

実行できない変更セットを残しておいてもしょうがないので自動的に削除するようにしました。

AWS CDKのソースコードを見ると cdk deploy --execute の場合にはやはり削除していましたよ。

https://github.com/aws/aws-cdk/blob/6e55c952d683f87bb815deb29124b9a37824749a/packages/aws-cdk/lib/api/deploy-stack.ts#L314-L320

さらに変更があるかどうかの判別方法を詳しく見てみると、

https://github.com/aws/aws-cdk/blob/6e55c952d683f87bb815deb29124b9a37824749a/packages/aws-cdk/lib/api/util/cloudformation.ts#L244-L254

変更セットの StatusReason の文言を startsWith() でチェックしていますね。へぇ〜。 私たちの環境ではシンプルに Status のチェックだけにとどめておきました。

⚠️ 変更セットを実行しても、すぐにデプロイ完了ではない

変更セットを実行するスクリプト自体は10秒程度で終わりました。

「えっ、これで終わり?速い!」と思いきや……。

念のためCloudFormationスタックのステータスを見ると UPDATE_IN_PROGRESS になっていました。 変更セットを実行しても即完了ではないんですね。 イベントログを確認するとスタックの更新が完了するまで待ち時間は大体2〜3分でした。

結果

それでは、リリース作業でデプロイにかかる時間がどれくらい速くなったか比較してみましょう。

経過時間

  • 改善前(cdk deploy でデプロイ): 6分57秒
  • 改善後(変更セット実行 + 待ち時間): 2分28秒

ということで大体3分の1くらいになりました!

スタック内で管理しているリソースの数にもよると思いますが皆さんの参考になれば幸いです。

まとめ

  • AWS CDKを使ったデプロイ作業を高速化する方法について紹介しました。
  • CloudFormation変更セットの作成と実行を分離して、作成は事前に済ませておきましょう。
  • 変更がなくても変更セットが作成されます。そんな変更セットは削除しましょう。
  • 変更セットを実行してから、実際にそれが完了するまで若干の時間がかかります。待ちましょう。