ALBの前にCloudFrontを置いたらいろいろ動かなくて焦った話

ざっくり構成図

この記事を三行にまとめると

CloudFrontをALBの前に設置してみました
致命的に動かないという事態になりました
体重が減らない
仕事でAWSを使って運用しているサービスがあるんですけど、これまでウェブページへは「ユーザー → ALB(ロードバランサー) → EC2」という流れでアクセスする設定にしていました。でも先日AWSの中の人とお話をする機会があり、いろいろと構成やら何やら見てもらったところ「まだユーザー → CloudFront → ALB → EC2にしてないの?」と言われたので、CloudFrontをALBの前に設置してみました。

ついでに「設定は簡単です。天井のシミを数えている間に終わりますよ」って言われたもんだから、そんならちょちょいとやってみるかーなんて軽い気持ちで設定してみたんですが、いざやってみたら思ったよりもウェブサービスが正常に動作しなくなってしまったので、戒め+これから同じようなことをやろうとしている人が同じ轍を踏まないように、今回どんな問題が発生したのかをここに残しておこうと思います。



HTTPSの判定

これはCloudFrontを使わずにALBとEC2だけで運用する場合もそうなんですが、SSLありでサービスを運用した場合、サーバー変数にはHTTPSという項目が入っています。なのでサイトのSSLが有効になっているかはこのHTTPSというサーバー変数を使って判定すれば良いのですが、ALB側にSSLを設定していてもEC2への接続が80番ポートになっているとこのHTTPSというサーバー変数が入ってこなくてSSLが有効になっていないという判定になり、そのせいで画像やcss、jsなどが正しく読み込めないことがあったります。「Wordpressでcssが読み込まれないんだけど〜><」みたいな時はこれが原因だったりします。

ELB + SSL + WordPressについて

なのでALBからEC2の80番ポートに接続する場合は「HTTP_X_FORWARDED_PROTO」や「HTTP_X_FORWARDED_PORT」というサーバー変数を使って自分でHTTPSを有効にする必要があります。

//HTTP_X_FORWARDED_PORT == 443とかでも良い
if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $_SERVER['HTTPS'] = 'on';
  $_ENV['HTTPS'] = 'on';
}

これと同じことがCloudFrontでも起こります。

CloudFrontのOrigin設定で「Origin Protocol Policy」というやつを「HTTP Only」にするとCloudFrontからALBへの接続がHTTPとなります。

HTTP Only

この設定だとサーバー変数のHTTPSが存在しなくてさっきと同じような状態になります。しかもこの場合はALBもSSLが無効な状態になっているので「HTTP_X_FORWARDED_PROTO」もhttpになってしまいます。

これを回避するにはCloudFrontのBehaviorで「CloudFront-Forwarded-Proto」というヘッダを送信するように設定する必要があります。

CloudFront-Forward-Proto

「Cache Based on Selected Request Headers」を「Whitelist」にすると送信するヘッダを選べるようになるので「CloudFront-Forwarded-Proto」を追加すればOKです。これでHTTP_X_FORWARDED_PROTOがhttpsになるので先ほどと同じような判定が使えるようになります。

追加するのがめんどいって場合は「All」にするという選択もありますが、Allにすると何でもかんでも送られてしまうので、CloudFrontの最大のメリットとも言うべきキャッシュの恩恵が弱まる可能性があります。CloudFrontはページ全体をがっつりキャッシュするので、値の変わりやすいものがヘッダに含まれているとあまりキャッシュが効かなくなるようです。

まあCloudFrontからALBへの接続をHTTPSにすればこんなことしなくて良いんじゃないかって気もしますがね……。

ちなみにAWSでSSLを有効にする場合はCertificate Managerで証明書を発行することになりますが、CloudFrontに使用する証明書はバージニア北部のリージョンで作成する必要があります。東京リージョンで作成した証明書は使用できないので注意。そのうち東京リージョンの証明書も使えるようになるかもしれませんが……。



Authorizationを使う

CloudFrontはデフォルトだとAuthorizationをリクエストヘッダに含めません。これを有効にする場合も先ほどと同様に「Cache Based on Selected Request Headers」をNone以外にすることでAutorizationを送れるようになります。

Authorization



