CloudFront + S3 のシンプル構成でブルーグリーンデプロイメントを試してみた

CloudFront + S3 のシンプル構成でブルーグリーンデプロイメントを試してみた

2026.03.22

はじめに

静的サイトのホスティングといえば CloudFront + S3 の組み合わせが定番ですね。
直近業務で、CloudFront Continuous Deployment(継続的デプロイ) 機能を利用する機会があったので
CDKを利用して構築検証した内容を、今回はブログにしました!

CloudFront Continuous Deployment(継続的デプロイ) とは・・・
2022年11月に GA となった機能で ヘッダーや、重みをつけることでルーティングを制御し継続的デプロイを実現する機能になります。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/continuous-deployment.html

今回はAWS CDK(TypeScript)を使ってインフラを構築し、実際にブルーグリーンデプロイメントを検証した手順と結果をまとめます。

アーキテクチャ概要

スクリーンショット 2026-03-22 17.01.49

S3 バケットは 1つだけ 使用し、/blue/green のプレフィックスでコンテンツを分離します。CloudFront の Origin Path でどちらのプレフィックスを参照するかを切り替えます。

ポイント

項目 内容
仕組み Primary Distribution に対して Staging Distribution を作成し、Continuous Deployment Policy でトラフィックを振り分ける
S3 構成 1 バケット内の /blue /green プレフィックスで Blue/Green を分離
トラフィック制御 Weight-based(重み付け、最大15%)または Header-based(特定ヘッダーで振り分け)
セッション維持 Cookie ベースのセッションスティッキネス(300〜3,600秒)
昇格(Promote) 検証 OK なら Staging の設定を Primary に昇格。DNS 変更は不要

CDK での構成

今回はなるべくシンプルに、以下の構成で検証します。

  • S3 バケット × 1: /blue(現行バージョン)と /green(新バージョン)のプレフィックスで管理
  • CloudFront Distribution × 1(Primary / L2): Origin Path /blue
  • Staging Distribution × 1(L1 / CfnDistribution): Origin Path /green
  • Continuous Deployment Policy(L1 / CfnContinuousDeploymentPolicy): Header-based で振り分け

重みづけでルーティングすることも可能ですが、今回は検証者が意図的にヘッダーを付与してリクエストすることで、確実に Staging 側のコンテンツを確認できるようにするためにHeader-based を選んでいます。

CDK 実装上の注意点

CloudFront Continuous Deployment は CDK の L2 コンストラクトが未提供 です。そのため以下の構成となります。

リソース コンストラクトレベル 理由
Primary Distribution L2 (cloudfront.Distribution) OAC 自動設定など L2 の恩恵を受ける
Staging Distribution L1 (cloudfront.CfnDistribution) staging: true プロパティが L2 に存在しない
Continuous Deployment Policy L1 (cloudfront.CfnContinuousDeploymentPolicy) L2 未提供
CDP → Primary の紐付け Escape Hatch (addPropertyOverride) L2 の Distribution に ContinuousDeploymentPolicyId を追加

プロジェクト構成

infra/
├── bin/
│   └── cloudfront-bg.ts        # CDK App エントリーポイント
├── lib/
│   └── cloudfront-bg-stack.ts  # メインスタック
├── html/
│   ├── blue/
│   │   └── index.html          # → S3 /blue/index.html
│   └── green/
│       └── index.html          # → S3 /green/index.html
├── cdk.json
├── package.json
└── tsconfig.json

CDK コードのポイント解説

S3 バケット(1つ)とコンテンツデプロイ

const bucket = new s3.Bucket(this, "ContentBucket", {
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  autoDeleteObjects: true,
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});

// blue/ プレフィックスにデプロイ
new s3deploy.BucketDeployment(this, "DeployBlue", {
  sources: [s3deploy.Source.asset("./html/blue")],
  destinationBucket: bucket,
  destinationKeyPrefix: "blue",
});

