4月 072017
 

5.1.2がリリースされました。
5.1.1の記事で4月中旬まで様子を見たほうがよいと言っていたのはこの件です。
(まだ確定じゃなかったので微妙にぼかした表現でしたが・・)
今回のリリースは1件の機能強化と主にHTTP/2関連のバグフィックスです。
[changelog] [リリース文] [パッケージDL] [ソースDL]
 

機能強化

std.collectでセパレータを指定できるようになりました
std.collectは同一名のヘッダが複数行送られてきた場合に1行にまとめる関数ですが今までは[, ]でまとめられていました。
通常の場合は特に問題ないのですが、Cookieの場合は[; ]でまとめる必要があり、追加で置換が必要など面倒でした。
デフォルト値には[, ]が指定されているので、既に使用している場合は特に追加指定は不要ですが
Cookieで使用する場合は以下のように指定するとよいです。


std.collect(req.http.cookie, "; ");

 

バグ修正(h2以外)

#2295 動的バックエンドを使用しているときにヘルスチェックが無限ループに陥ることが合ったのを修正
#2207 #2278 PROXYプロトコルを使用したバックエンドのヘルスチェックで競合状態がおきるのを修正
 

バグ修正(h2)

#2291 #2300 Cookieヘッダを1行にまとめるように修正
#2247 ボディを含むリクエストにContent-Lengthが存在した場合でもTransfer-Encoding: chunkedを追加していたのを修正
上記2つの修正により、前回紹介したVCLトリックは不要になりました。

#2238 ReqAcctの値がないためvarnishncsa等で入出力したバイト0になるのが修正されました
わかりやすいところだとvarnishncsaの%bなどが常に0になっていたのですが、この部分が実装されたためサイズがでるようになりました。

多数のassertを修正

Varnishでのhttp/2について

5.1.1では検証環境で使用できると書きましたが、今回は十分な検証を行った上であれば本番環境に入れることもできなくはないと考えています。
もちろんまだexperimentalで残バグもおそらく新規バグもあると思いますが
主にSimonや自分が相当数踏み抜き、修正されているので
以下の幾つかの注意点を守れば比較的安定的に動作すると思います。

#2268 PRIV_TASK/TOPを利用しているvmodを避ける
PRIV_TASK/TOPについてはこちらの記事を御覧ください。
このPRIVの確保・解放を行う際に競合状態が起きているようで、稀にassertが起こります。
現時点で回避するには使用しているvmodの関数を避けるしかありません(例:vmod_cookie)
使用しているかどうかの判定はvccファイルを見てPRIV_TASKかPRIV_TOPがあるかで判定できます。

スレッドのメモリ使用サイズがh/1の時と比べて大幅に増える可能性があるのでチューニングが必要
h2はh1と比べるとメモリをより多く使用するため一瞬なんか漏れてると思うぐらい増えます。
元々メモリに余裕がある環境であれば特に問題はないのですが、VPSなどあまりメモリがない環境の場合は問題といえます。
解決方法としては単純にスレッド数に余分があれば減らす感じです。
もし、減らしすぎてスレッドが足りなくなったとしても、そのリクエストはキューイングされて、すぐにスレッドを追加作成して処理されます。
関連するパラメータとカウンタはこれです。

パラメータ
thread_pools スレッドプール数
thread_pool_min スレッドプール毎の最低スレッド数
thread_pool_max スレッドプール毎の最大スレッド数
thread_queue_limit スレッド枯渇時のキュー長
カウンタ
MAIN.threads_limited ドロップされたセッション数
MAIN.sess_dropped ドロップされたセッション数
MAIN.sess_queued キューイングされたセッション数
MAIN.threads 現在のスレッド数

