レンタルサーバでCDN-Cache-Control を設定してみた
お前はいったい何をしているんだ……?
私の Web サイトは今(2021年7月)現在、Hugo で生成した Web サイトのファイルを、
のスタンダードプランでホスティングを行ない、それを CDN として Cloudflare を経由して配信しています。
その上で今回、 CDN のキャッシュを最適化してさくらのレンタルサーバのオリジンへのリクエスト数を減らすべく、
- ドラフト段階の
CDN-Cache-Control
を使う -
Cache-Control
を使ってブラウザの Cache をコントロールする -
CDN-Cache-Control
やCache-Control
の設定を自動化する
と言ったような事を今回をやっていました。
ただこれを行う際に、さくらのレンタルサーバでは Apache HTTP Server の .htaccess
で Location
ディレクティブが使えず、かといって個別のディレクトリに .htaccess
を配置して HTTP Header を設定するのも自動化しないとやってられなかったので、今回はその辺りを自動化してデプロイした、と言う辺りの話を書きたいと思います。
CDN-Cache-Control
ってなんや?
そのまえに 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-Control
や CDN-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
の値は各ページの性質ごとに若干数値を変えていますが、基本的な内容はこういった様な設定がされています。
なおこのスクリプトの内容自体は私の環境に完全に最適化されているので、他の方の参考にはなりにくいのですが、基本的な考え方は次の通りです:
-
find . -type d | grep XXX
で対象となるディレクトリ一覧を得る -
echo ${CONFIG} > $dir/.htaccess
で.htaccess
を書き出す - キャッシュを短かくする必要があるディレクトリの
.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
とスクリプトでやっていることの流れは、
-
rsync
でファイルをデプロイした際に変更のあったファイル一覧を得る - (1) で得たファイルを元に CDN から Purge すべき URL を得る
- あとは Cloudflare の仕様に合わせて Cache の Purge を実行する
と言った様な感じです。
それと補足になりますが、 Cloudflare の API を叩くためには認証用の Bearer
トークンと Purge する Cache を特定するために必要な ZoneID
トークンがありますが、これらの値は .env
ファイルに保存してあり、またこれらの値は curl
を手動で叩いで取得しました。
そのためこの辺りの詳細については Cloudflare の公式 API ドキュメントを参照すると良いと思います:
以上
と言う様な感じで、
レンタルサーバでCDN-Cache-Control を設定 する
と言うことを私は実現しました。
あとこれが上手く動いているかどうかですが、今のところ確認した範囲では動いている様に思います。と言うか動いてなかったら泣くしかない。
それと今回の方法は、
- レンタルサーバで
.htaccess
でHeader
ディレクティブが使えること -
rsync
経由でファイルがデプロイできること - 最後に
bash
やMakefile
が取り扱えること
と言う様な条件がそろい、かつ Cloudflare を使って無ければ実現できない環境ではありますが、
CDN-Cache-Control
が一般に使える様になった際にはレンタルサーバでも CDN のキャッシュの最適化がある程度は容易になると思っています。はい。
Discussion