Lambda@Edgeとは
CloudFrontのエッジサーバでコードを実行するLambda関数です。
配信をカスタマイズ出来ます。
感覚としてはCloudFrontのフックで動くLambda、という感じですが考慮する点・制約がいくつかあります。
詳しくはドキュメント
設定出来るフック
Lambda@Edgeがフックに設定出来る箇所は四つあります。
キャッシュがある時はオリジンリクエスト・オリジンレスポンスは発生しません。
例:webp画像を配信して表示速度高速化したい
このようなLambda@Edgeを作成し、オリジンリクエストに設定してみます。
前提:S3+CloudFrontの構成は既に作成されているものとする。
Lambdaを用いたS3画像の取得・加工はドキュメントを参考にしています。
①IAMロールを作成
アクセス許可
AWSLambdaExecute
をアタッチ
信頼関係
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "edgelambda.amazonaws.com", "lambda.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }
② Lambda関数を作成
最初に述べた制約に則り関数を作成します。 今回の設定は以下です。
リージョン:us-east-1
ランタイム言語:Node.js 12.x
実行ロール:前項目で作成したロールを設定
③関数コードを作成
index.js
ファイルを作成
const AWS = require('aws-sdk'); const sharp = require('sharp'); const https = require('https'); const url = require('url'); const s3 = new AWS.S3(); const BASE_URL = 'CloudFrontURL'; const BUCKET_NAME = 'S3バケット名'; exports.handler = async (event, context) => { const request = event.Records[0].cf.request; // 画像のみ if (request.uri.match(/\.(jpe?g|png)$/) && request.uri.split[1] !== 'tmp') { const webpUri = request.uri + '.webp'; let cloudfrontReq = url.parse(BASE_URL + webpUri); let webpExits = false; // webpファイルへ正常にアクセス出来るか await new Promise((resolve, reject) => { cloudfrontReq.method = 'HEAD'; cloudfrontReq.timeout = 3000; https.request(cloudfrontReq, cloudfrontres => { if (cloudfrontres.statusCode >= 200 && cloudfrontres.statusCode < 300) webpExits = true; resolve(); }).on('error', reject).end(); }); // webpファイルが無い場合は作成する if (!webpExits) { const key = request.uri.substr(1) const webpKey = webpUri.substr(1) // オリジンの画像を取得 try { const params = { Bucket: BUCKET_NAME, Key: key }; let origimage = await s3.getObject(params).promise(); } catch (error) { console.log(error); return; } // webpへ変換 try { let buffer = await sharp(origimage.Body).webp().toBuffer(); } catch (error) { console.log(error); return; } // S3へput try { const putParams = { Bucket: BUCKET_NAME, Key: webpKey, Body: buffer, ContentType: "image" }; const putResult = await s3.putObject(putParams).promise(); webpExits = true; } catch (error) { console.log(error); return; } } // webpが存在する or 変換が成功した場合webpのuriでrequestを上書きする if (webpExits) request.uri = webpUri; } return request; };
④デプロイパッケージを作成
convert-webp
フォルダ(名前は任意)を作成し、そこにindex.js
を入れ、そこにnpmでsharp
ライブラリをインストールします。
$ npm install --arch=x64 --platform=linux --target=12.13.0 sharp
今回の場合のファイル構造は、以下のようになります。
convert-webp ├ index.js └ node_modules ├ sharp ├ ...
⑤.zipファイルをアップロード
作成したファイルをzipへ圧縮し、「関数コード」→「アクション」→「.zipファイルをアップロード」の手順でアップロードを行います。
⑥テストを行ってみる
こちらのドキュメントより、オリジンリクエストのイベント構造を確認出来ます。
⑦ Lambda@EdgeをデプロイしCloudFrontへ設定する
「デザイナー」→「トリガーの追加」をクリックします。
「トリガーを選択」→「CloudFront」で出現する「Lambda@Edgeへのデプロイ」項目を選択します。
- ディストリビューション:対象のディストリビューションを選択する
- キャッシュ動作:*
- CloudFrontイベント:オリジンリクエスト
- ボディは含めない
- Lambda@Edge へのデプロイを確認をチェック
以上を入力し「デプロイ」をクリックで設定完了です。
⑧確認
DevToolを開き「Networkタブ」→対象の画像を選択
Responce headerのcontent-typeがimage/webp
になっていればOKです!
注意点なのですが今回はオリジンリクエストにフックしているため、キャッシュが残っている状態だとオリジンリクエストが発生せず関数が発火しません。
確認の際はご注意ください。
まとめ
今回は省略していますが、CloudFrontのheaderのホワイトリストでAccept
を追加し、webpを許可しているブラウザかの確認の処理があった方が良いかと思います。
Lambda@Edgeを使用せずにS3へのオブジェクト追加をフックにしたLambdaでwebpファイルを生成するという方法も考えられますが、同一バケットの「オブジェクト生成」フックで新規オブジェクト追加する構成は再起的に実行されてしまう危険性があるため推奨されていません。
やはりキャッシュがない時(オリジンリクエストが発生する時)の起動は体感でも遅く感じるので、ユースケースに合わせてキャッシュの有効期限を長くしたりなど対処した方が良いかなと思いました。
最近話題のLambdaのコンテナイメージフォーマットもどうやら使えるようなので、こちらも試してローカルまで一貫したサクサク開発にしてみたいです。
〜最後に〜
Wizではエンジニアを募集中です!
興味のある方は是非ご覧ください!