ユーザーエージェントがCloudFrontになる

CloudFrontはデフォルトだとユーザーエージェントが「Amazon CloudFront」となります。特に気にしないっていう場合はこのままでも良いと思いますが、ユーザーエージェントを使ってブラウザや端末の判定を行っている場合はこれだと困っちゃいますね。

これもまた「Cache Based on Selected Request Headers」をWhitelistにしてヘッダに「User-agent」を追加すればOKです。

User-agent

ただしデフォルトだとWhitelist HeadersにはUser-agentが入っていないので自分でUser-agentを入力して「Add Custom」で追加してやる必要があります。



CloudFrontはデフォルトだとクッキーも受け取ることができません。これもまたクッキーの値が変わるのはページをキャッシュさせる上で不都合みたいな理由があるんじゃないかと思うんですが、とにかくクッキーを使いたい場合は自分でクッキーを有効にする設定を行う必要があります。これもBehaviorの項目で設定します。

Forward Cookies

Behaviorで「Forward Cookies」を「Whitelist」か「All」にするとクッキーが使用可能な状態になります。Whitelistは自分で指定した項目のみ使用可能になります。例えば「akatsuki」という項目をリストに追加した場合は「$_COOKIE[‘akatsuki’]」だけが使用できる状態になるって感じですね。とりあえず全部使用できる状態にしたいって場合はAllにすればOKです。

ちなみにAllにすると何らかのクッキーの値が変わった時点でキャッシュが効かなくなるので、できるだけキャッシュをちゃんと効かせたいって場合はAll以外にしておくのが良いでしょう。Googleアナリティクスなどを導入していると、わりと細かくクッキーの値が変わってほとんどキャッシュが効かない状態になってしまうようです。



GETパラメータを使う

クッキーと似ているんですが、CloudFrontはGETパラメータもデフォルトでは渡さない設定になっています。これもパラメータの値によってキャッシュが効かなくなってしまうのを防ぎたいという意図があるんだと思いますが、GETパラメータを使いたい場合は、Behaviorで「Query String Forwarding and Caching」をNone以外にする必要があります。

Query String Forwarding and Caching

これもwhitelistやallの設定があり、whitelistは自分で許可したパラメータ以外は渡さないという設定になります。



おまけ(キャッシュのクリア)

もろもろ設定を変更してもキャッシュが効いているとページの内容が変更前のままだったりするので、そういう時は自分でキャッシュをクリアする必要があります。

キャッシュのクリアは「Invalidation」のタブから行います。

Invalidation

「Object Paths」を空の状態でInvalidateすればキャッシュが全てクリアされます。コンソール画面からもできますしAPIを使ってクリアしたりもできます。

ただしこのInvalidationは課金の対象となる機能なので「とりあえずキャッシュ効かせといて何かあればすぐにこれでクリアすればええやん」って思ってInvalidateしまくってると結構なお金がかかってしまう可能性もあるので注意が必要かと思います。

ファイルの無効化に対する支払い






今回僕が詰まったのはこんなところですかね。設定自体は別に大変じゃないんですが「よく分からないからなるべくデフォルトの設定のままにしておこう」とか思ったばっかりに、上記の設定ができていなくてサービスが致命的に動かないという事態になりました。

「https→http→httpsの無限リダイレクトが発生」「APIのトークンが読み取れない」「PCとモバイルの振り分けができてない」「GETパラメータの値を使った処理が軒並みエラー」「そもそもログインができない」「ページの表示内容が切り替わらない」「体重が減らない」など、もうてんやわんやでした。一応テスト環境でそれなりにテストはしたつもりだったんですけど、正直ユーザーエージェントまでは気にしてなかったわ。

上記の設定を行うとほとんどキャッシュは効かない状態になるのでわざわざCloudFrontを使う必要あんのか?って気がしなくもないんですけど、たぶんCloudFrontを経由した方がデータ通信の料金が少し安くなったり、500エラーが発生した時に表示するページのカスタマイズが簡単にできるなどのメリットがあるようなので、キャッシュをあまり効かせたくないウェブサイトを運用する場合でも使う価値はあるのかなと思います。
 もしかしたら何か関連しているかも? 
 みんなからのコメント 
まだコメントはいただけてないみたい……