CloudFront使用時の注意

こんにちは、HACKNOTEのJunya.kです。
この記事ではCloudFrontを通してEC2サーバーを利用しWordPressのWEBサイトを運用する際に、CloudFrontのキャッシュをどのように取り扱えばいいかを説明します。
CloudFrontを利用したEC2サーバーの利用については以下の記事をご覧ください。
WordPress基本構成パターン6 CloudFront+ec2インスタンス

CloudFrontとキャッシュ

CloudFrontはキャッシュ機能を持ったHTTPリバースプロシキです。オリジン(もともとのコンテンツを配信するサーバー)からのコンテンツを、世界各地にある配信用のサーバーにキャッシュすることによって高速なコンテンツ配信を実現する、いわゆるサーバーキャッシュと呼ばれるシステムです。
これによってオリジンが直接応答するリクエスト数が少なくなり、遅延も少なくなります。

CloudFrontの設定

CloudFrontEC2インスタンスに対する設定方法について説明していきます。

1.EC2インスタンスの作成

まずはEC2インスタンスを作成します。以下にWordPress運用のためのLAMP環境構築に必要なコマンドを列挙します。

$ ssh -i [公開鍵].pem ec2-user@[IPアドレス]
[ec2-user@ ~]$ sudo su -
[root@ ~]# amazon-linux-extras install php7.2
[root@ ~]# yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm -y
[root@ ~]# yum-config-manager --disable mysql80-community
[root@ ~]# yum-config-manager --enable mysql57-community
[root@ ~]# yum install -y httpd php mysql-community-server
[root@ ~]# systemctl start httpd mysqld
[root@ ~]# systemctl enable mysqld httpd
Created symlink from /etc/systemd/system/multi-user.target.wants/mysqld.service to /usr/lib/systemd/system/mysqld.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.
[root@ ~]# cd /var/www/html/
[root@ html]# wget https://ja.wordpress.org/latest-ja.tar.gz
[root@ html]# tar -xzvf latest-ja.tar.gz
[root@ html]# rm latest-ja.tar.gz

2.CloudFrontの作成

CloudFrontを作成し、EC2インスタンスを設定します。
サービスネットワーキング & コンテンツ配信からCloudFrontを選択します。
Webの部分のGet Startedから作成画面に飛びます。



Create Distributionを選択します。Origin Domain Nameの部分に作成したEC2サーバーのパブリックDNSを入れます。



Origin IDは勝手に設定されます。このままCreate Distributionを選択ししばらく待ちます。StatusEnableになれば完了です。

3.DNSの設定

Webサイトのドメインを取得済みで、このドメインを通してCloudFrontを利用したい場合はRoute53からDNSの設定を行います。以下の記事を参考にしてください。
WordPress基本構成パターン6 CloudFront+ec2インスタンス

キャッシュについての設定方法

キャッシュ情報の確認

まずはWebサイトのキャッシュ情報の確認方法から説明していきます。
サイトにアクセスし、デベロッパーツールを起動します。Networkのタブを開き、記事等にアクセスすると、以下のように表示されます。



適当なファイルを選択すると、HTTPのHeader情報が表示されます。ここのレスポンスヘッダーにキャッシュを利用していたらキャッシュの情報が含まれているはずです。

キャッシュ方法の選択

作成したCloudFront DistributionDistribution Settingsを見てみると、BehaviorDefault(*)という設定があります。
CloudFrontを介して通信が行われる場合、Behaviorの設定に従ってキャッシュが行われます。他に設定してある設定を通り抜けて最後に参照されるのがこのDefault(*)の設定です。

以下でキャッシュをする場合キャッシュしない場合の設定をそれぞれ書いていきます。例えばキャッシュするものを後から追加・編集したいのであれば、Default(*)をキャッシュしない設定にしておけばいいでしょう。

まずCreate Behaviorを選択します。

キャッシュする場合

