FastlyからCloudFrontへ段階的移行 ── 無停止で実現したWEAR CDNの刷新

FastlyからCloudFrontへ段階的移行 ── 無停止で実現したWEAR CDNの刷新

はじめに

こんにちは、WEAR開発部SREブロックの木内です。普段はWEARのSREとして開発、運用に携わっています。

WEARは2013年にサービスを開始し長年オンプレミスで運用されてきましたが、過去にクラウド(AWS)へのシステムリプレイスを実施しています。その際にWebアプリのCDNとしてFastlyを採用し、オンプレミスからクラウドへの段階的な移行を実現しました。

採用の決め手は主に以下の点です。

  • パスベースのルーティングが可能(パスごとにオンプレミスとクラウドのオリジンを切り替えられる)
  • ネイキッドドメインへの対応
  • 設定変更の即時反映による迅速なロールバック

リプレイスの詳細については、以下の記事をご参照ください。

techblog.zozo.com

Fastlyを用いた構成はサービスを止めずに安全なリプレイスを実現するうえで大きく貢献しました。一方で、リプレイスが完了しFastlyを導入した当初の目的を達成した後も残り続けたことで、運用負荷やコストといった課題が顕在化してきました。

本記事では、過渡期の構成を整理し、CDNをFastlyからAmazon CloudFront(以下、CloudFront)へ移行してAWSに統一した取り組みを紹介します。

目次

移行の背景・課題

移行前の構成は以下の通りです。

移行前の構成

Fastlyをフロントに置き、バックエンドにALBとS3が2つずつ、計4つのオリジンを持つ構成です。

ALBは動的コンテンツの配信や認証処理を担っており、S3はLPやアセットなどの静的コンテンツを配信しています。S3の前段にはそれぞれ個別のCloudFrontを使用しています。

また、FastlyではVCL[^1]を用いてCDN以外の多くの処理も担っていました。

  • パスごとのオリジン振り分け
  • 各種ブロック(IP / ASN / リファラ など)
  • Basic認証
  • メンテナンスモード
  • リダイレクト / リライト

前述の通り、リプレイス時にFastlyを採用したことで安全にリプレイスを進めることができましたが、リプレイス完了後に以下のような課題が残りました。

運用負荷

AWSとFastlyの二重管理が運用負荷に繋がっていました。

  • WEARの大部分はすでにCloudFrontを使用しており、2つのCDNそれぞれでキャッチアップが必要だった
  • AWSとFastlyそれぞれでユーザーやリソースの管理も必要だった
  • 設定変更をWebチームとSREチームで担っていたため、Fastly専用のインフラリポジトリを別途設けており、CIの整備やレビューフローの維持など、リポジトリが増えることに伴う管理コストが発生していた

コスト

FastlyをAWSの前段に置く構成であるため、大きく分けて2種類のコストが発生していました。

  • Fastlyがユーザーへ配信するコスト
  • AWSがFastlyへ配信するコスト

また、LPやアセットはすでにCloudFrontを使用していたため、FastlyとCloudFront両方のCDNコストが発生していた点も見過ごせません。

これらの課題を解消するために、CDNをCloudFrontへ移行し、配信基盤をAWSへ統一する方針を取りました。

移行後の構成

移行後の構成は以下の通りです。

移行後の構成

移行後は、ALB・S3など全てのバックエンドの前段に1つのCloudFrontを配置しています。

また、FastlyのVCLで実施していた処理はそれぞれ対応するAWSサービスへ移行しました。

処理 Fastly AWS
CDN・配信 Fastly CDN CloudFront
パスごとのオリジン振り分け VCL CloudFront(Cache Behavior)
各種ブロック(IP / ASN / リファラ など) VCL AWS WAF
Basic認証 VCL AWS WAF
メンテナンスモード VCL AWS WAF
リダイレクト / リライト VCL CloudFront Functions

Basic認証・メンテナンスモードはAWS WAF(以下、WAF)のカスタムレスポンス機能を利用して実装しています。WAFのルールにマッチしたリクエストに対して、任意のHTTPステータスコード・レスポンスヘッダー・レスポンスボディを返せる機能です。

