こんにちは、MSPの田所です。
みなさんは CloudFront でリダイレクト設定をしたいと思ったことはないでしょうか?
今回は特定パスの特定エラーだけをリダイレクトする方法を紹介します。
結論
Lambda@Edge をオリジンレスポンスに設定してリダイレクトします。
結果的に以下のサンプルの実装です。
概要
ここでは Amazon CloudFront + Amazon S3 の構成を考えます。
Amazon S3 で静的ウェブサイトホスティングをしています。
例えば、ネコとイヌの画像を表示するウェブサイトを作ったとします。
コンテンツはまだ未完成の状態です。
ここで、ネコフォルダ (/neko) の配下で存在しないページにリクエストが来た場合に、ネコ用のごめんねページにリダイレクトしたいとします。
Lambda@Edge をオリジンレスポンスに対して実行することで、この条件付きリダイレクトを実現します。
ここではネコフォルダ (/neko) 配下以外の 404 はリダイレクトしないことがポイントです。
Amazon CloudFront ではカスタムのエラーページを設定することができますが、指定のエラーコードに対してもれなく適用されます。
今回のように条件付きでエラーページにリダイレクトしたい場合には対応できません。
ステップ1:CloudFront ビヘイビアを設定する
まず、CloudFront でパスパターン /neko/* に対するビヘイビアを設定します。
パスを指定しておかないと、ページ内のあらゆるリクエストに対して Lambda が実行されることになり、無駄な実行で溢れてしまいます。
メディアファイルのキャッシュなど、すでにビヘイビアが設定されている場合は優先順位の考慮が必要です。
ビヘイビアは優先順位ごとに評価して、条件がマッチした時点で評価を終了します。
以下の場合、/neko/cat.mp3 は優先順位 0 のキャッシュビヘイビアが適用されますが、Lambda@Edge を仕込む優先順位 3 までは辿り着けず、ファイルが存在しない場合でも Lambda によるリダイレクトはされません。
どちらの挙動を優先するか、判断が必要になります。
ステップ2:Lambda 関数を作成する
次に Lambda 関数を作成します。
CloudFront に紐付けるので、us-east-1(バージニア北部)リージョンに作成する必要があります。
ここで「CloudFront Functions じゃだめなの?」と思ったそこのあなた。
これはハマりポイントなのですが、CloudFront Functions ではだめなんです。
CloudFront Functions はビューワーリクエスト、ビューワーレスポンスのみに設定可能ですが、
400 以上のレスポンスはビューワーレスポンスで関数を呼び出すことなく処理される仕様のようです。
オリジンが 400 以上の HTTP ステータスコードを返す場合、CloudFront はビューワーレスポンスイベントのエッジ関数を呼び出しません。
みなさまは私と同じ轍を踏まぬようご注意ください。
それでは Lambda 関数を作成していきます。
注意点としては、Lambda@Edge 用の実行ロールを作成する必要があります。
「AWS ポリシーテンプレートから新しいロールを作成」から
「基本的な Lambda@Edge のアクセス権限 (CloudFront トリガーの場合)」を選びましょう。
権限が足りていないと、Lambda@Edge にデプロイする時に怒られます。
コードを入れて Lambda 関数をデプロイします。
以下は Python コードの例です。
def lambda_handler(event, context): response = event['Records'][0]['cf']['response'] request = event['Records'][0]['cf']['request'] # Check if the response status is 404 and if the URI starts with '/neko/' if int(response['status']) == 404 and request['uri'].startswith('/neko/'): # Define the redirect path redirect_path = '/neko/sorry.html' response['status'] = 302 # Redirect status code response['statusDescription'] = 'Found' # Clear body as it's not needed for redirects response['body'] = '' # Set the Location header to point to the new URL response['headers']['location'] = [{ 'key': 'Location', 'value': '<ドメイン名>' + redirect_path }] return response
ここで「ドメイン名は環境変数にしないの?」と思ったそこのあなた。
Lambda@Edge は環境変数をサポートしていないそうです。
以下の Lambda 機能は、Lambda@Edge でサポートされていません。
- Lambda 環境変数 (自動的にサポートされる予約環境変数は除く)
私と同じ轍を踏まぬよう(2回目)
ステップ3:Lambda 関数 を Lambda@Edge にデプロイする
作成した関数を Lambda@Edge にデプロイします。
キャッシュ動作は /neko/* で、CloudFront イベントはオリジンレスポンスです。
関数に新しいバージョンが作成され、CloudFront ディストリビューションの再デプロイが始まります。
対象 CloudFront ディストリビューション ビヘイビアのオリジンレスポンスに Lambda@Edge が加わりました。
ステップ4:挙動確認
挙動のおさらいです。
ネコ配下の存在しないファイル(キジトラ)にアクセスした時に、ごめんねページにリダイレクトすれば OK です。
ネコを見に行きます。
キジトラを見に行きます。
キジトラのページは存在しません。
ごめんねページにリダイレクトされました。
Lambda@Edge 良い仕事してます。
キジトラに限らず存在しないページなら何でも OK です。
コマンドで /neko/mike/nandemoOK にアクセスしてみたところ、こちらもリダイレクトを確認できました。
$ curl -I <ドメイン名>/neko/mike/nandemoOK HTTP/1.1 302 Found Content-Length: 0 Connection: keep-alive x-amz-error-code: NoSuchKey x-amz-error-message: The specified key does not exist. x-amz-error-detail-Key: neko/mike/nandemoOK x-amz-request-id: XXXXX x-amz-id-2: YYYYY Date: Thu, 13 Mar 2025 15:04:14 GMT Server: AmazonS3 Location: <ドメイン名>/neko/sorry.html X-Cache: Error from cloudfront Via: 1.1 zzzzz.cloudfront.net (CloudFront) X-Amz-Cf-Pop: AAA00 X-Amz-Cf-Id: BBBBB
またネコ配下以外はリダイレクトしたくないという要件もありました。
イヌを見に行きます。
こちらも存在しないページです。
無事 404 となりました。
ごめんねページへのリダイレクトはされていません。
番外:Lambda 実行ログの在処
リダイレクトが上手くいったことを確認できました。
そこで Lambda の実行ログを CloudWatch Logs で見ようとしたのですが、ロググループが見当たりません。
リージョンは Lambda 関数を作成した us-east-1(バージニア北部)です。
なぜログがないのでしょうか。
その答えは、「ログは関数が実行されたリージョンに記録されるから」でした。
Lambda@Edge は、関数ログを CloudWatch Logs に自動的に送信し、関数が実行される AWS リージョン にログストリームを作成します。
今回アクセスしたエッジは東京リージョンでした。
東京リージョンで実行ログを確認できました。
ちなみにデフォルトでログの保持期間は「失効しない」に設定されています。
ログの保持期間を設定しようと思ったら、リージョンごとに設定変更が必要です。
世界中からアクセスがある場合にはてんやわんやですね。
以下の Lambda 機能は、Lambda@Edge でサポートされていません。
- Lambda がログを送信する Amazon CloudWatch ロググループの設定
まとめ
Amazon CloudFront で特定パスの特定エラーをリダイレクトする方法を見てきました。
なんだか AWS 認定試験に出てきそうな内容でしたね。
何かの助けになれば幸いです。
おしまい