// green/ プレフィックスにデプロイ
new s3deploy.BucketDeployment(this, "DeployGreen", {
  sources: [s3deploy.Source.asset("./html/green")],
  destinationBucket: bucket,
  destinationKeyPrefix: "green",
});

destinationKeyPrefix でアップロード先のプレフィックスを指定します。結果として S3 には blue/index.htmlgreen/index.html が配置されます。

Primary Distribution(L2)— Origin Path /blue

const primaryDistribution = new cloudfront.Distribution(this, "PrimaryDistribution", {
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withOriginAccessControl(bucket, {
      originPath: "/blue",   // ← S3 の /blue プレフィックスを参照
    }),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
  },
  defaultRootObject: "index.html",
  httpVersion: cloudfront.HttpVersion.HTTP2, // HTTP/3 は CDP 非対応
});

originPath: "/blue" を指定することで、CloudFront は S3 の blue/ 配下のオブジェクトを配信します。ユーザーが /index.html をリクエストすると、実際には s3://bucket/blue/index.html が返されます。

Staging Distribution(L1)— Origin Path /green

const stagingDistribution = new cloudfront.CfnDistribution(this, "StagingDistribution", {
  distributionConfig: {
    staging: true,  // ← これが Staging Distribution の鍵
    enabled: true,
    origins: [{
      id: "green-origin",
      domainName: bucket.bucketRegionalDomainName,
      originPath: "/green",  // ← S3 の /green プレフィックスを参照
      s3OriginConfig: { originAccessIdentity: "" },
      originAccessControlId: stagingOac.attrId,
    }],
    defaultCacheBehavior: {
      targetOriginId: "green-origin",
      viewerProtocolPolicy: "redirect-to-https",
      cachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6",
      compress: true,
    },
  },
});

同じバケットの /green プレフィックスを参照します。staging: true は L2 に存在しないプロパティのため、L1(CfnDistribution)を使用します。

Escape Hatch で CDP を Primary に紐付け

const cfnPrimary = primaryDistribution.node.defaultChild as cloudfront.CfnDistribution;
cfnPrimary.addPropertyOverride(
  "DistributionConfig.ContinuousDeploymentPolicyId",
  cdp.attrId,
);

L2 の Distribution には continuousDeploymentPolicyId プロパティがないため、escape hatch(addPropertyOverride)で L1 レベルのプロパティを直接追加します。

デプロイと検証

デプロイ(2段階)

当初は1回の cdk deploy で全リソースを作成しようとしましたが、以下のエラーが発生しました。

CREATE_FAILED | AWS::CloudFront::Distribution
Resource handler returned message: "Invalid request provided:
AWS::CloudFront::Distribution: Continuous deployment policy is not
supported during distribution creation."

CloudFront の CreateDistribution API は、新規作成時に ContinuousDeploymentPolicyId を指定するとエラーになります。 CDP の紐付けは UpdateDistribution(既存 Distribution の更新)でのみ可能です。

公式のワークフローガイドでも、手順は CopyDistributionCreateContinuousDeploymentPolicyUpdateDistribution の順で記載されており、CreateDistribution で CDP を紐付ける手順は存在しません。ただし「Create 時は不可」とは明記されておらず、API リファレンス上はパラメータ自体は存在するため、実際にやってみないとわからない制約です。

そのため、CDK コンテキスト変数を使って2段階でデプロイする構成にしました。

cd infra
pnpm install

# Phase 1: S3 + Primary Distribution を作成
pnpm cdk deploy

# Phase 2: Staging Distribution + CDP を追加し、Primary に紐付け
pnpm cdk deploy --context enableBlueGreen=true

CDK スタック内では this.node.tryGetContext("enableBlueGreen") で分岐し、Phase 1 では Primary Distribution のみ作成、Phase 2 で Staging + CDP を追加して addPropertyOverride で既存の Primary を更新します。

