less is more

心のフルスタックエンジニア👨‍💻バイブスでコードを書いています🤘

Lambda@Edgeを完全に理解する🧘‍♀️

とは

2017年7月17日に正式リリースされたサービス。

Lambda@Edge の一般提供を開始

AWS Lambda にコードをアップロードし、Amazon CloudFront イベント (ビューワーリクエスト、ビューワーレスポンス、オリジンリクエスト、オリジンリクエストなど) によってトリガーされるように設定するだけです。関連するリクエストが CloudFront によって受信されると、ビューワーに近い最適な AWS のロケーションに実行のためルーティングされます。次に、Lambda@Edge はコードを実行し、CloudFront のグローバルなネットワーク間のリクエストのボリュームに応じてスケールします。Lambda@Edge により、コードを実行して各個別のリクエストに基づいてウェブページをカスタマイズし、グローバルに実行されるカスタム認証ロジックを作成して、安全なカスタムヘッダーの配信を簡略化できます。

分かりにくいですね。言い換えると

エッジロケーションに分散された、CloudFrontのリクエストをプロキシする、アプリケーションサーバー(=コンピューティング環境=Lambda) というイメージです。

Lambdaとはどういう関係か

継承関係で言うところの、 Lambda@Edge is a Lambda.

Lambda@Edge とは、AWS Lambda の拡張機能で、CloudFront が配信するコンテンツをカスタマイズする関数を実行できるコンピューティングサービスです。

コードをアップロードして実行してくれるサーバーレスなコンピューティング環境という点では同じですが、Lambda@Edgeには色々と制約があります。

Node.jsとPythonのみをサポート

リリース時はNode.js一択でした。 2年後の2019年8月1日に、Python3.7が追加。 Lambda@Edge が Python 3.7 のサポートを追加

使い道が限られているので、これで十分です。

us-east-1 でしか作れない

これは制約といえば制約なんですが、そもそもLambda@Edgeを利用する上で、リージョンという概念を気にする必要はありません。

最適なエッジロケーションで実行するために、コンピューティング環境はあらゆるリージョンに置かれるので、最初にどこで作成しようが同じです。

あくまで便宜上 us-east-1 になっているという話。 S3などグローバルに考える必要のあるサービスではこういうルールがちょくちょくある。AWSで探し物してて見つからなかったら us-east-1 を探せとおばあちゃんが言ってました。

CloudFrontと紐づけて使う必要がある

この表現は半分正解で半分間違い。 必要がある、というよりは、CloudFrontに紐づけたLambdaをLambda@Edgeと呼びます

CloudWatch Event から定期的に Lambda@Edge をキックしよう、 みたいなことはできないし、そういう考え方自体が間違いです。

Lambda@Edge を呼び出すのは CloudFront であって、それはユーザーからのHTTPリクエストありきの話ですよね。

f:id:bluepixel:20200411212219p:plain

どうやって作るのか

普通にLambdaの関数を作る。 先述の通り、特別 Lambda@Edge を作るみたいなインターフェースは存在しない。 バージニア北部で、サポートされている言語で作るだけである。

f:id:bluepixel:20200411212101p:plain

f:id:bluepixel:20200411212118p:plain

この時点ではただのLambdaである。

作った関数は、CloudFront の [Behavior] の設定で使う。 イベントタイプを指定して、バージョン番号を含めた ARNを入力するだけ。 イベントタイプについては後述する。

f:id:bluepixel:20200411212041p:plain

ハマりどころ1: サービスプリンシパルが異なる

関数の実行ロールにLambda@Edgeを追加しないといけない。

自動で生成されるIAMロールにはLambdaしか含まれていないので、 edgelambda.amazonaws.com を追加する。

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": {
            "Service": [
               "lambda.amazonaws.com",
               "edgelambda.amazonaws.com"
            ]
         },
         "Action": "sts:AssumeRole"
      }
   ]
}

動作の仕組み

Lambda 関数をトリガーできる CloudFront イベント

リンク先のこの画像がわかりやすい。

f:id:bluepixel:20200411212013p:plain

イベントには4種類あり、

  • ビューワーリクエス
  • オリジンリクエス
  • オリジンレスポンス
  • ビューワーレスポンス

そろぞれをトリガーとして Lambda@Edge の処理を挟み込める。

どのイベントが適しているかはユースケースに応じて適切に判断する必要がある。

例えば、リクエストされたパスやリファラに応じて一部をリダイレクトさせたい場合、 オリジンサーバーにアクセスさせる必要はないので、 ビューワーリクエストのイベントを拾ってレスポンスを返してしまえばよい。

