CloudFrontで独自ドメイン利用の際にディストリビューション側にもCNAME設定が必要な点をその動作とともに検証してみた
はじめに
清水です。CloudFrontで独自ドメインを使用する際にはRoute 53などDNS側でのレコード登録のほか、CloudFrontディストリビューション側でCNAMEs (Alternate domain names、代替ドメイン名)を設定する必要があります。
- Using custom URLs by adding alternate domain names (CNAMEs) - Amazon CloudFront
- 代替ドメイン名 (CNAME) を追加することによるカスタム URL の使用 - Amazon CloudFront
この設定をした際の挙動について深堀りしていくと、CloudFrontがApache HTTP ServerのName-based Virtual Hostのような、リクエスト時のHostヘッダの内容に応じた動作をしていることが垣間見えます。この点は具体的に公式ドキュメント等には記載などがなかったかと思いますが、覚えておくとCloudFrontディストリビューション間でのレコード切り替えなどの作業のイメージがしやすくなるのかな、と思います。本エントリではこの動作確認の詳細と、この点を抑えておくと個人的にはすんなりと把握できると感じたCloudFront使用時の注意点2点をまとめています。
CloudFrontを独自ドメインで利用する〜ALBの場合と比較して〜
まずはCloudFrontを独自ドメインで利用する際の設定項目を確認しておきましょう。設定項目として(1) Route 53などDNS側でレコード登録、(2) CloudFront側でCNAMEs (Alternate domain names)の設定、の2つが必要です。また(2)のCloudFrontディストリビューションでのCNAME設定には、現在SSL/TLS証明書も必要な点も注意しておきましょう。 *1
なお、比較としてALBで独自ドメインを利用するケースも確認しておきます。こちらは(1) Route 53などDNS側でのレコード登録、のみが必要な作業となります。ALB側でCNAMEs (Alternate domain names)のような設定項目はありません。
CloudFrontの動作はHostヘッダの内容で決まる!?
CloudFrontを独自ドメインで利用する際にはDNS側でのレコード登録のほか、ディストリビューション側でCNAMEs (Alternate domain names、代替ドメイン名)の設定が必要である点を確認しました。
ここからは、これを踏まえたCloudFrontの挙動を深堀りしていきます。なお、以降の記載については筆者の独自検証をまとめたものです。ドキュメントなどAWS公式情報に記載がないものについては挙動は保証できません。挙動が変わる可能性もありますし、検証環境と条件が異なるなどで再現しない可能性もありますので、ご注意いただければと思います。
検証環境について
まずは今回の検証環境についてです。2つのCluodFrontディストリビューションを用意しました。1つはaaa.example.com
のCNAME設定をしたCloudFrontディストリビューションです。わかりやすいようにルート/
にアクセスするとaaa
と返すようにしています。(デフォルトルートオブジェクトに指定したindex.txt
の中身がaaa
です。)
もう一つはbbb.example.com
のCNAME設定を行ったCloudFrontディストリビューションです。こちらもわかりやすいようにルート/
にアクセスするとbbb
と返すように設定しています。(設定はaaa.example.com
と同様になります。)
DNS(Route 53)も設定して、独自ドメインでCloudFrontにアクセスできるようにしておきます。
なお、どちらのディストリビューションも専用IP独自SSL設定は行っていません。(後述しますが、この設定を行った場合は挙動が変わりそうな気がしています。未検証ではありますが。)
まとめると以下のようになります。
「aaa.example.com」にIPアドレスでアクセスしてみる
さてこの検証環境について、まずは通常の動作としてaaa.example.com
のドメイン名にアクセスしてみましょう。curl
コマンドでhttps://aaa.example.com/
にアクセスすると、当然ながらaaa
が返ってきます。
% curl https://aaa.example.com/ aaa
CloudFrontにデフォルトで付与されているドメイン「Distribution domain name」のドメインにもアクセスしてみます。dXXXXXXXX.cloudfront.netの形式のものですね。こちらもaaaが返ってきます。
% curl https://d3k0fxxxxxxxxx.cloudfront.net/ aaa
続いて、このCloudFrontディストリビューションにaaa.example.com
を名前解決した結果のIPアドレスでアクセスしてみます。なお、このCloudFrontに紐付いたドメインから名前解決してIPアドレスを取得、そのIPアドレスを固定して使い続けることはCloudFrontのメリットを消してしまう行為です。動作検証など短時間の使用にとどめ、実際に運用している本番環境でIPアドレスを固定してアクセスする、なんてことはやってはいけません。 *2
まずはaaa.example.com
の名前解決を行いIPアドレスを確認します。この名前解決結果ののち得られるIPアドレスについては、使用している環境で異なるIPアドレスが返ります。また名前解決を行うタイミングによっても変わる可能性があります。以下は私の検証の際の名前解決結果です。
% dig aaa.example.com ; <<>> DiG 9.10.6 <<>> aaa.example.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24637 ;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;aaa.example.com. IN A ;; ANSWER SECTION: aaa.example.com. 60 IN A 143.204.126.121 aaa.example.com. 60 IN A 143.204.126.117 aaa.example.com. 60 IN A 143.204.126.30 aaa.example.com. 60 IN A 143.204.126.10 ;; Query time: 15 msec ;; SERVER: 24xx:xx:xxxx:xxx:xx:xxxx:xxxx:xxde#53(24xx:xx:xxxx:xxx:xx:xxxx:xxxx:xxde) ;; WHEN: Thu Jul 07 12:14:03 JST 2022 ;; MSG SIZE rcvd: 108
このIPアドレスの1つににcurlコマンドでリクエストを投げてみましょう。aaa
のコンテンツは返らずエラーとなっています。curl -I
でヘッダ内容を確認してみると403 Forbidden
が返ってきていました。これはどのIPアドレスでも同じ結果となります。
% curl http://143.204.126.121/ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> <TITLE>ERROR: The request could not be satisfied</TITLE> </HEAD><BODY> <H1>403 ERROR</H1> <H2>The request could not be satisfied.</H2> <HR noshade size="1px"> Bad request. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner. <BR clear="all"> If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation. <BR clear="all"> <HR noshade size="1px"> <PRE> Generated by cloudfront (CloudFront) Request ID: FEtdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2A== </PRE> <ADDRESS> </ADDRESS> </BODY></HTML>
% curl -I http://143.204.126.121/ HTTP/1.1 403 Forbidden Server: CloudFront Date: Thu, 07 Jul 2022 03:15:06 GMT Content-Type: text/html Content-Length: 915 Connection: keep-alive X-Cache: Error from cloudfront Via: 1.1 e655xxxxxxxxxxxxxxxxxxxxxxxx191c.cloudfront.net (CloudFront) X-Amz-Cf-Pop: NRT20-C2 X-Amz-Cf-Id: 4cl6xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwQ==
さて、ここでCloudFrontディストリビューションにCNAME(代替ドメイン名)を設定したことを思い出してみましょう。CloudFront側にもCNAMEとしてアクセスの際に使用する独自ドメイン名を設定しているのだから、もしかするとHost名の情報がアクセスには必要ではないか、という推測ですね。ということで、curlコマンド実行の際にHostヘッダを付与してみます。すると今度はaaa
の結果(aaa.example.com
の内容)が返ってきました!
% curl -H "Host: aaa.example.com" http://143.204.126.121/ aaa
なお、独自ドメイン設定の際にCNAMEも設定したのだから、ということがHostヘッダ付与の発端になったわけですが、CloudFrontのデフォルトのドメイン名(Distribution domain name)でも同じ結果が得られました。
% curl -H "Host: d3k0fxxxxxxxxx.cloudfront.net" http://143.204.126.121/ aaa
CloudFrontのIPアドレス(名前解決したもの)へHostヘッダなしの場合は403
となり期待した動作はしないけれど、CNAMEで設定したドメイン名もしくはデフォルトドメイン名をHostヘッダとして付与すればIPアドレスでも期待した動作をする(正常にアクセス可能)、ということになりますね。
「bbb.example.com」にIPアドレスでアクセスしてみる
さて、aaa.example.com
のディストリビューションにIPアドレスでアクセスしたときの挙動を確認してみました。まったく同様のことがbbb.example.com
でも確認できます。簡単に結果を確認しておきましょう。
まずはhttps://bbb.example.com/
へのアクセスです。bbb
が返ってきます。
% curl https://bbb.example.com/ bbb
CloudFrontのDistribution domain nameにもアクセスしてみます。こちらもbbb
が返ってきます。
% curl https://d3b2yxxxxxxxxx.cloudfront.net/ bbb
bbb.example.com
を名前解決して、このIPアドレスにアクセスしてみます。この結果は403が返ります。
% dig bbb.example.com +short 18.65.168.79 18.65.168.48 18.65.168.71 18.65.168.74
% curl http://18.65.168.79/ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> <TITLE>ERROR: The request could not be satisfied</TITLE> </HEAD><BODY> <H1>403 ERROR</H1> <H2>The request could not be satisfied.</H2> <HR noshade size="1px"> Bad request. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner. <BR clear="all"> If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation. <BR clear="all"> <HR noshade size="1px"> <PRE> Generated by cloudfront (CloudFront) Request ID: X-fIrSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== </PRE> <ADDRESS> </ADDRESS> </BODY></HTML>
% curl -I http://18.65.168.79/ HTTP/1.1 403 Forbidden Server: CloudFront Date: Thu, 07 Jul 2022 03:29:28 GMT Content-Type: text/html Content-Length: 915 Connection: keep-alive X-Cache: Error from cloudfront Via: 1.1 c951xxxxxxxxxxxxxxxxxxxxxxxx5f9c.cloudfront.net (CloudFront) X-Amz-Cf-Pop: NRT57-P1 X-Amz-Cf-Id: 8Vurxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx8g==
Hostヘッダでbbb.example.com
を追加してアクセスしてみます。今度はbbb
が返ってきました。
% curl -H "Host: bbb.example.com" http://18.65.168.79/ bbb
CloudFrontのDistribution domain nameをHostヘッダに指定しても、bbb
が返ってきます。
% curl -H "Host: d3b2yxxxxxxxxx.cloudfront.net" http://18.65.168.79/ bbb
aaa.example.com
で確認できた、CloudFrontのIPアドレスへHostヘッダに設定した独自ドメインを付与するとアクセス可能になる(その独自ドメインをCNAMEに持つディストリビューションのレスポンスを返す)挙動がbbb.example.com
でも確認できました。
「aaa.example.com」のIPアドレスにHostヘッダ「bbb.example.com」でアクセスしてみると!?
CloudFrontに設定した独自ドメインについて、その名前解決結果のIPアドレスにHostヘッダで独自ドメインを指定すると想定通りのアクセスが可能である点を確認してきました。
さてaaa.example.com
の名前解決の際にも少し述べましたが、CloudFrontの名前解決結果は同じ環境から行っても、けっこうころころ変わる印象です。例えば同じドメイン名でも昨日と今日で変わる、ということは頻繁にあります。
この「IPアドレスがころころ変わる」状況に対し、1つやってみたいことが思いつきました。先ほどはCloudFrontに設定した独自ドメインの名前解決結果のIPアドレスに、Hostヘッダでその独自ドメインを指定する、というかたちでした。これに対して、ほかのCloudFrontに設定した独自ドメインの名前解決結果のIPアドレスに、名前解決したドメインとは別のCloudFrontに設定した独自ドメインをHostヘッダで指定するとどうなるでしょうか。aaa.example.com
とbbb.example.com
を名前解決結果とHostヘッダでテレコにする具合ですね。以下のようなイメージです。
いざ、やってみました。結果としてはHostヘッダで指定した独自ドメインに対するディストリビューションの動作(レスポンス)が返ってくる、という挙動でした。
それぞれ確認していきましょう。まずはaaa.example.com
をCNAMEで設定したディストリビューションのIPアドレス(aaa.example.com
の名前解決結果のIPアドレス)に、bbb.example.com
のHostヘッダをつけてアクセスしてみます。結果は文字列bbb
が返ってきました。CloudFrontとしては、Hostヘッダと同じbbb.example.com
のディストリビューションとして動作しているといえます。
% curl -H "Host: bbb.example.com" http://143.204.126.121/ # aaa.example.comの名前解決結果のIPアドレス bbb
続いて逆のパターンです。bbb.example.com
を指定したディストリビューションのIPアドレスに、aaa.example.com
のHostヘッダを付けてアクセスしてみます。こちらもHostヘッダの内容どおり、aaa.example.com
のディストリビューションとして動作した結果のaaa
が返ってきました。
% curl -H "Host: aaa.example.com" http://18.65.168.79/ # bbb.example.comの名前解決結果のIPアドレス aaa
CloudFrontはIPアドレスではなく、Hostヘッダの内容をみて動作している(Hostヘッダに設定されたドメイン名をCNAMEに持つディストリビューションの動作をレスポンスとして返している)と言えるかと思います。Distribution domain nameでも結果を見てみましょう。こちらも同様の結果になります。(Hostヘッダに指定したDistribution domain nameに応じたディストリビューションのレスポンスを返します。)
まずはCNAMEにbbb.example.com
を指定したディストリビューションのDistribution domain nameをHostヘッダに指定して、aaa.example.com
の名前解決結果で得られたIPアドレスにアクセスします。Hostヘッダに指定したディストリビューションの動作、bbb
が返ってきます。
% curl -H "Host: d3b2yxxxxxxxxx.cloudfront.net" http://143.204.126.121/ # aaa.example.comの名前解決結果のIPアドレス bbb
反対の、CNAMEにaaa.example.com
を指定したディストリビューションのDistribution domain nameをHostヘッダに指定して、bbb.example.com
の名前解決結果で得られたIPアドレスへのアクセスです。こちらもHostヘッダに指定したディストリビューションの動作であるaaa
が返ってきています。
% curl -H "Host: d3k0fxxxxxxxxx.cloudfront.net" http://18.65.168.79/ # bbb.example.comの名前解決結果のIPアドレス aaa
ここまでの動作検証の結果をまとめると、CloudFrontとしては、そのIPアドレス群に対してアクセスすると、その時のHostヘッダの値に応じたディストリビューションの設定内容の動作がレスポンスとして返る、という挙動になります。いわゆるApache HTTP ServerのName-based Virtual Host (名前ベースのバーチャルホスト)のような動作、というとわかりやすいかなとも思います。(単一のIPアドレスではなく、IPアドレス群というかたちではありますが。)
- Name-based Virtual Host Support - Apache HTTP Server Version 2.4
- 名前ベースのバーチャルホスト - Apache HTTP サーバ バージョン 2.4
- 「名前ベースバーチャルホスト」 - バーチャルホスト - Wikipedia
図にまとめると以下のようなぐあいです。CloudFrontの中にディストリビューションという設定単位(リソース)はあるのですが、アクセス先(IPアドレス)はCloudFrontとしては共通、そこでHostヘッダの値をみて動作するディストリビューションが決まっていく、というようなイメージです。
なおこの挙動については公式ドキュメント等には記載がなかったかと思います。あくまで検証結果の考察であるため、いつでもこのように動作するとは言い切れないのかなとも考えています。公式ドキュメント等でこの挙動に近い点を説明しているとすれば、「SNIを使用したHTTPSリクエストの処理」の部分でしょうか。
HTTPSリクエストを処理するよう設定したCloudFrontディストリビューションで専用IP独自SSLを使用しない場合、SNI (Server Name Indication)で動作します。この際、CNAME(代替ドメイン名)と各エッジロケーションとのIPアドレスを関連付けているということです。(ということはつまり、本エントリでは未検証となりますが、専用IP独自SSLを設定したCloudFrontディストリビューションでは今回の検証内容通りに動作しないのではないか、という気がしています。)
CloudFrontを指した独自ドメインの向き先を変えるときに注意するべきケース
CloudFrontはName based Virtual Hostっぽい挙動、つまり同じIPアドレス群に対してHostヘッダの値でそのレスポンスの内容が決まる、という動作をしているようであることが検証の結果からわかりました。この挙動を抑えておくと、なんとなく(個人的には)すんなりと理解できたCloudFrontの使用上の注意点が2つあります。1つ目はCNAMEの重複ができない点、2つ目はhostsファイルでの検証ができないケースがある点です。それぞれ確認していきましょう。
独自ドメインに紐付いたCloudFrontディストリビューションの変更はちょっと大変
使用している独自ドメインに対して、紐付いている(≒DNSでレコードとして登録している)リソースを変更する、という場面があるかと思います。これがALB(仮にALB-aとします)からALB(こちらはALB-bとしましょう)への変更であれば、DNSレコードの変更だけで作業が完了します。ここらへんは冒頭の独自ドメイン利用で確認したとおりですね。
変更前後のリソースがCloudFrontになるケースはどうでしょうか。例えば独自ドメインがALBを使用して(DNSレコードがALBを指していて)、これをCloudFrontに変えたいとします。おそらく手順としてはCloudFrontディストリビューションを作成・設定、CNAME設定を使用する独自ドメインにしたあと、DNSレコードを変更する、という流れになるかと思います。こちらも冒頭で確認したとおり、CloudFrontで独自ドメイン利用の場合はDNSレコード登録の他にCNAME設定が必要になる、というぐあいですね。
さて、変更前後のリソースがどちらもCloudFrontであるケースを考えてみましょう。独自ドメインの向き先があるCloudFrontディストリビューションから別のCloudFrontディストリビューションに変更になる、という場合です。DNSレコード変更のほかにCNAME設定変更が必要になりますよね。ここでディストリビューションに設定するCNAMEは重複が許されていません。設定時、ほかのディストリビューション(同じAWSアカウントではなく、すべてのCloudFrontユーザ)でそのドメインに応じたCNAMEが設定されている場合は、CNAMEAlreadyExistsエラーが発生してしまいます。(この点は、これまでの検証で確認してきた、CloudFrontは設定したCNAMEとHostヘッダの値で動作ディストリビューションを決めているようである、ということを考えるとCNAMEAlreadyExistsエラーの発生が予測できそうかなと思います。もしCNAMEの重複が許されている場合、CloudFrontとしてはどのディストリビューションの動作をするのかが一意に定まりません。)
CloudFrontディストリビューションでCNAME設定の重複が許されない中、ダウンタイムなしで独自ドメインの向き先を切り替えることができるのか、という点については、ALBなどのように単純なDNSレコード変更のみというわけには行きませんが、一時的にワイルドカードをCNAMEs設定に使うなどいくつか方法があります。特に2021年7月のアップデートで、特定の条件下ではAssociateAliasというAPIを使うことでアトミックに変更ができるようになりました。詳細については以下エントリをご参照ください。
- CloudFrontのCNAMEAlreadyExistsエラーを解決するフローチャート(2021年夏版) | DevelopersIO
- CloudFrontのCNAMEをディストリビューション間でダウンタイム無しにアトミックに移行できるようになりました | DevelopersIO CloudFrontのCNAMEAlreadyExistsエラーを回避しつつCNAMEを付け替える | DevelopersIO
ひとまず、CloudFrontディストリビューション間でCNAMEの重複が許されていないため、CloudFrontディストリビューション間で独自ドメインを変更するのはちょっと大変(DNSレコード変更のようにはいかない)点を抑えておきましょう。
独自ドメインのレコード変更で双方ともCloudFrontの場合にhostsファイルを使った検証ができない
こちらも独自ドメインを使用している場合で、その独自ドメインをCloudFrontディストリビューション間で変更する場合の話です。DNSレコード変更だけの場合よりも少し複雑にはなりますが、ダウンタイムなしで変更できることはわかりました。このようなドメインの向き先(≒DNSレコード)を変更する場合、事前の動作検証でhostsファイルを使って検証に使う環境のみあらかじめ変更後のリソースにアクセスさせる、というケースはよくあるかと思います。以下はALB間でドメインの向き先(≒DNSレコード)を変更する例で、hostsファイルによる動作検証をしている場面です。
これを今回の独自ドメインをCloudFrontディストリビューション間で変更する場合に当てはめてみましょう。変更後のCloudFrontディストリビューションのDistribution domain nameから名前解決してIPアドレス(つまりCloudFrontのIPアドレス群)を確認します。そのIPアドレスと使用している独自ドメインをhostsファイルに書き込み、検証に使用する環境は独自ドメインの名前解決結果がDNSに問い合わせせずこのIPアドレスになります。このIPアドレスに独自ドメインをHostヘッダの値としてアクセスしてみましょう。IPアドレスはCloudFrontのものであるため、CloudFront側はHostヘッダの値を確認しそれに応じたディストリビューションの動作をする挙動になるかと思います。つまり、変更前のCloudFrontディストリビューション動作です。
このように、独自ドメインをCloudFrontディストリビューション間で変更する場合はhostsファイルによる動作検証が行えません。テスト用のドメイン(例えばtest.example.com
など)を用意してこちらで検証を行う、といった代替案を採るようにしましょう。
まとめ
CloudFrontがアクセス(リクエスト)時のHostヘッダの内容によって動作(対応ディストリビューション)を変える、いわゆるName-based Virtual Host(名前ベースのバーチャルホスト)のようになっているっぽい、ということを検証して確認してみました。(繰り返しになりますがドキュメントなどに記載がないため、あくまで推測に過ぎません。)
またこの動作からCloudFront利用時の注意点として挙げられる、CNAMEの重複ができなく独自ドメインをディストリビューション間で変更する場合にちょっと大変である点、そして同様のケースでhostsファイルによる検証ができない点を確認してみました。本エントリで扱った検証内容はさておき、注意点で挙げたどちらの項目もCloudFront利用の際にはポイントになるものだと思っています。きちんと抑えておきたいなと思いました。
脚注
- 昔々はCNAME設定だけならSSL/TLS証明書は不要だったのですが、2019年4月からHTTPのみの利用(HTTPSを使わない)の場合でも必要になりました。(Amazon CloudFront の代替ドメイン名(CNAMEs)設定に SSL 証明書が必須になりました | DevelopersIO) ↩
- 関連する項目はCloudFront開発者ガイドの負荷テストの項目にも記載がありますね。(CloudFront の負荷テスト - Amazon CloudFront) ↩