セキュリティ関連の処理をWAFに集約することで一元管理できることに加え、CloudFront Functionsのコードサイズや実行時間の制限[^2]を避けられる点も採用の理由です。

リダイレクト・リライトの実装にあたり、Lambda@EdgeとCloudFront Functionsを比較検討しました。Lambda@Edgeは複雑な処理や長時間実行に向いている一方、今回のような軽量なリダイレクト・リライト処理にはCloudFront Functionsが適しています[^3]。加えてスケール量・リクエスト料金の面でも有利なため採用しました。

さらに、リダイレクト・リライトはルール数が多いため、jsをビルドしminify化しながらCloudFront Functionsのコードサイズ制限を超過しないよう工夫しています。CloudFront KeyValueStoreを使用したサイズ圧縮も検討しましたが、フロントとインフラ間の依存関係の簡素化や認知負荷の低減を優先したかったからです。

これらの設計を経て、FastlyのVCLに分散していたエッジ処理をAWS WAFとCloudFront Functionsに集約し、CDNレイヤーの機能をすべてAWS上で完結させる構成になりました。

移行方法

今回のCDN切り替えはフェーズを分けて段階的に実施しました。このCDNはPC・SP全体のトラフィックを受けているため、問題が発生すれば多くのユーザーへ影響が出てしまいます。そのため、いかにユーザー影響を出さず安全に移行するかが本プロジェクトの鍵でした。

具体的なフェーズは以下の通りです。Fastlyの後段にCloudFrontを配置し、影響範囲が小さいものから順にCloudFront経由へ切り替えていきました。

1. LP・アセットの切り替え

フェーズ1: LP・アセットの切り替え

最初はS3で配信しているLPとアセットの切り替えです。静的コンテンツは影響範囲が限定的で切り戻しもしやすいため、最初の移行対象としました。

切り替えにあたっては全パスを網羅するURLリストを用意してスクリプトで動作確認を行い、移行前後の挙動に差異がないことを確認しながら実施しました。

2. 動的コンテンツ・認証処理の切り替え

フェーズ2: 動的コンテンツ・認証処理の切り替え

次に動的コンテンツの配信や認証処理を担うALBの切り替えです。動的コンテンツの配信や認証処理を担っているため、CDN切り替えによるヘッダーやキャッシュの挙動の変化が配信や認証に影響を与えるリスクがありました。

影響範囲を抑えるためにALBは1台ずつ切り替え、バックエンドチームにも協力してもらい機能リグレッションがないことを確認しながら進めました。

加えて、ダークカナリアリリース(ユーザーには見えない形で一部のトラフィックを新しい構成に流し、本番環境で検証する手法)でCloudFront経由に流し、環境差異による不具合がないかも検証しました。

3. VCLの処理をCloudFront Functionsへ移行

フェーズ3: VCLの処理をCloudFront Functionsへ移行

続いてVCLのリダイレクト・リライト処理を移行しました。VCLの処理の中にはリクエスト元の国別判定をした上でリダイレクトをする処理もあり、障害につながりやすい部分であったため、切り戻しやすさを考慮して2段階に分けて対応しました。

動作確認はFastlyで実施していたリダイレクト・リライト処理を網羅的に検証できるスクリプトをWebチームが作成してくれており、そちらを活用し移行前後の挙動に差異がないことを確認しながら進めました。

スクリプトはDenoのテストフレームワークで書かれており、各パスへのリクエストに対してステータスコード・リダイレクト先・レスポンスヘッダーを検証します。さらにHTMLレスポンス内のJS・CSSアセットも正常に取得できるかまで確認しており、移行後の挙動を一通りカバーしています。

また、VCLの移行はオリジンの切り替えと異なり、パスごとに挙動が細かく異なります。そのため追加のスクリプトも作成し、リダイレクト・リライト対象外のパスへの影響がないか、Serverヘッダーでオリジンが意図した経路を通っているかを、1パスずつ地道に確認していきました。