スレッド数はthread_pools * thread_pool_min以上thread_pools * thread_pool_max以下となります。
チューニングをする際はMAIN.sess_droppedが起きるのは論外で、MAIN.sess_queuedもそんなに起きないのが望ましいです。
このあたりのチューニングは今回の本筋ではないので詳しくは解説しませんが、要は過剰であればthread_pool_minを減らしてみて
MAIN.threadsが止まったあたりでdropped/queuedも起きないように調整するとよいのではとか思います。
本当にメモリが辛い場合は多少パフォーマンスは落ちますがqueuedを覚悟してthread_pool_minをガッツリ下げてthread_queue_limitを上げるのも手だと思います。(その前にスペックとか他のパラメータを再検討したほうが良いと思いますが)
結局のところ、ここでいいたいのはメモリの使用傾向が変わるとおもうのできちんとチューニングしようねって話です。
追記:上記で長めに運用しましたが、多少ましになる程度でした。(とはいえスレッド数の調整はしたほうがよいかなと思います。

とはいえ、HTTP/2を使う際はpkg-varnishcacheを使ってなるだけ最新を追っておくと良いかと思います。

hitch1.4.4を使う場合の注意事項
hitchは設定のreloadをサポートしているのですが(HUPを投げる)
ocsp staplingが有効だとreload時にゾンビプロセスが残る場合があります(#167)
以下のように設定して無効にすることで回避も可能です。


ocsp-dir = ""

あくまでoffにすることを推奨しているのではなく、ここは各自考えてといった感じでお願いします。

最後に
ぜひVarnishのhttp/2を試してみてほしいなと思います。
また、Varnishの開発を助けるためにVMLを購入するのをぜひ検討してみてください


4月 022017
 

5.0.0の後継の5.1.1がリリースされました。 [changelog] [リリース文] [changes] [Upgrading]
また、今回からパッケージがpackagecloudで提供されるようになりました。(packagecloud)
5.1.0はどうなったの?という話ですが、ちょっと問題となるバグがあったため翌日にすぐ5.1.1を出した感じです。
今回のリリースでは4.1.4/4.1.5で変更された内容と細かい機能修正(VCL変更含む)があります。(それについては割愛してます)
VCLの変更はあるものの修正しないと動かないような変更はないのでよほどのことがない限りそのまま動作するはずです。
なお、VMODについては動かなくなるものもあるので事前に使用しているものが動くかを確認したほうが良いでしょう。
また、累積バグも修正されているのですが、よほど修正されているバグで困ってない限りは本番への適用は一旦4月中旬までは様子見たほうが良いかと思います。(何もなかったらすいません)

機能強化

varnishdのパラメータ追加(-I [cli file])
workerが起動する前に指定されたcli file読み込まれます。
このファイルはvarnishのcliコマンド(vcl.loadやvcl.useなど)が列挙されています。
これがなんの役に立つのかというと、5.0.0で追加されたvclのラベル機能を使っている人には非常に便利な機能です。
以前紹介したとおりlabel機能を使うには先にvcl.loadを利用してラベルを定義する必要がありました。
しかしvarnishdでvclを読み込む-fオプションではラベルを指定できなかったため、有効に使うには別にスクリプトを作るなど若干めんどくさかった面も否定できませんでした。
しかしこのオプションを使うことでworkerが起動する前にラベル付きでvclを読み込むことができるので非常に楽になります。


vcl.load panic /etc/varnish_panic.vcl
vcl.load siteA0 /etc/varnish_siteA.vcl
vcl.load siteB0 /etc/varnish_siteB.vcl
vcl.load siteC0 /etc/varnish_siteC.vcl
vcl.label siteA siteA0
vcl.label siteB siteB0
vcl.label siteC siteC0
vcl.load main /etc/varnish_main.vcl
vcl.use main

このようなcliファイルを作っておいて


sub vcl_recv {
    if (req.http.host ~ "asite.example.com$") {
        return(vcl(siteA));
    }
    if (req.http.host ~ "bsite.example.com$") {
        return(vcl(siteB));
    }
    if (req.http.host ~ "csite.example.com$") {
        return(vcl(siteC));
    }
    // Main site processing ...
}

このようなVCLを使うことでラベル機能を使うことが出来ます。

なお、コマンドの先頭に「-」をつけることでそのコマンドが失敗しても処理を続行することができます


起動に失敗する
vcl.load nf /etc/varnish/notfound.vcl
 
起動に失敗しない
- vcl.load nf /etc/varnish/notfound.vcl

varnishdのパラメータ追加(-x (parameter|vsl|cli|builtin))
これは何かしらの動作がというわけではなくて以下の情報が表示されるコマンドです

parameter
起動パラメータ(-p)の情報(デフォ値など)

vsl
vslのタグや出力フォーマットの説明

cli
varnishのcli(vcl.listなど)の説明を出力します

builtin
builtin.vclを出力します

HTTP/2がより使えるようになりました
5.0.0のHTTP/2は正直検証環境でも使うには辛いものだったのですが(POST投げると落ちるなど)
5.1.1では一部気をつけるポイントはあるものの検証環境で使えるぐらいには動きます。
また、HTTP/2関連は5.1.1リリース後も積極的にバグ修正が行われており、もし使いたい場合はこの記事で紹介したpkg-varnish-cacheを使って最新のコードで試すことをおすすめします。
なお、気をつけるポイントですが以下のとおりです。

  • 転送サイズ(varnishncsa %I %Oなど)などの統計がすべて0 #2238
  • bodyがあるリクエストでcontent-lengthがある場合でもTransfer-Encoding: chunkedを付与する #2247
  • クッキーの畳み込みを行わないのでVCLでクッキーの操作を行う場合に問題がある #2291 #2300

です。
統計については現時点ではどうしようもないのですが、ほかについては以下のVCLで一旦回避可能です。
もちろん修正された場合は不要となります。


sub vcl_recv{
  if(req.http.cookie){
    // temporary...
    // https://github.com/varnishcache/varnish-cache/issues/2291
    // https://github.com/varnishcache/varnish-cache/issues/2300
    std.collect(req.http.cookie);
    set req.http.cookie = regsuball(req.http.cookie,", ","; ");
  }
  if(req.proto ~ "HTTP/2"){
    if(req.http.content-length){
      // temporary...
      // https://github.com/varnishcache/varnish-cache/issues/2247
      unset req.http.content-length;
    }
  }
}

なお上記で行っているクッキーの畳み込みですが、もしESIを利用していない場合はHTTP/2に限定しても問題ありません。
他の注意点としてはPRIV_TASK/PRIV_TOPを利用しているvmodについては現時点では避けておいたほうが無難です。(HTTP/1では問題ありません)
またHTTP/2関連のパラメータとして以下が追加されています

  • h2_rx_window_increment
  • h2_rx_window_low_water

デフォルトは10MBで十分大きいので特に変更する必要ははなさそうですが、
大きなPOST/PUTを受ける場合は大きくしても良いかと思います。

即処理を終了するためのreturn(fail)が追加されました
これはどのvclアクションからも呼び出すことができます。
なおfailの後はvcl_synthがコールされますが、デフォルトでresp.statusには503がresp.reasonにはVCL failedが入っていますので、特に操作をしない場合はクライアントからは503が見えます。
なおvcl_synth内でfailを呼び出した場合は接続をアボート(即切り)します。

vcl_backend_responseでreturn(pass(DURATION))が追加されました
要はhit-for-passです。
TTLを同時に指定することができるので以前より指定しやすくなったので便利かと思います。
この辺(HFP等)わかりづらいとおもうので動きの解説記事をそのうちかきます・・

vsl queryでvxidの指定ができるようになりました
vxid(x-varnishレスポンスヘッダ)はリクエスト毎に発行されるので当然ながら事前にわかりません。
ヘルスチェックなどはvxid=0で実行されるのでそれの絞込ぐらいにしか使えないのかというとそうではなく
varnishncsaなどのlog系オプションの-dと組み合わせることで強力に使用できます。
varnishのログはメモリ上(VSM)に保存されるのですが、これはリングバッファのため古いログも多少残っています。
-dオプションは残っているログの先頭から出力するオプションのため、リクエストを行った後に取得したvxidを指定することでvarnishの挙動の確認を行うことが容易にできます。

fallback directorにstickeyオプションを追加しました


new fb = directors.fallback(sticky = true);

fallbackのdefaultの挙動では優先度の高いバックエンドがsickになった場合は当然fallbackします。
今回のstickeyオプションを指定するとこの優先度の高いバックエンドがhealthyになったときの挙動が変わります。
指定がない場合は優先度の高いバックエンドが選択されますが、指定がある場合はそのまま今繋いでるバックエンドを使用し続けます。
この動作は順繰り行われ、一番優先度の低いバックエンドがsickになった場合は再度先頭から評価されます。

起動時にvmodのバックアップを行うようにしました
これはVarnishの起動しているときにvmodの差し替えが行われて、その後の操作(reloadなど)でおかしくなることがあったので、使うvmodについてはバックアップを取るようになりました。

IPアドレス同士の比較ができるようになりました
vmod_stdにgetenvとlate_100_continueが追加されました

仕様変更

vcl_dirはvcl_pathに変更されます(起動パラメータ)
vmod_dirはvmod_pathに変更されます(起動パラメータ)
req.ttlは廃止予定です
beresp.storage_hintは廃止予定です
完全に同じものではないのですがberesp.storageで代替可能です。
レスポンスコードを1000以上でも指定できるようにしました
これはあくまでもVCL中で使うためのもので、実際にレスポンスされるときは1000以上の桁は削られます。
要はreturn(synth(1000))とかで指定しておいて、vcl_synthで処理するのに使う感じです。
DURATION型をBOOL値として使用することができるようになりました
0以下はfalseでそれ以外はtrueです
TIME型にDURATIONを加算・減算できるようになりました
REAL型にINTを加算・減算できようになりました

パラメータの追加

ban_cutoffが追加されました(起動パラメータ)
req.storageが追加されました
これはrequst bodyを保存するのに使用されます。
未指定の場合はTransientが使用されます。

バグ修正

基本的には4.1.4/4.1.5で修正されたものが大きいものだったので割愛します。


3月 272017
 

Varnishのリリースは少し前に半年に一回行う様になりました。
もちろんその間にBugfixなどがされたりするのですが、その場合でも半年待つ必要があります。(クリティカルなバグについてはその限りではないはず)
では、どうしてもすぐに修正されたものを使いたかったりする場合はどうすればよいでしょうか?
今回はpkg-varnishcacheを利用してパッケージを作成する方法を紹介します。

今回の環境はUbuntu16.04LTS(Xenial)で作成するパッケージの対象はXenialとしてます

sbuild環境の準備
最初にpkg-varnishcacheはsbuildを利用しているので環境を作ります。


ubuntu@xa:~$ sudo apt-get update
ubuntu@xa:~$ sudo apt-get install -y sbuild schroot ubuntu-dev-tools
ubuntu@xa:~$ sudo adduser ubuntu sbuild #ubuntu userの場合

adduserしたので一度セッションを切って再度ログインします。

buildに必要なパッケージをインストールします


ubuntu@xa:~$ sudo apt-get install -y git debhelper automake autotools-dev libtool pkg-config python-docutils libedit-dev libjemalloc-dev libncurses-dev libpcre3-dev python-sphinx

次にxenialのイメージを作成します。
やっているのはsources.listを日本のミラー(jp.archive.ubuntu.com)に向けているのとビルドに必要なパッケージのインストールです。
この作業をやっておくとビルドが早くなるのでお勧めです。
当然ながらパッケージはどんどん更新されているので、たまにupgradeしておくと良いと思います。
また、今回はxenialとしてますが、ここをtrustyにしたりすることで別のディストリビューション向けに作ることも可能です。


ubuntu@xa:~$ mk-sbuild xenial --arch=amd64
#↑で通らないときはmk-sbuild --distro=ubuntu xenial --arch=amd64で試してみてください
ubuntu@xa:~$ sudo schroot -c source:xenial-amd64 -u root
(xenial-amd64)root@xa:~# sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu%http://jp.archive.ubuntu.com/ubuntu%g" /etc/apt/sources.list
(xenial-amd64)root@xa:~# apt-get update
(xenial-amd64)root@xa:~# apt-get upgrade -y
(xenial-amd64)root@xa:~# apt-get install -y automake autotools-dev libedit-dev libjemalloc-dev libncurses-dev libpcre3-dev libtool pkg-config python-docutils python-sphinx git
(xenial-amd64)root@xa:~# apt-get install -y debhelper
(xenial-amd64)root@xa:~# logout

pkg-varnish-cacheの修正
次にpkg-varnish-cacheの取得と修正です。
そのままのものだとtrusty向けしかビルドできないので少し修正します。


ubuntu@xa:~$ git clone https://github.com/varnishcache/pkg-varnish-cache.git
ubuntu@xa:~$ cd pkg-varnish-cache
ubuntu@xa:~/pkg-varnish-cache$ vi package-deb

修正箇所は以下です。


ubuntu@xa:~/pkg-varnish-cache$ git diff
diff --git a/package-deb b/package-deb
index 2b5e9fc..7c68a1a 100755
--- a/package-deb
+++ b/package-deb
@@ -68,7 +68,7 @@ cd ..

 # By now we are done setting up and building the source package.

-if [ "$V" = "3.0" ] || [ "$V" = "4.0" ] || [ "$V" = "4.1" ]; then
+if [ "$V" = "3.0" ] || [ "$V" = "4.0" ] || [ "$V" = "4.1" ] || [ "$V" = "5.1" ]; then
        # Build binary packages for the requested releases inside chroots.
        for dist in $BINDISTS; do
                # Legacy packages embedded the release into the package version.

パッケージビルド
まずは公式で提供されているソースからパッケージを作ってみましょう。


ubuntu@xa:~$ curl http://repo.varnish-cache.org/source/varnish-5.1.1.tar.gz > sources/varnish-5.1.1.tar.gz
ubuntu@xa:~$ BINDISTS=xenial ./package-deb

するとこんな感じでパッケージができます。


ubuntu@xa:~/pkg-varnish-cache$ BINDISTS=xenial ./package-deb
...
+------------------------------------------------------------------------------+
| Summary                                                                      |
+------------------------------------------------------------------------------+

Build Architecture: amd64
Build-Space: 61528
Build-Time: 79
Distribution: xenial
Host Architecture: amd64
Install-Time: 6
Job: varnish_5.1.1-0+daily+20170322.012214.dsc
Machine Architecture: amd64
Package: varnish
Package-Time: 88
Source-Version: 5.1.1-0+daily+20170322.012214
Space: 61528
Status: successful
Version: 5.1.1-0+daily+20170322.012214
--------------------------------------------------------------------------------
Finished at 20170322-0123
Build needed 00:01:28, 61528k disc space
You have new mail in /var/mail/ubuntu
ubuntu@xa:~/pkg-varnish-cache$ ls build/
varnish-5.1.1                                        varnish_5.1.1-0+daily+20170322.012214.dsc               varnish-dbgsym_5.1.1-0+daily+20170322.012214~xenial_amd64.ddeb
varnish_5.1.1-0+daily+20170322.012214_amd64.build    varnish_5.1.1-0+daily+20170322.012214_source.changes    varnish-dev_5.1.1-0+daily+20170322.012214~xenial_amd64.deb
varnish_5.1.1-0+daily+20170322.012214_amd64.changes  varnish_5.1.1-0+daily+20170322.012214~xenial_amd64.deb
varnish_5.1.1-0+daily+20170322.012214.diff.gz        varnish_5.1.1.orig.tar.gz
ubuntu@xa:~/pkg-varnish-cache$

次はgithubのコードからパッケージを作成してみましょう。
まずはソースを持ってきて、足りないディレクトリ(doc/html)を作成します。


ubuntu@xa:~$ git clone https://github.com/varnishcache/varnish-cache.git
ubuntu@xa:~$ cd varnish-cache
ubuntu@xa:~/varnish-cache$ mkdir doc/html

後はautogen.shを走らせた後に圧縮してビルドします。


ubuntu@xa:~$ cd varnish-cache
ubuntu@xa:~/varnish-cache$ git pull
ubuntu@xa:~/varnish-cache$ ./autogen.sh
ubuntu@xa:~/varnish-cache$ cd ..
ubuntu@xa:~$ rm pkg-varnish-cache/sources/varnish-trunk.tar.gz
ubuntu@xa:~$ tar cfz pkg-varnish-cache/sources/varnish-trunk.tar.gz varnish-cache
ubuntu@xa:~$ cd pkg-varnish-cache/
ubuntu@xa:~/pkg-varnish-cache$ BINDISTS=xenial ./package-deb

これでパッケージができると思います。
自分は環境がないので試していませんがrpmも作成できるはずなので、
RHEL/CentOSな人はpackage-rpmを使うとよいと思います。(多少必要なパッケージが変わると思います)

ddebをインストールしたいとき
ビルドの指定でkeep-debugがあるので通常の場合は不要なのですが、入れたいひとは以下のようにすると入ります。
そのままだとkeep-debugで入る.debugとConflictとするのでそれを削る感じです。


diff --git a/debian/rules b/debian/rules
index c9e6246..b1203e8 100755
--- a/debian/rules
+++ b/debian/rules
@@ -79,4 +79,5 @@ override_dh_compress:
        dh_compress -X/usr/share/doc/varnish-doc/html

 override_dh_strip:
-       dh_strip --keep-debug
+       dh_strip


#with --keep-debug
ubuntu@xa:~/pkg-varnish-cache/build$ sudo dpkg -i *ddeb
(Reading database ... 76080 files and directories currently installed.)
Preparing to unpack varnish-dbgsym_5.1.1-0+daily+20170325.112955~xenial_amd64.ddeb ...
Unpacking varnish-dbgsym (5.1.1-0+daily+20170325.112955~xenial) over (5.1.1-0+daily+20170325.110252~xenial) ...
dpkg: error processing archive varnish-dbgsym_5.1.1-0+daily+20170325.112955~xenial_amd64.ddeb (--install):
 trying to overwrite '/usr/lib/debug/.build-id/a0/654ed2469d729bc1c9652860fe3243c6c84358.debug', which is also in package varnish 5.1.1-0+daily+20170325.112955~xenial
dpkg-deb: error: subprocess paste was killed by signal (Broken pipe)
Errors were encountered while processing:
 varnish-dbgsym_5.1.1-0+daily+20170325.112955~xenial_amd64.ddeb

#without --keep-debug
ubuntu@xa:~/pkg-varnish-cache/build$ sudo dpkg -i *ddeb
Selecting previously unselected package varnish-dbgsym.
(Reading database ... 76038 files and directories currently installed.)
Preparing to unpack varnish-dbgsym_5.1.1-0+daily+20170325.113630~xenial_amd64.ddeb ...
Unpacking varnish-dbgsym (5.1.1-0+daily+20170325.113630~xenial) ...
Setting up varnish-dbgsym (5.1.1-0+daily+20170325.113630~xenial) ...

多分必要なケースはほぼないと思います・・

[tips]panicメッセージでシンボルが表示されないものが何かを調べる
直接はpkg-varnishcacheとは関係ないのですが、これを使ってパッケージを作ってる人は
基本的に最新のものを使っていたりしてバグを踏んだりすることが多いと思いますのでtips的な・・・


Panic at: Wed, 22 Mar 2017 14:05:23 GMT
Assert error in vrt_priv_dynamic(), cache/cache_vrt_priv.c line 114:
  Condition((vp)->magic == 0x24157a52) not true.
version = varnish-5.1.1 revision 4d3037a, vrt api = 6.0
ident = Linux,4.4.0-24-generic,x86_64,-junix,-smalloc,-sfile,-sfile,-smalloc,-hcritbit,epoll
now = 2149357.291496 (mono), 1490191522.571085 (real)
Backtrace:
  0x438945: /usr/sbin/varnishd() [0x438945]
  0x44ac28: /usr/sbin/varnishd() [0x44ac28]
  0x7f3eb15411ce: vcl_boot.1490188582.198924065/vgc.so(VGC_function_verificationCookie+0x9e) [0x7f3eb15411ce]
  0x7f3eb15424ef: vcl_boot.1490188582.198924065/vgc.so(VGC_function_***_recv+0x1f) [0x7f3eb15424ef]
  0x7f3eb1544d32: vcl_boot.1490188582.198924065/vgc.so(VGC_function_vcl_recv+0x13b2) [0x7f3eb1544d32]
  0x4464f0: /usr/sbin/varnishd() [0x4464f0]
  0x448d0a: /usr/sbin/varnishd(VCL_recv_method+0x5a) [0x448d0a]
  0x43ccd9: /usr/sbin/varnishd(CNT_Request+0xd89) [0x43ccd9]
  0x46130f: /usr/sbin/varnishd(h2_do_req+0x4f) [0x46130f]
  0x451d72: /usr/sbin/varnishd() [0x451d72]
thread = (cache-worker)
...

例えば上記のようなvarnishadm panic.showで表示されたbacktraceがあったとして


  0x438945: /usr/sbin/varnishd() [0x438945]
  0x44ac28: /usr/sbin/varnishd() [0x44ac28]
  0x4464f0: /usr/sbin/varnishd() [0x4464f0]
  0x451d72: /usr/sbin/varnishd() [0x451d72]

このシンボルが出ていないのが何かを調べるにはgdbを使います。


ubuntu@xa:~$ gdb /usr/sbin/varnishd
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/sbin/varnishd...done.
(gdb) info symbol 0x438945
pan_ic + 373 in section .text
(gdb)

こんな感じで0x438945はpan_ic+373(0x175)だということがわかりました。
同じ様な感じで解決していくと


Backtrace:
  0x438945: /usr/sbin/varnishd(pan_ic+0x175) [0x438945]
  0x44ac28: /usr/sbin/varnishd(vrt_priv_dynamic+0x1b8) [0x44ac28]
  0x7f3eb15411ce: vcl_boot.1490188582.198924065/vgc.so(VGC_function_verificationCookie+0x9e) [0x7f3eb15411ce]
  0x7f3eb15424ef: vcl_boot.1490188582.198924065/vgc.so(VGC_function_***_recv+0x1f) [0x7f3eb15424ef]
  0x7f3eb1544d32: vcl_boot.1490188582.198924065/vgc.so(VGC_function_vcl_recv+0x13b2) [0x7f3eb1544d32]
  0x4464f0: /usr/sbin/varnishd(vcl_call_method+0x210) [0x4464f0]
  0x448d0a: /usr/sbin/varnishd(VCL_recv_method+0x5a) [0x448d0a]
  0x43ccd9: /usr/sbin/varnishd(CNT_Request+0xd89) [0x43ccd9]
  0x46130f: /usr/sbin/varnishd(h2_do_req+0x4f) [0x46130f]
  0x451d72: /usr/sbin/varnishd(WRK_Thread+0x4c2) [0x451d72]

わかりやすいバックトレースが得られるのでデバッグには便利かと思います。

まとめ
HTTP/2関連のコードの修正は活発なため最新のものを使うと良いと思います。
また、Varnish自体のリリース手順はこんな感じです。


2月 102017
 

4.1系の最新版の4.1.5がリリースされました。 [changelog] [公式のリリース文]

公式でも触れられてる通りほぼバグフィックス(あとドキュメント周り)なのですが、1点パフォーマンスに影響する修正があります。
個人的に重要かなと思うのは#2106の修正です

バグ修正

workspaceが溢れた場合にpanicしていたのを修正 #1834

Basic認証でAuthorizationのフォーマットがおかしい場合varnishncsaでデコードできない問題の修正 #2148

VCLでバックエンドの比較が動いていなかったのを修正 #2168

VSMの使用状況のカウンタ(vsm_free)がおかしかったのを修正 #2188

ESIのincludeでパスが存在しない場合にpanicしたのを修正 #2197
「http://foo/」の場合は問題なかったのですが「http://foo」の場合は今までpanicしていました
その修正になります。

INT/REAL型において「-」をパースできなかったのを修正 #2167
要は-10とかを扱えるようになりました。

beresp.backend.ipが本来使用できないbackend_errorで呼び出すとpanicしたのを修正 #1865
backend_responseでのみ利用できるように修正されました

CNT_Requestでパニックしていのを修正 #2106

仕様変更

バックエンドへの接続にNagleアルゴリズムを使っていたのをやめました #2134
クライアントとの通信はもともとTCP_NODELAYだったのですがバックエンドとの通信はTCP_NODELAYを指定していませんでした。
キャッシュが出来ないオブジェクト(pass)の場合だと多少効いてくるのではと考えています。

改善

devicedetect.vclをアップデートしました(bot情報)


2月 092017
 

少し前にスロットルでサイトを守る(vmod-vsthrottle)で異常なリクエストに対してスロットルをかける方法を紹介しました。
しかしこれはあくまでリクエストレートのみで、そのリクエストがどれだけ帯域を使うかは制限しません。
例えば特定のリソースについては帯域を制限をかけたい場合や(動画のようにDL中でも再生できるのであれば、必要なビットレート+αでもいいかなとか)
ユーザの属性(課金状況など)や、はたまたリクエストが多いユーザに対しては制限をかけたい場合などいろいろあるかと思います。
今回は帯域制限を行うvmod-tcp(ドキュメント)を紹介します。

環境について

vmod-tcpでの帯域制限はtc-fqのSO_MAX_PACING_RATEを指定することによって実現しています。
これはカーネル3.13からの機能のためUbuntuであればTrusty以降が必要です、CentOSの場合はカーネルを新しくする必要があるかと思います。(7だと3.10なので)

インストール

これはvarnish-modulesの中に入ってる一つなので、インストールについては前回の記事を参照ください。

tc-fqに切り替え

先程も述べたとおりtc-fqの機能を利用しているためこれを使うようにします。
tcについてはこちらが詳しいので参照ください(よくわかるLinux帯域制限)

まず、現在のqdiscを確認します


xcir@gw01:~$ tc qdisc show dev eth0
qdisc pfifo_fast 0: root refcnt 2 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

eth0のところは環境によって変更してください(制限したいインタフェース)
今はpfifo_fastを利用していることがわかります。

次にfqに変更します


xcir@gw01:~$ sudo tc qdisc add dev eth0 root handle 1: fq
xcir@gw01:~$ tc qdisc show dev eth0
qdisc fq 1: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 quantum 3028 initial_quantum 15140

qdiscがfqになりました。
これでvmod-tcpによる帯域制限が利用できます。

使い方

すごい簡単です。
例えばdatファイルに対して制限をかけたいのであれば


sub vcl_recv {
  if(req.url ~"\.dat(\?.*)?$"){
    // 1000KB/s(8Mbps)
    tcp.set_socket_pace(1000);
  }
}

とするだけです。

では、実際に動作確認をしてみましょう。


xcir@gw01:~$ curl http://*****:6081/10MB.dat  > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10.0M  100 10.0M    0     0   944k      0  0:00:10  0:00:10 --:--:--  943k

xcir@gw01:~$ curl http://*****:6081/10MB.dat2  > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10.0M  100 10.0M    0     0   260M      0 --:--:-- --:--:-- --:--:--  263M

10MBのファイルでやってみましたが、帯域制限に引っかかる方は943KiB/sなので大体8Mbpsでうまく制限されています。(10MB.dat)
対して引っかからない方では263MiB/sと制限されていないことがわかります。

注意事項

割りと簡単に使用できる帯域制限ですが注意する点がいくつかあります。

keep-alive時の制限の持続
この制限はtc-fqを利用してコネクション単位で行われます。
そのためkeep-aliveでコネクションを再利用した場合は当然のことながら制限は持続します。
なので例えば以下のように取得してみると


xcir@gw01:~$ wget http://*****:6081/10MB.dat  http://*****:6081/10MB.dat2
--2017-02-09 20:02:10--  http://*****:6081/10MB.dat
Connecting to *****:6081... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M) [application/x-ns-proxy-autoconfig]
Saving to: '10MB.dat'

100%[============================================================>] 10,485,760   943KB/s   in 11s

2017-02-09 20:02:20 (945 KB/s) - '10MB.dat' saved [10485760/10485760]

--2017-02-09 20:02:20--  http://*****:6081/10MB.dat2
Reusing existing connection to *****:6081.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M)
Saving to: '10MB.dat2'

