CloudFrontとLambda@Edgeでアクセスするドメインごとにオリジンを切り替えてみた
はじめに
清水です。AWSのCDNサービスであるAmazon CloudFront、マルチオリジン構成の場合ではパスパターンによって振り分けられるBehavior単位で使用するオリジンサーバを決める方法が基本です。(オリジンフェイルオーバのためオリジングループを使用するケースもあります。)先日ふと、Behavior(パスパターン)は同じで異なるオリジンを(フェイルオーバー用のオリジングループを使うケースでなく)使うことはできないか、ということを考えてみました。CloudFront単体では難しいのですが、Lambda@Edgeを使うことで実現が可能です。Lambda@Edgeのユースケース「コンテンツベースの動的オリジンの選択」として紹介されています。リクエストの場所やその他プロパティによって、S3オリジンのリージョンやカスタムオリジンのドメイン名を変えるという具合ですね。
今回はこれらの派生形として、CloudFrontへのリクエスト時のドメイン名、Hostヘッダの内容によってオリジンを切り替える、という構成を試してみました。イメージとしては以下となります。
リクエスト時のHostヘッダがred.example.com
であれば、red.example.com
用に準備したオリジンred-origin.example.com
からレスポンスが返ります。またリクエスト時のHostヘッダがblue.example.com
であればblue.example.com
用に準備したオリジンblue-origin.example.com
からレスポンスが返る、となります。
このように、CloudFrontで2つのドメインと、それに対応する2つのオリジンを使用する、というケースを考えた場合、以下のようにCloudFrontディストリビューションをわけてしまったほうが構成はシンプルになると思います。
例えばアクセスログの出力場所はCloudFrontディストリビューションごとに設定するので、2つのドメインのアクセスログが混在する、といったこともありません。またCloudFrontの利用料金は基本的にはデータ転送量とリクエスト数で決まりディストリビューションの数によらないため、1つにまとめることでの料金的メリットもありません。(ただし、例外として専用IPカスタムSSLを使用するケースではディストリビューションごとに利用料金が発生するため、この1つにまとめる構成で利用料金の削減ができるかもしれません。)その他、Behavior(パスパターン)ルールなどが複雑などの場合に、管理を容易にするため2つ目のディストリビューションを作成せず1つにまとめたい、といったケースでしょうか。(ただしLambda@Edgeを使用する分、こちらで多少なりとも構成が複雑になる感は否めません。)
ということで、個人的にはこの「CloudFrontディストリビューションを1つにまとめ、Hostヘッダの内容によってオリジンを切り替える」構成についてはそこまでメリットがないのかな、と考えるのですが、Lambda@Edgeを使うことでこんな構成も取れるんだ、ということの確認として実際に試してみたのでまとめておこうと思います。
オリジン環境の準備
まずはオリジン環境として、ドメイン名red-origin.example.com
とblue-origin.example.com
でアクセスできる環境を準備しておきました。それぞれアクセスすると以下のように、赤色と青色のページが表示されます。なおCloudFrontからオリジンサーバへの通信は簡略化のためHTTPとしました。(HTTPSは設定していません。)
CloudFrontディストリビューションの作成
続いてCloudFrontディストリビューションを作成します。Origin domainとしてred-origin.example.com
を入力します。
Behaviorはデフォルト(Default (*)
)のみ使用します。Cache keyとOrigin requestの設定では、Cache policyとしてCachingDisabled
を、Origin request policyとしてAllViewer
を選択しています。検証目的のためキャッシュは無効とし、リクエスト時にもすべての情報をオリジンに転送する動作とします。キャッシュを利用するような設定を行う場合でも、Hostヘッダによるキャッシュの出しわけ(Cache KeyとしてHostヘッダの利用)は必要になるかと考えます。(HostヘッダによりCloudFront側でコンテンツを出し分ける、という挙動のため。)
Function associationsはのちほど設定します。ここではいったん設定せずにCloudFrontディストリビューションを作成します。
Alternate domain name (CNAME)でred.example.com
とblue.example.com
、そしてgreen.example.com
の3つを指定します。(green.example.com
は先に示した図には記載していませんでしたが、「指定以外のドメインでアクセスした場合」の挙動確認用としてのちほど使用します。)またCustom SSL certificateで*.example.com
に対応したACM証明書を選択しました。
CloudFrontディストリビューションを作成後、red.example.com
、blue.example.com
、green.example.com
、3つそれぞれのレコードにCloudFrontディストリビューションのドメイン名d12345678.cloudfront.net
を登録して名前解決できるようにしておきます。(今回はDNSにRoute 53を利用しているため、ALISレコードで登録しました。)
実際にブラウザで各ドメインにアクセスしてみます。現状はオリジンがred-origin.example.com
のみであるため、いずれのドメインでアクセスしてもred-origin.example.com
の内容が表示されます。
Hostヘッダでオリジンを切り替えるLambda@Edge関数
続いて今回の構成の肝となる、Hostヘッダの内容によってオリジンを切り替えるLambda@Edge関数を設定していきます。まず関数のコードは以下となります。
'use strict'; exports.handler = (event, context, callback) => { console.log(JSON.stringify(event)); // 動作確認用 const request = event.Records[0].cf.request; if (request.headers['host']) { const hostHeader = request.headers.host[0].value; console.log('Host Header:' + hostHeader); // 動作確認用 if (hostHeader === 'blue.example.com') { const domainName = 'blue-origin.example.com'; request.origin.custom.domainName = domainName; console.log('Origin Changed:' + domainName ); } } callback(null, request); };
CloudFrontデベロッパーガイドの関数の例を参考に、Node.jsで記しました。
オリジンリクエストの内容は以下を参考にします。
Hostヘッダの値はRecords[0].cf.request.host[0].value
に格納されているので、それを参照します。この値がblue.example.com
であった場合は、オリジン(カスタムオリジン)のドメイン名を示すRecords[0].cf.request.origin.custom.domainName
の値をblue-origin.example.com
に書き換えます。request.origin.custom
の他の要素(ポート番号やプロトコル、タイムアウト指定)なども変更可能なようですが、今回はCloudFrontディストリビューションに設定してある値をそのまま使うことにしました。
このコードの内容を、Lambda@Edge Functionとして設定していきます。今回はLambdaのマネジメントコンソールから設定を行いました。リージョンがUS East (N. Virginia) us-east-1となっていることを確認します。Create functionから進み、function名などを設定します。RuntimeはNode.js 14.xを選択しました。そのほかはデフォルトのまま進めています。
Function作成後、Code sourceのindex.js
に先ほどのコードを貼り付け、[Deploy]ボタンを押します。
続いてIAMロールまわりの設定を行います。Configurationの項目のPermissions、Execution roleでこのFunctionに紐づいているIAMロールを確認します。このIAMロールにアタッチされているマネージドポリシーについて、操作許可対象がus-east-1
リージョンに限定しているのでこれを任意のリージョンで許可するよう変更します。またlogs:CreateLogStream
とlogs:PutLogEvents
の許可対象のResouce ARNについて、出力されるロググループ名に合うように書き換えておきます。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "arn:aws:logs:us-east-1:123456789012:*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/ChangeOriginDomainByHostHeader:*" ] } ] }
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "arn:aws:logs:*:123456789012:*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:*:123456789012:log-group:/aws/lambda/*" ] } ] }
続いて信頼関係True relationshipsでedgelambda.amazonaws.com
があるかを確認します。ない場合は追加します。
以上2つのIAMロールの設定までできたら、右上の[Actions]から「Deploy to Lambda@Edge」を選択します。
現れたダイアログでCloudFrontトリガの設定を行います。Distributionで先ほど作成したCloudFrontディストリビューションを選択します。Cache behaviorは*
とします(Default (*)
のBehaviorに適用する想定です。)CloudFront eventではOrigin request
を選択します。Confirm deploy to Lambda@Edgeのチェックボックスにチェックを付け、[Deploy]ボタンを押します。
新しくVersionが発行され、CloudFrontディストリビューションに紐付きます。
CloudFront側でも確認しましょう。(上記Lambdaのマネジメントコンソールからリンクを辿ると、CloudFrontの旧マネジメントコンソールに遷移しました。今回は記念(?)にここだけ、この旧コンソール画面のまま進めます。)該当のディストリビューションのBehavior、パスパターンDefault (*)
の内容を確認してみると以下のように、Origin Requestに対するEdge Functionが紐付いていることがわかります。
以上でLambda@Edgeの設定は完了です。
Hostヘッダの内容でオリジンが切り替わることの確認
CloudFrontディストリビューションならびにLambda@Edgeの設定が完了しました。CloudFrontディストリビューションのDeployが完了したことを確認したら、実際にアクセスして挙動を確認してみます。
まずはred.example.com
にアクセスします。こちらは先ほどと変わらず赤色のページ、red-origin.example.com
からのレスポンスですね。
続いてblue.example.com
にアクセスします。Hostヘッダがblue.example.com
の場合はblue-origin.example.com
をオリジンに使用する設定のため、こちらは青色のページが表示されました。blue-origin.example.com
からのレスポンスですね。
最後に、冒頭から考慮していなかったgreen.example.com
というHostヘッダでアクセスした場合です。CloudFrontのデフォルトの設定としてはオリジンにred-origin.example.com
を使用、Lambda@EdgeのFunctionコードでHostヘッダの値が'blue.example.com'の場合にblue-origin.example.com
を使用するようにしていました。ということは、green.example.com
でのアクセスの場合はデフォルトのオリジンred-origin.example.com
からのレスポンスとなる、ということになります。アクセスした結果も、下記のように赤色のページが表示されました。
まとめ
Amazon CloudFrontにてLambda@Edgeを使い、1つのディストリビューションでHostヘッダの内容によってオリジンを切り替える構成を試してみました。冒頭にも述べたとおり、個人的にはこのようなケースでは2つディストリビューションを用意してアクセスするドメインと使用するオリジンごとにディストリビューションを分けてしまうほうがシンプルになるかなと考えています。とはいえ、何らかの事情にて1つの今回の構成が役に立つこともあるかもしれません。何より今回、Lambda@Edgeの使用を通してCloudFrontのリクエストに対して(そしてレスポンスにも)色々な処理ができることも実感できました。2021年に入りリリースされたCloudFront Functionsとあわせて、ユースケースや実現可能なことなどを抑えておきたいと思いました。