💰

レンタルサーバでCDN-Cache-Control を設定してみた

6 min read

お前はいったい何をしているんだ……?

私の Web サイトは今(2021年7月)現在、Hugo で生成した Web サイトのファイルを、

https://www.sakura.ne.jp/

のスタンダードプランでホスティングを行ない、それを CDN として Cloudflare を経由して配信しています。

その上で今回、 CDN のキャッシュを最適化してさくらのレンタルサーバのオリジンへのリクエスト数を減らすべく、

  1. ドラフト段階の CDN-Cache-Control を使う
  2. Cache-Control を使ってブラウザの Cache をコントロールする
  3. CDN-Cache-ControlCache-Control の設定を自動化する

と言ったような事を今回をやっていました。

ただこれを行う際に、さくらのレンタルサーバでは Apache HTTP Server の .htaccessLocation ディレクティブが使えず、かといって個別のディレクトリに .htaccess を配置して HTTP Header を設定するのも自動化しないとやってられなかったので、今回はその辺りを自動化してデプロイした、と言う辺りの話を書きたいと思います。

そのまえに CDN-Cache-Control ってなんや?

CDN-Cache-Control の詳細な云々については、

https://dev.classmethod.jp/articles/cloudflare-supports-cdn-cache-control/

を見た方が早いのですが、一応説明しておくと、CDN-Cache-Control は 2021年7月現在 IETF のドラフトになっている HTTP Header で、一般向けに使われている Cache-Control の CDN 版みたいな感じの HTTP Header です。

そしてこれはまだドラフト段階の規格なので、これをサポートしている CDN は Cloudflare のみなのですが、たまたま私の Web サイトが Cloudflare を経由して配信していたのでこれを運良く利用できた、と言うのが今回の話の一部でもあります。が、この話は蛇足ですね。

Location ディレクティブが使えない件はどう解決したか

まずさくらのレンタルサーバの仕様について触れておくと、さくらのレンタルサーバでは Location ディレクティブが使えはしないものの、Apache HTTP Server の Header ディレクティブは使える様になっていて、かつ配信ディレクトリ単位で .htaccess が配置できます。

そして Hugo で生成している私の Web サイトの固有リンクなどは、ディレクトリ一覧の代わりにディレクトリ内の index.html を表示することで成り立っています。

そのため index.html と同じディレクトリ内に HTTP Response Header に Cache-ControlCDN-Cache-Control を設定する .htaccess を配置できれば CDN-Cache-Control などが設定できるため、私はこの仕様を元にスクリプトを組みました:

#!/usr/bin/env bash

set -euo pipefail

cd "$(dirname "${0}")/../dist"

STATIC='Header set Cache-Control "max-age=31536000"'
ENTRIES='Header set Cache-Control "max-age=7200"'
ARCHIVES='Header set Cache-Control "max-age=86400"'
PAGES='Header set Cache-Control "max-age=86400"'
CDNCACHE='Header set CDN-Cache-Control "max-age=3153600000"'

echo "Generating .htaccess ...";

# static files
for dir in "assets" "images"; do
  echo "${STATIC}" > $dir/.htaccess 
  echo "${CDNCACHE}" >> $dir/.htaccess
done

# entries (archives)
for dir in $(find . -type d | grep -P '^\./\w+/\d{4}/\d{2}/\d{2}/\d{6}'); do
  echo "${ARCHIVES}" > $dir/.htaccess
  echo "${CDNCACHE}" >> $dir/.htaccess
done

# entries (live)
for dir in $(find . -type d \
              | grep "./*/$(date +%Y/%m)" \
              | grep -P '^\./\w+/\d{4}/\d{2}/\d{2}/\d{6}'); do
  echo "${ENTRIES}" > $dir/.htaccess
  echo "${CDNCACHE}" >> $dir/.htaccess
done

# entries (notes)
for dir in $(find . -type d | grep -P '^\./notes'); do
  echo "${ENTRIES}" > $dir/.htaccess
  echo "${CDNCACHE}" >> $dir/.htaccess
done

# pages
for dir in "nyarla" "policies" "licenses"; do
  echo "${PAGES}" > $dir/.htaccess
  echo "${CDNCACHE}" >> $dir/.htaccess
done

echo "Done."

また設置される .htaccess の内容は下記の様な感じです:

Header set Cache-Control "max-age=7200"
Header set CDN-Cache-Control "max-age=3153600000"

