Varnishでテストコードを書こう!

はじめまして、サーバ基盤チームの田中祥平(@xcir)です。
最近入社しまして、チームではいわなちゃんと呼ばれています。よろしくお願いします。

入社してからGREEの配信システムをVarnish Cache(以下Varnish)に置き換える仕事をしていたのですが、少し前に問題なく山を超えました。
そこで今回利用したVarnishの特にテスト機能について紹介しようと思います。

なお、今回の説明に利用するVersionは3.0.3です。

Varnishとは

VCLというドメイン固有言語をもち、キャッシュもできる高速リバースプロキシです。
if文が書けるので柔軟に記述しやすいという特徴があります。

たとえば/admin/以下に許可したIP以外からのアクセスは弾くと言ったことは以下のように記述できます。

Varnishを全く知らない人でもなんとなくわかるのではないでしょうか?

varnishtestとは

Varnish本体のレグレッションテストを行うために作られたもので、同時にアクセスが来た時でも問題なくレスポンスできるかなど300個近いテストコードがあります。
別に本体専用というわけでもないため、VCLを記述した際にテストコードを書いておくと便利です。
このテストコードはVarnish Test Codeということで拡張子もvtc、内部でもvtcと呼ばれています。
しかしvarnishtestはコマンドの中でもあまりメジャーな存在ではありません。
理由はman varnishtestにあるので見てみましょう

  • 詳細なドキュメントが公式に存在しない。
  • 見てねと言ってる本体のテストコードでも使われていないコマンドがある。
  • (私の観測範囲では)海外のブログでも余り詳しく取り上げられていない。

そんな理由でそもそも存在が知られてない上級者向けコマンドです。

命令数もコマンド数も多いので全て紹介するのではなくVCLをデバッグする上で知っておくと便利なvarnishtestの使い方について解説します。

基本的なテストコード(vtc)の構造

以下が単純なvtcになります。

vtcは上から順に実行され、また構造として2階層となっています。
よく使うコマンドとして

コマンド名 説明
varnishtest テストのタイトルを記述する
server HTTPサーバの振る舞いを記述、基本的にオリジンとして使う
varnish Varnishサーバの振る舞いを記述、VCLなどを記述
client クライアントの振る舞いを記述

があります。
それぞれの関係は

となります。
serverはオリジンの役割をすると考えてもらえばわかりやすいとおもいます。

server

まずserverの箇所を見てみましょう。

{~}で囲まれた箇所でこのHTTPサーバで何をするかを定義しています。(後で解説)
次に「server s1 -start」とありますが、ここでHTTPサーバを起動しています。

varnish

次にvarnishの箇所を見てみましょう。

-vcl+backend{~}とあります。
ここの-vcl+backendはVCLの記述を含むことと、先に指定した「server s1」をデフォルトのバックエンドとして自動的に補完する事を表しています。
そして-startのところでVarnishを起動します。
先ほどのserverの指定では-startを別で書きましたがこのように同時に指定も可能です。
なので先ほどのserverの設定も

と言った風に記述することもできます。

client

そして最後にclientです

これもクライアントの振る舞いを記述しています。
クライアントはサーバではないので-start(起動)ではなく-run(実行)となります。

server(2階層目)

さて次に2階層目です。serverを見てみましょう。

これらを言葉に直してみると

といった感じです。

client(2階層目)

次にclientです。

これも言葉に直してみると

となります。

まとめ

一連の流れを書いてみると

となります。(クリックすると大きくなります)
以上がvtcの基本的な動きとなります。

ログの読み方

取り敢えず先ほどのvtcをvarnishtestで実行してみましょう

これが成功した時のログです。

逆に失敗した時は

失敗するためにレスポンスのステータスが404でなければエラーとしています。

非常に長くて何が何だかわかりませんが、大体見るべきポイントは同じなので解説します。

ログフォーマット

manに記載されている通り、varnishtestには厳密な定義はありません。そのため若干揺れがあり、必ずしも当てはまるものではありません。

logのレベル(0~4) 出力したコンポーネントの名前 経過時間 メッセージ
* top 1.4 TEST example_ng.vtc FAILED
レベルについて

logのレベルが上がるほど詳細になります。

レベル 表記 内容
0 # テストが成功した # top TEST example.vtc passed (0.498)
0 ---- テストが失敗した原因などの重要なログ ---- c1 0.4 EXPECT resp.status (200) == 404 (404) failed
1 * 各種コマンドのログ * top 1.4 TEST example_ng.vtc FAILED
2 ** 各種コマンドのログ ** s1 0.0 Starting server
3 *** 各種コマンドのログ *** s1 0.4 rxreq
4 **** 各種コマンドのログ **** s1 0.4 http[ 0] | GET

VCLのテストの場合では、レベル0と4のログを見れば基本的に解決すると思います。

抜粋例

コンポーネントの名前について
コンポーネント名 説明
top varnishtestが発行するログ
s~ serverが発行するログ
v~ varnishが発行するログ
c~ clientが発行するログ

server,varnish,clientの名前は

のs1部分になります。
またこの名前はそれぞれs,v,cから始まる必要があります。
例えばserverの定義で

とした場合はvtcのエラーとなります。

コマンド応用編

いままで、基本的な使い方とログの読み方について解説しました。
しかし、vclをデバッグするのに便利なコマンドが他にもあるので解説します。

