Amazon Web Services ブログ

CloudFront Functions の実装と本番トラフィックでのテスト

ウェブアプリケーションを維持しながら、低レイテンシーでの実行が要求されるシンプルなロジックの構築が必要とされる時があります。例えば、条件に基づいたウェブサイトのリダイレクト設定や、受信ヘッダーの素早い検証などが欲しくなるかもしれません。CloudFront Functions は、レスポンスヘッダーの追加や変更、リクエストの新しい URL へのリダイレクト、カスタムレスポンスでの応答といった軽量な JavaScript コードを記述することができるので、これらの使用例に最適と言えます。

このスクリプトベースのアプローチは高い柔軟性を提供しますが、効果的なコードを書き、正しくテストをする責任も伴います。以降で紹介するコード実装時のベストプラクティスによって、エッジコンピューティングの機能を最も効率的に使用し、ランタイムエラーのリスクを最小限に抑えることができます。

この記事では、CloudFront Functions のコードの実装とテストにおけるいくつかの考慮事項と、実際の HTTP トラフィックパターンで関数をテストする方法を共有します。

CloudFront Functions の特徴

CloudFront Functions は、シンプルな HTTP リクエストまたはレスポンスの操作に適した、セキュアで高速でコスト効率の高いエッジコンピューティングです。Amazon CloudFront のビューワーリクエストまたはビューワーレスポンスイベントにより CloudFront Functions の実行がトリガーされます。

CloudFront Functions のコードはどのようなものでしょうか。以下は1つの例となります(こちらの GitHub リポジトリ でより多くの例を見つけることができます)。

function handler(event) {
    var request = event.request;
    var headers = request.headers;
    var host = request.headers.host.value;
    var country = 'DE' // Choose a country code
    var newurl = `https://${host}/de/index.html` // Change the redirect URL to your choice 
  
    if (headers['cloudfront-viewer-country']) {
        var countryCode = headers['cloudfront-viewer-country'].value;
        if (countryCode === country) {
            var response = {
                statusCode: 302,
                statusDescription: 'Found',
                headers:
                    { "location": { "value": newurl } }
                }

            return response;
        }
    }
    return request;
}

ご覧のように、このコードは必須の handler(event) 関数を含んだ JavaScript のコードです。関数が CloudFront に関連付けられると、CloudFront は handler 関数をエントリーポイントとして実行します。 event オブジェクトは、イベントトリガーに応じた HTTP リクエストのメタデータまたはレスポンスのメタデータを含んでいます。handler 関数は HTTP リクエストオブジェクトまたはレスポンスオブジェクトを返す必要があり(ビューワーレスポンスイベントの場合は HTTP レスポンスのみが許可されます)、返却されたオブジェクトによって CloudFront は処理を継続します。 CloudFront Functions ではレスポンスオブジェクトのヘッダーのみ変更が行える一方で、Lambda@Edge はレスポンスボディも変更できることに留意してください。

CloudFront Functions はリクエスト event オブジェクトを返却してフローを続行するか、レスポンスを返してフローを変更することで、ビューワーに応答します。

図1. CloudFront Functions は HTTP リクエストまたは HTTP レスポンスオブジェクトを返す必要がある

CloudFront Functions は、レイテンシを最小限に抑えつつも最大規模で動作させるために、制限された CPU 時間内で実行されるように設計されています。関数の各実行ではコンピューティングリソースが制限されており、それはコンピューティング使用率(Compute Utlization)メトリックとしてパーセンテージで表現されます。許容量を超えてコンピューティングリソースを使用する場合、CloudFront Functions は失敗する可能性があります。これにより、CloudFront はビューワーに対してエラーレスポンスを返すことになります。CloudFront コンソールおよび TestFunction API では、テスト結果の一部としてコンピューティング使用率を提供しており、テスト実行中に最大許容コンピューティングリソースの何パーセントが利用されたかを表示します。なお、実装したコードはコンピューティングリソースを100%使用すべきではありません。コンピューティング使用率は変動する可能性があるため、80%以下の使用率など、余裕を持たせることをお勧めします。

CloudFront コンソールは、関数のコンピューティング使用率を表示します。

図2. CloudFront コンソールにおける関数のコンピューティング使用率の表示

CloudFront は、デプロイされた関数のコンピューティング使用率を Amazon CloudWatch のメトリックとして出力します。この CloudWatch メトリックを監視することで、ほぼリアルタイムで関数のコンピューティング使用率を確認できます。

CloudFront Functions のコンピューティング使用率を表示する CloudWatch メトリクスの例

図3. CloudFront Functions のコンピューティング使用率を示す CloudWatch メトリックの例

CloudFront Functions のコードのテスト

コードの記述が終了したら、いくつかのテストケースでコードをテストして、関数が意図した通りに動作することを確認しましょう。このようなテストは通常、ユニットテストと呼ばれます。少なくとも1つのテストケースでは、期待される入力に対して関数が正常に動作するかどうかを検証します。他のテストケースでは、コーナーケースに対しても関数が壊れないかどうかを検証します。CloudFront Functions においては、関数がコンピューティング使用率の許容範囲内に収まるかも確認する必要があります。