100%[============================================================>] 10,485,760   942KB/s   in 11s

2017-02-09 20:02:31 (944 KB/s) - '10MB.dat2' saved [10485760/10485760]

FINISHED --2017-02-09 20:02:31--
Total wall clock time: 22s
Downloaded: 2 files, 20M in 22s (945 KB/s)
xcir@gw01:~$

制限がかからないはずの10MB.dat2についても制限がかかっているのがわかります。

なので、例えば先程の場合であればdatファイルをダウンロードした後(8Mbps)にコネクションを再利用して画像などのリソースをダウンロードする場合は8Mbpsとなります。
なにかリセットする方法がないかなと0を指定しても特に解除されなかったため、vcl_recvの先頭で十分な大きさの指定をしておくと良いと思います。


sub vcl_recv {
//8Gbps
tcp.set_socket_pace(1000000);
  if(req.url ~"\.dat(\?.*)?$"){
    // 1000KB/s(8Mbps)
    tcp.set_socket_pace(1000);
  }
}

このようにして試してみると


xcir@gw01:~$ wget http://*****:6081/10MB.dat  http://*****:6081/10MB.dat2
--2017-02-09 20:03:40--  http://*****:6081/10MB.dat
Connecting to *****:6081... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M) [application/x-ns-proxy-autoconfig]
Saving to: '10MB.dat'

