注目の記事

エッジで爆速コード実行!CloudFront Functionsがリリースされました!

CloudFront Functionsがリリースされました!本記事ではLambda@Edgeとの使い分けやCloudFront Functionsの始め方、Lambda@Edgeとの比較を解説していきます。
2021.05.04

珍しく早起きをしてRSSを眺めてるとアッツアッツなアップデートが来ていました。

Amazon CloudFront announces CloudFront Functions, a lightweight edge compute capability

今回はCloudFront Functionsをご紹介していきます。

CloudFront Functionsとは?

CloudFront Functions(CF2)はLambda@Edgeより手前で、シンプルな処理をより高速に、素早く、安価に実行できるサービスです。

CloudFront Functionsを使うことでこれまでLambda@Edgeで実行していたシンプルな処理をよりユーザーに近いEdge Locationで実行しつつ、高速に処理を行う事ができます。

また、CloudFront FunctionsとLambda@Edgeを合わせて利用する事もできるので、シンプルな処理やHeader、URLの書き換えなどを処理をCloudFront Functionsで実行した上で、Lambda@Edgeでより高度な処理を行うような事もできます。

CloudFront Functionsではその特性上得意な処理・苦手な処理があり、使い分けが難しいと言われてしまいそうなため、本記事では重点的にLambda@Edgeとの使い分けを解説していきます。

Lambda@Edgeとの違い

Lambda@EdgeとCloudFront Functionsの差は次のようになります。

CloudFront Functions Lambda@Edge
Runtime support JavaScript(ECMAScript 5.1 compliant) Node.js, Python
実行されるロケーション (218+)CloudFront Edge Locations 13 CloudFront Regional Edge Caches
CloudFront triggers supported Viewer request, Viewer response Viewer request, Viewer response, Origin request, Origin response
最大実行時間 1ms未満 5 seconds (viewer triggers), 30 seconds (origin triggers)
最大メモリ 2MB 128MB (viewer triggers), 10GB (origin triggers)
Total package size 10 KB 1 MB (viewer triggers), 50 MB (origin triggers)
Network access No Yes
File system access No Yes
Access to the request body No Yes
料金 無料枠あり、リクエスト数ベースの料金 無料枠なし、リクエスト数と実行時間ベースの料金

(AWS公式ブログより、一部翻訳)

最大実行時間がLambda@Edgeは5秒に対して、CloudFront Functionsでは 1ms! と、制限が厳しい形となっています。

また、最大メモリは2MB、ネットワークのアクセスやファイルシステムのアクセスなどもできないため、DynamoDBや外部のAPIを叩くような処理についてはCloudFront Functionsではできない点にも注意が必要です。

個人的にはLambda@EdgeはRegional Edge Cacheで実行されるが、CloudFront FunctionsはEdge Locationで実行されるのが一番アツいと思ってます。

(画像はAWS公式ブログより引用)

(日本ではあまり地理的なメリットがないのかもしれませんが、)例えば、302などのリダイレクト処理が大量に走るような環境だと、Regional Edge Cacheに到達する前に各Edge Locationでレスポンスが返却されるため、高速にレスポンスを返せます。

Lambda@Edgeとの使い分け

CloudFront Functionsでは実行時間が1msで、シンプルなECMAScript 5.1 compliantなJavaScriptを書くことしかできないため、ヘビーなワークロードを動かすのには向いていません。

逆に言えば、渡された情報を元にちょろっと書き換える処理(国別にパスを書き換えたいなど)、ちょっとしたHeader/Cookieを書き換える処理、302を返す処理には最適な選択と言えます。

また、CloudFront FunctionsはCloudFrontと連携した機能で、1秒あたり数百万規模までスケーリングする事ができます。

Lambda@Edgeのデフォルトの同時実行数は1000で、必要に応じて事前に上限緩和申請を行って同時実行数を緩和する必要がありますが、CloudFront Functionsではその必要が無いため、「ちょっとしたHeader追加処理をLambda@Edgeで書いてたけど、同時実行数を監視していなかったせいでエラーになってしまった」なんてことが無くなりそうです。

ざっと思いつく限りのユースケースを書いてみました。

CloudFront Functionsのユースケース

  • Headerの書き換え、Cookieの挿入などシンプルな処理
  • JWTの検証
  • リクエストしてきた国をベースとしたパスルーティング処理
  • AWS WAFを使うまでも無いちょっとしたIP制限
  • オリジンをS3とした時のパスルーティング処理
  • CloudFrontキャッシュヒット率向上を狙うための、キャッシュキーの正規化処理

各ユースケースの詳細については下記の記事が参考になりますので、ぜひ合わせてご確認ください。

Lambda@Edgeを使うべきユースケース

逆にCloudFront Functionsで実行しきれないような処理は今まで通りLambda@Edgeで実行してあげると良いでしょう。例えば次のような処理が上げられると思います。

  • ヘビーな処理
  • 画像最適化処理
  • DynamoDB等の外部リソースを利用したLambda関数
  • ファイルシステムを利用したい場合や、外部のAPIを叩く処理
    • Request BodyをKinesisに流し込むなど
  • その他5秒以内(Origin Triggerの場合は30秒以内)に完結出来る処理

CloudFront Functionsの料金

リリース時点(2021/05/04)の料金は 100万回実行/$0.10、1リクエストあたり0.0000001$ となっています。

Lambda@Edgeは 100万回実行/$0.6に加えて実行時間にも料金が課金 されます。

そのため、Lambda@Edgeを使わなくともCloudFront Functionsで実行出来る簡単な処理などはコスト面などでも安価で採用できるかと思います。

Amazon CloudFront Pricing

やってみる

文章ベースで読んでいてもしっくり来なかったため、実際にCloudFront-Viewer-Countryを見てパスを出し別ける関数を使って検証をしてみました。

