tech.guitarrapc.cóm

Technical updates

CloudFrontでオリジンを書き換えるのにCloudFront Functionsを使う

これまではCloudFrontでオリジンを書き換えにLambda@Edgeを利用していましたが、CloudFront JavaScript runtime 2.0以降はCloudFront Functionsでもオリジンの書き換えが可能になりました。 今回は、CloudFront Functionsを利用してオリジンを書き換えるのがおすすめという話です。

CloudFrontでオリジンを書き換えるケース

一般的に、リクエストを別の場所へ転送する場合はリダイレクト(HTTP 3xx)を利用するのが単純かつ扱いやすいです。特にGETリクエストは、クライアント(ブラウザやAPIクライアント)がリダイレクト先に再リクエストを送信するため、サーバーとしても扱いやすくほとんどのケースで問題なく動作します。

ただ、POSTリクエストを処理する場合は、リダイレクトは適さないことがあります。例えば、npmはPOSTリクエストのクライアントリダイレクトをサポートしていません。この場合、CloudFrontでオリジンを書き換えて、受け取ったPOSTリクエストを転送する必要があります。

単純にいうと、GETリクエストならリダイレクトで問題ないですが、POSTリクエストなどリダイレクトできないリクエストをCloudFrontで受け取る場合にオリジン書き換えを検討する必要があります。

CloudFrontでオリジンを書き換える方法

CloudFrontでオリジンを書き換える方法は2つあります。

  1. Lambda@Edgeを利用する
  2. CloudFront Functionsを利用する

今回は、//fooはリダイレクト、それ以外はオリジンを書き換える例を示します。

CloudFront Functionsでもオリジン書き換えが可能になった

CloudFront FunctionsはHostヘッダーを変更できないため、JavaScript runtime 1.0ではオリジン書き換えは不可能でした。

ビューアリクエストイベントでHostヘッダーは書き換えられない

しかしJavaScript runtime 2.0からupdateRequestOriginメソッドが追加されたことでCloudFront Functionsでもオリジン書き換えが可能になりました。

Lambda@Edgeでオリジン書き換えをする

Lambda@Edgeを利用する場合、オリジンリクエストに設定するとHostヘッダーをいじってオリジン書き換えられます。なお、request.origin.custom.domainName = domainName;がなくてもオリジン書き換えは機能します。

'use strict';
exports.handler = (event, context, callback) => {
    const domainName = 'example.net';
    const request = event.Records[0].cf.request;
    const uri = request.uri;

    switch (uri) {
        case '/':
        case '/foo':
            // リダイレクト用レスポンスをクライアントに返す
            const response = {
                status: '301',
                statusDescription: 'Moved Permanently',
                headers: {
                    location: [{
                        key: 'Location',
                        value: `https://${domainName}${uri}`,
                    }],
                },
            };
            callback(null, response);
            break;
        default:
            // リライトはリクエストをアップストリームに転送する
            const clientIp = request.clientIp;
            request.origin.custom.domainName = domainName;
            request.headers['host'] = [{ key : 'host', value : domainName}];
            request.headers['X-Forwarded-For'] = [{ key: 'X-Forwarded-For', value: clientIp }];
            callback(null, request);
    }
};

Lambda@Edgeは月間100万リクエストまで無料です。また、リクエスト本文へのアクセスもでき、ビューアリクエストイベントで触らないヘッダー操作ができます。Lambda@EdgeはCloudFront Functionsより制約が緩く、ある程度複雑なこともできます。一方でただのオリジン書き換えにはオーバースペックです。

CloudFront Functionsでオリジン書き換えをする

CloudFront Functionsを利用する場合、ビューアリクエストイベントに設定します。JavaScript runtime 2.0を使うと、updateRequestOriginメソッドを利用してオリジン書き換えられます。JavaScript runtime 1.0はこの関数がなく、Hostヘッダーをいじれないためオリジン書き換えができません。

import cf from 'cloudfront';

function handler(event) {
  var domainName = 'example.net';
  var request = event.request;
  var uri = request.uri;
  var clientIP = event.viewer.ip;

  switch (uri) {
    case '/':
    case '/foo':
      // リダイレクト用レスポンスをクライアントに返す
      var response = {
        statusCode: 301,
        statusDescription: 'Moved Permanently',
        headers: {
          'location': { value: `https://${domainName}${uri}` }
        }
      };
      return response;
    default:
      // リライトはリクエストをアップストリームに転送する
      // cf.updateRequestOriginでオリジン書き換えができる
      cf.updateRequestOrigin({
        "domainName": domainName,
        "timeouts": {
          "readTimeout": 60,
          "connectionTimeout": 5
        },
        "customHeaders": {
          "X-Forwarded-For": clientIP,
        }
      });
      return request;
  }
}

CloudFront Functionsは月間1000万リクエストまで無料です。CloudFront FunctionsはLambda@Edgeに比べて制限が多く、リクエスト本文へのアクセスもできませんが、今回のようなオリジン書き換えには十分です。

Lambda@Edgeを使うかCloudFront Functionsを使うか

個人的には、リダイレクト、オリジン書き換えだけが目的ならCloudFront Functionsを利用するのがおすすめです。

Lambda@Edgeを避ける理由はいくつかあります。これまで出会ったケースでは、IaCとの相性、コスト、ログの3つであまり好みじゃありません。

IaCと相性の悪さは、Lambda@EdgeがLambdaのパブリッシュを必要とすることに起因します。例えばCloudFrontをLambda@EdgeからCloudFront Fucntionsへ差し替える操作は1度のterraform applyで完了できないため、2手順に分ける必要があります。まずCloudFrontとLambda@Edgeの紐づけを削除するterraform applyを実行してAWSがLambdaの公開を削除するまで待つ、次にLambdaを削除するterraform applyを実行します。Lambda発行をAWSが削除するタイミングは予測不能なため、IaCで1発操作できないのですが、本当に面倒です。

コスト1も避けたい理由です。LambdaはDatadogなどの監視サービスにて「サーバーレス」課金対象になり一定のコストがかかります。一方、CloudFront Functionsは「サーバーレス」課金対象になりません。

Lambda@Edgeは、CloudWatch Logsの管理リージョンがばらつくのも嫌な点です。Lambda@EdgeのCloudWatch LogsはCloudFrontのエッジリージョンで出力されるためログがばらつきます。地味にだるいです。

まとめ

2025年現在なら、CloudFront Functionsを利用してオリジン書き換えが可能です。Lambda@Edgeを使う強い理由がないなら、CloudFront Functionsを利用するのがおすすめです。

参考


  1. 無料枠を超えても微々たるコストでしょうから考慮には入れません