Lambda@Edgeを使ってリファラによってリダイレクトさせる

S3の静的Webホスティングにもリダイレクト機能はあるが、 こちらの方が迅速にエッジロケーションからレスポンスを返せる。

逆に、オリジンリクエストで処理すべきケースもある。 CloudFrontはキャッシュが効くので、オリジンリクエストが発生しない可能性がある。

その分無駄に Lambda@Edge がコールされないで済むのでコスト削減に役立つ。

ハマりどころ2: イベントが発生しないことがある

上述のキャッシュのケース以外にも、リクエストはされているが、 Lambda@Edge がコールされないケースがいくつか存在する。

ビューアープロトコルポリシーでHTTPをHTTPSにリダイレクトしていた場合や、 CloudFrontが400系のエラーレスポンスを生成する場合は Lambda@Edge は無視される。

オリジンレスポンス CloudFront がオリジンからのレスポンスを受け取った後、レスポンス内のオブジェクトをキャッシュする前に関数が実行されます。関数は、オリジンからエラーが返された場合でも実行されることに注意してください。

以下の場合、関数は実行されません。

リクエストされたファイルが CloudFront キャッシュ内にあり、その有効期限が切れていない場合。

オリジンリクエストイベントによってトリガーされた関数からレスポンスが生成された場合。


ビューワーレスポンス リクエストされたファイルがビューワーに返される前に関数が実行されます。ファイルが CloudFront キャッシュ内にすでに存在するかどうかに関係なく、関数が実行されることに注意してください。

以下の場合、関数は実行されません。

オリジンが HTTP ステータスコードとして 400 以上を返した場合。

カスタムエラーページが返された場合。

ビューワーリクエストイベントによってトリガーされた関数からレスポンスが生成された場合。

CloudFront が HTTP リクエストを自動的に HTTPS にリダイレクトする場合 (ビューアープロトコルポリシー の値が [Redirect HTTP to HTTPS (HTTP を HTTPS にリダイレクトする)] の場合)。

ハマりどころ3: ログがリージョンごとに吐かれる

Lambdaの実行ログは CloudWatch に記録されるが、 様々なエッジロケーションで実行される Lambda@Edge は、実行されたリージョンにそれぞれログを吐く。

AWS Lambda@Edgeのログはどこ?AWS Lambda@Edgeのログ出力先について

結構めんどくさい。

どういう使い方をするものなのか

他に覚えておきたい仕様

不正にヘッダーをいじれない

できたらやばいのだが。 追加不可能、読み取り専用のヘッダーが以下にリストされている。

ブラックリストに記載されているヘッダー

現在サポートされているランタイム

Python3.7 と Node.js10 のみ。

Node.js8 と Node.js6 については、すでにサポートが切れている。 すでに関連づけられている関数は引き続き動作し続けるが、新規で関連づけることはできない。

環境変数は使えない

Lambdaとは異なる部分。

まあ、使うユースケースはあまりないと思うが、 やるとしたらパラメータストアから引っこ抜くとかかな。

タイムアウトは厳しめ

非同期的な使い方は想定外なので、Lambdaに比べてだいぶ制限されている。

他にもメモリやコードサイズに制限が追加されている。

f:id:bluepixel:20200411211944p:plain

ボディの取り扱い

Include Body オプションを有効にすると、ボディの内容が Lambda@Edge に送られる。 GETなんだからいらねー気もするが、深く追求しないでおこう。

以下の2点注意が必要。

本文は公開される前、常に Lambda@Edge で Base64 エンコードされています。


リクエストボディが大きい場合は、次のように、公開する前に Lambda@Edge によって切り捨てられます。 ・ビューワーリクエストの場合、本文は 40 KB で切り捨てられます。 ・オリジンリクエストの場合、本文は 1 MB で切り捨てられます。

所感

S3+CloudFrontでWebを配信することも増えてきたし、 なんかかゆいところに手が届くいいソリューションが出てきたなという感じですね。 Dynamoとかにも接続できちゃうしSSR的な使い方もやろうと思ったらできてしまう。

Lambdaっていう名前はややこしいから変えてほしいなあとは思う。 サーバーレスコンピューティング環境っていうよりは、Webサーバーの設定の上位互換みたいなイメージだし、CloudFrontとがっつり絡んでるのだからCloudFront側の機能として立て付けられてた方がわかりやすい気がするんだがな。

裏側のリソースの関係でLambdaに寄せたほうが都合よかったのかな🤔

以上です。

最後まで読んだ人は「Lambda@Edge完全に理解した」って言って良いです。