はじめに
こんばんは、mirukyです。
CloudFormationのExpress modeは、リソースへ設定を適用した時点でスタック操作を完了します。リソースが利用可能になるまで待つStandard modeとは、完了の意味が異なるんですよね〜。
今回は同じCloudFrontテンプレートを両モードで作成し、CloudFormationがCREATE_COMPLETEを返すまでの秒数と、その時点のCloudFrontの状態をAWS CLIだけで比べてみます。
目次
- Express modeの完了条件
- ハンズオンの準備
- 検証用テンプレートの作成
- Standard modeの計測
- Express modeの計測
- CloudFrontの準備完了を待つ
- 計測結果の比較
- 後片付け
1. Express modeの完了条件
Standard modeは、リソースがトラフィックを処理できる状態までCloudFormationが待ってから操作を完了します。Express modeはリソースへ設定が適用されると完了し、リソース側の準備はバックグラウンドで続きます。
有効化はテンプレートではなく、作成、更新、削除といった操作単位です。AWS CLIでは--deployment-config 'Mode=EXPRESS'を付けます。
Express modeはロールバックを既定で無効にします。失敗した状態を残して修正しやすくする動作ですが、自動ロールバックが必要なら--deployment-config 'Mode=EXPRESS,DisableRollback=false'と明示します。
2. ハンズオンの準備
AWS CLI v2で認証済みの環境と、cfn-lintを用意してください。操作先のCloudFormationスタックは東京リージョン、CloudFront Distributionはグローバルリソースです。CloudFormationとCloudFrontの作成、参照、削除に必要なIAM権限も使います。
CloudFrontはデータ転送やHTTPリクエストなどで課金されます。今回のDistributionはEnabled: falseで作成し、リクエストは送りません。それでも実行前に料金ページとアカウントの利用状況を確認してください。
既存のAWS設定を変更しないように、リージョンとページャーはこのターミナル内の変数だけで指定します。以降は同じターミナルで続けてください。
# 検証専用の作業場所と固有名を作ります。
START_DIR=$(pwd)
WORK_DIR=$(mktemp -d)
cd "$WORK_DIR"
AWS_REGION=ap-northeast-1
AWS_PAGER=""
RUN_ID=$(date +%Y%m%d%H%M%S)
STANDARD_STACK="qiita-cfn-standard-${RUN_ID}"
EXPRESS_STACK="qiita-cfn-express-${RUN_ID}"
RUN_IDを付けるのは、同じアカウントで動く別の処理や過去のスタックと名前が重ならないようにするためです。
3. 検証用テンプレートの作成
nanoでtemplate.yamlを開きます。
# 新しいテンプレートファイルを開きます。
nano template.yaml
次の内容を貼り付け、Ctrl+O、Enterで保存し、Ctrl+Xで閉じます。配信は無効のままにし、CloudFrontの状態遷移だけを扱います。
# 配信を無効にしたCloudFront Distributionを作ります。
AWSTemplateFormatVersion: "2010-09-09"
Description: Compare CloudFormation standard and express modes with CloudFront
Resources:
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: Qiita CloudFormation express mode hands-on
Enabled: false
Origins:
- DomainName: example.com
Id: example-origin
CustomOriginConfig:
OriginProtocolPolicy: https-only
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
TargetOriginId: example-origin
ViewerProtocolPolicy: redirect-to-https
ForwardedValues:
QueryString: false
Cookies:
Forward: none
Tags:
- Key: Purpose
Value: qiita-cfn-express
ローカル検査とAWS側の構文検証を続けて実行します。どちらかがエラーを返した場合は作成へ進まず、行番号とメッセージを基にテンプレートを直してください。
# ローカル検査の後にAWS側でも構文を確認します。
cfn-lint template.yaml
aws cloudformation validate-template \
--template-body file://template.yaml \
--region "$AWS_REGION" \
--query 'Description' \
--output text \
--no-cli-pager
成功すると、cfn-lintは何も表示せず、AWS CLIはDescriptionを返します。
Compare CloudFormation standard and express modes with CloudFront
1秒間隔でスタック状態を確認する関数も作ります。AWS CLIの標準waiterは確認間隔が長いため、今回の秒数比較では同じ短い間隔を両モードへ使います。
# 両モードを同じ間隔で計測する関数です。
wait_stack_terminal() {
local stack_name="$1"
local stack_status
while true
do
stack_status=$(aws cloudformation describe-stacks \
--stack-name "$stack_name" \
--region "$AWS_REGION" \
--query 'Stacks[0].StackStatus' \
--output text \
--no-cli-pager)
case "$stack_status" in
*_COMPLETE|*_FAILED)
printf '%s\n' "$stack_status"
return
;;
esac
sleep 1
done
}
4. Standard modeの計測
最初は--deployment-configを付けず、既定のStandard modeで作成します。開始時刻から終端状態までの秒数を計算します。
# Standard modeで作成時間を計測します。
STANDARD_START=$(date +%s)
aws cloudformation create-stack \
--stack-name "$STANDARD_STACK" \
--template-body file://template.yaml \
--region "$AWS_REGION" \
--no-cli-pager >/dev/null
STANDARD_STATUS=$(wait_stack_terminal "$STANDARD_STACK")
STANDARD_SECONDS=$(($(date +%s)-STANDARD_START))
printf 'StackStatus=%s\n' "$STANDARD_STATUS"
printf 'ElapsedSeconds=%s\n' "$STANDARD_SECONDS"
if [ "$STANDARD_STATUS" != "CREATE_COMPLETE" ]
then
printf 'Standard stackの作成に失敗しました。\n' >&2
exit 1
fi
手元では171秒で完了しました。秒数はリージョンやサービスの状態で変わるので注意ですね。
StackStatus=CREATE_COMPLETE
ElapsedSeconds=171
CloudFormationが作ったDistribution IDを取得し、CloudFront側の状態を確認します。
# Standard modeが完了した時点のCloudFrontを確認します。
STANDARD_DISTRIBUTION_ID=$(aws cloudformation describe-stack-resource \
--stack-name "$STANDARD_STACK" \
--logical-resource-id Distribution \
--region "$AWS_REGION" \
--query 'StackResourceDetail.PhysicalResourceId' \
--output text \
--no-cli-pager)
aws cloudfront get-distribution \
--id "$STANDARD_DISTRIBUTION_ID" \
--query 'Distribution.{Status:Status,Enabled:DistributionConfig.Enabled}' \
--output json \
--no-cli-pager
Standard modeの完了時点では、CloudFrontもDeployedでした。EnabledはテンプレートどおりFalseです。
{
"Status": "Deployed",
"Enabled": false
}
5. Express modeの計測
同じテンプレートへMode=EXPRESSだけを追加します。リソース構成は変えません。
# Express modeで同じテンプレートを作成します。
EXPRESS_START=$(date +%s)
aws cloudformation create-stack \
--stack-name "$EXPRESS_STACK" \
--template-body file://template.yaml \
--deployment-config 'Mode=EXPRESS' \
--region "$AWS_REGION" \
--no-cli-pager >/dev/null
EXPRESS_STATUS=$(wait_stack_terminal "$EXPRESS_STACK")
EXPRESS_SECONDS=$(($(date +%s)-EXPRESS_START))
printf 'StackStatus=%s\n' "$EXPRESS_STATUS"
printf 'ElapsedSeconds=%s\n' "$EXPRESS_SECONDS"
if [ "$EXPRESS_STATUS" != "CREATE_COMPLETE" ]
then
printf 'Express stackの作成に失敗しました。\n' >&2
exit 1
fi
こちらは62秒でした。Standard modeの171秒に対し、CloudFormationの完了通知まで約2.8倍短い結果です。
StackStatus=CREATE_COMPLETE
ElapsedSeconds=62
適用されたモードとロールバック設定を取得します。
# 適用されたモードとロールバック設定を取得します。
aws cloudformation describe-stacks \
--stack-name "$EXPRESS_STACK" \
--region "$AWS_REGION" \
--query 'Stacks[0].{Mode:DeploymentConfig.Mode,DisableRollback:DisableRollback}' \
--output json \
--no-cli-pager
ModeはEXPRESS、DisableRollbackはTrueです。Express modeではロールバックが既定で無効になることも、スタック情報から確認できます。
{
"Mode": "EXPRESS",
"DisableRollback": true
}
Standard modeと同じ方法でDistribution IDを取得し、すぐにCloudFrontを確認します。
# Express modeが完了した直後のCloudFrontを確認します。
EXPRESS_DISTRIBUTION_ID=$(aws cloudformation describe-stack-resource \
--stack-name "$EXPRESS_STACK" \
--logical-resource-id Distribution \
--region "$AWS_REGION" \
--query 'StackResourceDetail.PhysicalResourceId' \
--output text \
--no-cli-pager)
aws cloudfront get-distribution \
--id "$EXPRESS_DISTRIBUTION_ID" \
--query 'Distribution.{Status:Status,Enabled:DistributionConfig.Enabled}' \
--output json \
--no-cli-pager
CloudFormationはCREATE_COMPLETEですが、CloudFrontはInProgressでした。Express modeの完了を、そのままアプリケーションの利用開始合図にしない理由がここにあります。
{
"Status": "InProgress",
"Enabled": false
}
6. CloudFrontの準備完了を待つ
CloudFrontを次の処理で使うなら、サービス側の準備完了を別途待ちます。CloudFront用のwaiterを実行し、待機後の状態を再確認します。
# CloudFront自身がDeployedになるまで待ちます。
BACKGROUND_START=$(date +%s)
aws cloudfront wait distribution-deployed \
--id "$EXPRESS_DISTRIBUTION_ID" \
--no-cli-pager
BACKGROUND_SECONDS=$(($(date +%s)-BACKGROUND_START))
printf 'BackgroundWaitSeconds=%s\n' "$BACKGROUND_SECONDS"
aws cloudfront get-distribution \
--id "$EXPRESS_DISTRIBUTION_ID" \
--query 'Distribution.{Status:Status,Enabled:DistributionConfig.Enabled}' \
--output json \
--no-cli-pager
追加で124秒待つとDeployedになりました。Express modeは待ち時間を消すのではなく、CloudFormationの完了通知とリソースの準備完了を分けます。
BackgroundWaitSeconds=124
{
"Status": "Deployed",
"Enabled": false
}
7. 計測結果の比較
今回の結果をまとめます。
| 確認対象 | Standard mode | Express mode |
|---|---|---|
| CloudFormation完了 | 171秒 | 62秒 |
| 完了直後のCloudFront | Deployed |
InProgress |
| CloudFrontの追加待機 | 不要 | 124秒 |
| 既定のロールバック | 有効 | 無効 |
Express modeは、開発中にCloudFormationの完了通知を手っ取り早く受け取りたい場面へ向きます。一方、デプロイ直後にCloudFrontへ依存する処理を動かすなら、distribution-deployedのようなサービス別の確認が必要です。
今回はOutputsを使っていません。公式ドキュメントでは、リソース属性をOutputsで参照すると、その値を取得できるまでExpress modeでも待つ場合があると説明されています。比較時はテンプレートの出力条件も同じにしてください。
おわりに
ここまでお付き合いいただきありがとうございます。
同じCloudFrontテンプレートでも、Standard modeはDeployedまで待ち、Express modeはInProgressの段階でCloudFormationを完了しました。短くなった待ち時間の先でリソースがどの状態にあるかまで確認して、次の処理を設計する必要があります。
ではまた、お会いしましょう。