CloudFront FunctionsとLambda@Edgeの違いの要点
CloudFront Functionsは超軽量で毎秒 10,000,000 件以上の大量のアクセスを捌けますが、可能な実行時間はミリ秒程度です。一方、Lambda@Edgeは1 リージョンあたり毎秒 10,000 件までのリクエストしか捌けませんが、Vewer Request、Vewer Responseのイベントタイプで最大5秒。Origin Request、Origin Responseで最大30秒の実行時間が可能となります。この点がどちらを選ぶかの大きなポイントになるでしょう。
- Origin Request: クライアントリクエストがオリジンサーバーに転送される前
- Origin Response: オリジンサーバーからのレスポンスがクライアントに送られる前
- Viewer Request: クライアントからCloudFrontへのリクエストを処理する前
- Viewer Response: CloudFrontからクライアントへレスポンスを返す前
軽量な処理であれば、まずはCloudFront Functionsを使うことを検討するのが良いでしょう。主にヘッダやルールに基づいてヘッダーをカスタマイズしたり、アクセス制限やリダイレクトを行ったりといったものが主になります。例えば以下のような処理です。
- HTTPヘッダーの内容をカスタマイズしてレスポンスをブラウザへ返す
- 拒否したいIPアドレスやBotのユーザエージェントのリストをベースとしたアクセス制限
- ルールに基づいてリクエストURLを書き換え、ABテストのためのリクエストの振り分けをしたり強制的に最新バージョンのURLにリダイレクトさせたり出来ます
- キャッシュのヒット効率をあげるためのクエリストリングの正規化
- 国に基づいたアクセス制限やリダイレクト
その他のCloudFront Functionsの制限としては、プログラミング言語が、ECMAScript 5.1 準拠のJavaScript(Node.jsやTypeScriptは使えない)で書く必要があったり、デプロイ出来るパッケージ含めたコードのサイズは10KBまでなので、ほぼ外部のライブラリは使えません。また、カスタマイズできるCloudFrontもビューワーリクエスト、ビューワーレスポンスのみになるため、オリジンとCloudFrontエッジ間の通信についてはカスタマイズが出来ません。
これ以外の場合はLambda@Edgeを使用することを基本方針としておくとよいでしょう。
機能比較
特徴 | Lambda@Edge | CloudFront Functions |
---|---|---|
実行タイミング | ・Viewer Request | ・Viewer Request |
サポートされる言語 | Node.js、Python | JavaScript(ECMAScript 5.1準拠) |
リクエスト数 | 毎秒 10,000,000 件以上 | 1 リージョンあたり毎秒 10,000 件まで |
関数の実行時間 | サブミリ秒 | 最大 5 秒 (Viewer Request、Viewer Response) 最大 30 秒 (Origin Request、Origin Response) |
最大メモリ | 2 MB | 128 MB – 10,240 MB (10 GB) |
コードとライブラリの最大サイズ | 10 KB | 1 MB (Viewer Request、Viewer Response) 50 MB (Origin Request、Origin Response) |
料金 | Lambda 実行時間に基づいて課金 | 非常に低コスト |
CloudFront Functionsの特徴
基本的には要点のところで述べましたが、特徴はコストが安くて大量のアクセスを捌ける代わりに、処理時間やメモリ、デプロイパッケージサイズ及び使える言語に制限があることです。その他の特徴としては、CloudFront FunctionsはAmazon CloudFront KeyValueStoreというキーバリューストアを使用できることです。ここにリダイレクトのルール、アクセス制限のルールやABテストの際のリクエスト振り分けのパーセンテージなどの設定を保存してソースコードとデータを切り分けることが主なユースケースです。使用できるデータ形式としては以下をサポートしています。
- 文字列
- バイトでエンコードされた文字列
- JSON
Lambda@Edge の特徴
処理時間が長く、複雑な処理を扱えることがCloudFront Functionsに対するLambda@edgeの特徴になるでしょう。また、オリジンとエッジロケーション間のデータをカスタマイズするためのイベントであるOrigin Request、Origin ResponseのイベントについてはLambda@Edgeでしか操作ができません。
例えば、画像のフォーマットを軽量なものに変換したり、画像がアップロードされた際にサムネイルを生成すると行った処理はCloudFront FunctionsではなくLambda@edgeを使ったほうが良いでしょう。また、他の認証サーバーにネットワーク経由でアクセスしてリクエストの認証を行うといった場合もネットワークの通信による遅延が発生するためLambda@edgeの方が良さそうです。
CloudFront Functionsの実装例
いくつか具体的なCloudFront Functionsの実装例を紹介していきます
Cache-Control headerをレスポンスヘッダに強制的に付与する
この関数は、CloudFrontからブラウザへのレスポンスに送信される「Cache-Control」ヘッダーを追加します。もしオリジンが「Cache-Control」ヘッダーを送信しておらず、CloudFrontのキャッシュの動作に依存してCloudFront側のキャッシュ制御をしている場合、CloudFrontはブラウザのキャッシュ用に「Cache-Control」ヘッダーを送信しません。この関数は、ブラウザ側でコンテンツをローカルにキャッシュできるように「Cache-Control」ヘッダーを追加します。これにより、CloudFrontのコスト削減ができ、同時にサイトのユーザーに対してより良いパフォーマンスを提供します。
イベントタイプ:Viewer Response
function handler(event) {
var response = event.response;
var headers = response.headers;
// cache-control headerをセット
headers['cache-control'] = {value: 'public, max-age=63072000'};
return response;
}
セキュリティ用のレスポンスヘッダーの付与
例えば、Webアプリケーションにおける脆弱性審査で以下のようなレスポンスヘッダーを付与することを指摘されるケースがあると思います。これらもCloudFront Functionsで可能です。
- HTTP Strict-Transport-Security(HSTS): ブラウザに対して、ウェブサイトにアクセスする際にはHTTPSのみを使用するよう指示するHTTPレスポンスヘッダーです。ブラウザは、自動的にHTTPでのアクセスをHTTPSリクエストに変換します。
- Content Security Policy(CSP): クロスサイトスクリプティング(XSS)やデータインジェクション攻撃など、特定の攻撃を検出し軽減するのに役立つHTTPレスポンスヘッダーです。
- X-Content-Type-Options: Content-Typeヘッダーで広告されているMIMEタイプが、そのまま使用されるべきであることを示すHTTPレスポンスヘッダーです。これにより、MIMEタイプのスニッフィング(自動判別)を無効にし、MIMEタイプが意図的に設定されていることを主張します。
- X-Frame-Options: これは、ブラウザが
<frame>
、<iframe>
、<embed>
、または<object>
タグ内でページを表示できるかどうかを示すHTTPレスポンスヘッダーです。サイトはこれを使用して、クリックジャッキング攻撃を防ぐために、コンテンツが他のサイトに埋め込まれないようにすることができます。 - X-XSS-Protection: これは、Internet Explorer、Chrome、Safariの機能で、反射型クロスサイトスクリプティング(XSS)攻撃を検出した場合にページの読み込みを停止するHTTPレスポンスヘッダーです。モダンブラウザでは、インラインJavaScript(
'unsafe-inline'
)の使用を無効にする強力なContent Security Policyが実装されている場合、この保護はほとんど不要ですが、CSPをまだサポートしていない古いブラウザのユーザーに対しては依然として保護を提供します。
イベントタイプ:Viewer Response
function handler(event) {
var response = event.response;
var headers = response.headers;
headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload'};
headers['content-security-policy'] = { value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; frame-ancestors 'none'"};
headers['x-content-type-options'] = { value: 'nosniff'};
headers['x-frame-options'] = {value: 'DENY'};
headers['x-xss-protection'] = {value: '1; mode=block'};
headers['referrer-policy'] = {value: 'same-origin'};
// Return the response to viewers
return response;
}
クエリストリングの正規化によるキャッシュヒット効率の改善
通常のリクエストでは、https://example.com?key1=value1&key2=value2
とhttps://example.com?key2=value2&key1=value1
は同じレスポンスになるにも関わらず、クエリストリングの並びが違うため同じキャッシュにはヒットしません。この関数を使うとクエリストリングの並びをアルファベット順にソートするため、キャッシュのヒット効率が上昇します。
イベントタイプ: Viewer Request
function handler(event) {
var qs=[];
for (var key in event.request.querystring) {
if (event.request.querystring[key].multiValue) {
event.request.querystring[key].multiValue.forEach((mv) => {qs.push(key + "=" + mv.value)});
} else {
qs.push(key + "=" + event.request.querystring[key].value);
}
};
event.request.querystring = qs.sort().join('&');
return event.request;
}
URLの最後にindex.htmlを追加する
この関数を使用すると、ファイル名や拡張子が含まれていないURLの末尾に「index.html」を追加するURLのリライトを実行できます。静的サイトやシングルページアプリケーションではURLパスからファイル名や拡張子を削除します。たとえば、ユーザーが「www.example.com/blog」にアクセスした場合、実際にはS3にあるファイルは「<bucket-name>/blog/index.html」として保存されています。CloudFrontが正しいファイルをS3から取得するためには、リクエストURLを「www.example.com/blog/index.html」にリライトする必要があります。この関数は、CloudFrontへのリクエストを受け取り、ファイル名と拡張子が含まれていないかチェックして、URIが「/」で終わっている場合に、「index.html」をURIに追加します。
イベントタイプ: Viewer Request
function handler(event) {
var request = event.request;
var uri = request.uri;
// 「/」でURLが終わっていればindex.htmlを追加
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// ファイルの拡張子「.」が存在しなければindex.htmlを追加
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
リクエスト元の国に応じて適切なURLにリダイレクト
多言語サイトなどではリクエスト元の国に応じて、その国の言語で書かれているページにリダイレクトさせたいケースがあるでしょう。これも以下の関数で可能になります。cloudfront-viewer-country
のヘッダーを見ることでリクエスト元の国コードがわかるため、それを元に処理を記述します。
イベントタイプ: Viewer Request
function handler(event) {
var request = event.request;
var headers = request.headers;
var host = request.headers.host.value;
var country = 'JP'
var newurl = `https://${host}/jp/index.html`
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;
}
まとめ
CloudFront Functionsのユースケース及びLambda@Edgeとの違いを説明しました。CloudFront Functionsはそれなりに制約が多すぎる環境ではありますが、その処理速度やコストは大きな魅力になると思います。主にヘッダーの操作や簡単なルールを元にしたアクセス制限やリダイレクトの場合にはまずCloudFront Functionsを検討するのが良いでしょう。一方、画像などのバイナリデータを直接エッジで操作したり、エッジからネットワーク通信を発生するようなものは処理も時間もかかるため、Lambda@Edgeを選定するとよいでしょう。