CloudFrontの継続的デプロイにメンテナンス画面を組み込む
CloudFrontの継続的デプロイ(ブルーグリーンデプロイ)にメンテナンス画面への切り替えを組み込んで構築した方法の紹介になります。
一般的なソリューションは以下で紹介されていますが、いくつかの制約や要求に基づいて少し異なる方法で構築をしました。
やりたかったこと
- ブルーグリーンデプロイでダウンタイムゼロのデプロイメントを実現する一方、どうしてもダウンタイムが発生してしまうリリースに備えてメンテナンス画面への切り替えができるようにしたかった。
- メンテナンスフローの統制のため、パイプラインとしてはStepFuctionを利用せずGitHubActionsで構築・管理ができるようにしたかった。
一方で、対象となるCloudFrontのディストリビューションには100を越えるサブドメインが紐づいており、Route53側でメンテナンス画面へ瞬時にレコードを付け替えることが難しいというような制約がありました。
メンテナンス画面組み込みの仕組み
前提
CloudFrontは動的なサイトのホスティングをしており、S3をオリジンとしています。
方針・ワークフローの状態遷移
CloudFrontの継続的デプロイの基本的な仕組みは、プライマリディストリビューションとは別にステージングディストリビューションを用意し、トラフィックを分離した後にPromote(昇格)をすることによってプライマリディストリビューションへの適用を行います。
トラフィックが分離されている時は、プライマリ・ステージングそれぞれで/blue
と/green
というようなイメージで別々のオリジンパスを参照します。
これに対し、/maintenance
というオリジンパスを参照する状態を作成し、デプロイワークフローの中で必要に応じてメンテナンス画面を表示するような方針としました。
/maintenance
には固定ページとしてメンテナンス表示のためのhtmlを配置しています。
メンテナンス画面にしない場合のオリジンパス状態遷移
メンテナンス画面切り替えを必要としない場合、以下のような流れでデプロイメントが行われます。
💡ディストリビューションにおけるリクエストの振り分け方法にはHeader-basedな方法とWeight-basedな方法の2種類が選択できます。
Header-basedで振り分けをする際は、ModHeaderという拡張機能を利用することによって、chromeでの動作確認をすることが可能です。
メンテナンス画面にする場合のオリジンパス状態遷移
一方、メンテナンス画面切り替えを必要とする場合、以下のような流れとなります。
実装
ステージングディストリビューションの準備
cdkで実装する場合は以下のようなイメージです。
const primaryDistribution = new cloudfront.CfnDistribution(this, 'PrimaryDistribution', {
distributionConfig: new CfDistributionConfiguration(
props,
false,
).configuration,
});
if (props.frontDistribution.needStagingDistribution) {
const stagingDistribution = new cloudfront.CfnDistribution(this, 'StagingDistribution', {
distributionConfig: new CfDistributionConfiguration(
props,
true,
).configuration,
});
const cdPolicy = new cloudfront.CfnContinuousDeploymentPolicy(
this,
"CfnContinuousDeploymentPolicy",
{
continuousDeploymentPolicyConfig: {
enabled: true,
stagingDistributionDnsNames: [ stagingDistribution.attrDomainName],
trafficConfig: {
type: "SingleHeader",
singleHeaderConfig: {
header: "aws-cf-cd-xxxx",
value: "true",
},
},
},
}
);
primaryDistribution.addPropertyOverride('DistributionConfig.ContinuousDeploymentPolicyId', cdPolicy.ref);
}
プライマリディストリビューションとステージングディストリビューションを作成した上で、デプロイポリシーをアタッチしています。
DistributionConfigは共通化していますが、プライマリかステージングかによってstagingプロパティを設定してあげるだけでOKです。
各GithubActions Workflowの作成
各ワークフローの流れとポイントを記載します。
ステージングディトリビューションへのdeploy workflow
steps:
# checkout, credentials設定, AWS CLIのインストール等
# デプロイポリシーの有効化
- name: Enable continuous deployment policy
run: |
aws cloudfront get-continuous-deployment-policy-config --id ${{ env.CD_POLICY_ID }} --output json > cd-policy-config.json
ETAG=$(jq -r '.ETag' cd-policy-config.json)
jq 'del(.ETag) |
.ContinuousDeploymentPolicyConfig.Enabled = true |
.ContinuousDeploymentPolicyConfig
' cd-policy-config.json > updated-policy-config.json
aws cloudfront update-continuous-deployment-policy --id ${{ env.CD_POLICY_ID }} --continuous-deployment-policy-config file://updated-policy-config.json --if-match "$ETAG"
- name: Build Application
# (省略)アプリケーションのビルド
- name: Sync S3 Bucket
# (省略)ステージングディストリビューションが参照するs3パスへsyncする
# オリジンパスを変更し、トラフィックを分離する
- name: Traffic separation
run: |
aws cloudfront get-distribution-config --id ${{ env.STAGING_DISTRIBUTION_ID }} --output json > stg-dist-config.json
ETAG=$(jq -r '.ETag' stg-dist-config.json)
STG_CURRENT_ORIGIN_PATH=$(jq -r '.DistributionConfig.Origins.Items[0].OriginPath' stg-dist-config.json)
aws cloudfront get-distribution-config --id ${{ env.PRIMARY_DISTRIBUTION_ID }} --output json > primary-dist-config.json
PRI_CURRENT_ORIGIN_PATH=$(jq -r '.DistributionConfig.Origins.Items[0].OriginPath' primary-dist-config.json)
if [ "$STG_CURRENT_ORIGIN_PATH" = "$PRI_CURRENT_ORIGIN_PATH" ]; then
NEW_ORIGIN_PATH=$([ "$STG_CURRENT_ORIGIN_PATH" = "/blue" ] && echo "/green" || echo "/blue")
echo "TEST_ORIGIN_PATH=$NEW_ORIGIN_PATH" >> $GITHUB_ENV
jq --arg path "$NEW_ORIGIN_PATH" '
del(.ETag) |
.DistributionConfig.Origins.Items[0].OriginPath = $path |
.DistributionConfig
' stg-dist-config.json > updated-stg-distribution-config.json
aws cloudfront update-distribution --id ${{ env.STAGING_DISTRIBUTION_ID }} --distribution-config file://updated-stg-distribution-config.json --if-match "$ETAG"
else
echo "TEST_ORIGIN_PATH=$STG_CURRENT_ORIGIN_PATH" >> $GITHUB_ENV
fi
- name: Invalidation Cloudfront Cache
# (省略)aws cloudfront create-invalidationでエッジロケーションのキャッシュクリア
distribution-configに関しては、現状のオリジンパスが/blue
か/green
かに応じてupdateするパスが変わるので現状のものを取得する形としています。
プライマリディトリビューションへのpromote workflow
steps:
# checkout, credentials設定, AWS CLIのインストール等
# ステージングディストリビューションの昇格
- name: Promote Stg Distribution
run: |
aws cloudfront get-distribution-config --id ${{ env.PRIMARY_DISTRIBUTION_ID }} --output json > primary-dist-config.json
PRIMARY_ETAG=$(jq -r '.ETag' primary-dist-config.json)
aws cloudfront get-distribution-config --id ${{ env.STAGING_DISTRIBUTION_ID }} --output json > staging-dist-config.json
STAGING_ETAG=$(jq -r '.ETag' staging-dist-config.json)
aws cloudfront update-distribution-with-staging-config --id ${{ env.PRIMARY_DISTRIBUTION_ID }} \
--staging-distribution-id ${{ env.STAGING_DISTRIBUTION_ID }} \
--if-match "${PRIMARY_ETAG},${STAGING_ETAG}"
- name: Invalidation Cloudfront Cache
# (省略)aws cloudfront create-invalidationでエッジロケーションのキャッシュクリア
ステージングディトリビューションへのdeploy workflow内でも同様の課題に触れていますが、update-distributionを連続して実行することはできません。
deploy workflowの実行後、CloudFrontディトリビューションのステータスを見て、デプロイが完了している必要があります。(ステータスはAWSのマネコンからも確認できます。)
メンテナンス画面への切り替えworkflow
サンプルコードは省略しますが、同様にプライマリディストリビューションのオリジンパスを/maintenance
に変更します。
まとめ
ブルーグリーンデプロイを実装していても、諸々の都合でメンテナンス画面にしたいときはあるため完全なダウンタイムゼロのデプロイを実現することは難しいなと思います。
必要に応じてブルーグリーンデプロイを利用したり、メンテナンス画面を利用しながら少しずつでもダウンタイムを短縮し、安心安全のメンテナンスができるようにして行けたら良いですね!
Discussion