Phase 2 完了後、スタック出力に以下が表示されます。

Outputs:
CloudFrontBlueGreenStack.PrimaryDistributionDomain = dxxxxxxxxxxxxx.cloudfront.net
CloudFrontBlueGreenStack.PrimaryDistributionId = Exxxxxxxxxx
CloudFrontBlueGreenStack.StagingDistributionId = Eyyyyyyyyyy
CloudFrontBlueGreenStack.BucketName = cloudfront-bg-contentbucket-xxxxxxxxx
CloudFrontBlueGreenStack.TestBlueCommand = curl https://dxxxxxxxxxxxxx.cloudfront.net/
CloudFrontBlueGreenStack.TestGreenCommand = curl -H "aws-cf-cd-staging: true" https://dxxxxxxxxxxxxx.cloudfront.net/

動作確認

# Blue(Primary)の確認 - 通常のリクエスト
curl https://dxxxxxxxxxxxxx.cloudfront.net/
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Blue Environment</title>
  <style>
    body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #e8f4fd; }
    .container { text-align: center; padding: 2rem; border-radius: 12px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
    h1 { color: #1565c0; font-size: 3em; }
    .version { color: #666; font-size: 1.2em; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Blue Environment</h1>
    <p class="version">v1.0 - Production</p>
    <p>This is the current production environment.</p>
  </div>
</body>
</html>

# Green(Staging)の確認 - ヘッダー付きリクエスト
curl -H "aws-cf-cd-staging: true" https://dxxxxxxxxxxxxx.cloudfront.net/
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Green Environment</title>
  <style>
    body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #e8f5e9; }
    .container { text-align: center; padding: 2rem; border-radius: 12px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
    h1 { color: #2e7d32; font-size: 3em; }
    .version { color: #666; font-size: 1.2em; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Green Environment</h1>
    <p class="version">v2.0 - Staging</p>
    <p>This is the new version under verification.</p>
  </div>
</body>
</html>

同一ドメイン に対してヘッダーの有無だけで Blue / Green が切り替わることが確認できます。

昇格(Promote)

検証 OK なら、Staging の設定を Primary に昇格させます。この操作は GUI で実行 しました。
スクリーンショット 2026-03-22 16.56.16

昇格後、通常のリクエスト(ヘッダーなし)でも Green の内容が返るようになります。

Note: 昇格は Staging の Distribution 設定(Origin Path 含む)を Primary にコピー する操作です。昇格後、Primary の Origin Path は /green に切り替わります。

curl  https://d2357u01coqn58.cloudfront.net/
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Green Environment</title>
  <style>
    body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #e8f5e9; }
    .container { text-align: center; padding: 2rem; border-radius: 12px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
    h1 { color: #2e7d32; font-size: 3em; }
    .version { color: #666; font-size: 1.2em; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Green Environment</h1>
    <p class="version">v2.0 - Staging</p>
    <p>This is the new version under verification.</p>
  </div>
</body>
</html>

検証結果

検証項目 結果
CDK による2段階デプロイ Phase 1(Primary)→ Phase 2(Staging + CDP)の2コマンドで全リソースがデプロイされた
S3 バケット 1つでの運用 /blue /green プレフィックスで問題なく分離できた
Header-based でのトラフィック振り分け ヘッダーの有無で Blue / Green を正しく切り替え可能
Staging → Primary への昇格 CLI 1 コマンドで全トラフィックが新バージョンに切り替わった
DNS 変更 不要。CloudFront のドメイン名は変わらない
ダウンタイム 昇格時にダウンタイムは発生しなかった
ロールバック 昇格前であれば CDP を無効化するだけで即座にロールバック可能

注意点・制約

  • Distribution 作成時に CDP を紐付けできない(更新時のみ可)。CDK では2段階デプロイで対応
  • Weight-based の場合、最大 15% までしかトラフィックを Staging に流せない
  • アカウントあたり最大 20 個 の Staging Distribution / Continuous Deployment Policy
  • HTTP/3 が有効な Distribution では利用不可
  • S3 バケットポリシーに Staging Distribution からのアクセスも許可が必要(CDK L1 では自動設定されない)
  • Primary と Staging はキャッシュを共有しない(Staging への最初のリクエストはキャッシュミスになる)
  • 高トラフィック時は CloudFront がすべてのリクエストを Primary に送る場合がある
  • 昇格後、CDK のスタック状態と実際のリソース状態にドリフトが発生する(後述)

昇格後の CDK 同期: ドリフト解消と次サイクルの準備

昇格(Promote)は CLI の update-distribution-with-staging-config で行うため、昇格後は CDK のスタック状態と実際の CloudFront 設定にドリフトが生じます。ここからが意外とハマりどころでした。

やりたいこと

昇格により Primary の Origin Path が /blue/green に切り替わったので、CDK コードを実態に合わせつつ、次のリリースサイクル(Green → Blue への切り替え)に備えたい。

手順

1. CDK コードの Origin Path を入れ替える

lib/cloudfront-bg-stack.ts で Primary と Staging の Origin Path を交換します。

// Primary: 昇格後の実態に合わせて /green に
origin: origins.S3BucketOrigin.withOriginAccessControl(bucket, {
  originPath: "/green",  // 変更前: "/blue"
}),

// Staging: 次に検証するバージョンとして /blue に
originPath: "/blue",  // 変更前: "/green"

2. enableBlueGreen なしでデプロイ → エラー発生

pnpm cdk deploy

ここで以下のエラーが発生しました。

DELETE_FAILED | AWS::CloudFront::ContinuousDeploymentPolicy
The specified continuous deployment policy is currently associated with a distribution.

DELETE_FAILED | AWS::CloudFront::Distribution (StagingDistribution)
The specified staging distribution is currently used by a continuous deployment policy.

enableBlueGreen コンテキストなしでデプロイすると、CDK は Staging Distribution と CDP を削除しようとします。しかし CDP が Primary に紐付いたままのため、CDP も Staging も削除できない という循環的なエラーになります。

3. GUI で CDP を Primary から切り離す

先に CDP の紐付けを解除する必要があります。
スクリーンショット 2026-03-22 16.56.16

4. CDK で再デプロイ

# DELETE_FAILED のリソースを削除
pnpm cdk deploy

# 入れ替え後のパスで Blue-Green を再構成
pnpm cdk deploy --context enableBlueGreen=true

まとめ

昇格後の CDK 同期には 3回のデプロイ/操作 が必要になります。

手順 操作 目的
1 GUI: CDP を Primary から切り離し 削除可能な状態にする
2 pnpm cdk deploy 不要になった CDP / Staging / OAC を削除
3 pnpm cdk deploy --context enableBlueGreen=true 入れ替え後のパスで Blue-Green を再構成

CI/CD パイプラインに組み込む場合は、この一連の手順を自動化する必要があります。AWS 公式の CDK Pipeline サンプル が参考になります。

CloudFront Continuous Deployment を使えば、CloudFront + S3 バケット 1つ というシンプルな構成のまま、DNS 変更もカスタムルーティングロジックも不要で、安全なブルーグリーンデプロイメントが実現できました。
S3 バケットを 2つ用意する必要はなく、プレフィックス(/blue /green)と CloudFront の Origin Path で切り替えることで、管理がよりシンプルになります。
CDK では L2 コンストラクトが未対応のため L1 + escape hatch の組み合わせが必要ですが、1 スタック・1 コマンドで全リソースをデプロイ可能 な構成にできます。
特に Header-based の振り分けは、検証者だけが新バージョンを確認できるため、静的サイトのリリースプロセスとして非常に実用的です。

参考リンク

この記事をシェアする

FacebookHatena blogX

関連記事