1日に飲むコーヒーはカップ3杯までと決めています。 プラットフォームチームのさだです。
デプロイが遅い
私たちのチームは、弊社の各サービスから利用される共通の認証基盤を開発し、またそのアカウント管理機能をユーザー向けに提供しています。本番環境をリリースするときは次のような手順でやっています。
- リリース作業開始の社内アナウンス
- バックエンドをデプロイ
- フロントエンドをデプロイ
- 主に手作業でリグレッションテスト
- リリース作業完了の社内アナウンス
バックエンドはサーバーレスアーキテクチャを採用していて、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
の場合にはやはり削除していましたよ。
さらに変更があるかどうかの判別方法を詳しく見てみると、
変更セットの StatusReason
の文言を startsWith()
でチェックしていますね。へぇ〜。
私たちの環境ではシンプルに Status
のチェックだけにとどめておきました。
⚠️ 変更セットを実行しても、すぐにデプロイ完了ではない
変更セットを実行するスクリプト自体は10秒程度で終わりました。
「えっ、これで終わり?速い!」と思いきや……。
念のためCloudFormationスタックのステータスを見ると UPDATE_IN_PROGRESS
になっていました。
変更セットを実行しても即完了ではないんですね。
イベントログを確認するとスタックの更新が完了するまで待ち時間は大体2〜3分でした。
結果
それでは、リリース作業でデプロイにかかる時間がどれくらい速くなったか比較してみましょう。
- 改善前(
cdk deploy
でデプロイ): 6分57秒 - 改善後(変更セット実行 + 待ち時間): 2分28秒
ということで大体3分の1くらいになりました!
スタック内で管理しているリソースの数にもよると思いますが皆さんの参考になれば幸いです。
まとめ
- AWS CDKを使ったデプロイ作業を高速化する方法について紹介しました。
- CloudFormation変更セットの作成と実行を分離して、作成は事前に済ませておきましょう。
- 変更がなくても変更セットが作成されます。そんな変更セットは削除しましょう。
- 変更セットを実行してから、実際にそれが完了するまで若干の時間がかかります。待ちましょう。