検証で使うCloudFrontはUse All Edge Locations (Best Performance)でデプロイして、オリジンにS3を利用する簡単な構成となっています。

また、Whitelist HeadersにCloudFront-Viewer-Countryを入れています。

$ curl -I https://d19m0yfxx7s0th.cloudfront.net/index.html
HTTP/2 200 
content-type: text/html
content-length: 14
date: Tue, 04 May 2021 03:06:08 GMT
last-modified: Mon, 28 Sep 2020 22:50:14 GMT
etag: "7b4b47772e9d6247ebf08303ca04b8df"
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 70e24e789a7f5c3f75693b4d637a2d22.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C1
x-amz-cf-id: 2UXrN_JacM2WdJu-F1vprV7eC3u5jtMJBhblesJtXt4Im0zVCV4jPg==
age: 61

早速CloudFront Functionsの設定をしていきましょう。マネジメントコンソールより左側に新たにFunctionsが出ていますので、そちらからCreate functionをしていきます。

今回利用するコードはaws-samples/amazon-cloudfront-functionsのredirect-based-on-countryを一部書き換えた下記のようなコードとなります。

function handler(event) {
    var request = event.request;
    var headers = request.headers;
    var host = request.headers.host.value;
    var country = 'JP' // Choose a country code
    var newurl = `https://${host}/jp/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;
}

マネジメントコンソールで上記コードをペーストして保存してあげます。

次にAssociateからCloudFrontへの紐付けを行っておきましょう。デプロイしたいCloudFrontとイベント、Cache behaviorを指定する事ができます。

Lambda@Edgeでは一旦Lambda関数を保存した上でLambda@Edgeへデプロイするボタンをポチッとすることでデプロイされますが、CloudFront Functionsの場合はDevelopmentとLiveの2つがあり、Developmentでテストをした上でLiveでデプロイするような流れとなりそうです。

マネジメントコンソールでテストができるそうなので、ヘッダーにcloudfront-viewer-countryを入れてテストしてみましょう。

OutputにStatusCode、StatusDescriptionが表示されて、正常に遷移していることを確認できます。

ここで緑色に表示されている Compute utilization は100を最大とした場合の実行時間を数値化した物となり、今回の関数は32だったので1msの許容範囲内であることを確認できます。

テストが完了しましたら、Publish画面からPublish and updateをしていきましょう。

数分後、curlでリクエストを投げると302が帰ってきて、/jp/index.htmlを指している事を確認できるかと思います。

$ curl -I d1myqfs1kr9dq.cloudfront.net
HTTP/1.1 302 Found
Server: CloudFront
Date: Tue, 04 May 2021 04:07:49 GMT
Content-Length: 0
Connection: keep-alive
Location: https://d1myqfs1kr9dq.cloudfront.net/jp/index.html
X-Cache: FunctionGeneratedResponse from cloudfront
Via: 1.1 ece495703bac6f634e6e16b4037affae.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: NRT57-C4
X-Amz-Cf-Id: -kbjLBaxz26faDagHTsxU2DmtPwuOxsHaanS8k5041-npYM3Wwiy5w==

ロギング

console.logがコード内にある場合、us-east-1のCloudWatch Logsへ送信されます。なお、Lambdaのように実行時間が何ms〜のような情報は出力されない点に留意が必要です。

CloudWatch Metrics

CloudWatch Metricsへは次の4つのメトリクスがパブリッシュされます。

メトリクス名 説明
FunctionInvocations Functionの呼び出し
FunctionValidationErrors returnしたオブジェクトのValidationに失敗
FunctionExecutionErrors Functionの実行エラー
ComputeUtilization Compute利用率(100が最大)

実行するコードにもよりますが、監視するとなると、FunctionValidationErrorsとFunctionExecutionErrorsを設定しつつ、ComputeUtilizationを少し厳しめ(60〜80くらい)な閾値で設定すると良い気がします。

同じ物をLambda@Edgeで実行した場合

Lambda@Edgeを利用していたときは実行に1ms以上掛かっていたような気がするので、同じような物をLambda@Edgeでデプロイして測定してみました。

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const host = request.headers['host'][0].value;
    const country = 'JP' // Choose a country code
    const newurl = `https://${host}/jp/index.html` // Change the redirect URL to your choice
    if (headers['cloudfront-viewer-country']) {

        const countryCode = headers['cloudfront-viewer-country'][0].value;
        if (countryCode === country) {
            const response = {
                status: '302',
                statusDescription: '302 found',
                headers: {
                    location: [{
                        key: 'Location',
                        value: newurl,
                    }],
                }
            }
            return callback(null, response);
        }
    }

    callback(null, request);
};

結果、CloudWatchメトリクスベースで見ると平均でだいたい10〜20ms程度で実行されていることを確認できました。CloudFront Functionsで実行できるような内容であれば積極的に利用した方がスピード面でも良くなるかと思います。

まとめ

CloudFront Functionsを一言で表すなら「エッジでの実行に最適化されたライトなLambda」だと思います。

ヘッダーの書き換えや302などのレスポンスなどのシンプルな処理をこれまで通りLambda@Edgeで作る事もできますが、CloudFront Functionsでは安価かつ高速にレスポンスできるので、使えるポイントでは是非CloudFront Functionsを使って行きましょう。

また、恥ずかしながらLambda@EdgeはEdge Locationで実行される物かと思っていましたが、Edge Locationの1個奥のCloudFront Regional Edge Cachesで実行されることを初めて知りました。AWSの物理的なレイヤーを感じられて、検証していてとても面白かったです。

参考

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html

https://aws.amazon.com/jp/about-aws/whats-new/2021/05/cloudfront-functions/