Path Pattern(Defaultを編集する際は選択→Editから)
適用するパスパターンを選択します。例えばテキストファイルの拡張子を指定したい場合/*.txtと入れます。

Allowed HTTP Methods
キャッシュするリクエストを選択します。GET,HEADを選択します。

Forward Cookies
Webサーバーに渡すCookieの設定です。Noneを選択します。
状況によっては設定してもいいですが、ALLを選択するとキャッシュが使われないので注意してください。(後述)

Object Caching
キャッシュ保持時間に関する設定です。Use Origin Cache Headersを選択します。自分でカスタマイズしても構いません。

Query String Forwarding and Caching
コンテンツのキャッシュにおけるクエリ文字列パラメータの設定です。forward all, cache based on allを選択します。

キャッシュしない場合

Allowed HTTP Methods
GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETEを選択します。

Forward Cookies
ALLを選択します。

Object Caching
Customizeを選択し、全て0に設定します。

Query String Forwarding and Caching
forward all, cache based on allを選択します。

指定拡張子のキャッシュの例

上記のキャッシュする場合/*.gifに設定します。
また、Defaultに対してキャッシュしない場合として設定します。
デベロッパツールで確認すると、以下のように表示されます。



CloudFrontからキャッシュされていることがわかります。

Cookieを利用している場合の注意

先程出てきましたが、Forward CookiesにおいてALLを選択して運用すると、CloudFrontはCookieの値が変動した際に新たなキャッシュを作成します。すなわち、Cookieの値によって別キャッシュとなります。
Cookieの値を見てみることにします。以下のように/var/www/html/index.php/を作成します。Google Analyticsを入れてあります。
なお、以下の検証ではキャッシュ状態の変更をすぐに確認したいので、TTL時間を短く設定してあげましょう(全て60に設定する、など)。

<?php
  setcookie( "cookie01", "value01" );
  setcookie( "cookie02", date('Y/m/d H:i:s') );
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>test_cookie</title>
</head>
<body>
<span>Cookieの情報を表示します。<br></span><br>
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-********-*', 'auto');
  ga('send', 'pageview');

</script>
<?php
  $tmp = json_encode( $_COOKIE );
  echo $tmp;
  echo '<br>';
?>
</body>
</html>

この場合Cookieが時間によって変動することになります。キャッシュは常に

X-Cache: Miss from cloudfront

となっているはずです。次に、日付の部分を消して、以下のように書き直しましょう。

<?php
  setcookie( "cookie01", "value01" );
  setcookie( "cookie02", "" );
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>test_cookie</title>
</head>
<body>
<span>Cookieの情報を表示します。<br></span><br>
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-********-*', 'auto');
  ga('send', 'pageview');

</script>
<?php
  $tmp = json_encode( $_COOKIE );
  echo $tmp;
  echo '<br>';
?>
</body>
</html>

Chromeからアクセスするとこのように表示されます。

Cookieの情報を表示します。

{"cookie01":"value01","_ga":"GA1.1.1864164139.1550552388","_gid":"GA1.1.494975640.1550552388","_gat":"1"}

一方でSafariからアクセスするとこのようになります。

Cookieの情報を表示します。

{"_ga":"GA1.1.1324530818.1550553618","_gid":"GA1.1.1726637859.1550553618","cookie01":"value01","_gat":"1"}

この状態でキャッシュの状態を見てみると

X-Cache: Miss from cloudfront

となってしまいます。GoogleAnalyticsのCookieのせいでユーザーごとにキャッシュが作成されるため、せっかくCloudFrontを設定しているのにHitする確率が低くなってしまっています。

この現象を回避する

Forward CookiesにはNoneALL以外にWhitelistを設定することができます。ここに原因となるCookie以外を指定してあげます。
このHTMLページの場合にはcookie01を指定してあげます。すると、cookie1以外のCookie情報は渡されないことがわかります。

Cookieの情報を表示します。

{"cookie01":"value01"}

これで常にCloudFrontからのHitを得ることができます。このようにCookieの変動によってCloud Frontの動作に影響を与えるものを個別に取り除ことができます。

また、例えば画像についてのキャッシュはCookieによって変動してほしくない、など特定の拡張子について制限を設けたい場合についてはその拡張子の個別設定において(設定の仕方は前述)Noneに設定してあげれば面倒くさい 設定なしでキャッシュを利用することができます。