100%[============================================================>] 10,485,760   943KB/s   in 11s

2017-02-09 20:03:51 (945 KB/s) - '10MB.dat' saved [10485760/10485760]

--2017-02-09 20:03:51--  http://*****:6081/10MB.dat2
Reusing existing connection to *****:6081.
HTTP request sent, awaiting response... 200 OK
Length: 10485760 (10M)
Saving to: '10MB.dat2'

100%[============================================================>] 10,485,760  47.7MB/s   in 0.2s

2017-02-09 20:03:51 (47.7 MB/s) - '10MB.dat2' saved [10485760/10485760]

FINISHED --2017-02-09 20:03:51--
Total wall clock time: 11s
Downloaded: 2 files, 20M in 11s (1.81 MB/s)

と、意図したどおりに動作します

TLS終端を別MWで行いlocal接続しているケースに効かない
Varnishはhttpsを喋れないのでTLS終端をnginxやhaproxyやhitchなどで行いローカルで接続しているケースもあると思いますが
その場合はloインタフェースもtc-fqを適用する必要があります。

http/2利用時
http/2は複数のストリームが1コネクションに入っています。
そのため特定のリソース(=特定のストリーム)のDL時のみを帯域制限をかけたい場合には使用できません。
この場合全体での制限になります。
もちろん、特定の行動をしたクライアントの帯域を絞りたいという用途には利用できます。


12月 042016
 

VarnishCacheはOSSですがPHKとVarnishSoftwareなどが主となって行っています。
個人や組織は当然ながら資金なしで活動できるわけではなく、VarnishSoftwareの場合はVarnishに多数の機能が追加されているVarnishPlusをサブスクリプション販売したいたりします。
ではPHKはどんな感じで資金を得ているかというとVarnish Moral License(VML)というので資金を得ています。
VMLで資金を得てその範囲でPHKが様々な新機能や改修を行っていたりするのですが、少し前のVarnish5の開発で資金が足りなくなってきました。
そのためVarnishの開発時間を取るためにVML購入を求める記事が公式にあがっていましたが
(Send us (more) money!)
今回、自分が実際にVMLを500EURほど購入してみたので、どんな感じのフローでやったのかなどを紹介します。

そもそもVMLとは

先程も述べたとおりPHKがVarnish関連の活動をするための資金です。
VMLのページに書いてあるとおりなのですが

Software development may be open, and the result shared with an Open Source Software licence, but the actual hours of programming are not gratis.
Just like everybody else, I need money for mortgage, kids and food, money I make by doing things with computers, for people who are willing to pay for that.
One of the things I do for money, is develop Varnish, and the Varnish Moral License is the vehicle I invented for the paperwork.

(意訳)ソフトウェアの開発はオープンで、その成果物はOSSとして共有されますが、その開発に費やされる時間は無償ではありません。皆さんと同じように、私は住宅ローンを、子供を養う、食べていくためにコンピュータを使って稼ぐ必要があります。私が稼ぐ一つの手段がVarnishの開発で、VMLはそのために開発した仕組みです。

ちなみに、現在VMLを継続的に購入しているユーザは

  • Fastly
  • VarnishSoftware
  • UPLEX

の3社です。

ちなにみVMLを購入することで何かしらのサービスやサポートが受けられたりするわけではありません。
(当然、欲しい機能をリクエストして優先されるわけでもありません)
支払った金額の分だけPHKがVarnishに時間を費やすことで開発が前に進む感じです。
VMLのFAQについてはここにあるのでぜひ目を通してみてください。

実際にVMLを購入したときの手順

購入希望のメールを送る
まずはPHK宛にメールを送ります。
メアドはここにあるものです。

含まれる必要がある内容は

  • 購入するラインセンス額(How much do you want to pay for your license ?)
  • それは定期購入(期ごと、月ごと)か1回のみか(Do you want to pay it monthly or quarterly ? (or onetime ?))
  • Webページに記載される名前(What name should appear on the web-page ?)
  • 請求書に記載される名前(What name should appear on the invoice ?)

です。
自分が実際に送ったメールはこんな感じです。
vml1
ついでにこの記事を書きたかったので書いてもいいかを聞いています。

ちなみに今回500EUR(6万円ぐらい)を送金しています。もっと少なくてもいいのでしょうか?
これは海外送金の難しいところで自分はそこまで詳しくないのですが、
海外送金の場合だと受け取るのにもお金が必要で、FAQにもあるのですが100EUR以下の場合は手数料のほうがかかってしまうらしいです。
また、当然ながらPHK側の事務コストが発生しますのでそのあたりを勘案して考えると良いかなと思っています。
(メールで「私が避けたいのは5ドルの請求書を200枚も書くような状態に陥ることだ」と言ってました)
もし、VMLは煩雑なので他の方法がないかといえばWishlistもあります。
実際、自分はVMLの海外送金がめんどくさくて何度かリリースがあったタイミングでWishlistを消化してました。
なぜ今回VMLを購入したかというと公式サイトにVMLについて書いてあったのと、Wishlistに買えるものがなかったからです。

請求書が来る
請求書はPDFで送られてきます。(自分の場合は1週間ぐらいできました)
vml2
マスクしてる箇所は住所とか電話番号が書いてあった感じです(一応公開してOKって確認とりました)

海外送金をする
最初なんでPaypalが使えないんだろうと思ったんですが
以前に資金集めを行った際に使っていろいろあったようです。
ということで銀行で海外送金をする必要があります。
海外送金のめんどくさいところが、500EURを送ろうとしても実際に500EURが相手に渡るかわからないところです。
中継銀行で手数料が引かれたりすることもあります。
海外送金できる銀行はたくさんあるのですが(ゆうちょでもできます)
中継手数料などを先に支払ってしまえる銀行を探したところ楽天や三井住友などがあったので今回は三井住友銀行を使いました。
vml3
これ書いた後に大変だったのが、銀行側で「æ」に対応するキーがなくてなんて入力していいかがわからなかったことですw
aeで書いて着金したのでこれで大丈夫みたいです。

たしか翌週に三井住友から明細書が自宅に送られてきました。
vml4

これで送金は完了です。

で、翌月ぐらいにVMLの明細のところに名前がのります。
vml5

まとめ

Varnish5がリリースされてhttp2に実験的なサポートをするなどしましたが、まだまだバギーで改善が必要です。
今回少額ながらVMLを購入しましたが少しでもその改善に役に立てば嬉しいですし、この記事を公開することでVarnishを使っている方でVMLの購入を検討してくれると嬉しいなと思います。


12月 022016
 

4.1系の最新版の4.1.4がリリースされました。 [changelog] [公式のリリース文]
主にバグ修正とドキュメントの改善ですが
1件すごくタイムリーなバグ修正が存在します。
また、今回のリリースで4.0.xがEOLになりました。

バグ修正

バックエンドの現在の接続数のカウンタが機能していないのを修正 #2011

ラウンドロビンDirector利用時にクラッシュすることがあるのを修正 #2024

フェッチ時にワークスペース・ストレージ確保失敗するとリークする可能性があったのを修正しました

return(pipe)時にstd.cache_req_bodyが機能しないのを修正 #1881

vmod_named利用時にVBE_Deleteでpanicすることがあるのを修正しました #2008
コードの修正を見た感じnamedに限らずに動的にバックエンド定義をUpdateしていくようなDirectorの場合に問題が起きそうです。

エラーなどで起動できなかった場合にvsmファイルが残っていたのを修正しました #2115

keepalive時に複数のrangeリクエストが来た場合に先頭リクエスト以外をしばらくブロックすることがあるのを修正しました #2035

バックエンド選択時に稀にpanicするのを修正しました #2027

