既存 Lambda@Edge がある環境で CloudFront のメンテナンスモードを設計してみた
はじめに
CloudFront を使ったメンテナンスモードの設計・実装を経験しました。
メンテナンスモードは、システムの更新作業中にユーザーのアクセスを一時的に止めるための仕組みです。たとえば DB のテーブル構造を変えるようなリリースでは、作業中にユーザーがアクセスしてくるとデータの整合性が壊れかねません。こうしたとき確実にアクセスを止めてメンテナンス画面を表示できると安心です。
CloudFront でメンテナンスページを出す方法は、先行記事がたくさんあります。ただ、すでに認証用の Lambda@Edge が動いている構成では、その制約とどう折り合いをつけるかというより深い論点に向き合うことになりました。
そこで本記事では、既存の Lambda@Edge が組み込まれたマルチオリジン構成でメンテナンスモードをどう設計したかを整理します。基本的な実装手順そのものよりも、AWS の制約と自分たちの構成を突き合わせて手段を選ぶプロセス に自分としては学びがありました。
前提となるアーキテクチャ
今回の対象は、CloudFront の背後に 2 種類のオリジンを持つ構成です。
┌─────────────┐
ユーザー ──────────────▶│ CloudFront │
└──────┬───────┘
path: / │ path: /static/*
┌──────────────────┴──────────────────┐
▼ ▼
┌─────────────┐ ┌────────────────────┐
│ ALB → ECS │ │ S3 │
│ (Next.js + │ │ (SPA配信) │
│ API) │ │ + OAC │
└─────────────┘ │ + Lambda@Edge(認証) │
└────────────────────┘
- メインの Web アプリ: Next.js + API が ECS 上で動作しています(CloudFront → ALB → ECS の経路)。
- 静的コンテンツ配信: サーバーサイドの処理を必要としないページを、SPA としてビルドし S3 から直接配信しています。
- S3 側の付帯要素:
- OAC (Origin Access Control): S3 への直接アクセスを禁止し、CloudFront 経由以外からのアクセスを遮断する仕組みです。
- 認証用 Lambda@Edge: S3 は静的ファイルを返すだけなので、アプリのような認証処理を持てません。そのためエッジで認証をかけています。この Lambda@Edge は Viewer Request のタイミングで動いています(これが後述する制約に関わってきます)。
どこでリクエストを止めるか
メンテナンスモードを設計・実装したことのない私にとっては、「メンテナンス画面を表示するなら、フロントエンド側で止めるのではないか」と最初は考えていました。しかし、この構成では止める場所を慎重に選ぶ必要があることがわかりました。
ポイントは、アプリや ALB で止めても、S3 パスは素通りしてしまうことです。
ユーザー ──▶ CloudFront ──▶ ALB ──▶ ECS(アプリ) ← アプリや ALB 側のパスを止めても…
│
└──────▶ S3 ← S3 パスは ALB を通らないので素通り
S3 パスは ALB を経由しないため、ALB で 503 を返すような対策では静的ページ側のアクセスを止められません。そこで、全トラフィックが必ず通る CloudFront で止める のが漏れのない設計になります。アプリに到達しないので、DB 変更作業中にアプリが DB へアクセスしてしまうリスクも同時に消せます。
実現手段としてはアプリレベル・ALB の固定レスポンス・Lambda@Edge・CloudFront Functions の 4 つを比較検討しましたが、上記の理由で CloudFront レベルで止める 方針に絞られました。
CloudFront のイベントタイミングとは
CloudFront には、リクエスト/レスポンスの各段階で処理を差し込める 4 つのイベントがあります。
Viewer Origin Origin Viewer
Request ──▶ Request ──▶ Response ──▶ Response
(受信直後) (オリジンへ) (オリジンから) (返却直前)
このうち Viewer Request は、CloudFront がリクエストを受け取った直後のタイミングです。キャッシュ判定よりも前なので、ここで返してしまえばオリジンにもキャッシュにも触れず遮断できます。メンテナンス判定にはうってつけです。
注意点:同一 Viewer イベントで Functions と Lambda@Edge は併用できない
当初は「デフォルトパスにも S3 パスにも、同じように CloudFront Functions を追加すればいい」と考えていました。ところが、S3 パスに CloudFront Functions を関連付けようとしたところ、うまく設定できません。原因を調べてみると、CloudFront には次の制約があることがわかりました。
ビューワーイベント (ビューワーリクエストとビューワーレスポンス) で CloudFront Functions と Lambda@Edge を組み合わせることはできません。
S3 パスにはすでに認証用の Lambda@Edge が Viewer Request に設定されていました。そこに CloudFront Functions を同じ Viewer Request に追加しようとしたため、この制約に引っかかっていたのです。
CloudFront Functions が使えるかどうかは AWS の制約だけでは決まらず、「S3 パスにはすでに Lambda@Edge が Viewer Request で動いている」という自分たちの構成と突き合わせて初めて判断できます。ドキュメント上の制約を読むだけでなく、現行構成でどのエッジ関数がどのタイミングに設定されているかを棚卸しすることが、手段を選ぶうえで欠かせませんでした。
既存 Lambda@Edge との共存方法
というわけで、制約を踏まえ、パスごとに手段を使い分ける ことにしました。
| パス | 既存の設定 | メンテ判定の追加方法 |
|---|---|---|
デフォルトパス / |
なし | CloudFront Functions を新規追加 |
S3 パス /static/* |
認証 Lambda@Edge が Viewer Request に設定済み | 既存 Lambda@Edge にメンテ判定を追記 |
ここでのポイントは、S3 パスでは新しい関数を追加するのではなく、すでに動いている認証 Lambda@Edge の中にメンテ判定ロジックを足した ことです。これで併用制約を回避しつつ、認証とメンテ判定という 2 つの Viewer Request 処理を 1 つの関数にまとめられました。
メンテナンスモードの ON/OFF をどう切り替えるか
実現手段が決まったので、次は ON/OFF の切り替え方法です。候補は 3 つありました。
| 方式 | 切り替え方法 | CloudFront Functions から参照可能か |
|---|---|---|
| SSM Parameter Store | フラグ更新のみ(デプロイ不要) | ❌ 外部 API コール不可のため参照できない |
| CloudFront KeyValueStore | CLI 等で値を更新 | ⭕️ Functions が参照できる KVS |
| コード内変数 + CDK デプロイ | フラグを変えて再デプロイ | ⭕️(値がコードに埋め込まれる) |
最初に浮かんだのは「SSM Parameter Store にフラグを置いて、リクエストごとに参照する」案です。コードを変更せず即座に切り替えられるので、良さそうに見えました。
ところが調べてみると、CloudFront Functions は外部サービスへの API コールができない ことがわかりました。公式ドキュメントの比較表でも、CloudFront Functions は「ネットワークアクセス」が いいえ とされています。CloudFront Functions は軽量で高速実行を目的とした設計のため、ネットワークアクセスが制限されています。
項目 CloudFront Functions Lambda@Edge ネットワークアクセス いいえ はい — CloudFront Functions と Lambda@Edge の違い - Amazon CloudFront
そのため SSM を参照できず、この案は不採用としました。外部参照が必要なら CloudFront KeyValueStore(Functions から参照できる KVS)という手もありますが、更新に CLI 操作が必要になります。
いずれの方式にも一長一短がありますが、今回は コード内の変数で管理し、CDK デプロイで切り替える方式 を採用しました。理由は次の 2 点です。
- 切り替え操作をインフラの変更履歴として残せる: フラグの変更が CDK のコード差分とデプロイ履歴に残るため、「いつ・誰が・どの状態に切り替えたか」を追跡できます。SSM や KeyValueStore の手動更新では、この変更履歴が残りにくいです。
- 構成管理を一元化できる: メンテナンスの状態をインフラ定義(CDK)の外に置くと、管理対象が分散します。フラグもコードで管理することで、インフラの状態を一箇所で把握できます。
即時切り替えが必要な場合は KeyValueStore の方が向いていますが、今回のユースケースでは切り替えの追跡性と構成管理の一貫性を優先しました。
なお、CloudFront での遮断はブラウザ経由のアクセスに対するものです。VPC 内部から ALB / ECS へ直接アクセスする経路がある場合、そこは CloudFront を通らないため対象外になる点には注意が必要です。
まとめ
CloudFront 配下のマルチオリジン構成で、既存の Lambda@Edge と共存させながらメンテナンスモードを設計しました。
| 論点 | 結論 |
|---|---|
| どこで止めるか | 全トラフィックが通る CloudFront(アプリ/ALB では S3 パスが素通り) |
| Viewer イベント併用制約 | 同一パスで Functions と Lambda@Edge は併用不可 |
| 既存 Lambda@Edge との共存 | デフォルトパスは Functions 新規追加、S3 パスは既存 Lambda@Edge に追記 |
| ON/OFF 切り替え | Functions は外部 API 不可 → 要件次第で コード内変数 + CDK or KeyValueStore |
「CloudFront でメンテページを出す」という実装自体は先行記事が充実しています。ただ、既存の Lambda@Edge が動いている環境 では、Viewer イベントの併用制約という一段深い問題に突き当たりました。実現手段を並べるだけでなく、それぞれの AWS の制約と自分たちの構成を突き合わせて選ぶことが、この種の設計では大切だと実感しました。