serverコマンド

コマンド名 説明 使い方例
-repeat {~}で囲まれた部分を指定回数実行する -repeat 2
-listen アドレスとポートを指定する -listen "localhost:9821"
-start サーバを開始する -start

ここで重要なのが-repeatです。

と指定した場合、serverは何回でもレスポンスするわけではなく1回のみレスポンスします。
そのため、2回以上serverがリクエストを受け付ける場合は

といった様に書く必要があります。
回数は実際のリクエストの回数より多めに指定しても問題ありません。

varnishコマンド

コマンド名 説明 使い方例
-storage ストレージを指定する -storage "-smalloc,1m"
-arg 起動パラメータを指定 -arg "-p thread_pool_stack=262144"
-cli CLIコマンド発行 -cli "vcl.list"
-vcl VCLを定義する -vcl {...}
-vcl+backend VCLを定義する(defaultのバックエンドを自動生成) -vcl+backend {...}
-start varnishを開始する -start
-stop varnishを停止する -stop

clientコマンド

コマンド名 説明 使い方例
-repeat {~}で囲まれた部分を指定回数実行する -repeat 2
-connect 接続先を指定 -connect "localhost:9821"
-run 開始する -run

clientとserverの{...}で使うコマンド

コマンド名 コマンドのオプション server client 説明 使い方例
timeout タイムアウト秒数を指定 timeout 10
txreq リクエストを送信 txreq -url "/"
-url リクエストを送信(url指定) txreq -url "/"
-req リクエストを送信(メソッド指定) txreq -req "POST" -url "/"
-proto リクエストを送信(プロトコル指定) txreq -proto HTTP/1.0 -url "/"
-hdr リクエストを送信(ヘッダ指定) txreq -hdr "hoge: hoge" -hdr "mage: mage" -url "/"
-body リクエストを送信(ボディ指定) txreq -req "POST" -url "/" -body {hello}
rxreq リクエストを受信 rxreq
txresp レスポンスを送信 txresp
-proto レスポンスを送信(プロトコル指定) txresp -proto HTTP/1.0
-hdr レスポンスを送信(ヘッダ指定) txresp -hdr "hoge: hoge" -hdr "mage: mage"
-body レスポンスを送信(ボディ指定) txresp -body {hello}
-status レスポンスを送信(ステータス指定) txresp -status 404
rxresp レスポンスを受信 rxresp
expect 式を評価する expect req.url == "/"
send そのまま送信 send "HTTP/1.1 200 Ok\r\nConnection: close\r\n\r\n"
sendhex そのまま送信(16進表記) sendhex "2e 51 30 36 54 30 b0 b4"
delay スリープする delay 1
loop {...}で囲んだ箇所を指定回数ループする loop 2 {...}

expectで使える演算子は以下のとおりです

演算子
==
!=
>
<
>=
<=

delayでは以下の表記方法があります。

表記 説明
delay 1 1秒スリープ
delay 1.5 1.5秒スリープ
delay .5 0.5秒スリープ

shellコマンド

shellコマンドを実行できます。

といった風に使用可能です。

起動オプション

varnishテスト自体の起動オプションです

オプション名 説明 使い方
-D マクロ指定(後述) -Dkey=val
-n テストを指定回数繰り返す -n 2
-j テストを並列で動かす -j 2

varnishtestは結構時間がかかるので並列で動かしたくなりますが、重いので2,3ぐらいで留めておくのが良いかと思います。

マクロについて

vtc中に

といった風に記述しておいて

と実行すると、置換されて

上記のようになります。
非常に便利ですが例えばvclのincludeを使った先については置換されないので注意が必要です。

またデフォルトで定義されるものとして

マクロ名 説明
pwd 現在のディレクトリ
date 現在時刻
tmpdir このテストで使ってるテンポラリディレクトリ
[server名]_sock 指定serverのアドレス・ポート
[varnish名]_sock 指定varnishのアドレス・ポート

などがあります。

その他記述する際の留意点など

server,varnish,clientについて

必ず3つが定義されている必要はありません。serverがなくてもいいですし、varnishがなくてもOKです。

コメントについて

vtc中にコメントを入れたい場合は先頭行に#を入れてください。

複数行のテキストの使い方

テキストは通常"..."で囲まれた範囲ですが、複数行を指定したい場合は{...}で囲むことで可能です。

単一行

複数行

謎のエラーへの対処(Syntax)

rxreqはあるのにエラーになる場合があります。大抵の場合は前の行に問題があるケースが多いです。

NGパタン

OKパタン

違いはs1と{の間にスペースがあることなのですが、こういうところが弱いのでよくわからないエラーが出た場合はスペース省略していないかをチェックしてみてください

異常終了時

ctrl+cで終了させたときや何らかの原因で異常終了した場合は、/tmpにvtc.~で始まるディレクトリが残っているケースがあります。
案外サイズが大きいので削除すると良いと思います。

まとめ

以上がvarnishtestの使い方でした。
実は、他にもsema(セマフォを使った同期)などのコマンドもあるのですが、VCLをテストする場合はこれぐらい知っておけば十分だと思います。
varnishはプログラムのように複雑な条件も書くことができるため、言ってしまうとバグが混入しやすいとも言えます。
そのためうまくvarnishtestを使うと良いのではないかと思います。


参考ページ

Varnish Cache公式ページ