はじめに

グローバルに展開するWebサイトのインフラ構築に携わった際、ユーザーのアクセス元の国を判別し、対応する言語のWebサイトへ自動リダイレクトする仕組み を実装する機会がありました。
このような国ごとのリダイレクト処理を実現する方法として、CloudFront Functions や Lambda@Edgeを利用する選択肢があります。今回は処理の軽量性、コスト効率、パフォーマンスの観点から、CloudFront Functionsを採用し、アクセス元の国情報 (cloudfront-viewer-country ヘッダー) を基に、適切なURLへリダイレクトする仕組みを構築しました。

CloudFront FunctionsとLambda@Edgeの違い

CloudFront FunctionsとLambda@Edgeにはそれぞれ特徴があり、公式ドキュメントで両者の違いをわかりやすく比較した一覧表が掲載されています。
参照元:CloudFront Functions と Lambda@Edge の違い

CloudFront Functionsを選択した理由

今回のリダイレクト処理は、リクエストヘッダー(CloudFront-Viewer-Country)を判定し、適切なURLにリダイレクトするだけのシンプルかつ軽量な処理でした。
CloudFront Functionsはサブミリ秒(1ミリ秒未満)での処理を前提として設計されているのに対し、Lambda@Edgeは最大5秒(ビューワーリクエスト、ビューワーレスポンス)/ 最大30秒(オリジンリクエスト、オリジンレスポンス) の実行時間が許容されています。
そのため、今回のような 「シンプルなリダイレクト処理」 では、CloudFront Functionsを使用することで不要なオーバーヘッドを抑えつつ、サブミリ秒レベルでの高速レスポンスを実現し、ユーザーの待ち時間を最小限に抑えることができます。
また、コスト面においてもCloudFront Functionsは優位です。
Lambda@Edgeは「リクエスト数 × 実行時間」に基づいた従量課金ですが、CloudFront Functionsは「リクエスト数のみ」に基づく料金体系を採用しているため、大規模なWebサイトでも低コストで運用可能です。
さらに、実装に関しても単純なJavaScriptの条件分岐で完結するため、構築コストを抑えながら短期間での開発が可能でした。
以上の理由から、今回はCloudFront Functionsを選択しました。

一方で、以下のようなユースケースのではLambda@Edgeの方が適しているようです。

  • 完了までに数ミリ秒以上かかる関数。
  • 調整可能なCPUまたはメモリを必要とする機能。
  • サードパーティライブラリに依存する関数 (他の AWS のサービスとの統合のため、AWS SDK を含む)。
  • 外部サービスを使用して処理するために、ネットワークアクセスを必要とする関数。
  • ファイルシステムへのアクセスまたは HTTP リクエストの本文へのアクセスを必要とする関数。

CloudFront Functionsの実装

以下公式ドキュメントに記載されているJavaScriptコードを参考に実装を行いました。

CloudFront Functions ビューワーリクエストイベントで新しい URL にリダイレクトする

テスト検証用として、CloudFrontにアクセスがあった際に、アクセス元の国を判定し、その国の言語に対応したAWS公式ドキュメントのページへリダイレクトするコードを作成しました。

function handler(event) {
var request = event.request;
var headers = request.headers;
var newurl = "https://docs.aws.amazon.com/ja_jp/"; // デフォルトは日本語ページ

// `cloudfront-viewer-country` のヘッダー情報を元に分岐処理
if (headers['cloudfront-viewer-country']) {
var countryCode = headers['cloudfront-viewer-country'].value.toUpperCase();

// 国コードに応じたリダイレクト先を設定
switch (countryCode) {
case 'JP':
newurl = "https://docs.aws.amazon.com/ja_jp/"; // 日本
break;
case 'KR':
newurl = "https://docs.aws.amazon.com/ko_kr/"; // 韓国
break;
case 'US':
newurl = "https://docs.aws.amazon.com/en_us/"; // アメリカ
break;
case 'FR':
newurl = "https://docs.aws.amazon.com/fr_fr/"; // フランス
break;
case 'DE':
newurl = "https://docs.aws.amazon.com/de_de/"; // ドイツ
break;
// それ以外の国はデフォルト (日本語ページ)
}
}

// 302リダイレクト処理
return {
statusCode: 302,
statusDescription: "Found",
headers: {
"location": { "value": newurl } // リダイレクト先URLを指定
}
};
}

次に、CloudFront Functionsを作成後、ビヘイビアのオリジンリクエストポリシーに「CloudFront-Viewer-Country」ヘッダーを含むカスタムポリシーを設定します。
これにより、リクエストヘッダーに含まれる国コード情報をもとにリダイレクト処理を実行できるようになります。

最後に、ビヘイビアの関数の関連付け設定において、ビューワーリクエストに作成したCloudFront Functionsを適用すれば設定は完了です。

テスト検証

CloudFront Functionsでは、作成した関数のテスト実行が可能なため、本番環境へデプロイする前に動作確認を行います。

デプロイ後、実際に各ロケーションから対象のCloudFront(xxxxxxxjab25.cloudfront.net)へのアクセス結果も、国別のURLへ適切にリダイレクトされていることを確認できました。

## 日本
~ $ curl -s ipinfo.io/ | jq -r '"country: " + .country'
country: JP
~ $
~ $ curl -sI https://xxxxxxxjab25.cloudfront.net | grep -i location
location: https://docs.aws.amazon.com/ja_jp/
~ $
## 韓国
~ $ curl -s ipinfo.io/ | jq -r '"country: " + .country'
country: KR
~ $ curl -sI https://xxxxxxxjab25.cloudfront.net | grep -i location
location: https://docs.aws.amazon.com/ko_kr/
~ $
## アメリカ
~ $ curl -s ipinfo.io/ | jq -r '"country: " + .country'
country: US
~ $ curl -sI https://xxxxxxxjab25.cloudfront.net | grep -i location
location: https://docs.aws.amazon.com/en_us/
~ $
## フランス
~ $ curl -s ipinfo.io/ | jq -r '"country: " + .country'
country: FR
~ $ curl -sI https://xxxxxxxjab25.cloudfront.net | grep -i location
location: https://docs.aws.amazon.com/fr_fr/
~ $
## ドイツ
~ $ curl -s ipinfo.io/ | jq -r '"country: " + .country'
country: DE
~ $ curl -sI https://xxxxxxxjab25.cloudfront.net | grep -i location
location: https://docs.aws.amazon.com/de_de/
~ $

最後に

実際の案件では 20ヵ国以上の国へのリダイレクト設定や、パスパターンによる分岐処理も組み合わせたため、テスト検証よりも複雑なコードでしたが、CloudFront Functionsのコンピューティング使用率は最大でも約25%程度に収まり、スロットリングも発生せずスムーズに動作しました。


参照元:コンピューティング使用率を理解する

今回CloudFront Functionsを活用することで、シンプルかつ高速な国別リダイレクトを実現できたことは、大きな学びとなりました。今後も適用できる場面があればCloudFront Functionsを積極的に利用していきたいと思います。