無論 .htaccess 内の Cache-Control の値は各ページの性質ごとに若干数値を変えていますが、基本的な内容はこういった様な設定がされています。

なおこのスクリプトの内容自体は私の環境に完全に最適化されているので、他の方の参考にはなりにくいのですが、基本的な考え方は次の通りです:

  1. find . -type d | grep XXX で対象となるディレクトリ一覧を得る
  2. echo ${CONFIG} > $dir/.htaccess.htaccess を書き出す
  3. キャッシュを短かくする必要があるディレクトリの .htaccess は適宜上書きする

そして上記のスクリプトを Makefile 経由で Web サイトをデプロイする際に使うコマンドに仕込み、この辺りのスクリプトを自動実行して Web サイトをデプロイする様にしています。

CDN-Cache-Control で Cache 時間 100年って何?

ちなみに先のスクリプト内の .htaccess で、

Header set CDN-Cache-Control "max-age=3153600000" # 100年間キャッシュする

と言う値を設定し、Cloudflare 自体が 100年後も存在するのかとか、そも100年後にもインターネットは存在するのか? と考え込む様な値を設定していますが、これは私の実用上問題ない値で、何故かと言えば Web サイトのデプロイ時に更新されたページの Cache を Purge スクリプトが存在するからです。

つまりとても長い時間 Cache を CDN に持たせたとしても、適宜 Web サイトのデプロイ時に CDN の Cache を削除するスクリプトを自動実行しているためにこれが問題にならない、と言う訳ですね。なるほど賢い(?)

また上記の機能を実現するスクリプトと Makefile はおおよそ次の通りとなります:

# 一部抜粋
up: clean dist
	@rsync -crvz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
	  dist/ \
	  nyarla@nyarla.sakura.ne.jp:/home/nyarla/www/the.kalaclista.com/ \
	  | head --lines=-3 | tail --lines=+2 >resources/_gen/purge.txt
	@echo Updated:
	@cat resources/_gen/purge.txt
	@bash scripts/purge_cache.sh
#!/usr/bin/env bash

source .env

if ! test -e resources/_gen/purge.txt || test -z resources/_gen/purge.txt ; then
  exit 0
fi

if test $(cat resources/_gen/purge.txt | wc -l) -gt 30 ; then
  curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
    -H "Authorization: Bearer ${ZONE_TOKEN}" \
    -H "Content-Type: application/json" \
    --data '{"purge_everything":true}'
else
  curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
    -H "Authorization: Bearer ${ZONE_TOKEN}" \
    -H "Content-Type: application/json" \
    --data "$(cat resources/_gen/purge.txt | sed 's/^\(.\+\)$/"https:\/\/the.kalaclista.com\/\1",/g' | tr "\n" " " | (echo -n '{"files":['; cat - ; echo -n "]}") | sed 's/, ]}$/]}/')"
fi

なおこの Makefile とスクリプトでやっていることの流れは、

  1. rsync でファイルをデプロイした際に変更のあったファイル一覧を得る
  2. (1) で得たファイルを元に CDN から Purge すべき URL を得る
  3. あとは Cloudflare の仕様に合わせて Cache の Purge を実行する

と言った様な感じです。

それと補足になりますが、 Cloudflare の API を叩くためには認証用の Bearer トークンと Purge する Cache を特定するために必要な ZoneID トークンがありますが、これらの値は .env ファイルに保存してあり、またこれらの値は curl を手動で叩いで取得しました。

そのためこの辺りの詳細については Cloudflare の公式 API ドキュメントを参照すると良いと思います:

https://api.cloudflare.com/

以上

と言う様な感じで、

レンタルサーバでCDN-Cache-Control を設定 する

と言うことを私は実現しました。

あとこれが上手く動いているかどうかですが、今のところ確認した範囲では動いている様に思います。と言うか動いてなかったら泣くしかない。

それと今回の方法は、

  1. レンタルサーバで .htaccessHeader ディレクティブが使えること
  2. rsync 経由でファイルがデプロイできること
  3. 最後に bashMakefile が取り扱えること

と言う様な条件がそろい、かつ Cloudflare を使って無ければ実現できない環境ではありますが、
CDN-Cache-Control が一般に使える様になった際にはレンタルサーバでも CDN のキャッシュの最適化がある程度は容易になると思っています。はい。