CDNのテストをvarnishtestを使って行おう
インフラのいわなちゃんさん(@xcir)です。
先の記事ではTerraformでAkamaiを運用するということを書きました。
記事中でテストについて触れましたが、ふと考えたらCDNに対してテストを行う事例をあまり聞いたことないなと思いテストツールを公開します。
CDNのテストどうやっていますか?
CDNで複雑な処理を行うようになりテストがより重要なのは疑いもないのですが、あまりCDNに対してテストをということを聞きません。
新規に作成する場合であればCNAME切り替え前にhostsを書き換えてブラウザで確認したり、CLIで行う場合でもcurlで--resolveオプションで200なことを確認するだけというサイトも多そうです。
もちろん、PlaywrightなどでサイトのE2Eテストの一環で行っていたり、PytestやRSpecで行っているサイトもあると思いますが、今回はvarnishtestを使ってCDNのシンプルなUTを行う方法を紹介します。
varnishtestとは
10年ぐらい前にもVarnishでテストコードを書こう!という記事を書いているのでそちらも合わせ読んでほしいのですが、要はVarnishCacheのテストツールです。
非常に細かい部分のテストもできるためか、最近ではHAProxyもvarnishtest(正確には切り出したVTestですが)を使っているようです。
それでいて記述は比較的シンプルです。
弊社はVarnishCacheを長年利用しており、Varnishで処理しているドメインはすべてvarnishtestをテストを行っていることもありCDNに対しても使うのは自然でした。
varnishtestを外部に接続してテストできるようにする
ところがvarnishtestをCDNのテストツールとして使うには一苦労ありました。
そもそもがvarnishのテストツールであり、外部に接続することを(おそらく)想定していません。
接続先をIPアドレスで指定すれば可能ですが実運用で行うには厳しいです。
他にもテストclientがHTTPにしか対応しておらずHTTPSでは接続できないという問題がありました。
これらをどう解決したかというと先に触れたHAProxyです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
vtest "example.net" feature ignore_unknown_macro feature cmd {haproxy --version 2>&1 | grep -q 'HA-*Proxy version'} haproxy h1 -conf { defaults mode http timeout connect 5s timeout server 5s timeout client 5s backend be1 #https server srv1 ${target}:443 ssl verify none sni req.hdr(Host) backend be2 #http server srv1 ${target}:80 frontend fe1 #https use_backend be1 bind "fd@${fe1}" frontend fe2 #http use_backend be2 bind "fd@${fe2}" } -start #----------------------------------------- client c_http -connect ${h1_fe2_sock} { txreq -req GET -url "/" -hdr "Host: example.net" rxresp expect resp.status == "200" } -run client c_https -connect ${h1_fe1_sock} { txreq -req GET -url "/" -hdr "Host: example.net" rxresp expect resp.status == "200" } -run |
これはスクリプトに含まれるexample.netを対象としたサンプル定義です。
1 2 3 4 5 6 7 8 9 10 11 |
client c_http -connect ${h1_fe2_sock} { txreq -req GET -url "/" -hdr "Host: example.net" rxresp expect resp.status == "200" } -run client c_https -connect ${h1_fe1_sock} { txreq -req GET -url "/" -hdr "Host: example.net" rxresp expect resp.status == "200" } -run |
実際のテスト定義はこれだけで比較的シンプルです。他のところはhaproxyの設定です。
要はhaproxyにDNSの解決やhttpsでの接続を任せているわけです。
また最初にhostsを書き換えてブラウザでテストする例を挙げましたが、実際にテストを行う場合は本番に反映する前に実施する必要があるわけで接続先を変える必要があります。
Akamaiの場合はStaging/Productionのネットワークがあり、先にStagingに展開した後に接続先を変えてテストを行い問題がなければProductionに展開するのが一般的です。
他のCDNを使う場合でも多くの場合は少し変えたドメインで設定の確認を行い問題がなければ展開する運用が一般的でしょう。
先のHAProxyの設定ではexample.netに接続する定義はなく、その部分は${target}というマクロ定義になっています。
ここに接続先を指定することでCNAME切り替え前のCDN設定、Akamaiであれば異なるネットワークでのテスト、テストの書き方にもよりますがマルチCDNでのテストも可能です。
vtc-external-test
https://github.com/xcir/vtc-external-test
まずは動かしてみる
先ほどのテストサンプルをexample.netに対して接続して行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
xcir@DESKTOP-UL5EP50:~/git/vtc-ext-test$ cat tests/example.vtc vtest "example.net" feature ignore_unknown_macro feature cmd {haproxy --version 2>&1 | grep -q 'HA-*Proxy version'} haproxy h1 -conf { defaults mode http timeout connect 5s timeout server 5s timeout client 5s backend be1 #https server srv1 ${target}:443 ssl verify none sni req.hdr(Host) backend be2 #http server srv1 ${target}:80 frontend fe1 #https use_backend be1 bind "fd@${fe1}" frontend fe2 #http use_backend be2 bind "fd@${fe2}" } -start #----------------------------------------- client c_http -connect ${h1_fe2_sock} { txreq -req GET -url "/" -hdr "Host: example.net" rxresp expect resp.status == "200" } -run client c_https -connect ${h1_fe1_sock} { txreq -req GET -url "/" -hdr "Host: example.net" rxresp expect resp.status == "200" } -run xcir@DESKTOP-UL5EP50:~/git/vtc-ext-test$ ./vtc.sh -c example.net tests/example.vtc ============================================== Target Server: example.net VTC: /home/xcir/git/vtc-ext-test/tests/example.vtc ============================================== [+] Building 1.6s (6/6) FINISHED .... # top TEST /mnt/tests/test.vtc passed (6.198) |
ちなみに拡張子がvtcとなっているのはvarnish test codeの略で、vtcというと実際のテストコードをさします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ ./vtc.sh -c example.net tests/example.vtc ============================================== Target Server: example.net VTC: <span class="crayon-o">/</span><span class="crayon-v">home</span><span class="crayon-o">/</span><span class="crayon-v">xcir</span><span class="crayon-o">/</span><span class="crayon-v">git</span><span class="crayon-o">/</span><span class="crayon-v">vtc</span><span class="crayon-o">-</span><span class="crayon-v">ext</span><span class="crayon-o">-</span><span class="crayon-r">test</span><span class="crayon-o">/</span><span class="crayon-v">tests</span><span class="crayon-o">/</span><span class="crayon-v">example</span><span class="crayon-e">.vtc</span> ============================================== **** dT 0.000 * top TEST /mnt/tests/test.vtc starting ... **** c_http c-l|</html> **** c_http bodylen = 1256 ** c_http === expect resp.status == "404" ---- c_http EXPECT resp.status (200) == "404" failed ★★ * top RESETTING after /mnt/tests/test.vtc ** h1 Reset and free h1 haproxy 15 ** h1 Wait ** h1 Stop HAproxy pid=15 **** dT 5.579 **** h1 Kill(2)=0: Success **** dT 5.580 **** h1 STDOUT EOF **** dT 5.679 ** h1 WAIT4 pid=15 status=0x0002 (user 0.121659 sys 0.022119) **** dT 5.680 * top TEST /mnt/tests/test.vtc FAILED # top TEST /mnt/tests/test.vtc FAILED (5.681) exit=2 |
試しに200のexpect部分を404にしててみました。
きちんとFAILしていることがわかります。
起動オプション
option | 説明 | default | example |
---|---|---|---|
-h | help | - | - |
-v | verbose表示 | - | - |
-s | コンテナに入る | - | - |
-f | docker imageのリビルドを行う | - | - |
-n [target name] | 接続先をconf.sh で定義されたものを利用する |
default |
-n stg |
-c [connection server] | 明示的に接続先を設定する | - | -c example.net |
-o [extra varnishtest option] | varnishtestに追加のパラメータを渡す。主にマクロ定義で利用 | - | -o "-Dmacro=1" |
[vtc_file or vtc_dir] | vtc(varnish test case)ファイルかvtcが格納されているパスを指定。globや複数指定不可 | tests/ |
tests/example.net.vtc |
自分は毎回-cで接続先を指定するのが面倒だったため、次に説明するconf.shで接続先を定義しておいて
./vtc.sh -n prod tests/foo.vtc
といったように利用しています。
conf.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/bin/sh # Define the target server to be used with the -n option. # Example: if you want to target example.net when specifying `-n staging``, define C_staging="example.net". # default is `C_default`. C_example="example.net" C_default="${C_example}" # Specify the path of the VTC. # This is the default value if VTC is not specified at execution. DEFAULT_VTC_DIR="${SCRIPT_DIR}/tests" # varnishd -j value(parallel) VTC_JOBS=3 # varnishtest -b option(buffer size, default 1M) VTC_BUFFER_SIZE=3M # docker image name DOCKER_IMAGE_NAME="vtc-external-test" |
重要なのはC_[name]の変数です。
要はこれが接続先なので、Akamaiであれば
1 2 3 |
C_stg="example.akamaized-staging.net" C_prod="example.akamaized.net" C_default="${C_stg}" |
と書いておくと未指定の時はstgに接続、prodを指定すれば本番ネットワークに接続という切り替えが可能です。
書き方サンプル
先ほどのサンプルのHAProxy部分は常に固定なのでその部分はコピペしつつclient部分から書くことになります。
なお、次のVarnishのバージョンからincludeが使えるようになる(はず)ので、HAProxy部分を切り出してシンプルに書くことができるはずです。
client部も基本はステータスコードとヘッダ程度しかないのでそこまで難しくはないのですが、いくつか書き方の例をあげます。
Akamaiのデバッグヘッダを利用する
Pragmaで指定することで詳細な情報を得ることができます。
1 2 3 4 5 6 7 8 9 |
client c_cid -connect ${h1_fe1_sock} { txreq -req GET -url "/img/test.gif" -hdr "Host: example.net" -hdr "Accept-Encoding: gzip" \ -hdr "Pragma: akamai-x-check-cacheable,akamai-x-get-true-cache-key" rxresp expect resp.status == "200" expect resp.http.content-type == "image/gif" expect resp.http.x-check-cacheable == "YES" expect resp.http.cache-control == "max-age=10800, no-transform" } -run |
デバッグヘッダを指定することでキャッシュ対象かどうかをテストしています。
もちろん他にもキャッシュキーのチェックも可能です。
Pragmaをフルセットで指定していないのはvarnishtestの制限でヘッダが64行を超えるとエラーとなるからです。
また、割とsensitiveな情報がふくまれることがあるので素のまま出さずに特定のヘッダとセットでないと動かないように設定するのが通常です。
外部コマンドを利用してより詳細なチェックを行う(identify)
最近はサムネイルは動的に作ることが多いですが、縦長や横長画像で意図したサイズに収まっているか、フォーマットは意図している通りかといったテストを行うにはどうすればよいでしょうか?
1 2 3 4 5 6 7 8 |
FROM ubuntu:jammy RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ curl -s https://packagecloud.io/install/repositories/varnishcache/varnish74/script.deb.sh | /bin/bash && \ apt-get install --no-install-recommends -yq haproxy curl ca-certificates && \ apt-get install --no-install-recommends -yq varnish && \ apt-get install --no-install-recommends -yq imagemagick ★これを追加 |
Dockerfileにコマンド(今回はidentifyを使うのでimagemagick)を追加します。
更新後のvtc.sh初回実行時は-fオプションを付けることでimageが更新されます。
1 2 3 4 5 6 7 8 9 10 11 |
client c_rsz1 -connect ${h1_fe1_sock} { txreq -req GET -url "/52x52/imgtest.png" -hdr "Host: example.net" \ -hdr "Pragma: akamai-x-check-cacheable,akamai-x-get-true-cache-key" rxresp expect resp.status == "200" expect resp.http.x-check-cacheable == "YES" expect resp.http.cache-control == "max-age=3600, no-transform" expect resp.http.content-type == "image/png" write_body testimg shell -match "PNG 52x52 " { identify testimg } } -runて |
テストのポイントはwrite_body [filename]とshell -match "[REGEXP]" {[command]}です。
write_bodyを使うとレスポンスをファイルに書き込むことができ、shellで任意のコマンドを実行可能です。
ホスト名を変えたい
本番ドメインはexample.netでステージングドメインはexample-stg.netの場合でvtcは同じものを使いたい場合はマクロを使うと実現できます。
1 2 3 4 5 |
client c_http -connect ${h1_fe1_sock} { txreq -req GET -url "/" -hdr "Host: example${sfx}.net" rxresp expect resp.status == "200" } -run |
${sfx}がマクロで、後はvtc.sh実行時に-Dsfx='[replacement]'を指定します。
1 2 3 4 5 6 7 |
$ ./vtc.sh -c example.net -o "-Dsfx=''" tests/example.vtc ============================================== Target Server: example.net VTC: /home/xcir/work/akamai/infra-akamai-pm-terraform/vtc/tests/example.vtc VTC Option: -Dsfx='' ============================================== # top TEST /mnt/tests/test.vtc passed (5.926) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$ ./vtc.sh -c example.net -o "-Dsfx='-stg'" tests/example.vtc ============================================== Target Server: example.net VTC: /home/xcir/work/akamai/infra-akamai-pm-terraform/vtc/tests/example.vtc VTC Option: -Dsfx='-stg' ============================================== **** dT 0.000 ... *** c_http connected fd 6 from 127.0.0.1 38120 to 127.0.0.1:38985 ** c_http === txreq -req GET -url "/" -hdr "Host: example-stg.net" **** c_http txreq|GET / HTTP/1.1\r **** c_http txreq|Host: example-stg.net\r★★example-stg.netになっている **** c_http txreq|\r ... ---- c_http EXPECT resp.status (404) == "200" failed * top RESETTING after /mnt/tests/test.vtc ** h1 Reset and free h1 haproxy 15 ** h1 Wait ** h1 Stop HAproxy pid=15 **** h1 Kill(2)=0: Success **** dT 5.848 **** h1 STDOUT EOF **** dT 5.947 ** h1 WAIT4 pid=15 status=0x0002 (user 0.146294 sys 0.000000) **** dT 5.948 * top TEST /mnt/tests/test.vtc FAILED # top TEST /mnt/tests/test.vtc FAILED (5.948) exit=2 |
一点注意があるのは未指定の場合はそのままの文字列(${sfx})が残るので、-stgが不要な時でも指定が必要になります。
vtcは様々な機能があるので詳細はこちらを参照してください。
テスト実行時の注意点
当たり前ですがCDNはキャッシュをします。
まずテストを行う場合はステージングネットワークだったり別ドメインだと思いますのでパージを行うなどしてテストを行える状態にしましょう
最後に
CDNに複雑なコードを置く以上テストを十分する必要があります。
かといってhosts書き換えでがんばったりするのはなかなか辛いですし一つの案としてvarnishtestを利用したテスト方法を紹介しました。
今回はCDNをテストするがテーマでしたが、もちろんCDN以外でもテストは可能なのでちょっとしたHTTP/S経由でUTを行いたい場合には有用だと考えています。