AWS Edge/CDN本番運用Vol1|CloudFront・Lambda@Edge・Route53

目次

1. なぜEdge/CDN本番運用 Vol1 か — Edge層の役割と配信層・セキュリティ層の分業

fig01: AWS Edge/CDN 5サービス全体俯瞰アーキテクチャ

1-1. 本記事のゴール

Origin (S3 / ALB / API Gateway) は構築済みで稼働中だが、「Edge層はとりあえず CloudFront を置いた」という状態から抜け出せていない本番運用エンジニアを対象とする。CloudFront / Lambda@Edge / CloudFront Functions / Global Accelerator / Route53 の5サービスを 配信層 として横断的に網羅し、選定マトリクスと本番設計指針を提示する。

本記事を読み終えると次の判断ができるようになる:

  • Cache Policy / Origin Request Policy / Response Headers Policy の三者を正しく設計できる
  • Lambda@Edge と CloudFront Functions の使い分けを 4 trigger event 軸で説明できる
  • Global Accelerator が必要なユースケースと CloudFront で足りるケースを即答できる
  • Route53 の 7 Routing Policy から最適解を選べる

1-2. 読者像

Origin (S3 / ALB / API Gateway) は構築済みだが、Edge層の本番運用設計に迷っている本番運用エンジニア。CloudFront は日常的に使えるが Lambda@Edge / CloudFront Functions / Global Accelerator の使い分けで判断が止まる層。Terraform で IaC 管理しているが、CloudFront まわりのリソース依存関係で詰まった経験がある層。

1-3. 配信層 vs セキュリティ層 — 役割分担の明示

Edge層には大きく2つの責務がある。本記事は 配信層 に集中し、セキュリティ層は姉妹記事に委ねる。

Edge層 二大責務の分担 — 配信層と防御層

  • 配信層 (本記事 Vol1): Cache Policy / Edge Logic / Anycast IP / DNS Routing — レイテンシ最小化と可用性向上が主眼
  • 防御層 (セキュリティ Vol2): ルール制御 / DDoS対策 / Bot Control / Geo制限 — 攻撃面の最小化と検知・遮断が主眼

両者は同じ CloudFront ディストリビューション上に同居するが、設計判断軸が異なる。本記事では配信層の判断軸 (Cache Hit率最大化・Edge Logic選定・Anycast活用・DNS Routing設計) に集中する。WAF Rules / Shield Advanced / Bot Control / Geo制限の設計指針はセキュリティ Vol2 を参照されたい。

この分業が崩れると何が起きるか。配信層の記事に防御層の設定を混在させると、「CloudFront に WAF アタッチしたら Cache Hit率が落ちた」原因の切り分けができなくなる。Origin Shield 有効化は配信層の判断だが、Rate-based Rule の閾値設計は防御層の判断だ。責務を分けて考えることで、それぞれの最適化が独立して進められる。

1-4. Edge層の役割 — なぜ Origin だけでは不十分か

グローバルなサービスを提供する場合、Origin を 1 リージョンに置くだけでは次の問題が生じる:

レイテンシの問題: 東京 Origin に対して北米・欧州ユーザーが直接アクセスすると、RTT が 150〜250ms に達する。CloudFront の 450+ PoP によりエッジでキャッシュすることで、ユーザーが最寄り PoP から取得できる。

Origin 負荷集中の問題: 静的コンテンツのリクエストが全て Origin に到達すると、S3 リクエスト課金・ALB 処理コストが増大する。Cache Hit率 90% 以上を達成すれば Origin への実トラフィックを 1/10 に削減できる。

可用性の問題: Origin が単一障害点になる。Route53 Failover Policy + Health Check を組み合わせることで、Origin 異常時にフェイルオーバー先へ自動切替できる。

静的 IP 要件の問題: CloudFront の IP は固定されないため、IP許可リスト (Allowlist) が必要な相手先に CloudFront を使えないケースがある。Global Accelerator が提供する 2 つの Anycast IP を使うことでこの問題を解決できる。

1-5. 5サービス選定の現実解

サービス主用途決め手
CloudFrontHTTP/HTTPS CDN + Edge Logicキャッシュ前提のコンテンツ配信
Lambda@Edge複雑な Edge Logic (DB/外部API呼び出し)Origin/Viewer 4 trigger + 最大30秒
CloudFront Functions軽量 Edge Logic (URL書換/Header操作)sub-ms + 無料枠 2M req/月
Global AcceleratorTCP/UDP Anycast + 静的IP非HTTPワークロード or 静的IP要件
Route53DNS Routing + Health CheckFailover / Latency / Weighted
Edge/CDN本番運用 痛点5選

  • Cache Policy 設計ミスで Origin負荷爆発: Managed-CachingDisabled をデフォルトのまま本番適用し、全リクエストが Origin に転送され続ける
  • Lambda@Edge 4 trigger event 誤選定で性能劣化: 重量級処理を Viewer Request に置き、全ユーザーの TTFB が悪化する
  • CloudFront Functions と Lambda@Edge の使い分け誤り: 外部API呼び出しを CloudFront Functions で実装しようとしてタイムアウト連発
  • Global Accelerator vs CloudFront の用途混同: HTTP API に Global Accelerator を採用し、キャッシュ効果ゼロで料金だけ増加
  • Route53 Routing Policy 7種の選定誤り: Failover Policy で Health Check を未設定のまま運用し、Origin 障害時に切替が発動しない

2. CloudFront本番運用 — Origin × Behavior × Cache Policy × Origin Shield

fig02: CloudFront リクエスト処理フロー (Viewer→Edge→Origin Shield→Origin)

2-1. Origin 設計 — S3 OAC / ALB / Custom Origin

CloudFront Origin は「誰からリクエストを受けてコンテンツを返すか」の起点だ。Origin タイプによって認証方式・タイムアウト・プロトコル設定が異なる。

S3 Origin — OAC (Origin Access Control) 必須化

S3 Origin で最も重要なのは OAC の適用だ。旧来の OAI (Origin Access Identity) は廃止方向であり、現行では OAC が標準だ。OAC を使うと S3 バケットポリシーで CloudFront サービスプリンシパルからのアクセスのみを許可でき、S3 バケットを完全にプライベートに保てる。

resource "aws_cloudfront_origin_access_control" "s3_oac" {
  name= "s3-oac-${var.env}"
  description  = "OAC for S3 origin"
  origin_access_control_origin_type = "s3"
  signing_behavior= "always"
  signing_protocol= "sigv4"
}

S3 バケットポリシー側では CloudFront ディストリビューションの ARN を条件に指定する:

data "aws_iam_policy_document" "s3_origin_policy" {
  statement {
 principals {
type  = "Service"
identifiers = ["cloudfront.amazonaws.com"]
 }
 actions= ["s3:GetObject"]
 resources = ["${aws_s3_bucket.origin.arn}/*"]
 condition {
test  = "StringEquals"
variable = "AWS:SourceArn"
values= [aws_cloudfront_distribution.main.arn]
 }
  }
}

ALB Origin

ALB を Origin にする場合、CloudFront → ALB 間の通信を HTTPS で強制し、ALB のセキュリティグループを CloudFront のマネージドプレフィックスリスト (com.amazonaws.global.cloudfront.origin-facing) からのみ許可することで、ALB への直接アクセスを防止する。

Custom Origin タイムアウト設定

Custom Origin では origin_read_timeout (デフォルト30秒) と origin_keepalive_timeout (デフォルト5秒) をワークロードに合わせて調整する。API 系の Origin では Keepalive を 60 秒程度に延ばすと接続再利用率が向上する。

2-2. Cache Behavior と Path Pattern

Behavior 優先順位の基本則

CloudFront は Path Pattern を上から順にマッチングし、最初にマッチした Behavior を適用する。Default Behavior (*) は最後に評価される。正しい順序設計が Cache 分離の鍵だ。

典型的な本番 Behavior 構成:

優先度Path Pattern用途Cache
1/api/*API リクエストCachingDisabled
2/static/*静的アセットCachingOptimized
3/images/*画像CachingOptimized + 長TTL
Default*HTML ページManaged-CachingOptimized (短TTL)

Default Behavior の落とし穴

Default Behavior に CachingDisabled を設定すると全リクエストが Origin に転送される。まず CachingOptimized から始め、キャッシュしてはいけないパスだけ個別 Behavior で除外する設計が推奨だ。

2-3. Cache Policy + Origin Request Policy + Response Headers Policy

この3つの Policy は役割が明確に分かれている。混同するとキャッシュキーの汚染やセキュリティヘッダ漏れが発生する。

Cache Policy — キャッシュキーの決定

Cache Policy はキャッシュキー (どの属性の組み合わせでキャッシュを分けるか) を定義する。不要な Header / Cookie / Query String をキャッシュキーに含めると、同一コンテンツなのに別キャッシュとして扱われ Cache Hit率が低下する。

resource "aws_cloudfront_cache_policy" "api_cache" {
  name  = "api-cache-policy-${var.env}"
  min_ttl  = 0
  default_ttl = 0
  max_ttl  = 0

  parameters_in_cache_key_and_forwarded_to_origin {
 cookies_config {
cookie_behavior = "none"
 }
 headers_config {
header_behavior = "none"
 }
 query_strings_config {
query_string_behavior = "none"
 }
 enable_accept_encoding_brotli = true
 enable_accept_encoding_gzip= true
  }
}

Origin Request Policy — Origin への転送制御

Origin Request Policy はキャッシュキーには含めないが Origin に転送する Header / Cookie / Query String を定義する。例えば認証ヘッダは Cache Key に含めず (全ユーザーで同一キャッシュを使いたい) だが Origin には転送したい、という場合に使う。

resource "aws_cloudfront_origin_request_policy" "forward_auth" {
  name = "forward-auth-header-${var.env}"

  cookies_config {
 cookie_behavior = "none"
  }
  headers_config {
 header_behavior = "whitelist"
 headers {
items = ["Authorization", "X-Forwarded-For"]
 }
  }
  query_strings_config {
 query_string_behavior = "all"
  }
}

Response Headers Policy — レスポンスヘッダの自動付与

Response Headers Policy で CORS / CSP / HSTS / X-Frame-Options などのセキュリティヘッダを一元管理できる。各 Origin 側でヘッダを設定する必要がなく、ディストリビューション単位で統一適用できる。

resource "aws_cloudfront_response_headers_policy" "security_headers" {
  name = "security-headers-${var.env}"

  security_headers_config {
 strict_transport_security {
access_control_max_age_sec = 31536000
include_subdomains = true
preload= true
override  = true
 }
 content_type_options {
override = true
 }
 frame_options {
frame_option = "DENY"
override  = true
 }
 xss_protection {
mode_block = true
protection = true
override= true
 }
  }

  cors_config {
 access_control_allow_credentials = false
 access_control_allow_headers {
items = ["*"]
 }
 access_control_allow_methods {
items = ["GET", "HEAD", "OPTIONS"]
 }
 access_control_allow_origins {
items = ["https://www.example.com"]
 }
 origin_override = true
  }
}

2-4. Origin Shield — Cache Hit率最大化と Origin 負荷削減

Origin Shield とは

Origin Shield は CloudFront の PoP と Origin の間に挟む中間キャッシュ層だ。世界中の PoP からの Origin へのリクエストを Origin Shield 1箇所に集約することで、Cache Miss 時も Origin へのリクエスト数を最小化できる。

有効化の判断基準

状況Origin Shield 有効化
Origin が 1 リージョンのみ推奨 (全PoP→Origin Shieldに集約)
グローバルトラフィックが多い推奨 (Cache Hit率向上)
Origin が高コスト (RDS/外部API)強く推奨
Origin がマルチリージョン不要の場合が多い

リージョン選定基準

Origin Shield のリージョンは Origin に最も近いリージョンを選択する。東京 Origin であれば ap-northeast-1 が最適だ。

resource "aws_cloudfront_distribution" "main" {
  origin {
 domain_name  = aws_s3_bucket.origin.bucket_regional_domain_name
 origin_id = "s3-origin"
 origin_access_control_id = aws_cloudfront_origin_access_control.s3_oac.id

 origin_shield {
enabled  = true
origin_shield_region = "ap-northeast-1"
 }
  }
  # ...
}

コスト試算

Origin Shield は追加料金が発生する。例えば ap-northeast-1 で月間 1TB の Origin Shield 経由リクエストがある場合、$0.0090/GB × 1024GB ≈ $9.2/月 の追加コストだ。一方で Origin へのリクエスト削減効果 (S3 リクエスト課金・ALB 処理コスト削減) と比較してROI を判断する。

2-5. Terraform 実装 (Distribution + Cache Policy + OAC)

本番運用で最低限必要な CloudFront ディストリビューションの Terraform 実装を示す。

resource "aws_cloudfront_distribution" "main" {
  enabled = true
  is_ipv6_enabled  = true
  http_version  = "http2and3"
  price_class= "PriceClass_All"
  aliases = [var.domain_name]

  origin {
 domain_name  = aws_s3_bucket.origin.bucket_regional_domain_name
 origin_id = "s3-static-origin"
 origin_access_control_id = aws_cloudfront_origin_access_control.s3_oac.id

 origin_shield {
enabled  = true
origin_shield_region = "ap-northeast-1"
 }
  }

  origin {
 domain_name = aws_lb.api.dns_name
 origin_id= "alb-api-origin"

 custom_origin_config {
http_port  = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols= ["TLSv1.2"]
origin_read_timeout = 30
origin_keepalive_timeout = 60
 }
  }

  # API Behavior — キャッシュ無効
  ordered_cache_behavior {
 path_pattern = "/api/*"
 allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
 cached_methods  = ["GET", "HEAD"]
 target_origin_id= "alb-api-origin"
 cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # CachingDisabled
 origin_request_policy_id = aws_cloudfront_origin_request_policy.forward_auth.id
 viewer_protocol_policy= "redirect-to-https"
 compress  = true
  }

  # 静的アセット Behavior — 長期キャッシュ
  ordered_cache_behavior {
 path_pattern = "/static/*"
 allowed_methods = ["GET", "HEAD", "OPTIONS"]
 cached_methods  = ["GET", "HEAD"]
 target_origin_id= "s3-static-origin"
 cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
 response_headers_policy_id = aws_cloudfront_response_headers_policy.security_headers.id
 viewer_protocol_policy= "redirect-to-https"
 compress  = true

 function_association {
event_type= "viewer-request"
function_arn = aws_cloudfront_function.url_rewrite.arn
 }
  }

  # Default Behavior
  default_cache_behavior {
 allowed_methods = ["GET", "HEAD", "OPTIONS"]
 cached_methods  = ["GET", "HEAD"]
 target_origin_id= "s3-static-origin"
 cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
 response_headers_policy_id = aws_cloudfront_response_headers_policy.security_headers.id
 viewer_protocol_policy= "redirect-to-https"
 compress  = true
  }

  viewer_certificate {
 acm_certificate_arn= aws_acm_certificate.main.arn
 ssl_support_method = "sni-only"
 minimum_protocol_version = "TLSv1.2_2021"
  }

  restrictions {
 geo_restriction {
restriction_type = "none"
 }
  }

  logging_config {
 include_cookies = false
 bucket = aws_s3_bucket.cf_logs.bucket_domain_name
 prefix = "cloudfront/"
  }

  tags = var.common_tags
}

HTTP/3 有効化の注意点

http_version = "http2and3" で HTTP/3 (QUIC) が有効になる。HTTP/3 は UDP 443 を使うため、クライアント側のファイアウォールが UDP 443 を許可していない環境では HTTP/2 にフォールバックする。本番導入前にクライアント環境の UDP 443 疎通を確認すること。

Price Class の選定

Price Classカバーリージョン月額イメージ
PriceClass_All全450+ PoP最高
PriceClass_200北米・欧州・アジア中間
PriceClass_100北米・欧州のみ最安

日本がメインターゲットの場合は PriceClass_All を選択しないと東京 PoP が外れる可能性がある。

2-6. CloudFront Functions vs Lambda@Edge の橋渡し

§2 までで CloudFront の静的設定 (Distribution / Cache Policy / Origin) を網羅した。実際の本番運用では Edge で動的処理が必要になるケースが多い。

  • URL 書き換え・A/Bテスト振分・軽量 Header 操作 → CloudFront Functions (§3 で詳述)
  • 認証チェック・外部API呼び出し・Origin/Viewer レスポンス加工 → Lambda@Edge (§3 で詳述)

Edge Logic の選定は「実行タイミング」「実行時間」「使用言語」「コスト」の4軸で判断する。

flowchart LR
 Viewer["ユーザー\nブラウザ"]
 EdgePoP["CloudFront\nPoP (Edge)"]
 OriginShield["Origin Shield\n(任意)"]
 Origin["Origin\nS3 / ALB"]

 Viewer -- "① Viewer Request\n(CF Functions / Lambda@Edge)" --> EdgePoP
 EdgePoP -- "Cache Hit → レスポンス" --> Viewer
 EdgePoP -- "Cache Miss:\n② Origin Request\n(Lambda@Edge)" --> OriginShield
 OriginShield -- "Shield Cache Hit → レスポンス" --> EdgePoP
 OriginShield -- "Shield Cache Miss" --> Origin
 Origin -- "レスポンス:\n③ Origin Response\n(Lambda@Edge)" --> OriginShield
 OriginShield --> EdgePoP
 EdgePoP -- "④ Viewer Response\n(CF Functions / Lambda@Edge)" --> Viewer
CloudFront よくある落とし穴 3選

  • Cache Miss 率が改善しない: Cache Policy に不要な Cookie / Header が含まれている。aws cloudfront get-distribution-config で Cache Key の中身を確認し、不要属性を除去する
  • TTL 設計ミス: S3 オブジェクトの Cache-Control ヘッダと CloudFront の Max TTL が競合する。CloudFront は min(Max TTL, Cache-Control: max-age) を使う。静的アセットは S3 側で max-age=31536000 を設定し、Cache Policy の Max TTL を同値以上にする
  • OAC 移行漏れ: 既存ディストリビューションが OAI のまま残っている。OAI を使い続けていると S3 への署名方式が旧仕様のままになる。aws cloudfront list-distributions | jq '.DistributionList.Items[].Origins.Items[].S3OriginConfig' で OAI 使用状況を棚卸しする
CloudFront 本番運用ベストプラクティス

  • OAC を S3 Origin に必須適用: OAI は非推奨。新規ディストリビューションは全て OAC で作成し、既存は移行計画を立てる
  • HTTP/3 + Brotli 圧縮をデフォルト有効: http_version = "http2and3" + compress = true で TTFB と転送量を改善
  • Real-time Logs を Kinesis Data Streams 連携: Standard Access Logs (5〜24時間遅延) では障害対応が遅れる。Real-time Logs でリアルタイム監視を実現する
  • Response Headers Policy でセキュリティヘッダ自動付与: HSTS / CSP / X-Frame-Options を Origin 側ではなく CloudFront で一元管理する
  • Origin Shield をグローバルトラフィック配信に適用: Origin が東京のみであれば ap-northeast-1 で Origin Shield を有効化し、北米・欧州の PoP からの Cache Miss を集約する

3. Lambda@Edge本番運用 — 4 trigger events × Regional execution

fig03: Lambda@Edge 4 trigger event 配置と実行リージョン

Lambda@Edge は CloudFront に紐付けた Lambda 関数を エッジロケーション上で実行 するサービスである。
リクエスト・レスポンスのライフサイクルに4つの割込ポイント(trigger event)が存在し、それぞれに異なる用途と制約がある。
「どの trigger を選ぶか」を誤ると、性能劣化・コスト爆発・機能不全の3つが同時に発生する。

3-1. 4 trigger events 詳解

Lambda@Edge の 4 trigger event はリクエストの「どこで割り込むか」によって役割が根本的に異なる。
以下に各 trigger の実行タイミング・用途・副作用を網羅する。

Viewer Request — ユーザー→CloudFront / 認証・URL書換・ヘッダ追加

実行タイミング: CloudFront がユーザーのリクエストを受信した直後、キャッシュチェックより前

ユーザー → [Viewer Request Lambda] → CloudFront キャッシュ確認 → ...

キャッシュの有無に関わらず 毎リクエストで実行される ため、処理コストが直接リクエスト数に比例する。
その分、最も強力な制御点であり、認証・認可のゲートキーパーとして機能する。

主な用途:
– JWT / Cookie ベースの認証チェック(未認証なら 401/302 を即時返却)
– URL の正規化・書換(/old-path/new-path のリダイレクト)
– カスタムリクエストヘッダの追加(X-Country-Code: JP 等の地理情報付与)
– A/B テストの振分(ユーザー属性に応じてリクエスト先を変更)

実行制限(Viewer trigger 共通):
– 実行時間: 5秒(タイムアウトで 504 が返る)
– メモリ: 128MB〜10,240MB(設定値)
– パッケージサイズ: 展開後 1MB 以内(依存ライブラリ込み)

// Viewer Request: JWT 認証チェック例
exports.handler = async (event) => {
  const request = event.Records[0].cf.request;
  const headers = request.headers;

  const authHeader = headers['authorization'];
  if (!authHeader || !authHeader[0]) {
 return {
status: '401',
statusDescription: 'Unauthorized',
headers: {
  'www-authenticate': [{ key: 'WWW-Authenticate', value: 'Bearer' }],
},
 };
  }

  const token = authHeader[0].value.replace('Bearer ', '');
  if (!isValidToken(token)) {
 return { status: '403', statusDescription: 'Forbidden', headers: {} };
  }

  return request;
};

Origin Request — CloudFront→Origin / A/Bテスト・カスタムヘッダ

実行タイミング: キャッシュミス時に CloudFront が Origin へリクエストを転送する 直前

... → キャッシュミス → [Origin Request Lambda] → Origin (S3/ALB) → ...

キャッシュヒット時は実行されないため、Viewer Request と比べてコストを大幅に削減できる。
Origin への接続前に実行されるため、リクエスト先 Origin の動的変更が可能。

主な用途:
– A/B テスト: Cookie 値に応じて Origin の S3 バケットパスを切替
– カスタムリクエストヘッダの追加(X-Custom-Auth 等の Origin 認証ヘッダ)
– URLリライト(CloudFront 側パスと Origin 側パスの変換)
– 動的 Origin 選択(ユーザーの地域に応じて最近傍 ALB へルーティング)

実行制限(Origin trigger 共通):
– 実行時間: 30秒(Viewer trigger の6倍の余裕)
– コールドスタートの影響が Viewer trigger より小さい(30秒あればリカバリ可能)

// Origin Request: A/B テスト用 Origin パス切替例
exports.handler = async (event) => {
  const request = event.Records[0].cf.request;
  const headers = request.headers;

  const cookieHeader = headers['cookie'];
  const isVariantB = cookieHeader &&
 cookieHeader[0].value.includes('experiment=B');

  if (isVariantB) {
 request.uri = request.uri.replace('/v1/', '/v2/');
  }

  return request;
};

Origin Response — Origin→CloudFront / レスポンス変換・エラーカスタマイズ

実行タイミング: Origin からレスポンスを受信した後、CloudFront がキャッシュに書き込む

... Origin → [Origin Response Lambda] → CloudFront キャッシュ書込 → ...

Origin が返したエラーレスポンス(4xx/5xx)を キャッシュに書き込まれる前に書換 できる点が最大の強み。
エラーレスポンスを正常レスポンスに変換することで、ネガティブキャッシュの生成を防ぐことも可能。

主な用途:
– Origin が返す 404 エラーを独自のエラーページに差し替え
– レスポンスヘッダの変換・追加(Cache-Control の上書き)
– S3 からの XML エラーレスポンスを JSON 形式に変換
– Origin のレスポンスボディを書き換えてコンテンツを動的加工

// Origin Response: 404 エラーページのカスタマイズ例
exports.handler = async (event) => {
  const response = event.Records[0].cf.response;

  if (response.status === '404') {
 return {
status: '200',
statusDescription: 'OK',
headers: {
  'content-type': [{ key: 'Content-Type', value: 'text/html; charset=utf-8' }],
  'cache-control': [{ key: 'Cache-Control', value: 'max-age=60' }],
},
body: '<html><body><h1>ページが見つかりません</h1></body></html>',
 };
  }

  return response;
};

Viewer Response — CloudFront→ユーザー / セキュリティヘッダ追加

実行タイミング: CloudFront がユーザーへレスポンスを返す 直前(キャッシュ読み出し後)。

... CloudFront キャッシュ読出 → [Viewer Response Lambda] → ユーザー

キャッシュの有無に関わらず 毎レスポンスで実行される。Viewer Request と同様にリクエスト数に比例してコストが発生する。
レスポンスボディの変更は不可(ヘッダ操作のみ)。

主な用途:
– セキュリティヘッダの付与(Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options
– Cookie の属性変更(Secure; HttpOnly フラグ追加)
– CORS ヘッダの動的付与

// Viewer Response: セキュリティヘッダ一括付与例
exports.handler = async (event) => {
  const response = event.Records[0].cf.response;
  const headers = response.headers;

  headers['strict-transport-security'] = [{
 key: 'Strict-Transport-Security',
 value: 'max-age=63072000; includeSubDomains; preload',
  }];
  headers['x-content-type-options'] = [{ key: 'X-Content-Type-Options', value: 'nosniff' }];
  headers['x-frame-options'] = [{ key: 'X-Frame-Options', value: 'DENY' }];
  headers['referrer-policy'] = [{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }];

  return response;
};

3-2. Regional execution — us-east-1 デプロイと自動レプリケーション

Lambda@Edge の関数作成・デプロイは us-east-1(バージニア北部)のみ で行う必要がある。
これは CloudFront のコントロールプレーンが us-east-1 に集中しているためである。

デプロイフロー:
開発者 → us-east-1 に Lambda 関数作成 → バージョン発行
→ CloudFront Distribution に関連付け
→ AWS が自動的に全エッジロケーション(450箇所以上)にレプリケート

重要な制約:
– us-east-1 以外のリージョンで作成した関数は CloudFront に関連付けられない
– Terraform でマルチリージョン構成を組む場合、provider "aws" { alias = "us_east_1" } を明示的に定義する必要がある
– 関数の更新後、全エッジロケーションへの反映には数分〜15分かかる場合がある

ホームリージョンの影響:
Lambda@Edge 関数はユーザーに最も近いエッジロケーションで実行されるが、ログ(CloudWatch Logs)は 実行されたエッジロケーションに対応するリージョン に書き込まれる。
東京で実行された場合は ap-northeast-1 の CloudWatch Logs に、シドニーで実行された場合は ap-southeast-2 に記録される。
これにより、ログの収集・分析が複雑になる点を設計段階で考慮する必要がある。

3-3. コールドスタート対策 — 軽量化・Provisioned Concurrency 制約・回避策

Lambda@Edge はサービス設計上、Provisioned Concurrency(プロビジョニング済み同時実行)が使用できない
通常の Lambda では Provisioned Concurrency でコールドスタートを排除できるが、Lambda@Edge にはこのオプションが存在しない。

コールドスタートの影響範囲:

Trigger実行時間制限コールドスタート影響
Viewer Request5秒大(全体の5秒中1〜2秒消費で深刻)
Viewer Response5秒大(同上)
Origin Request30秒小(余裕があるため影響軽微)
Origin Response30秒小(同上)

軽量化によるコールドスタート短縮策:

  1. 依存ライブラリを最小化: node_modules を含むパッケージを 1MB 以内に抑える。
    aws-sdk は実行環境に含まれているため、バンドルに含める必要はない。

  2. 関数の初期化コストを下げる: ハンドラ外の初期化処理(接続確立、設定読込)は最小限にとどめる。
    外部 API 呼び出しや DB 接続はコールドスタート時に最も重い。

  3. esbuild / webpack でバンドル: ツリーシェイクにより使用していないコードを除去し、ロード時間を短縮。

  4. Viewer trigger には CloudFront Functions を優先検討: サブミリ秒で実行できる CloudFront Functions は JS(ES5.1)のみで外部呼び出し不可だが、URL書換やヘッダ操作はほぼ代替可能(§4で詳解)。

// コールドスタート軽量化: 不要な初期化を避ける例
// NG: ハンドラ外でヘビーな初期化(コールドスタート時に毎回実行)
// const client = new SomeHeavyClient({ ... });

// OK: ハンドラ内で遅延初期化(必要時のみ)
let cachedConfig = null;

exports.handler = async (event) => {
  if (!cachedConfig) {
 cachedConfig = loadLightweightConfig();
  }
  const request = event.Records[0].cf.request;
  return processRequest(request, cachedConfig);
};

3-4. 制限事項 — 本番設計で必ず確認する6項目

Lambda@Edge には通常の Lambda にない独自の制限が存在する。以下を本番設計前に全て確認すること。

制限項目Viewer triggerOrigin trigger
実行時間5秒30秒
メモリ128MB〜10,240MB128MB〜10,240MB
パッケージサイズ(展開後)1MB1MB
ランタイムNode.js のみ (18.x / 20.x)Node.js のみ
VPC 接続不可不可
環境変数不可不可
Provisioned Concurrency不可不可
Layer可(1MB制限内)可(1MB制限内)
レスポンスボディ変更Viewer Request のみ可、Viewer Response は不可

特に注意すべき点:

  • VPC 非対応: RDS / ElastiCache などの VPC 内リソースに Lambda@Edge から直接接続することができない。
    VPC 内リソースへのアクセスが必要な場合は、API Gateway + Lambda(VPC Lambda)を経由する設計が必要。

  • 環境変数非対応: 設定値(API キー、エンドポイント URL 等)をコードに直書きするか、
    CloudFront カスタムヘッダを通じて渡すか、S3 等から起動時に取得するパターンのいずれかを選択する。

  • Node.js のみ: Python・Go・Java など他のランタイムは使用不可。
    既存のビジネスロジックが Python 等で実装されている場合、JavaScript への書き直しが必要になる。

  • 1MB 上限: sharp(画像処理)や重い ORM ライブラリは 1MB 超えが頻発する。
    画像リサイズ等の重い処理は Lambda@Edge には不向きで、別サービス(S3 オブジェクト Lambda 等)への委譲を検討する。

3-5. Lambda@Edge vs CloudFront Functions 選定マトリクス (§4への橋渡し)

Lambda@Edge と CloudFront Functions はどちらも CloudFront エッジで JavaScript を実行するが、
設計哲学が根本的に異なる。

比較軸Lambda@EdgeCloudFront Functions
実行場所エッジロケーションエッジロケーション
トリガー対応4 イベント全てViewer Request / Response のみ
実行時間5秒(Viewer)/ 30秒(Origin)1ms 以内
ランタイムNode.js 18.x / 20.xJavaScript ES5.1(制約あり)
メモリ128MB〜10,240MB2MB
外部ネットワーク呼び出し不可
環境変数不可不可
料金(リクエスト単価)$0.60/100万req + 実行時間課金$0.10/100万req(無料枠2M req/月)
無料枠なし2M req/月

選定フロー:

外部 API 呼び出し / DB アクセスが必要?
  → YES → Lambda@Edge(Origin Request/Response trigger 推奨)

Origin trigger(キャッシュミス時のみ)が必要?
  → YES → Lambda@Edge(Origin Request / Origin Response)

処理がシンプルな URL 書換 / ヘッダ操作のみ?
  → YES → CloudFront Functions(コスト最小・レイテンシ最小)

複雑な認証ロジック(JWT 検証等)が Viewer 段階に必要?
  → YES → Lambda@Edge(Viewer Request)

§4 では CloudFront Functions の詳細実装と、Lambda@Edge との使い分けパターンをさらに掘り下げる。

3-6. Terraform 実装 — Lambda + Version + CloudFront 関連付け

Lambda@Edge の Terraform 実装では、us-east-1 専用の provider 設定関数バージョンの発行が必須となる。

# us-east-1 専用プロバイダ(Lambda@Edge 必須)
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

# Lambda@Edge 実行ロール
resource "aws_iam_role" "lambda_edge_role" {
  name = "lambda-edge-execution-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Action = "sts:AssumeRole"
  Effect = "Allow"
  Principal = {
 Service = [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
 ]
  }
}
 ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_edge_basic" {
  role = aws_iam_role.lambda_edge_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

# Lambda@Edge 関数(us-east-1 プロバイダ必須)
resource "aws_lambda_function" "viewer_request" {
  provider = aws.us_east_1

  filename= "viewer_request.zip"
  function_name = "viewer-request-handler"
  role = aws_iam_role.lambda_edge_role.arn
  handler = "index.handler"
  runtime = "nodejs20.x"
  memory_size= 128
  timeout = 5  # Viewer trigger は最大5秒

  source_code_hash = filebase64sha256("viewer_request.zip")

  publish = true  # Lambda@Edge にはバージョン発行が必須
}

resource "aws_lambda_function" "origin_request" {
  provider = aws.us_east_1

  filename= "origin_request.zip"
  function_name = "origin-request-handler"
  role = aws_iam_role.lambda_edge_role.arn
  handler = "index.handler"
  runtime = "nodejs20.x"
  memory_size= 256
  timeout = 30  # Origin trigger は最大30秒

  source_code_hash = filebase64sha256("origin_request.zip")

  publish = true
}

# CloudFront Distribution に Lambda@Edge を関連付け
resource "aws_cloudfront_distribution" "main" {
  # ... origin / cache_behavior 設定は §2 参照

  default_cache_behavior {
 allowed_methods  = ["GET", "HEAD", "OPTIONS"]
 cached_methods= ["GET", "HEAD"]
 target_origin_id = "primary-origin"

 forwarded_values {
query_string = false
cookies { forward = "none" }
 }

 viewer_protocol_policy = "redirect-to-https"
 min_ttl = 0
 default_ttl= 86400
 max_ttl = 31536000

 # Viewer Request に Lambda@Edge を関連付け
 lambda_function_association {
event_type= "viewer-request"
lambda_arn= aws_lambda_function.viewer_request.qualified_arn  # バージョン ARN 必須
include_body = false
 }

 # Origin Request に Lambda@Edge を関連付け
 lambda_function_association {
event_type= "origin-request"
lambda_arn= aws_lambda_function.origin_request.qualified_arn
include_body = false
 }
  }

  viewer_certificate {
 acm_certificate_arn= var.acm_certificate_arn  # us-east-1 の ACM 証明書必須
 ssl_support_method = "sni-only"
 minimum_protocol_version = "TLSv1.2_2021"
  }

  restrictions {
 geo_restriction { restriction_type = "none" }
  }

  enabled = true
}

Terraform 実装の重要ポイント:

  1. publish = true が必須: Lambda@Edge は公開済みバージョンのみ CloudFront に関連付けられる。$LATEST は使用不可。
  2. qualified_arn を使用: aws_lambda_function.xxx.qualified_arn はバージョン番号付きの ARN を返す。
    arn だけでは $LATEST を指し、lambda_function_association でエラーになる。
  3. ACM 証明書は us-east-1: CloudFront で HTTPS を使用する場合、ACM 証明書も us-east-1 に作成する必要がある。
Lambda@Edge 落とし穴 — 本番で踏む3大失敗

  • Viewer trigger のコールドスタートで TTFB 劣化: 認証ロジックを Viewer Request に実装したところ、コールドスタート時に 2〜3 秒の遅延が発生。5秒制限に近づきタイムアウト寸前に。軽量な CloudFront Functions への切替か、Origin Request への移動を検討する。
  • us-east-1 以外のリージョンでデプロイしようとして失敗: Terraform で provider alias を指定し忘れ、デフォルトリージョン(例: ap-northeast-1)に関数が作成される。CloudFront への関連付け時に「関数が us-east-1 に存在しない」エラーで詰まる。
  • Layer / 環境変数 / VPC 制約を設計段階で見落とし: 既存 Lambda 関数を Lambda@Edge に移植しようとしたところ、VPC 接続・環境変数・Python ランタイムを使っており、全て書き直しが必要になった。Lambda@Edge への移植前に必ず制限を確認する。
Lambda@Edge コスト注意 — Viewer Request は全リクエスト課金

  • Viewer Request は全リクエストに課金される: キャッシュヒットの有無に関わらず実行されるため、月間1億リクエストのサイトでは Lambda@Edge だけで$60以上の実行コストが発生する(実行時間課金を除く)。
  • Origin Request はキャッシュミス時のみ: キャッシュヒット率 80% のサイトなら、Viewer Request の 1/5 のリクエスト数で済む。認証ロジックを Origin Request に移せないか検討する価値がある。
  • CloudFront Functions への置換でコスト削減: URL 書換やヘッダ操作のみなら、CloudFront Functions($0.10/100万req、無料枠2M/月)に置換することで Lambda@Edge($0.60/100万req)比 1/6 のコストに削減できる。
  • ログのマルチリージョン分散: CloudWatch Logs が実行リージョンに分散するため、ログ集約コスト(Lambda + Firehose 等)が別途発生する点も見積もりに含めること。

4. CloudFront Functions本番運用 — Sub-millisecond × Viewer専用

CloudFront Functions は 2021年5月に AWS が一般提供を開始した、エッジロケーション上で動作する軽量 JavaScript 実行エンジンである。Lambda@Edge と並ぶ Edge Logic の実行環境だが、設計思想は根本的に異なる。「1ms 以内で完結できる処理しか書かせない」 というハードな制約を課す代わりに、sub-millisecond の超低レイテンシと圧倒的なコスト効率を実現する。

本番設計における最重要判断は「CloudFront Functions か Lambda@Edge か」の選定である。この判断を誤ると、コスト超過や機能不足、あるいは不要なアーキテクチャ複雑化を招く。


4-1. CloudFront Functions とは — Viewer専用 × ES5.1 × 2MB制約

CloudFront Functions の実行環境は以下の仕様で固定される。

サポートイベント (Viewer 専用)

CloudFront Functions が処理できるのは viewer-requestviewer-response の2イベントのみである。Origin に到達する前後のリクエスト/レスポンス加工、すなわち Origin Request / Origin Response イベントには対応しない。これは制約ではなく設計思想である。Origin に到達する前に処理を完結させることで、コールドスタートをほぼゼロに抑えている。

JavaScript ランタイム (ES5.1 固定)

ランタイムは JavaScript ES5.1 で固定される。ES6 以降の構文は利用できない。

// ES6 構文は全て使用不可
// let, const, arrow function, template literal, async/await, Promise, class, etc.

// 正しい ES5.1 記法
function handler(event) {
  var request = event.request;
  var uri = request.uri;
  return request;
}

リソース上限

リソース上限値
CPU 時間1ms
メモリ2MB
コードサイズ10KB (gzip 圧縮後)
ネットワーク呼び出し不可
ファイルシステム不可

料金

  • 無料枠: 2,000,000 リクエスト/月
  • 有料: $0.10 / 1,000,000 リクエスト
  • Lambda@Edge との比較: 約 1/6 のコスト

4-2. Lambda@Edge との使い分けマトリクス (★本番設計の核心)

CloudFront Functions と Lambda@Edge の選定は、以下のマトリクスで判断する。

比較軸CloudFront FunctionsLambda@Edge
対応イベントViewer Request / Response のみViewer + Origin Request / Response (4イベント)
実行時間上限1msViewer: 5秒 / Origin: 30秒
メモリ上限2MB128MB〜10,240MB
ランタイムJavaScript ES5.1 固定Node.js / Python (複数バージョン)
外部ネットワーク不可可 (外部 API / DB 呼び出し)
環境変数不可 (コード内ハードコード)不可 (SSM Parameter Store 代替推奨)
VPC アクセス不可不可 (Lambda@Edge 固有制約)
無料枠2M リクエスト/月なし
コスト低 (Lambda@Edge の約 1/6)
主要ユースケースURL 書換・ヘッダ操作・簡易認証複雑ロジック・A/B テスト・Origin 連携
CloudFront Functions vs Lambda@Edge 選定フローチャート

  • Step 1: Origin Request/Response イベントが必要か?
    → 必要 → Lambda@Edge 一択 (CloudFront Functions は Viewer 専用)
  • Step 2: 外部 API / DB / ネットワーク呼び出しが必要か?
    → 必要 → Lambda@Edge (CloudFront Functions はネットワーク不可)
  • Step 3: 実行ロジックが 1ms 以内に完結するか?
    → 完結しない (文字列処理以上の計算) → Lambda@Edge
  • Step 4: ES5.1 のみで実装できるか?
    → ES6+ 構文が必要 → Lambda@Edge
  • Step 5: 上記すべて「不要・完結・可能」なら → CloudFront Functions
    コスト効率・レイテンシ・シンプルさで優れる

4-3. 主要ユースケース実装

URL 書換・正規化 (trailing slash / www 有無)

function handler(event) {
  var request = event.request;
  var uri = request.uri;

  // trailing slash を除去して正規化 (/path/ → /path)
  if (uri !== '/' && uri.endsWith('/')) {
 return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
  location: { value: uri.slice(0, -1) }
}
 };
  }

  // /index.html を除去 (/path/index.html → /path/)
  if (uri.endsWith('/index.html')) {
 return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
  location: { value: uri.slice(0, -10) }
}
 };
  }

  return request;
}

Basic 認証 (簡易アクセス制限)

function handler(event) {
  var request = event.request;
  var authHeader = request.headers['authorization'];

  // Base64("user:password") を事前計算してコード内に埋め込む
  var expectedAuth = 'Basic dXNlcjpwYXNzd29yZA==';

  if (!authHeader || authHeader.value !== expectedAuth) {
 return {
statusCode: 401,
statusDescription: 'Unauthorized',
headers: {
  'www-authenticate': { value: 'Basic realm="Restricted"' }
}
 };
  }

  return request;
}

本番注意: Basic 認証の認証情報はコード内ハードコードになる (環境変数不可)。機密度の高い認証には Lambda@Edge + Secrets Manager 連携を検討すること。

カスタムヘッダ注入 (Origin への情報付与)

function handler(event) {
  var request = event.request;

  // Origin に渡す識別ヘッダを注入 (Viewer Request イベントでのみ有効)
  request.headers['x-forwarded-env'] = { value: 'production' };
  request.headers['x-request-id'] = { value: event.context.requestId };

  return request;
}

301/302 リダイレクト (旧 URL → 新 URL)

function handler(event) {
  var request = event.request;
  var uri = request.uri;

  // 旧パスから新パスへの恒久リダイレクト
  var redirectMap = {
 '/old-path': '/new-path',
 '/blog': '/articles',
 '/docs': '/documentation'
  };

  if (redirectMap[uri]) {
 return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
  location: { value: redirectMap[uri] }
}
 };
  }

  return request;
}

4-4. Runtime 制限と落とし穴

CloudFront Functions Runtime 制限による落とし穴

  • async/await 不可 (ES5.1 制約)
    非同期処理は構文エラーになる。外部 API 呼び出しが必要な場合は Lambda@Edge へ移行する。
  • fetch() / XMLHttpRequest 不可
    ネットワーク呼び出しはランタイムレベルで禁止。JWT の公開鍵取得など「外部リソース参照」を伴う認証は不可。
  • require() / import 不可
    モジュールシステムなし。依存ライブラリはすべてコード内にインライン展開する必要がある。コードサイズ上限 10KB に注意。
  • console.log は CloudWatch に記録されない
    デバッグログは CloudFront 標準ログまたは Real-time Logs で確認する。console.log の内容は破棄される。
  • JSON.parse() は 10KB のコード制限内で動く
    大きな JSON マッピングテーブルをコード内に持つとサイズ上限に抵触する。リダイレクトマップは20件程度を目安に。
  • テスト環境は CloudFront コンソールの “Test function” を活用
    ローカル実行環境がないため、コンソール上のテスト機能でイベントオブジェクトを模擬して動作確認する。

4-5. Terraform 実装 (aws_cloudfront_function + Distribution 関連付け)

CloudFront Functions リソース定義

# functions/url_rewrite.js を別ファイルで管理する構成
resource "aws_cloudfront_function" "url_rewrite" {
  name = "${var.project}-url-rewrite"
  runtime = "cloudfront-js-1.0"
  comment = "URL trailing slash normalization and /index.html removal"
  publish = true

  # コードはファイル参照で管理 (インラインは可読性が低い)
  code = file("${path.module}/functions/url_rewrite.js")
}

resource "aws_cloudfront_function" "basic_auth" {
  name = "${var.project}-basic-auth"
  runtime = "cloudfront-js-1.0"
  comment = "Simple Basic Auth for staging environment"
  publish = true

  code = file("${path.module}/functions/basic_auth.js")
}

Distribution への関連付け

resource "aws_cloudfront_distribution" "main" {
  enabled = true
  is_ipv6_enabled  = true
  http_version  = "http3"
  price_class= "PriceClass_All"
  default_root_object = "index.html"

  origin {
 domain_name  = aws_s3_bucket.content.bucket_regional_domain_name
 origin_id = "s3-content"
 origin_access_control_id = aws_cloudfront_origin_access_control.main.id
  }

  default_cache_behavior {
 target_origin_id = "s3-content"
 viewer_protocol_policy = "redirect-to-https"
 cached_methods= ["GET", "HEAD"]
 allowed_methods  = ["GET", "HEAD"]

 cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
 origin_request_policy_id = data.aws_cloudfront_origin_request_policy.cors_s3.id

 compress = true

 # CloudFront Functions の関連付け
 function_association {
event_type= "viewer-request"
function_arn = aws_cloudfront_function.url_rewrite.arn
 }

 # Viewer Response にも関連付け可能 (同時に最大2つ: Viewer Request + Viewer Response)
 # function_association {
 #event_type= "viewer-response"
 #function_arn = aws_cloudfront_function.security_headers.arn
 # }
  }

  # ステージング用 Behavior (パスパターンで Basic Auth を適用)
  ordered_cache_behavior {
 path_pattern  = "/staging/*"
 target_origin_id = "s3-content"
 viewer_protocol_policy = "https-only"
 cached_methods= ["GET", "HEAD"]
 allowed_methods  = ["GET", "HEAD"]

 cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
 compress  = true

 function_association {
event_type= "viewer-request"
function_arn = aws_cloudfront_function.basic_auth.arn
 }
  }

  restrictions {
 geo_restriction {
restriction_type = "none"
 }
  }

  viewer_certificate {
 acm_certificate_arn= aws_acm_certificate.main.arn
 ssl_support_method = "sni-only"
 minimum_protocol_version = "TLSv1.2_2021"
  }

  tags = var.common_tags
}

Functions のバージョン管理と切り替え

# publish = false でステージング確認後に true に変更してデプロイ
resource "aws_cloudfront_function" "url_rewrite_v2" {
  name = "${var.project}-url-rewrite-v2"
  runtime = "cloudfront-js-1.0"
  comment = "URL rewrite v2 - added /api prefix handling"

  # publish = false でコンソール上の Test function でテスト可能
  publish = true

  code = file("${path.module}/functions/url_rewrite_v2.js")
}

# Distribution の function_arn を v2 に切り替えるだけでカットオーバー

本番デプロイ手順: Terraform で publish = true のまま apply → CloudFront コンソールで “Test function” → 問題なければ Distribution の function_arn を新バージョンに差し替え → apply でカットオーバー。


4-6. CloudFront Functions 本番運用のベストプラクティス

1. コード管理は必ずファイル分離

code = file(...) でコードファイルを別管理する。インライン HCL は可読性が低く、テストが困難になる。JavaScript ファイルは functions/ ディレクトリにまとめて Git 管理する。

2. コンソールの “Test function” を CI に組み込む

AWS CLI でテストイベントを実行できる。

aws cloudfront test-function \
  --name "${PROJECT}-url-rewrite" \
  --if-match "$(aws cloudfront describe-function --name ${PROJECT}-url-rewrite --query 'ETag' --output text)" \
  --event-object fileb://tests/viewer-request-test.json \
  --stage LIVE

3. 1 Behavior に同種イベントは 1 Function のみ

viewer-requestviewer-response はそれぞれ 1 つしか関連付けられない。URL 書換と Basic 認証を同時に適用したい場合は、1 つの Function にロジックをまとめる。

4. CloudFront Functions と Lambda@Edge の混在

同一 Behavior に CloudFront Functions (Viewer Request) と Lambda@Edge (Origin Request) を併用できる。Viewer 側は CloudFront Functions で軽量処理、Origin 側は Lambda@Edge で重い処理という分担が可能である。

default_cache_behavior {
  # CloudFront Functions: Viewer Request (URL正規化)
  function_association {
 event_type= "viewer-request"
 function_arn = aws_cloudfront_function.url_rewrite.arn
  }

  # Lambda@Edge: Origin Request (動的コンテンツ生成)
  lambda_function_association {
 event_type= "origin-request"
 lambda_arn= aws_lambda_function.dynamic_render.qualified_arn
 include_body = false
  }
}

5. Global Accelerator本番運用: Anycast IPとAWSバックボーン活用

fig04: Global Accelerator Anycast IP routing と CloudFront との使い分け

5-1. CloudFront vs Global Accelerator — 用途の決定的差異

Global Accelerator と CloudFront は「AWS のグローバルネットワークを使うサービス」という点で混同されやすいが、動作レイヤーと用途が根本から異なる。

観点CloudFrontGlobal Accelerator
対象プロトコルHTTP / HTTPS (L7)TCP / UDP (L4)
キャッシュあり (Edge PoP でキャッシュ)なし (全リクエスト Origin へ転送)
IP アドレス動的 (Anycast 共有)静的 Anycast IP × 2 固定
TLS 終端Edge PoP (CloudFront 側)Origin 側 (GA は透過)
主な用途Web サイト / API / 画像配信ゲーム / IoT / 金融 API / VoIP
コスト構造リクエスト + データ転送固定 $0.025/時間 + データ転送

判断基準は「キャッシュが効くか・静的 IP が必要か」の2点に集約される。HTTP ワークロードで Origin 負荷を下げたいなら CloudFront、UDP 対応や固定 IP 要件があるなら Global Accelerator を選ぶ。

5-2. Global Accelerator のアーキテクチャ設計

Anycast IP と AWSグローバルネットワーク

Global Accelerator は2つの静的 Anycast IP をインターネット公告し、クライアントは最近傍の AWS Edge PoP に接続する。その後は AWS バックボーン (低レイテンシ・高帯域の専用線) を経由して Origin リージョンまで転送する。

クライアント
  → (インターネット: 最短経路のみ)
  → AWS Edge PoP (Anycast IP 着信)
  → AWSバックボーン (専用線: 低ジッター・高信頼)
  → Endpoint (ALB / NLB / EC2 / Elastic IP)

パブリックインターネットを経由する区間を最小化することで、レイテンシ変動 (ジッター) を大幅に抑制できる。ゲームや VoIP など遅延感度の高いワークロードで効果が顕著。

Listener と Endpoint Group

Global Accelerator の構成要素は3層:

  1. Accelerator — エントリポイント。Anycast IP 2つを保有。
  2. Listener — プロトコル (TCP/UDP) + ポート範囲を定義。
  3. Endpoint Group — リージョン単位。Traffic Dial と Endpoint Weight を持つ。
Accelerator (Anycast IP: 1.2.3.4 / 5.6.7.8)
  └── Listener (TCP:443)
  ├── Endpoint Group: ap-northeast-1 (Traffic Dial=100%)
  │  ├── ALB-Tokyo (Weight=128)
  │  └── ALB-Osaka (Weight=128)
  └── Endpoint Group: us-east-1 (Traffic Dial=0%)  ← 平常時は0%
  └── ALB-Virginia (Weight=128)

Traffic Dial — 段階的フェイルオーバーとカナリアデプロイ

Traffic Dial (0〜100%) はエンドポイントグループ単位でトラフィック割合を調整する機能。リージョン間フェイルオーバーとカナリアデプロイの両方に使える点が特徴。

  • 段階的移行: ap-northeast-1 を 100→90→50→0 と段階的に下げながら us-east-1 を引き上げ
  • 障害時自動切換: Endpoint のヘルスチェックが失敗すると Traffic Dial に関係なく自動で他リージョンへ切換
  • カナリア: 新リージョンに Traffic Dial=10% を当て、問題なければ引き上げ

5-3. 用途別ユースケース

ゲーム・IoT (UDP 対応)

CloudFront は HTTP 専用のため UDP ゲームサーバーや MQTT ブローカーには対応できない。Global Accelerator は UDP を L4 レベルで透過転送するため、ゲームセッションの確立とパケットロス最小化を同時に実現できる。

  • ゲームサーバー (EC2 / ECS): UDP/TCP をそのまま転送
  • IoT MQTT (Port 8883): 固定 IP のホワイトリスト登録が可能

金融系 (固定 IP 要件)

金融機関との API 連携では「接続元 IP を事前申告しホワイトリスト登録する」要件が多い。CloudFront の Anycast IP は動的に変わるが、Global Accelerator の Anycast IP は Accelerator が存在する限り変わらない

  • 接続元 IP 固定が必要な API 連携
  • 規制要件 (PCI DSS 等) でネットワーク境界を IP で定義する場合

マルチリージョン可用性 (リージョン障害時の自動切換)

Global Accelerator のヘルスチェックは Endpoint (ALB / NLB / EC2) 単位で行われ、障害検知から数十秒以内に別リージョンの Endpoint Group へ自動切換する。Route53 Failover Routing との比較:

観点Global AcceleratorRoute53 Failover
切換速度~30秒 (ヘルスチェック連動)TTL 依存 (最短 60秒〜)
TTL の影響受けない (Anycast IP 固定)受ける (クライアントキャッシュ)
対象プロトコルTCP / UDPHTTP / HTTPS / TCP
コスト高 (固定料金あり)低 (クエリ課金のみ)

Direct Connect 連携 (ハイブリッド環境)

オンプレミス側の ALB や NLB を Global Accelerator の Endpoint として登録することはできないが、オンプレ → AWS バックボーン → Origin の経路設計で Direct Connect と組み合わせることで、ハイブリッド環境への低レイテンシ配信を実現できる。

5-4. Terraform 実装

# Global Accelerator 本体
resource "aws_globalaccelerator_accelerator" "main" {
  name= "production-accelerator"
  ip_address_type = "IPV4"
  enabled= true

  attributes {
 flow_logs_enabled= true
 flow_logs_s3_bucket = aws_s3_bucket.ga_logs.bucket
 flow_logs_s3_prefix = "ga-flow-logs/"
  }
}

# Listener (TCP:443)
resource "aws_globalaccelerator_listener" "https" {
  accelerator_arn = aws_globalaccelerator_accelerator.main.id
  protocol  = "TCP"

  port_range {
 from_port = 443
 to_port= 443
  }
}

# Endpoint Group: ap-northeast-1 (Primary)
resource "aws_globalaccelerator_endpoint_group" "tokyo" {
  listener_arn = aws_globalaccelerator_listener.https.id
  endpoint_group_region = "ap-northeast-1"
  traffic_dial_percentage = 100

  health_check_path = "/health"
  health_check_port = 443
  health_check_protocol= "HTTPS"
  health_check_interval_seconds = 30
  threshold_count= 3

  endpoint_configuration {
 endpoint_id = aws_lb.tokyo_alb.arn
 weight= 128
  }
}

# Endpoint Group: us-east-1 (Standby)
resource "aws_globalaccelerator_endpoint_group" "virginia" {
  listener_arn = aws_globalaccelerator_listener.https.id
  endpoint_group_region = "us-east-1"
  traffic_dial_percentage = 0  # 平常時は0%、障害時は自動切換

  health_check_path = "/health"
  health_check_port = 443
  health_check_protocol= "HTTPS"
  health_check_interval_seconds = 30
  threshold_count= 3

  endpoint_configuration {
 endpoint_id = aws_lb.virginia_alb.arn
 weight= 128
  }
}
Global Accelerator コスト警戒: 固定料金の罠

  • 固定料金: $0.025/時間 × Accelerator数。月額 ~$18/台。使わなくても課金が続く。
  • データ転送料金: CloudFront より高め。大容量ファイル配信 (画像・動画) には不向き。
  • HTTPワークロードに使うな: キャッシュが効く HTTP は CloudFront の方がコスト・性能ともに優位。
  • 不要になったら即削除: 開発環境で検証後に消し忘れると固定料金が積み上がる。
Global Accelerator vs CloudFront 混同パターン: 最多ミス4選

  • HTTP API に GA を使う: L4 透過転送のためキャッシュ不可。CloudFront + Cache Policy で Origin 負荷を下げるべき。
  • 静的ファイル配信に GA を使う: 画像・動画は CloudFront が正解。GA はキャッシュなしで全量 Origin へ。
  • GA の IP を CloudFront の IP と混同: CloudFront の IP は動的 (BGP Anycast 共有)。GA の Anycast IP のみが固定保証。
  • コスト試算をせずに導入: Traffic Dial=0% でも固定料金が発生する。PoC 後に要件を再精査すること。

6. Route53本番運用: Routing Policy 7種完全マスター

flowchart TD
 A[Route53 Routing Policy 選定開始] --> B{複数エンドポイントが必要か?}
 B -- No --> C[Simple Routing\n単一エンドポイント]
 B -- Yes --> D{フェイルオーバー/HA が必要か?}
 D -- Yes --> E{地理情報を考慮するか?}
 E -- No --> F[Failover Routing\nActive/Passive HA]
 E -- Yes --> G{地域別コンテンツ配信か?}
 G -- Yes --> H[Geolocation Routing\n地域別コンテンツ]
 G -- No --> I[Geoproximity Routing\n地理的近接性 + Traffic Flow]
 D -- No --> J{レイテンシ最小化が優先か?}
 J -- Yes --> K[Latency-based Routing\n最速リージョン選択]
 J -- No --> L{A/Bテスト・カナリアか?}
 L -- Yes --> M[Weighted Routing\n重み付けトラフィック分散]
 L -- No --> N{ヘルスチェック付き簡易分散?}
 N -- Yes --> O[Multivalue Answer Routing\nDNSラウンドロビン+ヘルスチェック]
 N -- No --> C
 style C fill:#e8f5e9
 style F fill:#ffebee
 style H fill:#e3f2fd
 style I fill:#e3f2fd
 style K fill:#fff3e0
 style M fill:#f3e5f5
 style O fill:#fce4ec

6-1. Routing Policy 7種 詳解

Simple Routing — 単一エンドポイント・最シンプル

最も基本的なポリシー。1つの DNS レコードに1つ (または複数) の値を設定する。ヘルスチェックとの連動はない。

  • 用途: 単一 Origin へ全トラフィックを向ける。社内ツール・検証環境など HA 不要の場合。
  • 注意: 複数 IP を返す場合はランダム選択だが、ヘルスチェックなしのため障害時も有効 IP として返し続ける。

Weighted Routing — A/Bテスト・カナリアデプロイ

各レコードに 0〜255 の重みを割り当て、重みの比率でトラフィックを分散する。

レコードA (Weight=90) → Production v1 (90%)
レコードB (Weight=10) → Production v2 (10%: カナリア)
  • A/B テスト: 2つのエンドポイントに異なる重みを付けてユーザー体験を比較
  • カナリアデプロイ: 新バージョンに 5〜10% を当て、エラー率を監視しながら引き上げ
  • Weight=0: そのレコードへのトラフィックをゼロにしつつレコードを残せる (本番デプロイ完了後に削除前の安全策として使用)

Latency-based Routing — レイテンシ最小化

クライアントのソース IP から最もレイテンシが低い AWS リージョンのエンドポイントを返す。

  • マルチリージョン Active-Active の基本構成
  • リージョン障害時に Failover と組み合わせることで可用性を担保
  • 「物理的に近い = 速い」ではなく「AWSが測定したレイテンシが低い」リージョンを選ぶ

Failover Routing — Active/Passive HA

Primary レコードのヘルスチェックが失敗すると自動で Secondary に切り替わる。

  • Primary: 通常時の本番エンドポイント (必ずヘルスチェックを設定すること)
  • Secondary: スタンバイエンドポイント (最小限の構成でよい)
  • 復旧後の切り戻し: Primary ヘルスチェックが回復すると自動で Primary に戻る
  • 注意: Secondary にもヘルスチェックを設定しないと、Primary 障害中に Secondary が無効でも返し続ける

Geolocation Routing — 地域別コンテンツ配信

クライアントの IP から地理情報 (国・大陸) を判定し、対応するエンドポイントを返す。

日本 (JP) → ap-northeast-1 の ALB (日本語コンテンツ)
米国 (US) → us-east-1 の ALB (英語コンテンツ)
Default→ eu-west-1 の ALB (グローバルフォールバック)
  • 法規制対応: 特定国から特定エンドポイントへのアクセスを強制する (GDPR データ居住要件など)
  • Default レコード必須: 判定不能な IP (衛星回線・プロキシ等) のフォールバックがないと NXDOMAIN になる

Geoproximity Routing — 地理的近接性 + Traffic Flow

エンドポイントの地理的位置とバイアス値 (-99〜+99) でトラフィックを制御する。Traffic Flow (視覚的ポリシーエディター) が必要。

  • バイアス +値: そのエンドポイントへ引き寄せるトラフィック範囲を拡大
  • バイアス -値: 縮小 (他エンドポイントに流す)
  • Latency Routing との違い: 地理的範囲を細かく制御したい場合 に使う (Latency Routing はレイテンシ計測値優先)

Multivalue Answer Routing — 擬似ロードバランシング

最大8つのエンドポイントにランダム選択で応答し、ヘルスチェックで正常なエンドポイントのみ返す。

  • 本物の LB ではない: DNS クライアントがランダムに選ぶだけであり、接続数・重みの制御はない
  • 用途: ALB/NLB を使えない場合のシンプルな分散 (EC2 直接参照など)
  • 8台制限: 8エンドポイントを超える場合は ALB を使うべき

6-2. Health Check 設計

Route53 の Health Check は3形態:

形態対象用途
エンドポイント監視HTTP / HTTPS / TCPALB・EC2 の死活監視
Calculated Health Check他のヘルスチェックの論理結合複数コンポーネントの AND/OR 条件
CloudWatch アラームCloudWatch メトリクスDB 接続数・キューサイズ等の状態連動

エンドポイント監視の設計ポイント

チェック間隔: 30秒 (標準) / 10秒 (高速: 追加料金)
失敗閾値: 3回連続失敗 → Unhealthy (デフォルト)
復旧閾値: 3回連続成功 → Healthy
  • パスの選択: /health はアプリケーション層の依存関係 (DB・キャッシュ) を確認するエンドポイントを用意
  • String matching: レスポンスボディに特定文字列 (OK など) が含まれるか検証可能
  • CloudWatch 連携: ヘルスチェック結果を CloudWatch メトリクスとして記録し、アラーム → SNS 通知

Calculated Health Check — 複合条件

Calculated HC (AND): HC-Web AND HC-DB AND HC-Cache
→ 3つすべて Healthy の時のみ Primary として動作

データベースや外部 API が障害の場合、Web 自体は起動していても「サービスとして不健全」と判断してフェイルオーバーできる。

6-3. DNSSEC 設計

DNSSEC は DNS レスポンスを暗号署名し、DNS キャッシュ汚染 (キャッシュポイズニング) 攻撃を防ぐ仕組み。

Route53 での DNSSEC 有効化手順

  1. Route53 で Enable DNSSEC signing → KMS CMK (非対称 RSA_2048) が自動生成
  2. DS レコードをレジストラ (Route53 Registrar 等) に登録 — このステップが最重要
  3. 24〜48 時間後に上位ゾーンへの信頼チェーン確立を確認
クライアント
  → .com ゾーン (DS レコードで信頼チェーン確認)
  → example.com ゾーン (KMS で署名された RRSIG 検証)
  → 応答 (改ざんなし確認)

DS レコード登録を忘れると: DNSSEC 有効化しても信頼チェーンが断絶し、DNSSEC バリデーターで名前解決が失敗するケースがある。

Route53 Resolver — VPC内DNSとハイブリッドDNS

コンポーネント方向用途
Inbound Endpointオンプレ → VPCオンプレの DNS リゾルバーから VPC 内リソース名を解決
Outbound EndpointVPC → オンプレVPC 内から オンプレの DNS ドメインを解決
Resolver RulesVPC → 特定ドメイン特定ドメインのクエリを指定 DNS サーバーへ転送
# Inbound Endpoint (オンプレ → VPC)
resource "aws_route53_resolver_endpoint" "inbound" {
  name= "inbound-endpoint"
  direction = "INBOUND"

  security_group_ids = [aws_security_group.resolver.id]

  ip_address {
 subnet_id = aws_subnet.private_a.id
  }
  ip_address {
 subnet_id = aws_subnet.private_b.id
  }
}

6-4. Terraform 実装

# Hosted Zone
data "aws_route53_zone" "main" {
  name = "example.com."
}

# Health Check
resource "aws_route53_health_check" "primary" {
  fqdn  = "primary.example.com"
  port  = 443
  type  = "HTTPS"
  resource_path  = "/health"
  failure_threshold = "3"
  request_interval  = "30"

  tags = {
 Name = "primary-health-check"
  }
}

resource "aws_route53_health_check" "secondary" {
  fqdn  = "secondary.example.com"
  port  = 443
  type  = "HTTPS"
  resource_path  = "/health"
  failure_threshold = "3"
  request_interval  = "30"

  tags = {
 Name = "secondary-health-check"
  }
}

# Failover Routing: Primary
resource "aws_route53_record" "primary" {
  zone_id = data.aws_route53_zone.main.zone_id
  name = "api.example.com"
  type = "A"

  failover_routing_policy {
 type = "PRIMARY"
  }

  alias {
 name = aws_lb.primary.dns_name
 zone_id = aws_lb.primary.zone_id
 evaluate_target_health = true
  }

  set_identifier  = "primary"
  health_check_id = aws_route53_health_check.primary.id
}

# Failover Routing: Secondary
resource "aws_route53_record" "secondary" {
  zone_id = data.aws_route53_zone.main.zone_id
  name = "api.example.com"
  type = "A"

  failover_routing_policy {
 type = "SECONDARY"
  }

  alias {
 name = aws_lb.secondary.dns_name
 zone_id = aws_lb.secondary.zone_id
 evaluate_target_health = true
  }

  set_identifier  = "secondary"
  health_check_id = aws_route53_health_check.secondary.id
}

# Weighted Routing: Canary (v2 = 10%)
resource "aws_route53_record" "canary" {
  zone_id = data.aws_route53_zone.main.zone_id
  name = "app.example.com"
  type = "A"

  weighted_routing_policy {
 weight = 10
  }

  alias {
 name = aws_lb.canary.dns_name
 zone_id = aws_lb.canary.zone_id
 evaluate_target_health = true
  }

  set_identifier = "canary-v2"
}

resource "aws_route53_record" "production" {
  zone_id = data.aws_route53_zone.main.zone_id
  name = "app.example.com"
  type = "A"

  weighted_routing_policy {
 weight = 90
  }

  alias {
 name = aws_lb.production.dns_name
 zone_id = aws_lb.production.zone_id
 evaluate_target_health = true
  }

  set_identifier = "production-v1"
}

# DNSSEC: KMS Key
resource "aws_kms_key" "dnssec" {
  provider  = aws.us-east-1  # DNSSEC KMS は us-east-1 必須
  customer_master_key_spec = "ECC_NIST_P256"
  deletion_window_in_days  = 7
  key_usage = "SIGN_VERIFY"

  policy = data.aws_iam_policy_document.dnssec_key.json
}

resource "aws_route53_key_signing_key" "main" {
  hosted_zone_id = data.aws_route53_zone.main.zone_id
  key_management_service_arn = aws_kms_key.dnssec.arn
  name  = "main-ksk"
}

resource "aws_route53_hosted_zone_dnssec" "main" {
  hosted_zone_id = data.aws_route53_zone.main.zone_id
  depends_on  = [aws_route53_key_signing_key.main]
}
Route53 設計ミス7選: 本番で踏んだ落とし穴

  • TTL を長くしすぎる: TTL=3600秒のまま Failover を設定。障害時に1時間クライアントが古い IP をキャッシュし続ける。フェイルオーバー用途は TTL=60秒 が目安。
  • Primary に Health Check を設定しない: Failover Routing で Primary のヘルスチェックを省略すると、Primary が死んでも Secondary に切り替わらない。
  • Multivalue Answer を本物の LB として使う: 接続数・重みの制御はなく DNS ラウンドロビンのみ。高負荷環境では ALB を使うこと。
  • Geolocation の Default レコード漏れ: 判定不能 IP からのクエリが NXDOMAIN になりサービスが落ちる。Default レコードは必須。
  • DNSSEC 有効化で DS レコードをレジストラに登録忘れ: 信頼チェーンが断絶し、DNSSEC バリデーター有効のクライアントで名前解決失敗。
  • Latency Routing + Health Check の組み合わせを省略: Latency Routing だけでは障害リージョンのエンドポイントが返り続ける。必ず Health Check と組み合わせること。
  • Resolver Endpoint の Security Group 設計ミス: Inbound Endpoint はオンプレの DNS リゾルバー IP からのポート 53 (UDP/TCP) を許可する必要がある。デフォルト SG では詰まる。
Weighted + Latency 組み合わせ: マルチリージョン・カナリア戦略

  • 構成: Latency Routing で最速リージョンを選択し、各リージョン内で Weighted Routing (90/10) によりカナリア版に10%を割り当て。
  • 効果: 「ユーザーに最も近いリージョンで新バージョンを10%に当てる」ことで地域別の影響を限定しながら段階展開できる。
  • 実装: Latency レコードに set_identifier + latency_routing_policy を使い、各リージョン・バージョンの組み合わせをユニーク化すること。
  • 監視: カナリア期間中は CloudWatch でエラー率・レイテンシを監視し、異常があれば Weight=0 で即座に切り離す。

7. Edge/CDN本番運用 詰まりポイント7選

CloudFront・Lambda@Edge・CloudFront Functions・Global Accelerator・Route53を本番運用する際に、エンジニアが繰り返しハマる7つの落とし穴を整理する。原因と対策を押さえることで、トラブルシューティング時間を大幅に短縮できる。

詰まり1: CloudFront Cache Miss 連発 — Cache Policy 設定ミスとVaryヘッダ問題

症状: キャッシュヒット率が5%以下でOriginへのリクエストが急増し、Origin負荷が爆増する。

根本原因①: キャッシュキー設計ミス

Cache Policyのheaders/cookies/queryStringsに不要な値を含めるとキャッシュキーが爆発的に増加してCache Missが連発する。特にAuthorizationヘッダをキャッシュキーに含めると、ユーザーごとに異なるキャッシュエントリが生成され実質キャッシュ無効化と同等になる。

resource "aws_cloudfront_cache_policy" "api" {
  name  = "api-cache-policy"
  default_ttl = 300
  max_ttl  = 3600
  min_ttl  = 0
  parameters_in_cache_key_and_forwarded_to_origin {
 cookies_config  { cookie_behavior = "none" }  # NG: "all" → 全Cookie
 headers_config  { header_behavior = "none" }  # NG: "allViewer" → 全Header
 query_strings_config {
query_string_behavior = "whitelist"
query_strings { items = ["version", "lang"] }
 }
  }
}

根本原因②: バックエンドがVaryヘッダを返している

OriginがレスポンスにVaryヘッダを返すと、CloudFrontはそのヘッダ値をキャッシュキーの一部として扱う。Vary: Accept-Encoding以外のVaryは実質キャッシュ無効化になる可能性がある。

対策: Cache PolicyでキャッシュキーをWhitelist最小構成にする。Origin側のVaryヘッダをAccept-Encodingのみに制限する。CloudWatch Metrics CacheHitRateで継続監視する。

詰まり2: Lambda@Edge コールドスタートによるタイムアウト

症状: リクエストが断続的にタイムアウトし、トラフィックの少ない時間帯の初回アクセスでTTFBが5秒超になる。

根本原因: Lambda@EdgeはProvisioned Concurrencyが使用不可である。Viewer Requestトリガーではタイムアウト上限が5秒しかなく、コールドスタートだけで上限に達する場合がある。

対策:
– Viewer RequestトリガーのLambda@Edgeは極限まで軽量化する(パッケージサイズ1MB以下を目標)
– 重いロジック(DBアクセス・外部API呼び出し)はOrigin Requestトリガーに移動(タイムアウト上限30秒)
– コールドスタートが許容できない用途にはLambda@Edgeを使わない設計を優先する

詰まり3: CloudFront Functions で async/await が動かない

症状: CloudFront Functionsにデプロイしたコードでasync functionawaitを使ったところ、構文エラーでリクエスト処理が全失敗する。

根本原因: CloudFront FunctionsのランタイムはJavaScript ES5.1準拠であり、async/await (ES2017)・Promise・アロー関数 (ES6)は使用不可である。

// NG: async/await → CloudFront Functions では動作しない
// OK: ES5.1互換の同期処理のみ
function handler(event) {
  var request = event.request;
  if (request.uri.endsWith('/')) {
 request.uri += 'index.html';
  }
  return request;
}

対策: CloudFront FunctionsはURL書き換え・ヘッダ操作・A/Bテスト振り分けの同期処理に限定する。非同期処理・外部API呼び出しが必要な場合はLambda@Edge (Origin Requestトリガー) に切り替える。

詰まり4: Global Accelerator と CloudFront の混同 — TCP/UDP層 vs HTTP層

症状: HTTP APIのレイテンシ改善目的でGlobal Acceleratorを導入したが、キャッシュ効果もなく期待した性能改善が得られず、固定費だけが発生する。

根本原因: Global AcceleratorはTCP/UDP層のAnycast IP最適化サービスであり、HTTP/HTTPSのキャッシュ機能は持たない。

比較軸CloudFrontGlobal Accelerator
プロトコルHTTP/HTTPS専用TCP/UDP汎用
キャッシュありなし
静的IPなし (DNS Anycast)あり (2固定IP)
主用途Web配信・API・CDNゲーミング/IoT/非HTTP
月額固定費なし$0.025/時 × Accelerator数

対策: HTTP/HTTPSのWebサイト・API → CloudFront。静的IPが必要・UDP/非HTTPワークロード → Global Accelerator。HTTPワークロードへのGlobal Accelerator導入は原則回避する。

詰まり5: Route53 Failover の Health Check 設定漏れ

症状: Primaryリージョンがダウンしたにもかかわらず、Route53がSecondaryに自動切り替えしない。

根本原因: Route53 Failover Routing PolicyはHealth Check IDをDNSレコードに紐付けなければ自動フェイルオーバーが機能しない。Health Checkを作成しただけでレコードへの設定を忘れるケースが非常に多い。

# OK: health_check_id を必ず設定する
resource "aws_route53_record" "primary" {
  zone_id= aws_route53_zone.main.zone_id
  name= "api.example.com"
  type= "A"
  set_identifier  = "primary"
  health_check_id = aws_route53_health_check.primary.id  # 必須: 未設定=フェイルオーバー無効
  failover_routing_policy { type = "PRIMARY" }
  alias {
 name = aws_lb.primary.dns_name
 zone_id = aws_lb.primary.zone_id
 evaluate_target_health = true
  }
}

対策: Failover Routingレコードには必ずHealth Check IDを設定する。Health Checkのfailure_thresholdrequest_intervalはSLO要件に合わせて設計する(デフォルト: 30秒間隔×3回失敗=90秒後に切替)。

詰まり6: Lambda@Edge の Regional Deploy エラー — us-east-1 必須

症状: ap-northeast-1などus-east-1以外のリージョンでLambda@Edge用の関数を作成しようとするとエラーになる。

根本原因: Lambda@Edgeの関数は us-east-1 (バージニア北部) でのみ作成可能であり、Terraformでprovider = aws.us_east_1の指定を忘れると別リージョンに作成されてしまう。

provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

resource "aws_lambda_function" "edge" {
  provider= aws.us_east_1  # 必須
  function_name = "my-edge-function"
  publish = true  # CloudFront関連付けにはpublished versionが必須
  runtime = "nodejs20.x"
}

追加制約: VPC内配置・環境変数・Lambda Layer・Provisioned Concurrencyはすべて使用不可。これらが必要な場合はOriginバックエンド (ALB + Lambda) への処理移管を検討する。

詰まり7: Origin Shield 有効化で逆にキャッシュが効かない

症状: Origin Shieldを有効化してOrigin負荷削減を期待したが、キャッシュヒット率が改善しない。

根本原因: Cache Policyが正しく設定されていない場合、Origin Shield層でもキャッシュが機能しない。具体的な原因パターン: CachingDisabled Managed Policyを使用している、Cache PolicyのTTLが0、OriginがCache-Control: no-storeを返している。

対策: Origin Shield有効化前にCache Policyが正しく機能しキャッシュヒット率が改善されていることを確認する。CloudWatch MetricsでOriginShieldRequestsCacheHitsの比率を確認する。Origin Shieldのリージョンは接続する主要エッジロケーションに地理的に近いリージョンを選択する。

詰まり7選 本番デプロイ前チェックリスト

  • [ ] Cache PolicyのキャッシュキーはWhitelist最小構成か (VaryヘッダをAccept-Encodingのみに制限)
  • [ ] Lambda@Edge Viewer Requestは5秒以内に処理完了するか (Provisioned Concurrency非対応)
  • [ ] CloudFront FunctionsのコードはES5.1互換か (async/await・Promise・アロー関数禁止)
  • [ ] HTTP/HTTPSワークロードにGlobal Acceleratorを使っていないか (CloudFront推奨)
  • [ ] Route53 Failover RecordにHealth Check IDが設定されているか
  • [ ] Lambda@EdgeはすべてTerraform us-east-1 providerで作成されているか
  • [ ] Origin Shield有効化前にCache PolicyのTTLとCache-Control設定を確認したか

7-2. アンチパターン→正解パターン 演習5問

実際の現場で繰り返し発生するアンチパターンを5問形式で整理する。アンチパターンの何が問題か、正解パターンへの修正方針を自分で考えてから答えを確認する学習方式で活用されたい。

Q1: CloudFront + S3 アクセス制御 — OAIからOACへの移行

アンチパターン: S3バケットをPublicアクセス可能に設定した上でCloudFrontのOriginとして使用している。あるいはOAI (Origin Access Identity) のまま運用しており、OACへの移行が未対応。

何が問題か: CloudFrontをバイパスした直接S3 URLへのアクセスが可能になる。OAIはKMS暗号化S3バケットのアクセスをサポートしない。

正解パターン: OAC (Origin Access Control) を使用し、S3バケットはブロックパブリックアクセスを有効化する。

resource "aws_cloudfront_origin_access_control" "s3" {
  name= "s3-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior= "always"
  signing_protocol= "sigv4"
}

# S3バケットポリシー: CloudFrontサービスプリンシパルのみ許可
data "aws_iam_policy_document" "s3_policy" {
  statement {
 principals { type = "Service"; identifiers = ["cloudfront.amazonaws.com"] }
 actions = ["s3:GetObject"]
 resources  = ["${aws_s3_bucket.main.arn}/*"]
 condition {
test  = "StringEquals"
variable = "AWS:SourceArn"
values= [aws_cloudfront_distribution.main.arn]
 }
  }
}

Q2: Lambda@Edge vs CloudFront Functions 選定

アンチパターン: URL書き換え (//index.html のSPA対応) にLambda@Edgeを使用している。

何が問題か: 単純なURL書き換えにLambda@Edgeを使うとコールドスタートリスクと不要なコストが発生する。Viewer Requestトリガーのためキャッシュヒット時もLambda@Edgeが実行され続ける。

正解パターン: CloudFront Functionsを使用する。ES5.1同期処理として実装可能でサブミリ秒で処理できる。

function handler(event) {
  var request = event.request;
  var uri = request.uri;
  if (uri.endsWith('/')) { request.uri = uri + 'index.html'; }
  else if (uri.indexOf('.') === -1) { request.uri = uri + '/index.html'; }
  return request;
}

選定基準: URL書き換え・ヘッダ追加・A/Bテスト振り分け (同期) → CloudFront Functions。外部API呼び出し・認証検証・複雑ロジック → Lambda@Edge (Origin Request、タイムアウト30秒)。

Q3: Global Accelerator vs CloudFront 用途選定

アンチパターン: 静的ファイルを配信するWebサイトにGlobal Acceleratorを導入している。

何が問題か: Global AcceleratorはHTTPキャッシュ機能を持たないため全リクエストがOriginまで到達する。固定費 ($0.025/時/Accelerator) が発生するがキャッシュ効果がない。

正解パターン: 静的WebサイトにはCloudFrontを使用する。Global AcceleratorはTCP/UDPワークロードや静的IPが必要なケースに限定する。

ユースケース推奨サービス
静的/動的Webサイト・REST APICloudFront
ゲーミング (UDP/低レイテンシ)Global Accelerator
IoT MQTT (TCP長時間接続)Global Accelerator
オンプレ → AWS (静的IP要件)Global Accelerator

Q4: Route53 Routing Policy 選定 — マルチリージョン HA

アンチパターン: 東京 (ap-northeast-1) とバージニア (us-east-1) にALBを配置しているが、Route53をSimple Routingのままにしてフェイルオーバーが機能していない。

何が問題か: Simple Routingはヘルスチェック連動フェイルオーバーをサポートしない。レイテンシベースのルーティングもなく、一方のリージョンがダウンしても自動的な切り替えが発生しない。

正解パターン: Latency Routing + Health Checkを組み合わせ、Active-Active構成と自動フェイルオーバーを実現する。

resource "aws_route53_health_check" "tokyo" {
  fqdn  = "api-tokyo.example.com"
  port  = 443
  type  = "HTTPS"
  resource_path  = "/health"
  failure_threshold = 3
  request_interval  = 30
}

resource "aws_route53_record" "tokyo" {
  zone_id= aws_route53_zone.main.zone_id
  name= "api.example.com"
  type= "A"
  set_identifier  = "tokyo"
  health_check_id = aws_route53_health_check.tokyo.id
  latency_routing_policy { region = "ap-northeast-1" }
  alias {
 name = aws_lb.tokyo.dns_name
 zone_id = aws_lb.tokyo.zone_id
 evaluate_target_health = true
  }
}

通常時は最も近いリージョン(レイテンシ最小)にルーティングされ、片方がHealth Check失敗となった場合は自動的に正常なリージョンへ切り替わる。

Q5: CloudFront Cache Policy + Origin Request Policy 設計

アンチパターン: Managed Policy CachingDisabledを使い続けており、Cache PolicyとOrigin Request Policyを分けて設計していない。

何が問題か: CachingDisabledではCloudFrontがキャッシュを一切行わず全リクエストがOriginに転送される。Origin Request Policyを設定していないためOriginへの不要なヘッダ転送が発生する。

正解パターン: キャッシュキー (Cache Policy) とOriginへの転送設定 (Origin Request Policy) を独立して設計する。

# Cache Policy: キャッシュキーの最小化 (Hit率最大化)
resource "aws_cloudfront_cache_policy" "optimized" {
  name = "custom-cache-policy"
  default_ttl = 86400
  max_ttl  = 604800
  min_ttl  = 1
  parameters_in_cache_key_and_forwarded_to_origin {
 cookies_config  { cookie_behavior = "none" }
 headers_config  { header_behavior = "none" }
 query_strings_config {
query_string_behavior = "whitelist"
query_strings { items = ["v", "lang"] }
 }
 enable_accept_encoding_brotli = true
 enable_accept_encoding_gzip= true
  }
}

設計の核心: Cache PolicyはキャッシュHit率を決める変数を最小化する。Origin Request PolicyはOriginが必要とするヘッダ/Cookie/QueryStringをキャッシュキーに含めずOriginへ転送する。両者は独立して設計することでセキュリティとパフォーマンスを両立できる。


8. まとめ — Vol2予告 + 落とし穴10選 + 全17軸クロスリンク

CloudFront・Lambda@Edge・CloudFront Functions・Global Accelerator・Route53の5サービスを組み合わせたEdge/CDN本番運用は、「キャッシュ設計」「Edgeロジック配置」「ルーティング戦略」の3軸で判断を固めることが成功の鍵である。

本記事で学んだ判断軸:
1. キャッシュ設計: Cache PolicyでキャッシュキーをWhitelist最小化、Origin Request PolicyはOriginへの転送設定と分離
2. Edgeロジック: sub-millisecond同期処理 → CloudFront Functions / 非同期・外部連携 → Lambda@Edge (Origin Request)
3. Anycast選定: HTTP/Web → CloudFront / TCP/UDP・静的IP → Global Accelerator
4. DNS設計: マルチリージョンHAにはLatency/Failover Routing + Health Check必須
5. デプロイ制約: Lambda@EdgeはすべてTerraform us-east-1 providerで管理する

Edge/CDN本番運用 落とし穴10選

  • 【1】Cache PolicyのキャッシュキーにAuthorizationヘッダや全Cookieを含めてキャッシュ効率が0%になる
  • 【2】Lambda@Edge Viewer RequestにProvisioned Concurrencyを設定しようとしてエラー (非対応)
  • 【3】CloudFront FunctionsにES6以降の構文 (async/await・アロー関数) を使用して全リクエスト失敗
  • 【4】HTTP APIにGlobal Acceleratorを導入してキャッシュ恩恵なし・固定費過払い
  • 【5】Route53 Failover RecordにHealth Check IDを設定せずフェイルオーバーが機能しない
  • 【6】Lambda@Edgeをus-east-1以外で作成してCloudFront関連付けエラー
  • 【7】Origin ShieldをCache Policy未整備のまま有効化してキャッシュヒット率が改善されない
  • 【8】S3 OriginにOAIを使い続け、KMS暗号化バケットでアクセス不可になる
  • 【9】ACM証明書をus-east-1以外で作成してCloudFrontに適用できない
  • 【10】CloudFrontのPrice ClassをPriceClass_Allに設定し、低トラフィックサービスで過剰課金
Edge/CDN本番運用 推奨設計サマリ

  • Origin層: S3はOAC必須 / ALBはOrigin Custom Header検証で直接アクセス遮断
  • キャッシュ層: Cache Policy = キャッシュキー最小化 / Origin Request Policy = Origin転送設定
  • Edge Logic層: Viewer同期処理 → CloudFront Functions / 非同期・外部連携 → Lambda@Edge
  • ルーティング層: HTTP/Web → CloudFront / TCP/UDP・静的IP → Global Accelerator
  • DNS層: Latency or Geolocation Routing + Health Check で マルチリージョンHA
  • 監視層: CloudFront Real-time Logs + Route53 Health Check Alarm + CloudWatch統合
Vol2に向けた実務アクションアイテム

  • 本記事の Cache Policy / Origin Request Policy 設計を本番環境に適用する
  • Lambda@Edge と CloudFront Functions の使い分けマトリクスを社内設計ガイドとして文書化する
  • Route53 Routing Policy 7種を実際にコンソールで作成し挙動を確認する
  • CloudFront Real-time Logs を Kinesis に流す検証環境を準備する (Vol2 内容の事前学習)

Vol2 予告 — Edge Real-time Analytics と高度なEdge設計

Vol2では以下のトピックを扱う予定:

  • CloudFront Real-time Logs × Kinesis Data Streams: 秒単位のアクセスログ分析基盤の構築
  • CloudFront Continuous Deployment: 本番トラフィックの段階的テスト (Staging Distribution)
  • Lambda@Edge × DynamoDB Global Tables: グローバルセッション管理の本番設計
  • Route53 Application Recovery Controller: マルチリージョン復旧の細粒度制御

Vol2 (Edge Real-time Analytics) の公開を待つ

AWS本番運用 全17軸 クロスリンク

本シリーズはAWSの主要ドメインを縦横に網羅する全17軸で構成されている。各記事が相互参照するハブ型ナビゲーションにより、関連領域への深掘りを効率化している。

AWS本番運用 全17軸 ナビゲーション (Edge/CDN Vol1 起点)

関連: Storage本番運用 Vol1 (S3×EFS×FSx×Storage Gateway) を読む

関連: セキュリティ Vol2 (CloudFront×WAF×Shield 防御層) を読む