時刻が逆行(STEP)した場合に子プロセスが再起動する場合があるのを修正しました #1874
うるう秒もありますのでこのあたり注意しておいたほうが良いと思います。
条件としてはセッションがクローズ(close)されるタイミングの時刻がセッション開始時刻(open)が早くなっていた場合です。
Varnishのこの時間の精度はナノ(clock_gettime/CLOCK_REALTIME)もしくはマイクロ秒(gettimeofday)なので
うるう秒で23:59:58->59->59->00のように59を繰り返す場合(STEP)でリクエスト自体を1秒未満で処理していると引っかかる可能性があります。
そのため、今回のうるう秒でSTEP動作をする可能性がある場合は強くアップデートを勧めます。
なお5.0.0も影響を受けます。今のところ5.0.1がリリースされる気配はないのでSLEWで動かすか独自で現時点のmasterのものをビルドするかをおすすめします。(4.0.xは影響なさそうです)
また、この問題の解決のためにclock_stepといパラメータが追加されています。

機能強化

varnishhistの表示にy軸が追加されました
5.0.0のhistと同じ表示です。

仕様変更・追加

varnishtestで外部のコマンドを実行するfeature cmdが追加されました。
すでにあるshellコマンドとの違いですが、shellの場合はその実行結果でテストが成功・失敗しますが
feature cmdの場合はテスト自体が実行されるかされないかです。
混在環境(OSやCPU)で特定環境のみのテストを行う際に便利に使えると思います。

varnishtestのresp.msgはresp.reasonに変更されました

clock_stepが追加されました
時刻同期で一気に時刻が変更(STEP)された場合の許容秒数です。
デフォルトは1秒です。

thread_pool_reserveが追加されました
重要なタスクのために予約されるスレッド数です。
例えば、クライアントの処理でスレッドを使い尽くした場合でもreserveされているスレッドがあればバックエンドの問い合わせが可能です。
この値は引き上げるとクライアント処理で使われるスレッド数が少なくなるため注意する必要があります。
あくまで一気にリクエストが来てスレッドが足りなくなった場合に発動するものなので、普段から適切なスレッド数を指定していればそこまで問題無いと思います。
デフォルトは0(auto)で、thread_pool_minの5%が自動的にセットされます。

パッケージ構成が変更されました
5.0の構成とおなじになりました。(varnish = varnish + libvarnishapi + varnish-doc)
そのためそのままapt-get installで更新しようとすると失敗します。
一度4.1.3までのバージョンをremoveしてアップデートするのをおすすめします

その他変更

ドキュメントの改善やRaceコンディションで起きる幾つかのバグが修正されています。


10月 032016
 

二年ぶりぐらいにメジャーバージョンが更新されました。[公式リリース] [ダウンロード]
今回の目玉機能としてHTTP/2対応とDirectorのConsistent Hash対応があります。
なにはともあれ、とりあえずHTTP/2を使うセットアップをしてみましょう。

Hitch1.4.0+Varnish5でHTTP/2を使う

Varnishはよく知られてるようにHTTPSに対応していません。
HTTP/2に対応したこのバージョンにおいても、やはり本体では対応をしていません。
そのため通常のブラウザからVarnishに対してHTTP/2にアクセスするにはTLSを解かなくては行けません。
今回はVarnishSoftwareが提供しているHitchを利用します。
なお、HTTP/2を利用するには1.4.0以降のバージョンが必要です。
また、一部ディストリビューションではパッケージが提供されていますが、私が利用しているUbuntu 16.04向けのパッケージはなかったのでソースからbuildしていきます。

Hitchのインストール
公式手順を参考に行います


sudo apt-get install libev-dev libssl-dev automake python-docutils flex bison pkg-config
git clone https://github.com/varnish/hitch.git
cd hitch
./bootstrap
./configure --prefix=/opt/hitch/
make
sudo make install

これだけです。(prefixはお好きなところにでも)
なお、HTTP/2を使うにはNPNもしくはALPNのサポートが必要です。
NPNはOpenSSL1.0.1、ALPNの場合は1.0.2が必要ですが、既にChromeでは今年の6月ごろにNPNでのHTTP/2サポートは打ち切られALPNのみでのサポートとなりました。
そのため実質HTTP/2を利用するにはALPNをサポートしている必要があります。
とりあえず確認する程度であれば、FireFox49はNPNでも接続できるので1.0.1でも問題はないです。

Hitchの設定


HTTPS:443           HTTP:80
   ↓               ↓
Hitch:443--(localhost:81)-->Varnish:80
                 ↓
                Origin

今回はこんな感じで構成します。
まずはHitchの設定です。


#/etc/hitch/hitch.conf
frontend       = "[*]:443"
ciphers        = "ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:EECDH+HIGH:EDH+HIGH:HIGH:+3DES:!RC4:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5:!DH"
backend        = "[127.0.0.1]:81"
write-proxy-v2 = on
workers        = 2
backlog        = 1024
keepalive      = 3600
syslog         = on
user           = "nobody"
daemon         = on
alpn-protos    = "h2,http/1.1"
pem-file       = "/etc/hitch/******.pem"

ciphersとかは割と適当ですがきちんと設定したい人はこちらで考えると良いかと思います。
なお、hitchのpem-fileは
秘密鍵 + 証明書 + 中間CA証明書 + dhparam
です。
Let’s Encryptでやる場合は
privkey.pem + fullchain.pem + dhparam
になります。
また、どうやって複数のvhostsに対応するのかというとpem-fileを列挙します。
ちなみにデフォルトのものは最後に定義されたものになります。
Hitchの起動


/opt/hitch/sbin/hitch --config=/etc/hitch/hitch.conf

次はVarnishの設定です。
Varnishの設定
まずはListenの指定をしましょう。
ポート80で通常のHTTPを受付して81でPROXYプロトコルで受付します。
そのための設定はこのような感じです。


-a :80 \
-a 127.0.0.1:81,PROXY \

また、HTTP/2はデフォルトでは有効にされていません。
そのためfeatureでhttp2を指定する必要があります。
varnishadmから設定する場合は


root@proxy02:~# varnishadm param.set feature +http2

root@proxy02:~# varnishadm param.show feature
feature
        Value is: +esi_disable_xml_check,+http2
        Default is: none

        Enable/Disable various minor features.
           none                       Disable all features.

        Use +/- prefix to enable/disable individual feature:
           short_panic                Short panic message.
           wait_silo                  Wait for persistent silo.
           no_coredump                No coredumps.
           esi_ignore_https           Treat HTTPS as HTTP in
                                      ESI:includes
           esi_disable_xml_check      Don't check of body looks like
                                      XML
           esi_ignore_other_elements  Ignore non-esi XML-elements
           esi_remove_bom             Remove UTF-8 BOM
           https_scheme               Also split https URIs
           http2                      Support HTTP/2 protocol

です。
起動パラメータで指定する場合は


-p feature=+http2 \

を追加すれば良いです。
これで動作します。

なお、後述しますが絶対本番環境では行わないでください。

それでは、HTTP/2での動作のさせかたを紹介したところで、いつも通り新機能などの紹介を行います。

全体的な話

過去のメジャーバージョンアップを振り返ってみると
2.1->3.0

  • VMODのサポートとVCL文法の整理

3.0->4.0

  • リクエスト・フェッチ・レスポンスといった一連の動作をClient/Backendスレッドに分割
  • VCLにバージョン番号付与とスレッド分割に対応するための変更

4.0->4.1(マイナーバージョンアップですが)

  • PROXYプロトコルのサポート
  • 内部処理にフックできるようなしくみの追加

4.1->5.0

  • HTTP/2の実験的サポート
  • shard director(consistent-hash)対応
  • バックエンドフェッチがPROXYプロトコルサポート
  • 別VCLを動的リンクして使うlabel機能のサポート

こうしてみると4.0の頃からHTTP/2の準備を着実にすすめていたのがわかるかと思います。
また、嬉しいことに5.0では3.0->4.0のときのような破壊的なVCLの変更はありません。
もちろん幾つかのVCLの変更はあります。
しかし大抵の場合は変更せずとも動き、もしくは少しのreplaceで済んでしまいます。
また、statsの項目も削られたものはあるものの出力フォーマットは変わっておらず、
LOGについてもHTTP/2関連の追加はあるものの大きな変更はなく、
大抵のVMODもリビルドすれば動き、累積バグは修正されています。
そのため、4.1で動いていた監視や周辺ツールはほぼ変更なしで動き既存の資産を使いやすいと思います。
10/03時点で知り合いのサイトの本番に投入して、20日程度経過していますが大きな問題もなく動作しています。
当然メジャーバージョンアップのため慎重な検証は必要ですが、3.0->4.0のときのような苦しみはなく
HTTP/2を使わないのであれば、どちらかと言うと4.0->4.1のときのような変更と思ってもらえればよいかと思います。

新機能

HTTP/2のサポート(experimental)

先程も紹介しましたがHTTP/2の極めて実験的(Very Experimental)なサポートを行っています。
有効にする場合は起動パラメータ、もしくはvarnishadmでfeatureパラメータを+http2に指定します。
なお、現時点で私が確認しているバグだと回避不能でリクエストで容易にクラッシュを起こすものが1件、回避可能なものが1件、仕様なのか判断できないが実装漏れっぽいのを1件見つけてます。(また今度泣きながら英語書きます)
そのため決して本番に投入することはおすすめしません。
しかし実際に触ってみていろいろバグを見つけて報告するとよいかと思いますので触ってみてほしいところです。

vmod-directorでsharedをサポート