4. DNS切り替え

フェーズ4: DNS切り替え

最後にDNSを切り替えてFastlyからCloudFrontへ完全移行しました。

WEARはDNSにAkamaiを使用しています。今回、AkamaiのChange Listを活用し、ダウンタイムなしで切り替えを実現しました。

CloudFrontのIPアドレスは固定されていないため、FastlyのIPを登録していたAレコードからCloudFrontのドメインを指定するCNAMEへの変更が必要でした。しかしAレコードとCNAMEは共存できないため、削除と追加を別々に適用すると瞬断のリスクがありました。Change ListはDNSレコードの変更をまとめてアトミックに適用できる機能で、これを活用することでダウンタイムなしでの切り替えを可能にしています。切り替えは有事の際に素早く切り戻せるよう、事前にTTLを60秒に下げた上で実施しました。

また、次のセクションで詳しく説明しますが、ブロックやBasic認証等のWAF切り替えもこのタイミングで同時に実施しています。

WAFの移行タイミング

WAFはIP・国・ASNなどの判定を、送信元IPまたはX-Forwarded-For(XFF)ヘッダーのIPアドレスを基に行います。

DNS切り替え前はFastlyがリクエストを受け取るため、AWS WAFから見るとアクセス元のIPがFastlyのIPになります。この状態でWAFを先に切り替えると、ブロックルールがFastlyのIPに対して適用され、意図しないブロックや、本来ブロックすべきリクエストが通過してしまうリスクがありました。

XFFを参照することでクライアントIPに基づく判定も可能ですが、以下の理由から採用しませんでした。

  • Fastlyの判定ロジックが送信元IPを利用しており、仕様を変えたくなかった
  • DNS切り替え前後でXFFの内容が変わるため、切り替えのタイミングで挙動が変わることを避けたかった

そのため、DNS切り替えまでの間はFastly側のブロック設定を残し、WAFの切り替えはDNS切り替えと同時に行いました。

なお、事前の動作確認はCloudFrontのFQDNに直接curlでアクセスして行いました。DNS切り替え前はFastlyがエンドポイントのため、CloudFrontのFQDNに対してアクセスする必要があります。CloudFrontはHTTPS接続時にHostヘッダーの値でSSL証明書を選択するため、Hostヘッダーにドメインを指定することで正しく証明書検証が行えます。

CloudFront FunctionsについてもDNS切り替え前はリクエストがFastlyを経由するため、リクエスト元の国別判定で同様の問題がありました。

こちらはWebチームが解決策を検討・実装してくれました。DNS切り替え前の移行期間中に限り、Fastly側でCloudFrontの仕様に準ずるヘッダーを付与しました。CloudFront Functions側でもそのヘッダーを参照することで、正しく地域判定が行える構成にしています。

設計のポイント

ここまで移行の手順を紹介しました。次に、移行にあたって設計上特に考慮が必要だったCloudFrontのビヘイビア設計について紹介します。

CloudFrontでは、リクエストのURIパターンに応じて処理方法を定義する「Cache Behavior」という仕組みがあります。今回はオリジンの振り分けをビヘイビアで制御しており、その設計がFunctions実装やエラーハンドリングに直結するため、いくつか考慮する必要がありました。

移行期間中のリクエスト

Cache BehaviorはURIパターンで優先度順に評価され、先にマッチしたものが適用されます。パターンにはワイルドカード(*)が使えますが、ワイルドカードなしの場合は完全一致での評価になります。

この仕様を踏まえてビヘイビアを設計しました。また、移行期間中はFastly側の既存のロジックでリダイレクトされたパスがCloudFrontに届くケースもあるため、リダイレクト後のパスに対応するBehaviorも明示的に用意しました。

CloudFront Functionsを用いたリダイレクト・リライト

CloudFront Functionsを用いたリダイレクト・リライトの実装にあたり、CloudFrontの処理順序を正しく理解することが重要でした。

