エッジで軽量な処理を実行できる CloudFront Functions を Terraform で作ってみた

4 min read読了の目安(約4200字

はじめに

CloudFront Functions が GW にリリースされました。
発表から 1 ヶ月近く経ち既にいろんな記事が出されていますが、今回試してみたのでどうせならと思い、まとめてみることにしました。

CloudFront Functions とは

概要

CloudFront Functions 以下 CF2 とは、世界中にある CloudFront のエッジロケーションで軽量の JavaScript コードを実行できる機能です。

ユースケースとしては、HTTP(s) リクエスト/レスポンスの操作が代表的です。任意のアプリケーションを運用していてエッジで何かしらのロジックを実行したい場合が考えられます。これまでだと、Lambda@Edge という CloudFront の機能を使うのが一般的でしたが、CF2 の誕生により高パフォーマンス、低コストで実現できるようになりました。

ユースケース

下記のような軽量な処理に向いているとされています。

  • URL やヘッダ、Cookie、クエリなどの HTTP リクエストの操作、正規化
  • A/B テストなどの URL リダイレクト
  • JWT の検証やそれによるアクセス制御

上記では賄いきれないような複雑な重い処理は Lambda@Edge を選択すべきです。CF2 にはメモリや最大実行時間など制約が厳しいため達成したい要件を制限内に実現できるかこちらを参考に検討してみてください。

やってみる

前提

今回は、URL 正規化をやってみました。下記の図のように CloudFront のオリジンとして Amazon S3 を設定しています。

CloudFront には、デフォルトルートオブジェクトと呼ばれる機能があり、ルートオブジェクトにのみ適用されますがサブフォルダには適用されないインデックスドキュメントを指定できます。つまりどういうことかというと、index.html をデフォルトのルートオブジェクトとして設定している場合、ユーザーが example.com にアクセスすると、CloudFront はリクエストを自動的に example.com/index.html に書き換えます。しかし、example.com/sample という URL にアクセスした場合、CloudFront は URL を書き換えず、オリジン S3 にリクエストを送信してします。そうすると、sample というオブジェクトは存在しないため 404 が発生してしまいます。

どうすればいいかというと、URL がスラッシュで終わっている場合は末尾に index.html と付けたり、URL にファイル拡張子を含んでいなかったら /index.html を付けたりということをすれば解決できそうです。

この処理を CF2 を使って解決していきます。

Function を作成

マネジメントコンソールより、CloudFront のページを開いて、Functions > Create Function と進んでいきます。関数名を決めた後に、コードエディタがあるので、こちら を参考にとりあえずコピペしました。

sample.js
function handler(event) {
    var request = event.request;
    var uri = request.uri;

    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

CloudFront に Function をアタッチ

マネジメントコンソールより、CloudFront のページを開いて、Distribution > Distribution Settings > Behavior > Edit behavior と進んでいきます。そして下記のように、先ほど作成した関数名を選択します。

確認

変更が反映されるまで待ち、先ほど 404 となっていた URL にアクセスすると無事 200 が返ってくるようになりました。CloudFront のビューアリクエストで URL の正規化がなされているのが分かります。

Terraform でコード化

既存の CloudFront, S3 を既に Terraform で管理している場合が多いため、CF2 もコード化しておこうと思いやってみました。

下記について簡単に説明します。

  • CF2
  • CloudFront の変更

このようなディレクトリ構成です。もし、全体を見たい場合は下記にサンプルコードを置いているのでそちらを参照ください。

https://github.com/ysmtegsr/cloudfront-functions-sample
$ tree
.
├── cloudfront
│   ├── functions
│   │   └── sample.js   # <-- CF2
│   ├── main.tf
│   ├── output.tf
│   └── variables.tf

CF2

まず CF2 リソースを作成します。リソース名は、aws_cloudfront_function です。

cloudfront/main.tf
resource "aws_cloudfront_function" "url_normalization" {
  name    = "url_normalization"
  runtime = "cloudfront-js-1.0"
  comment = "Appends index.html to request URLs"
  publish = true
  code    = file("${path.module}/functions/sample.js")
}

CloudFront の変更

続いて CloudFront を変更していきますが、設定項目がかなり多いので今回変更した部分だけ記載します。

cloudfront/main.tf
resource "aws_cloudfront_distribution" "s3_distribution" {
  ...

  default_cache_behavior {
+    function_association {
+      event_type   = "viewer-request"
+      function_arn = aws_cloudfront_function.url_normalization.arn
+    }
  }
}

まとめ

遅ればせながら CF2 を触ってみました。URL 正規化を試してみましたが、他にできることは多そうです。これまで、Lambda@Edge を使っていたようなシンプルな処理はどんどん CF2 に置き換えていこうかなと考えています。

途中でも述べましたが、Terraform でコード化したサンプルコードを こちら に置いているので、気軽に試してみたい方は是非使ってみてください。

参考にさせていただいたサイト

https://aws.amazon.com/jp/blogs/news/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/
https://dev.classmethod.jp/sample/cloudfront-functions-usecases/
https://dev.classmethod.jp/sample/amazon-cloudfront-functions-release/
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example-function-add-index.html