CloudFront 運用で覚えておきたいこと

CloudFront でコンテンツをキャッシュし、システム負荷の軽減・レスポンス速度の向上を図ることも多いと思います。
私自身も最近、CloudFront で CDN 環境を構築したばかりです。
運用フェーズに入り、構築時には気付けなかった CloudFront の設定・仕様を今後の為にまとめました。 

## 覚えておきたいこと
* [オブジェクトのキャッシュ設定](#オブジェクトのキャッシュ設定)
* [カスタムエラーページのキャッシュ設定](#カスタムエラーページのキャッシュ設定)
* [オリジンとの通信でhttpとhttpsを有効にするときの注意](#オリジンとの通信でhttpとhttpsを有効にするときの注意)
* [s3オリジンでオブジェクトが存在しないとき403エラーが返ってくる](#s3オリジンでオブジェクトが存在しないとき403エラーが返ってくる)
* [デフォルトのルートオブジェクトの指定](#デフォルトのルートオブジェクトの指定)
* [アクセスログのステータスがゼロ](#アクセスログのステータスがゼロ)

## オブジェクトのキャッシュ設定
ラジオボタン選択形式であることから勝手に誤った解釈をしてしまいました

|<img width="400" alt="オブジェクトキャッシュ設定" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/5e65665e-65f4-532f-f759-0f7f1a64d882.png">|
|:--|

#### どのように誤った解釈をしてしまったか
* Use Origin Cache Headers
  * オリジンの Cache-Control、Expires のみでキャッシュされる
* Customize
  * カスタマイズ設定で指定した TTL 値でキャッシュされる = オリジンの Cache-Control、Expires を無視する

指定した TTL 値でキャッシュされないケースが発生した為、調査したが原因不明。あらためて公式ドキュメントを読み返すとCloudFront がキャッシュにオブジェクトを保持する期間の指定の解釈に誤りがあることに気付きました。
現在はどちらか片方の設定のみが有効ということはなく、「オリジンが指定するキャッシュ制御を基準としてTTL値をカスタマイズできる」と解釈しています。

#### 詳しくは、公式ドキュメントをご確認ください
「ディストリビューションを作成または更新する場合に指定する値」ページ
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html

「コンテンツがエッジキャッシュに保持される期間の管理 (有効期限)」ページ
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Expiration.html

## カスタムエラーページのキャッシュ設定
エラーに対するキャッシュ時間は、オブジェクトが正常になったときを考慮して短めに設定していますが、先の「オブジェクトのキャッシュ設定」に続き、こちらも誤った解釈をしておりました

|<img width="400" alt="カスタムエラーキャッシュ設定" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/e5eb0bd4-521f-6dbb-8c07-c15173379681.png">|
|:--|

#### どのように誤った解釈をしてしまったか
「Error Caching Minimum TTL (seconds)」で設定した TTL 値でキャッシュされる = オリジンの Cache-Control、Expires を無視すると解釈してしまいました

エラーのキャッシュが長すぎる展開になってしまいました。
このケースでも公式ドキュメントを読み返し間違いに気付きました。
オリジンの Cache-Control が優先されるので「Error Caching Minimum TTL (seconds)」で設定した値でキャッシュさせてたい場合、オリジンのCache-Control を調整しています

動作について公式ドキュメントから抜粋します
「CloudFront がエラーをキャッシュする時間を制御する」ページ
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/custom-error-pages-expiration.html

>オリジンが Cache-Control max-age または Cache-Control s-maxage ディレクティブ、あるいは Expires ヘッダーを追加する場合: CloudFront は、ヘッダーの値と [Error Caching Minimum TTL (エラーキャッシュ最小 TTL)] の値のどちらか大きい方の値の期間、エラーレスポンスをキャッシュします。

## オリジンとの通信でhttpとhttpsを有効にするときの注意
オリジンポリシーを「Match Viewer (ビューワーに合わせる)」に設定しているケースになります。
この場合、オリジンからのレスポンスがプロトコル毎に異なっていたら注意ください！

#### 例えば、http リクエストを https に301リダイレクトしてるケース

パターン1. 先に https リクエストされる
  -> コンテンツがキャッシュされる
  -> 後に http リクエストされる
  -> コンテンツがキャッシュされているのでコンテンツを正常表示

パターン2. 先に http リクエストされる
  -> 301 リダイレクトがキャッシュされる
  -> 後に https リクエストされた
  -> 301 リダイレクトがキャッシュされているのでリダイレクトループ

パターン2では、リダイレクトループに陥るケースとなりました。
対応として「リクエストのプロトコルに基づいてキャッシュを設定する」必要があります。
公式ドキュメントから抜粋します。

「リクエストヘッダーに基づくコンテンツのキャッシュ」ページ
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/header-caching.html

>リクエストのプロトコル (HTTP または HTTPS) に基づいて、オブジェクトの異なるバージョンを CloudFront でキャッシュするには、CloudFront-Forwarded-Proto ヘッダーをオリジンに転送するように CloudFront を設定します。

この動作は、オリジンポリシーの「Match Viewer (ビューワーに合わせる)」の仕様に起因します。
「ディストリビューションを作成または更新する場合に指定する値」のページから抜粋します。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html

> CloudFront は、ビューワーリクエストのプロトコルに応じて HTTP または HTTPS を使用してオリジンと通信します。ビューワーが HTTP と HTTPS の両方のプロトコルを使用してリクエストを行った場合でも、CloudFront がオブジェクトをキャッシュするのは 1 回だけです。


## s3オリジンでオブジェクトが存在しないとき403エラーが返ってくる
CloudFront のオリジンとして S3バケットを OAI 接続するケースもあるかと思います。
S3 バケットポリシーに "s3:ListBucket" の許可がないことでオブジェクトが存在しない URL にアクセスしたとき 403 エラーが返ってしまいます。404 を返したいですよね。

公式ドキュメントから抜粋します。

「Amazon S3 から HTTP 403: Access Denied エラーをトラブルシューティングする方法」ページ
https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-troubleshoot-403/
ページ内「存在しないオブジェクト」を参照ください

>リクエストされたオブジェクトがバケット内に存在することを確認します。ユーザーに s3:ListBucket のアクセス許可がない場合、ユーザーには「404 Not Found」エラーの代わりに、存在しないオブジェクトに対し、アクセス拒否エラーが表示されます。


## デフォルトのルートオブジェクトの指定
オリジンが S3 の場合、注意が必要でした。
https\://hogehoge.jp のリクエストに対してあらかじめ設定したオブジェクトを返すものです。
あくまでもルートに対する設定となりサブディレクトリには有効でないことに注意ください。
https\://hogehoge.jp/hoge/ とアクセスしてコンテンツのダウンロードが開始されたときは「なぜ？」となってしまいました。

公式ドキュメントから抜粋します。

「デフォルトのルートオブジェクトの指定」ページ
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html
ページ内「ヘッダーとデフォルトのルートオブジェクトの連携方法」を参照ください
>ただし、デフォルトルートオブジェクトを定義しても、ディストリビューションのサブディレクトリに対するエンドユーザーリクエストはデフォルトルートオブジェクトを返しません。たとえば、index.html がデフォルトルートオブジェクトであり、CloudFront が CloudFront ディストリビューション下の install ディレクトリに対するエンドユーザーリクエストを受け取ったと仮定します。
>
>http://d111111abcdef8.cloudfront.net/install/
>
>index.html のコピーが install ディレクトリ内にあっても、CloudFront はデフォルトルートオブジェクトを返しません。


## アクセスログのステータスがゼロ
運用中にアクセスログをチェックしたところ status = 0 のアクセスログが存在していました。
公式ドキュメントを確認するとビューワーはリクエスト送信後にレスポンスを受信することなく接続が終了したときの値とのこと。

公式ドキュメントから抜粋します。

「アクセスログの設定および使用」ページ
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html
ページ内「ウェブディストリビューションのログファイル形式」セクションの表を参照ください。

>000。CloudFront がリクエストに応答する前に、ビューワーが接続をクローズしたこと (ブラウザタブを閉じたなど) を示します。CloudFront でレスポンスの送信を開始した後でビューワーで接続を終了すると、該当する HTTP ステータスコードがログに記録されます。


## あとがき
いろいろと誤認識してしまったと反省。
同じ過ちを繰り返さない為の「CloudFront 運用で覚えておきたいこと」でした。