CloudFrontはリクエストを受信した時点のURIをもとにCache Behaviorを選択します。その後CloudFront Functions内でrequest.uriを書き換えても選択済みのBehaviorは変わりません。そのため、リライト後のパスが意図したオリジンに届くよう、元のURIの段階で適切なBehaviorを選択できる設計にしました。

カスタムエラーレスポンス

CloudFrontのカスタムエラーレスポンスは、オリジンが特定のHTTPステータスコードを返した際に指定のパスへフォールバックできる機能です。今回はすべてのオリジンの404をALBオリジンのエラーページにフォールバックするよう設定しています。

カスタムエラーレスポンスは、CloudFront Functionsの挙動と違いフォールバック先に指定したパスでビヘイビアが再評価されます。そのため、フォールバック先に指定したパスがALBオリジンのビヘイビアに正しくルーティングされるよう設計しました。

また、S3オリジンはデフォルトでオブジェクトが存在しない場合に404ではなく403を返すため、このままではカスタムエラーレスポンスが正しく動作しません。これを解消するために、OAC(Origin Access Control)のバケットポリシーにs3:ListBucketを追加し、S3がオブジェクトの有無を判断して404を返せるようにしました。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::amzn-s3-demo-bucket/*",
        "arn:aws:s3:::amzn-s3-demo-bucket"
      ],
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
        }
      }
    }
  ]
}

得られた成果

月間数億リクエストを処理するCDNの切り替えは、ユーザーへの影響が出れば大きな障害につながりかねないものでした。段階的なトラフィック移行や入念な動作確認を重ね、一切のダウンタイムなしで移行を完了できたことは、本プロジェクト最大の成果です。

加えて、今回の移行は単なるCDNの切り替えにとどまらず、長年の過渡期構成を整理し、運用・コスト・セキュリティの三面で改善を実現できた取り組みでした。具体的には次のとおりです。

運用のシンプル化

VCLに集約されていた処理がAWS WAFとCloudFront Functionsへ役割ごと分離されたことで、各機能の責任範囲が明確化しました。変更が必要な際も影響箇所を絞り込みやすく、必要な箇所だけ修正できるようになりました。

また、Fastlyのアカウント管理やVCL設定の維持が不要になり、AWSに運用を集約できるようになりました。

さらに、AWSに統一したことで、追加機能の検証や導入の敷居も下がりました。

実際に、移行完了後1か月以内にAWSが提供するWAFマネージドルールの追加導入が完了しています。WAFのCountモードで事前に検証し、誤検知がないこと・不審なリクエストのブロック効果があることを確認した上で導入を判断しました。

今後も既存のルールを拡充し、さらなるセキュリティ強化を進めていく予定です。

インフラコストの削減

コスト削減は今回の移行における主要な目的の1つであり、結果としてCDN関連のインフラコストを約40%削減できました。

主な内訳は以下です。

  • CloudFrontをCDNとすることにより、ALBからのトラフィックコストが大幅に緩和されたこと
  • LPとアセットで発生していた二重の配信コストが解消されたこと

この削減は構成変更による恒久的なものであり、継続的なコスト改善として今後も効果が持続します。

まとめ

本記事では、FastlyからCloudFrontへの移行を通じて、CDN構成をAWSに統一した取り組みを紹介しました。

今回の移行は単なるCDNの乗り換えにとどまらず、長年の過渡期構成を整理し、運用・コスト・セキュリティの三面で改善を実現できた取り組みでした。

「当時は最善だった構成が、今となって見直しどきを迎えている」という状況は多くのサービスで共通する課題だと思います。CDNの移行や構成整理を検討している方にとって、本記事が参考になれば幸いです。

ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com

[^1]: VCL(Varnish Configuration Language)はVarnishというキャッシュサーバー向けの設定言語です。

[^2]: CloudFront Functionsはコードサイズが10KB以下、実行時間が1ms以下という制限があります(参考:CloudFront Functions のクォータ

[^3]: CloudFront Functions と Lambda@Edge の違い

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-05-29 02:08
浙ICP备14020137号-1 $访客地图$