コンソールまたは API を使用してコードをテストし、HTTP リクエストのコンテキストデータ(ビューワーレスポンスイベント用の関数であれば、HTTP レスポンスのコンテキストデータも)を準備します。CloudFront のコンソールでは、シミュレートされたコンテキストデータを用いて関数を迅速にテストできるように、入力データのスケルトンを提供しています。

CloudFront コンソールで関数をテストする

図4. CloudFront コンソールを使用した関数のテスト

また、API 呼び出しを行う場合は、以下のようなコンテキストデータをJSONオブジェクトとして与える必要があります。

{
    "version": "1.0",
    "context": {
        "eventType": "viewer-request"
    },
    "viewer": {
        "ip": "198.51.1.1"
    },
    "request": {
        "method": "GET",
        "uri": "/example.png",
        "headers": {
            "host": {"value": "example.org"}
        }
    }
}

ご覧の通り、ヘッダー、クッキー、クエリ文字列のセットによるテスト対象に適した正しい入力が作り出され、結果を確認できます。ここで、テストの効果を高める方法がいくつかあります。

可能な限りの全てのコードパスのテストと自動化

CloudFront Functions のコードは軽量であるため、多くの場合、コードの全ての実行パスに対してテストケースを書くことができます。テストへの入力は1つ以上あるため、テストへの入力をそれぞれ読み込んで API 呼び出しを実行するテストプロセスは自動化すると良いでしょう。例えば、複数のテストオブジェクトを用いて関数をテストする場合は、以下のコマンドを実行します。

for f in *.json; do aws cloudfront test-function \ 
    --name ExampleFunction \ 
    --if-match ETVABCEXAMPLE \ 
    --event-object fileb://$f \
    --stage DEVELOPMENT; \
done

実際のトラフィックデータでのテストとコンピューティング使用量の監視

既存の CloudFront ディストリビューションに対して新しい CloudFront Functions を実装する場合、本番環境へのデプロイ前に、実際のトラフィックデータを用いてテストすることは有益です。それにより、ユニットテストの時には予期していなかったコーナーケースが明らかになる場合があります。例えば、User-Agent ヘッダーが異常値であるクライアント、またはヘッダー自体が欠落しているクライアントを見つけられるかもしれません 。また、関数のコンピューティング使用率をテストすることもできます。実際には、コードの有効性を高めるために、コードを書く前に実際のトラフィックデータをレビューすることが望ましいです。まだデプロイしていない新規の CloudFront ディストリビューションに対する関数を実装する場合であっても、類似する既存のディストリビューションからテスト可能なデータを取得することを推奨します。

本番トラフィックに基づいたテストの作成

本番トラフィックに基づいたテストデータを作成するにはどうすればよいでしょうか。アクセスログを収集し、それらをテストの入力へと加工する必要があります。アクセスログを処理する方法は数多くありますが、本記事では、例として Amazon Athena を使用します。仮に、ビューワーリクエストにてリクエストヘッダーの referer と URI パスを読み取る CloudFront Functions を実装するとしましょう。以下のクエリを使用することで、最も頻繁に現れる referer ヘッダーと URI パスを抽出できます。

SELECT
count(*) cnt, uri, referrer 
-- please change the table name to yours
FROM combined 
-- filtering 24 hours(1d) data
WHERE concat(year, month, day, hour) >= DATE_FORMAT 
GROUP BY uri, referrer 
ORDER BY cnt desc
-- top 10 requests
limit 10

さらに一歩進んで、以下のように、CloudFront Functions のコードに対する自動テストに利用することもできます。詳細については サンプルコード を参照してください。

python3 testingCFF.py --function CORS-Preflight 
input(url, referer) --> output(status(Err or OK), ComputeUtilization%) 
input(/images/sample.jpg, https://example.com/) --> output(OK, 11%) 
input(/images/sample2.jpg, https://example.com/) --> output(OK, 15%) 
input(/images/sample2.jpg, -) --> output(OK, 14%) 
……………

考慮事項

この方法を採用する場合、コストを考慮する必要があります。 Athena によるログのクエリコストはスキャンされたデータに基づいて計算されます。したがって、存在する全てのログに対するクエリの実行はお勧めできません。その代わりに、ログをパーティションにロードし、ログの一部だけをスキャンするようにクエリを制限できます。さらに、 CloudFront ディストリビューションが HTTP リクエストとレスポンスを大量に配信する場合には、サンプリングレートを低く設定した CloudFront のリアルタイムログも使用できます。

また、CloudFront 標準ログにはヘッダーの数に制限があります。そのため、標準ログに含まれていないヘッダーを使用する場合は、リアルタイムアクセスログが必要になるかもしれません。

他のリソースと一緒に CloudFront ディストリビューションを管理しているようであれば、CloudFront Functions のテストをコードのパイプラインに統合すると良いでしょう。

まとめ

このブログでは、CloudFront Functions のコンピューティング使用率をテストする必要性と、実際のトラフィックデータを用いたテストによってコンピューティング使用率の過剰利用を避ける方法について学びました。

詳細については、CloudFront Functions のドキュメントを参照するか、GitHub のサンプルコードを確認してください。

AWS Management Console からコードを書き始めることができます。

本記事は「Writing and testing CloudFront Functions with production traffic」と題された記事の翻訳となります。翻訳はプロフェッショナルサービスの 鈴木(隆) が担当しました。