consistent-hashのサポートです。
元々はUPLEXが提供していたvslpを公式に持ってきた形になります。
consistent-hashについてはぐぐるといろいろ出てくるとおもうので詳しくは割愛します(参考
今まではbackend数に変動があった場合には全体で再計算が走るのでキャッシュヒット率が全体で落ちる問題がありましたがこれを使うことで解決することが出来ます。
特に複数台のクラスタで複数段のキャッシュを組んでる環境ではかなり効いてくると思います。

VOID shard.set_rampup(DURATION duration=0)
0秒以上を指定した場合、バックエンドがhealthyになった場合にいきなり100%のトラフィックを流すのではなく、指定秒数(duration)の間に徐々にトラフィックを流していくようになります。(slow start)

BOOL shard.add_backend(BACKEND backend, STRING ident=0, DURATION rampup=973279260)
バックエンドを追加します。
identはノードの識別子で、デフォルトではバックエンドの名前を使うようになっています。
通常は使うことはないと思いますが、weight的な指定をしたいときなどに使うと良いかもしれないです。
rampupはサーバ個別に指定する場合に指定します。(973279260はmagic valueでset_rampupで指定された値を使うようになります)

BOOL shard.remove_backend(BACKEND backend=0, STRING ident=0)
バックエンドを削除します。
backendを指定した場合は一致するすべてが削除されます。

BOOL shard.clear(PRIV_TASK)
全てのバックエンドを削除します。

VOID shard.set_warmup(REAL probability=0.0)
指定の確率で次の代替バックエンドにリクエストがいくようになります。(0.5=50%)

warm

要はそのバックエンドが死んでキャッシュが落ちたとしても、
代替バックエンドがキャッシュをある程度持っている状態なのでダウン時の影響をより減らすことができるようになります
(ただし当然ながらキャッシュの二重持ちが起きるので空間効率は悪くなります)
なお、rampup期間中のバックエンドはwarmupの対象外になります。

BOOL shard.reconfigure(INT replicas=67, ENUM {CRC32,SHA256,RS} alg=”SHA256″)
consistent-hashで使うハッシュリングを作ります。
replicasは1つのバックエンドが仮想的に何個のバックエンドになるかです。(consistent-hashの仮想ノード)
algはどのハッシュ関数を使ってハッシュリングに配置していくかです。
なお、shardディレクターを使う場合は必ず一度は呼び出す必要があります。
イメージ的にはcommitと思ってもらえれば良いかなと思います。

BACKEND shard.backend(ENUM {HASH,URL,KEY,BLOB} by=”HASH”, INT key=0, BLOB key_blob=0, INT alt=0, REAL warmup=-1, BOOL rampup=1, ENUM {CHOSEN,IGNORE,ALL} healthy=”CHOSEN”)
バックエンドを選択します。

by=HASH(デフォルト)
vcl_hashで生成したhash値を使用して振り分けサーバを決めます。

by=URL
req.url/bereq.urlを使用します。

by=KEY
keyで指定したINT数値を使って振り分けします。
例えばcookieをもとに振り分けしたい場合は


shard.backend(by=KEY,key=shard.key(bereq.http.cookie))

といった感じです。

by=BLOB
key_blob指定したblobを使って振り分けします。
通常は使うことはないと思います。
また、blob全体を使うのではなく先頭4バイトを使用します。

key
by=keyを指定した場合に指定する数値です

key_blob
by=blobを指定した場合に使用するblobです

alt
指定した場合はn番目の代替バックエンドを選択します。
どのようなときに使うかというと何かしらのエラーが起きて別のバックエンドを選択したい場合(restart/retries時)に使用します。
alt=req.restart or bereq.retriesと指定しているとリスタート/リトライのたびに別のバックエンドを選択することが出来ます。
なお、rampup/warmupはaltが0のときのみ有効で、1以上のときはaltが優先されます。

warmup
代替サーバが選択される確率を指定します。(-1,0~1)
-1の場合はset_warmupで指定した値を使用します。

rampup
rampup機能を使うかどうかです。

healthy=CHOSEN(デフォルト)
healthyなバックエンドを選択します。
altが0以上の場合はバックエンドのhealthyを無視してn番目の代替バックエンドを選択します。
ドキュメントにはthen return the next healthy backend.とあるのですが、コードと実際の動作を見る限りだとalt指定時の代替バックエンド選択ではhealthyかの評価をしていません。

healthy=IGNORE
helathyを無視します。
altを指定した場合はhealthyを無視した状態で代替バックエンドを選択します。
またrampup/warmupは機能しません。

healthy=ALL
基本的にはCHOSENと同じですが、altが0以上のときに代替バックエンドを選択する際にもヘルスチェックをします。

ちょっとややこしいのでまとめてみました

healthy alt==0 alt>0
CHOSEN ヘルスチェックを行う ヘルスチェックを行わない
IGNORE ヘルスチェックを行わない ヘルスチェックを行わない
ALL ヘルスチェックを行う ヘルスチェックを行う

 

INT shard.key(STRING string, ENUM {CRC32,SHA256,RS} alg=”SHA256″)
指定した文字列を指定したハッシュアルゴリズムを通したときのINT値を得ます。
例えばSHA256は256bitなんですが、32bit分だけ返します。
この関数はshard.backendをby=KEY指定で使う場合に使用します。

で、実際のところどうやって使うかというとこんな感じです。



sub vcl_init {
  new ws_cache = directors.shard();
  ws_cache.set_rampup(10s);
  ws_cache.add_backend(ws01);
  ws_cache.add_backend(ws02);
  ws_cache.add_backend(ws03);
  ws_cache.add_backend(ws04);
  ws_cache.reconfigure();
}

sub vcl_recv {
  //x-varnish-hashにはユーザIDとかそんなのが入ってる
  ...
  set req.backend_hint = ws_cache.backend(by=KEY, key=ws_cache.key(req.url + ":" + req.http.host + ":" + req.http.x-varnish-hash));
}

 

INT,BACKEND型がBOOLで評価できるようになりました

こういうことができるようなります。


sub vcl_recv {
  if(1){
    set resp.http.foo = "1";
  }
  if(req.backend_hint){
    set resp.http.bar = "1";
  }
}

INT型で0はfalseになります。
また、BACKEND型では割り当てられていない場合はfalseになります。

別VCLを動的リンクして使うlabel機能のサポート

いままで異なるVCLを読み込む方法としてはincludeがありました。
これは単純にincludeを記載した場所に指定したVCLを展開するものでした。
そして今回追加されたlabelは別のコンパイルされたVCLを動的リンクします。
とりあえず使い方を紹介します

まずラベルを持つVCLをloadします


varnishadm vcl.load a_example_net /etc/varnish/a_examplet_net.vcl
varnishadm vcl.load b_example_net /etc/varnish/b_examplet_net.vcl
varnishadm vcl.load c_example_net /etc/varnish/c_examplet_net.vcl

varnishadm vcl.label vcl_a_example_net a_example_net
varnishadm vcl.label vcl_b_example_net b_example_net
varnishadm vcl.label vcl_c_example_net c_example_net

default.vcl


vcl 4.0;
//最低限1つはバックエンドがないと動かないのでダミー
backend default { .host = "127.0.0.1"; }

sub vcl_recv {
  if(req.http.host ~ "a\.example\.net$"){
    return(vcl(vcl_a_example_net));
  }
  if(req.http.host ~ "b\.example\.net$"){
    return(vcl(vcl_b_example_net));
  }
  if(req.http.host ~ "c\.example\.net$"){
    return(vcl(vcl_c_example_net));
  }
  return (synth(403));
}

a.example.netの場合はvcl_a_example_netというラベルを持つVCL(/etc/varnish/a_examplet_net.vcl)を使うようになります。
これは何に使えるかというとCDNのMWとしてVarnishを使っていたりして外部の人にVCLの記述を解放したいけど
VCLがそれぞれ干渉しないようにしたい場合に使えます。
vcl_a_example_netの中からb.examplet.netに関する処理を書いてもそもそもそういうリクエストが割り振られないからです。
また、今回はreq.http.hostで分けましたが、もちろん他のキーで振り分けを行うことが出来ます。

なお、設定を更新する場合は再度vcl.loadしてvcl.labelを叩く感じです


varnishadm vcl.load a_example_net_1 /etc/varnish/a_examplet_net.vcl
varnishadm vcl.label vcl_a_example_net a_example_net_1

ちなみにvcl.listを見てみるとこんな感じになります


# varnishadm vcl.list
active      auto/warm          0 boot
available   auto/warm          0 a_example_net
available  label/warm          0 vcl_a_example_net -> a_example_net_1
available   auto/warm          6 a_example_net_1 (1 label)

vcl_a_example_netがa_example_net_1になっているのがわかると思います。
また、reloadを行った場合はラベルの先のVCLについてはreloadされませんので注意が必要です。

バックエンドフェッチがPROXYプロトコルサポート

バックエンドの定義に.proxy_header=[プロトコルバージョン];を指定することでproxyプロトコルが使えるようになりました。


backend ws01 {.host = "192.168.1.1";.proxy_header = 1;}//PROXYプロトコルv1
backend ws02 {.host = "192.168.1.2";.proxy_header = 2;}//PROXYプロトコルv2

varnishtestの強化・変更

HTTP/2のテストやproxyプロトコルのサポートが行われました。
また、resp.msgはresp.reasonへ
semaはbarrierに名前が変更されました。

std.integerがREAL型の数値をパースできるようになりました

1.2といったものはもちろん1E+2みたいな表記もパースできるようになりました。
もちろんINT型になるように小数点以下は削除されます。

std.logがvcl_initで動くようになりました

 

varnishhistにy軸の目盛りが付きました

 

varnishstatの-fオプションがglob(7)の形式が使えるようになりました

 

POSTをキャッシュできるようになりました

正確にはVCLを正しく書けばPOSTもキャッシュできるようになったということです。(もちろんデフォルトではされないです)
内部的な変更としてbodyを常にバックエンドに送るように変更されており、これによりGETとbodyを使うPOSTなどと扱いが変わらなくなったためです。
とはいえGETはbodyがいらないのでbuiltin.vclでは削除するようになっています。


sub vcl_backend_fetch {
    if (bereq.method == "GET") {
        unset bereq.body;
    }
    return (fetch);
}

もし、自身のVCLがbuiltin.vclを通らない(return)している場合はunset bereq.bodyを追加する必要があるでしょう。
また、POSTのキャッシュができるようになったとはいえbuiltin.vclではやはりpass動作になっています。
試していませんがここをhashにすればキャッシュできると思いますが、オブジェクトのハッシュを作る際にbodyを評価していないため
ここらへんはbodyに対してアクセスできるvmodを使って中身を精査してハッシュを作る必要があります。

仕様変更

statsからn_waitinglistがなくなりました

VCL名に指定可能な文字が制限されました
わざわざvcl名を使っている人はそんなにいないと思うんですが
[A-Za-z][A-Za-z0-9_-]*にマッチする名前じゃないとだめになりました。

すべてのVCLオブジェクトは使う前に定義しないといけなくなりました
backendとかaclとかそういうのですね、むしろ今まで意識してなかった(いつも先頭に持ってきてた)ので
え、いけたの?ぐらいです。

pass(uncacheable)されたリクエストではcache-control/ExpiresのTTLはパースされなくなりました
今まではpassした場合でもTTLのパースはしていたのですがされなくなったので、対応するログも出力されなくなりました。

-T/-Sを無効化する場合は明示的にnoneを指定する必要があります

パラメータの変更

追加:ban_lurker_holdoff
効率的でないbanの指定などでban.listが肥大化した場合、ban_lurkerスレッドとの潜在的な競合によりlookup性能が悪化するケースがありました。これを防ぐためにsleepを入れるようになってその値です。
デフォルトは0.1sとban_lurker_sleepと同じです。

追加:session_max
session_maxは削除されました。
もともと4.0のリリースで効果がなくなったものです。
なお、将来的に復活する可能性はあるとのことです。

名前の変更:vcl_dir->vcl_path / vmod_dir->vmod_path
まだどちらも使えますがおそらく将来的に_dirのほうはなくなります。
また、:で分割して複数のpathを指定できるようになりました。
vcl_pathのデフォルトは/etc/varnish:/usr/share/varnish/vclとなっていますが
これはincludeなどでVCLを読もうとした際にまず/etc/varnishを探索し、
存在しない場合は/usr/share/varnish/vclも探すといった動きになります。
また、/usr/share/varnish/vclにはデバイスを判定するためのdevicedetect.vclが入っていています。

builtin.vclの変更点

GET時にbereq.bodyのunset
これは先程「POSTをキャッシュできるようにしました」で説明したとおりです。

hit-for-passの取扱について
今まではreturn(pass)したりそもそもキャッシュできないオブジェクトはhit-for-pass(HFP)としてマークされて
2分間は常にpass扱いとされていましたが、HFPとしてマークしないようにしました。
以下が4.1と5.0のdiffです。


 sub vcl_backend_response {
-    if (beresp.ttl <= 0s ||
+    if (bereq.uncacheable) {
+        return (deliver);
+    } else if (beresp.ttl <= 0s ||
       beresp.http.Set-Cookie ||
       beresp.http.Surrogate-control ~ "no-store" ||
       (!beresp.http.Surrogate-Control &&
         beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
       beresp.http.Vary == "*") {
-        /*
-        * Mark as "Hit-For-Pass" for the next 2 minutes
-        */
+        # Mark as "Hit-For-Pass" for the next 2 minutes
         set beresp.ttl = 120s;
         set beresp.uncacheable = true;
     }

VCLの変更点

backendsの定義に.proxy_headerが追加されました
新機能で触れたものです

vcl_recv:returnにvcl(label)が追加されました
新機能で触れたものです

vcl_recv:rollbackが削除されました

vcl_hit:return(fetch)が削除されました
元々4.1の時点でfetch->missに変更するようにアナウンスが出てたものですが、このタイミングで削除さらえました。

vcl_backend_*:remote.ip, client.ip, local.ip, server.ipにアクセスできるようになりました

vcl_backend_fetch:bereq.bodyが追加されました
新機能で触れたものです

vcl_backend_error:synthetic()がberesp.bodyに変更されました
こんな感じの表記になります


    set beresp.body = {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + beresp.status + " " + beresp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + beresp.status + " " + beresp.reason + {"</h1>
    <p>"} + beresp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + bereq.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};

vcl_synth:synthetic()がresp.bodyに変更されました

vcl_deliver: obj.ttl, obj.age, obj.grace, obj.keepにアクセスできるようになりました(read)

正規表現でmatchした時の結果をBOOLとしてそのままセットできるようになりました


set req.http.foo = req.http.bar ~ "bar";

このような表記ができるようになりました。

アップデート時の注意

パッケージの構成が変更された
元々は

  • Varnish本体
  • libs
  • libs-dev
  • docs

の4種類でパッケージが別れていたのですが

  • Varnish本体+lib+docs
  • dev

の2種類に変更されました。
そのためインストールするときに失敗する場合は一度既存パッケージをアンインストールしてみてください。

VMODのABIが変更されたのでリビルドが必要
VMODのABIが変更されたので使用しているVMODについてはリビルドが必要です。

環境によってはreloadに失敗するかも
これは私が引っ掛かった問題+私のsystemd力が低いのでパッと解決策が思いつかなかった問題なのですが
VCL名が仕様変更(先頭文字はアルファベットである必要がある)されたけど、reloadスクリプトがそれに追随していないように見えます。
Xenial(systemd)でリロードしようとするとこの部分のLOGNAMEが入らないため先頭がuuidになります。
uuidは数字を含むためreload時にVCL名の仕様変更に引っかかってしまう感じです。
とりあえず先頭にreload_とかつけとけば回避はできるのでもし引っかかった場合は


-    vcl_label="${LOGNAME}${LOGNAME:+_}${uuid}"
+    vcl_label="reload_${LOGNAME}${LOGNAME:+_}${uuid}"

こんな感じで修正すると良いかなと思います。
また、私がRHEL/CentOSの環境を持っていないので試していませんが、おそらくRHELでも同様の問題がおきます。(:は使えない)

その他変更

累積バグが直ってたり、細かい改善があったりします。


7月 302016
 

キャパシティプランニングをする際に頭がいたいものの一つに通常ではないアクセスがあります。
ぱっと思いつくので

  • 閲覧数がページに表示されているのでF5押しっぱなし
  • スクリプトでスクレイピングしようとしているのか暴走している
  • 足跡をつけるために尋常じゃない速度で訪問しまくる
  • ログイン試行
  • 画像をひたすらダウンロード

などなどいろいろあります。
これらに共通なのが、通常ではないリクエストで大量のリソースを消費することです。(もちろん他の問題(セキュリティ)があるものもあります)
もしキャッシュしていたとしても、アウトバウンド帯域を過剰に利用しますし、キャッシュが出来なければwsやdbなどでの負荷になります。
キャパシティプランニングをする際には様々な条件を考えて構築していきます。
単純にユーザーが増えて負荷が増えていくのは望ましく、喜んでインスタンスを増やしたり負荷対策をしますが
そうでない場合は、通常のユーザが巻き添えを食らわないようにキャップしたいものです。
今回は、Varnishでスロットルを行ってみようという記事です。

Varnishでスロットルをかけるのはすごい簡単で、vmod_vsthrottleというVMODを使用します。(ドキュメント)
これはvarnish-modulesに含まれています。
varnish-modulesはVarnish Softwareが作ったVMODで特に他のライブラリに依存しないものを集めたものです。(なのでdigestが入っていない)
ちなみに以下のVMODが含まれています。

  • cookie
  • header
  • saintmode
  • softpurge
  • tcp
  • var
  • vsthrottle
  • xkey

機会があれば、ほかのもそのうち紹介します。

まずはインストールです。
READMEにも書いてあるとおりにインストールします。


xcir@gw01:~$ sudo apt-get install libvarnishapi-dev
xcir@gw01:~$ git clone https://github.com/varnish/varnish-modules.git
xcir@gw01:~$ cd varnish-modules/
xcir@gw01:~/varnish-modules$ ./bootstrap
xcir@gw01:~/varnish-modules$ ./configure
xcir@gw01:~/varnish-modules$ make
xcir@gw01:~/varnish-modules$ sudo make install

./bootstrapでaclocalとかlibtoolizeで引っかかったらautomakeとかlibtoolを入れましょう
インストールはこれだけです。

次に使い方です。


BOOL is_denied(STRING key, INT limit, DURATION period)

keyはクライアントの識別子(IPなど)をいれます。
limitとperiodは組み合わせてつかいます。
period時間中にlimit回数を超えたらアウトみたいな感じです。
要はトークンバケットアルゴリズムです。
なので時間経過で使用した分は回復していきます。(呼び出し時に経過時間を調べて回復させます)
また、limitとperiodの組み合わせで平均RPSとバースト時のRPSを表現できます。
limitがバースト時RPSです。
limit / periodで平均のRPSになります。
なので例えば
limit=6 period=6sであれば平均は1RPSでバースト時は6RPS
limit=6 period=2sであれば平均は3RPSでバースト時は6RPS
みたいな感じになります。
うまく組み合わせて使いたいですね。

また、keyとありますが実際のkey生成にはkey + limit + periodのsha256をとっていますので
同じkeyを指定しても、limitやperiodが変われば別物として扱われます。

とりあえずテストでは/img/以下の画像(jpg|gif|png)を10秒の間に5回リクエストしたら制限をかけて429を返すようにします。
keyはclient.ipです。
image:というprefixをつけているのは、ルールを複数作ることも考えて意図せず被らないようにするためです。


vcl 4.0;
import vsthrottle;

sub vcl_recv {
  if(req.url ~ "^/img/.*\.(jpg|gif|png)" && vsthrottle.is_denied("image:" + client.ip, 5, 10s)) {
    return (synth(429, "Too Many Requests"));
  }
}

早速リロードしてみましょう


xcir@gw01:~/varnish-modules$ sudo varnishncsa -q "requrl ~ '/img/'"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:14 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429★ 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:15 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:32★ +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200★ 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:01:40:33 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 429 275 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
6

6件目から429が帰ってるのがわかります。
また制限が10秒のうちに5回なので、しばらくまってリクエストして制限が解除されているのもわかります。
割と簡単だと思います。
ちなみに、1keyあたり100byteのメモリを使うのと、mutexを使用しているのでそこは頭の片隅に入れておくと良いかと思います。
といってもmutexは16分割してるので、通常の利用であればそこまで気にする程ではないと思います。


※ここから先は特にそのサイトのワークロードによってかなり考え方が変わります。
 なのでそのまま適用するのではなく、それぞれのサイトで考えてもらえればよいかと思います。
 また、異常行動に対してのキャップなので、継続的な改善サイクルを回す必要があります。
 そういうのは最初に想定仕切るの難しかったり、ページによってコストも違うからです。

とりあえずこれでスロットル早速やってみよう!という人がいましたらちょっと注意が必要です。
スロットルをかけるということは、ユーザの体験を制限してしまう可能性があります。
そのため安易に設定し、誤爆して普通の使い方をしているユーザを制限してしまわないように細心の注意を払う必要があります。
とはいっても、それでゆるゆるな制限をかけても全く有効ではありません。
個人的にスロットルをする上では2つのことが重要だと考えています。

  • 誤爆しない
  • 理不尽でない

あまりにも厳しく、普通に使っているユーザが誤爆されるような低い閾値であればイライラして離れていってしまうでしょう。
また、仮に引っかかっても、まぁしょうがないなとある程度納得ができるのも重要です。
例えば連打が許容されるボタンがあったとして、それを10秒で100回も連打して引っかかったら、まぁしょうがないなと思えるんじゃないかなと思います。
この辺りは、サイトの特性によって違いますので各々で考える必要があります。

誤爆しないための工夫
閾値の設定はすごく難しいです。
サイトの保護を優先したければ閾値は低くしたいですし
誤爆を防ぐには閾値は高くしたいです。

しかし、低くすれば誤爆が増え、高くすれば意味がなくなります。
ではどうすればよいかというと、トークンバケットへの理解とユーザの行動を考えてみることです。

最初にトークンバケットですが、これを何かに例えるとするとSuicaのチャージです。
飲み物や昼食などのお小遣いとして毎朝1000円チャージするとします。
チャージされてる分は自由に使って良くて、例えば一気に5000円使っても問題はありません(残額があれば)
また、Suicaのチャージ上限は2万円なので、そこに達した時点でそれ以上チャージができなくなります。
さらに開始時点から上限の2万円がチャージされているとします。
これをvsthrottle.is_deniedで表現すると


vsthrottle.is_denied(client.ip, 20000, 20d);

といった感じです。
平均すると1日辺り1000円使えますが、もし毎日2000円使うとなると20日経過した時点で初期にチャージされてた2万を使いきって一日待たないと2000円は使えなくなります。
1000円を超えてる部分はburstといった感じになります。

次に、ユーザの行動をYahooのトップ/ニュースページで考えてみましょう。(記事ページを守る)
ニュースの欄には複数のリンクがあって複数気になる記事があります。
その場合、行動は2パタンに別れると思います。

  1. 開いて読んで、読み終わったら戻って次の記事を読む
  2. 気になる記事を一気に開いて順次読んでいく

1は特に気にする必要はありません。記事を1秒で読んで素早く戻って次の記事を見ることができる人類はまずいないでしょう。
もし、1だけを考えるのであれば1秒に1回で制限をかけてしまえば良いと思います。
しかし、2の場合は一気にリンクを開くため、1秒で2~3は開くことは人類でもできるので先ほどのの制限では誤爆してしまいます。(本筋ではないですがChromeがbackspaceで戻れなくなったのでこういう人増えそうですね)
では、次に制限したいものを考えてみましょう。
今回はすべての記事を舐めてくようなクローラーで想定してみます。
このクローラーは3RPSで舐めていきます。
とりあえず登場するものが揃ったので条件を整理します。

1. 1ページ開いて5秒程度で読んで戻ってまた開くユーザの場合

  • avg=0.2RPS
  • burst=1RPS

2. リンクを最大6リンク程度開いて(秒間3程度)各ページを5秒程度で読むユーザの場合

  • avg=0.1875RPS
  • burst=3RPS

3. クローラー

  • avg=3RPS
  • burst=3RPS

条件が揃いました。
許容したいburstは2の3RPSです。
しかしavgでは0.2もあれば十分です。
これを単純に落としこむと
limit=3 period=15s (avg=0.2 burst=3)
となります。
しかし制限したいクローラーに対してはまだ余裕があるので自分ならですが
limit=4 period=10s (avg=0.4 burst=4)
ぐらいに設定すると思います。

とりあえずVCLを書いてみます。


vcl 4.0;
import vsthrottle;
import std;

sub vcl_recv {
  if(req.url ~ "^/pickup/" && vsthrottle.is_denied("pickup:" + client.ip, 4, 10s)) {
    std.log("THROTTLE:pickup:");
    //return (synth(429, "Too Many Requests"));
  }
}

ここでは一旦429は出さずにlogに出力しています。
これは実際に投入した後に、どういうクライアントが引っかかるかを試すためです。(テストのため引っ掛けるルールをちょろっと変えてます)


xcir@gw01:~$ sudo varnishncsa -q "vcl_log ~ 'THROTTLE:pickup:'"
***** - - [30/Jul/2016:19:48:38 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "http://blog.xcir.net/?p=2283" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
***** - - [30/Jul/2016:19:48:38 +0900] "GET http://xcir.net/img/bg2.png HTTP/1.1" 200 10355 "http://blog.xcir.net/?p=2283" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"

vslqueryを使用して引っかかるものだけを確認して、問題がなければreturnのコメントを外して有効にしましょう。
個人的には、std.logはそのまま残しておいたほうがqueryで絞込ができるので便利だと思います。

IPは本当に個人を識別するものか
先ほどの例では、クライアントを識別するのにclient.ipを利用しました。
しかし、これは本当に個人を識別できているでしょうか?
例えば、アクセスログを見てるとHost名がgoogle-proxy-*.google.comというホストでアクセスしてくるクライアントがいます。
これはChromeのデータセーバーで一旦Googleを経由しています。(参考1, 2
つまり、同時に同一proxyから異なるユーザがリクエストしてくる可能性があります。
その場合はスロットルが意図せず掛かる可能性があります。
もちろん、Chromeのデータセーバー以外にもこのようなproxyがあります。
サイトの規模にもよりますが、大規模な場合はこの辺りも意識する必要があります。(リクエストが多ければ多いほどかぶる可能性が上がるので)
じゃぁキーをどうすればよいのかというといろいろあります。
ログイン後のページで制限をかけたいのであれば、クッキーからキーを抜き出しても良いと思います。
非ログインの場合は、ブラウザフィンガープリントを調べると良いかなと思います。(参考1, 2
他にもいろいろありますが、制限をしたい箇所によって最適な方法を模索していくと良いかなと思います。

最後に
スロットルはサイトでユーザがどう行動するかや、何が異常でそうでないかの見極めをきちんとしないとユーザ・運営共に酷いことになってしまいます。
しかしうまく使うことができれば、サイトの安定性を向上させることが出来ますので一度検討してみてはいかがでしょうか?


7月 132016
 

Varnish4.1.3がリリースされました。
バグフィックスが主でアップデートを強く進めますが、他にも結構使える新機能が増えています。

ダウンロードはこちら
changes

新機能・変更

varnishncsaにバックエンドに問い合わせにいったものだけを表示するオプションが追加されました(-b)
今までは幾つか絞込の指定をしないと取れなかったのですが-bだけで済むので便利です
また同時にクライアントで絞り込んで表示するオプション(-c)も追加されています。(デフォルト動作)
ちなみに-b -cを同時に指定することも出来ます。

VSMを開放する際にどれだけ待機するかのパラメータが追加されました(vsm_free_cooldown)
今までは60秒でハードコードされていました。デフォルト値も60秒です。
通常の使い方ではいじることはないと思いますが、かなり激しいログの書き込みをするとかの環境だといじる機会はあるかもしれないです。

varnishlogの出力でバックエンドのトランザクションが開始する際にBackendStartが入るようになりました
クライアントのReqStartと対になるものです。

varnishncsaの-Fで指定できるものが増えました
3つ増えました。
 Varnish:side
 先ほど紹介した-b -cオプションに伴って増えたものです。
 バックエンドで絞込をしている場合はbを、クライアントからの表示の場合はcを出力します。
 -b -cを両方指定している場合に使うとどちらのログなのかがわかって良いと思います。
 Varnish:vxid
 vxidを出力します
 VSL:tag VSL:tag[field]
 指定したタグ(TimeStampなど)を出力します。
 基本vsl-queryと同じ指定ですがヘッダ指定は出来ません(VSL:ReqHeader:User-Agentみたいなのは出来ない)
 これできるといいなーと思うのでp-r書こうかな・・
 ちなみに複数引っかかる場合は最初のを出力します。
 例えばVSL:TimeStampを指定した場合は毎回Startのタイムスタンプが表示されます

TCP Fast Openをサポートしました
デフォルトはoffです(tcp_fastopen)

varnishtestに新しい同期用の命令を追加しました(barriers)
semaより使いやすいです

varnishstatで12桁以上の数値を出力する場合は丸めるようにしました#1855
CURRENTの表示でK/M/G/Tのように単位がつけられないカウンタは” %12ju”だったのですが12桁を超える場合は1000で割って” %9ju…”という表示になります。
コードを見た限りではxml/jsonで出力するときには影響しないのでそこまで気にすることはないかなと思います。

バグ修正

今回はかなりバグ修正が多いので幾つかピックアップして紹介します。
特にESI周りの修正が多いのでESIを使っている場合はアップデートすると良いと思います。

varnishncsaで-Lオプションが受け付けられないようになってたのを修正しました#1994

たまにAgeとAccept-Rangesヘッダが複数レスポンスされることがあるのを修正しました#1955

同名のVCLでvarnishをstop->startを繰り返すとその後segfaultを繰り返す事があったのを修正しました。#1933
dlopenのリファレンスカウンタが信用ならなかったみたいです。
vcl名は変わらないのですが、コンパイルしたvgc.soのパスに現在時刻(ナノ秒)が付与されるようになりました。
バッドアイデアとメッセージにあるので割と苦悩した感じがあります。

vcl_init/finiでstd.log/std.syslogを利用するとクラッシュするのを修正しました#1924

VSMのサイズが小さくてオーバーラン検出に問題が合ったのを修正しました#1873
varnishncsa等でオーバーランした際にもクラッシュしなくなりますが
オーバーランしたらvsmサイズを増やしたほうが良いでしょう。
それでも困るようであれば今回追加されたcooldownの調整も考えると良いかも

-Cオプション利用時にテンポラリで使用したディレクトリを消していなかったのを消すようにしました#1869

POSTリクエストをpipeで繋いだ場合に1分待たされることがあるのを修正しました#1806
4.1からはPOSTリクエストのデフォルトの動作がpassになっているので通常の場合は問題ないのですが
以前からの設定を使いまわしてる人などは割りとハマるかもしれないです。

バックエンドの接続数を示すカウンタを復活させました#1725
バックエンド周りのコード修正した際に意図せず消えたカウンタを復活させました。
確かに消えてました・・気づかなかった・・

次は5.0.0かなー
楽しみですね