5月 312012
 

若干小ネタですが、こんな書き方もできるという例です。

先日のエントリ(Varnishを使う際に覚えておきたいデフォルトの罠)において、
Varnishはユーザの入力したVCLコードの後に、デフォルトのVCLコードを挿入すると紹介しました。

これはユーザーのVCLとデフォルトのVCLの組み合わせですが、
実はユーザのVCLを複数定義して連結することが可能です。

やり方は非常に簡単で、同じアクションを複数定義するだけです。


■VCL
sub vcl_deliver{
  set resp.http.a = "a";
}
sub vcl_deliver{
  set resp.http.a = resp.http.a + "b";
}
sub vcl_deliver{
  set resp.http.a = resp.http.a + "c";
}

■出力ヘッダ
a: abc

上記のVCLは以下のように結合されVarnishで使用されます


sub vcl_deliver {
  {//ユーザの定義1
    set resp.http.a = "a";
  }
  {//ユーザの定義2
    set resp.http.a = resp.http.a + "b";
  }
  {//ユーザの定義3
    set resp.http.a = resp.http.a + "c";
  }
  {//デフォルト
    return (deliver);
  }
}

因みに結合する際の順番は先に出てきたものからどんどん突っ込んでいく感じです。

実際のところどのように使えばいいかというと、includeと組み合わせて使うのが便利だと思います。


include"/etc/varnish/ext.vcl";

先頭や末尾にincludeで別処理を入れることで、本来の処理を邪魔せず
デバッグすることや、複数のサーバにまたがる共通処理などを記述することが可能です。

ちょっと小ネタですが役に立てばいいなぁとおもいます。


5月 302012
 

最近とある人が嵌っていて聞かれたので
Varnishを使う上で覚えておきたいデフォルト設定の罠を説明したいと思います。

デフォルトのLISTENポートは6081、VCLでなく起動パラメータで設定する

そのかたはbackendの設定をLISTENポートと勘違いしていました。
Varnishの設定は主に2つにわかれています

    起動パラメータ
    VCL設定(default.vcl)

起動パラメータはキャッシュを保存するストレージのサイズを指定したり、保持するスレッド数や、ワークスペースのサイズ、LISTENポートを指定したりなどと
主に変更する場合はVarnish自体の再起動が必要などと即時に反映できないものが多いです。
(デフォルトのTTLなどもあり、必ずしも全てではない)

そしてVCL設定はリクエストに対してVarnishがどのように振る舞うかを定義しています。

redhat系の場合起動パラメータは
/etc/sysconfig/varnish
に記述されており、LISTENポートは以下のように定義されています


# # Default address and port to bind to
# # Blank address means all IPv4 and IPv6 interfaces, otherwise specify
# # a host name, an IPv4 dotted quad, or an IPv6 address in brackets.
# VARNISH_LISTEN_ADDRESS=
VARNISH_LISTEN_PORT=6081


if-modified-sinceに対して常に304を返そうとするが動作が不安定

保存されている画像などが変更されずに、URLに対してユニークであることが保証できる場合
if-modified-sinceを受け取った段階で、即304を返す場合があります。

たとえば以下のようなコードです


...バックエンドの設定...
sub vcl_recv{
  if(req.http.If-Modified-Since) {
    error 304 "Not Modified";
  }
}

特に問題もなく動くブラウザと動かないブラウザが出てくると思います。
Varnishの304時のレスポンスをよく観察してみると、ボディがくっついているのが確認できると思います。

ステータス304はレスポンスボディを含んではならないため
ブラウザによっては予期せぬ動作になったりします。

これはVarnishのデフォルト設定が原因です。
まずVarnishがユーザーの記述したVCLコードと、デフォルトのVCLコードをどのように扱っているかを説明します。

Varnishはユーザの入力したVCLコードの後に、デフォルトのVCLコードを挿入します。

そのため、たとえば以下のようにvcl_recvを指定した場合でも


sub vcl_recv{
  if(req.http.If-Modified-Since) {
    error 304 "Not Modified";
  }
}

実際にVarnishは以下のように解釈します


sub vcl_recv{
  //ユーザの設定したvcl_recv
  {
    if(req.http.If-Modified-Since) {
      error 304 "Not Modified";
    }
  }
  //Varnishのデフォルトのvcl_recv
  {
    if (req.restarts == 0) {
        if (req.http.x-forwarded-for) {
            set req.http.X-Forwarded-For =
                req.http.X-Forwarded-For + ", " + client.ip;
        } else {
            set req.http.X-Forwarded-For = client.ip;
        }
    }
    if (req.request != "GET" &&
      req.request != "HEAD" &&
      req.request != "PUT" &&
      req.request != "POST" &&
      req.request != "TRACE" &&
      req.request != "OPTIONS" &&
      req.request != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }
    if (req.request != "GET" && req.request != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }
    if (req.http.Authorization || req.http.Cookie) {
        /* Not cacheable by default */
        return (pass);
    }
    return (lookup);
  }
}

つまり、ユーザが何も指定しないアクションは全てデフォルトの動作となります。

vcl_errorのデフォルト設定は以下のとおりです


sub vcl_error {
    set obj.http.Content-Type = "text/html; charset=utf-8";
    set obj.http.Retry-After = "5";
    synthetic {"
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>"} + obj.status + " " + obj.response + {"</title>
  </head>
  <body>
    <h1>Error "} + obj.status + " " + obj.response + {"</h1>
    <p>"} + obj.response + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + req.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};
    return (deliver);
}

つまり、以下のようにデフォルトの設定が動いてしまいレスポンスボディが生成されたため
ブラウザによって不具合が起きてしまいます。

これを防ぐためには、vcl_errorで以下のような記述をする必要があります。


sub vcl_error {
    if(obj.status == 304){
        return (deliver);
    }
}

この設定によりerrorで304を発行する場合、
デフォルトの設定を行わずそのまま次の処理に移動します。

また204 No Contentや1XXについてもレスポンスボディを含んではいけないので


sub vcl_error {
    if((obj.status >= 100 && obj.status < 200) || obj.status == 204 || obj.status == 304){
        return (deliver);
    }
}

とするのも良いでしょう。


キャッシュできるはずなのにキャッシュできない

全て静的コンテンツで、バックエンドは200を返しているのにキャッシュされない場合があります。
これは先程のデフォルトの設定と絡んでいます。

vcl_recvのデフォルト設定を見てみましょう。


sub vcl_recv {
    if (req.restarts == 0) {
        if (req.http.x-forwarded-for) {
            set req.http.X-Forwarded-For =
                req.http.X-Forwarded-For + ", " + client.ip;
        } else {
            set req.http.X-Forwarded-For = client.ip;
        }
    }
    if (req.request != "GET" &&
      req.request != "HEAD" &&
      req.request != "PUT" &&
      req.request != "POST" &&
      req.request != "TRACE" &&
      req.request != "OPTIONS" &&
      req.request != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }
    if (req.request != "GET" && req.request != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }
    if (req.http.Authorization || req.http.Cookie) {
        /* Not cacheable by default */
        return (pass);
    }
    return (lookup);
}

見ての通り、認証時やクッキーが付いている場合はデフォルトではpass動作となりキャッシュされません。
VCLの記述をする際は、必ず自分の書いたコードの後にデフォルトのコードが付与されることを意識して書く必要があります。
全て自分の記述したコードで完結し、デフォルトの設定を動かしたくない場合は
必ずreturn(XXXX)で終わるようにしましょう。

以上
意外と嵌りやすいんじゃないかなーという点を解説しました。
(僕も触り始めの頃デフォルト設定が動いて嵌りました)
何かの参考になればと思います。


5月 262012
 

VMOD processing want to before/after VCL function processing.
But, I do not want to write extra line.
How to do it?


Can be realized by the replace the pointer to sp->vcl->XXXX_func (VCL_XXXX)


Can be added to the processing by replacing pointer



static vcl_func_f         *vmod_redirect_Hook_vcl_error = NULL;
static pthread_mutex_t    vmod_redirect_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned           hook_done = 0;

static int vmod_Hook_vcl_error(struct sess *sp){

    ....    //write the processing

    //call original vcl_error
    return(vmod_redirect_Hook_vcl_error(sp));

}

int
vmod_location(struct sess *sp, int status, const char*p,...)
{
        //vcl reload detection.
        if(hook_done == 1
           && sp->vcl->error_func != vmod_redirect_Hook_vcl_error ) hook_done = 0;

        if(hook_done == 0){
            //lock to prevent another thread write
            AZ(pthread_mutex_lock(&vmod_redirect_mutex));

            if(hook_done == 0)
            {
                //store the original vcl_error pointer
                vmod_redirect_Hook_vcl_error = sp->vcl->error_func;

                //hook
                sp->vcl->error_func = vmod_Hook_vcl_error;

                hook_done = 1;
            }
            //unlock
            AZ(pthread_mutex_unlock(&vmod_redirect_mutex));
        }

    ....

    return (status);
}

(via:vmod_redirect)

Hook to vcl_error at vmod processing in first time.
because there is no way to write-access the pointer at vmod_Init.


also, has locked using the mutex, because to prevent the loop by thread concurrent access.

I hope that this code is of help to you.

ATTENTION
don’t use varnishadm’s command “vcl.use” and “vcl.discard” . because to the segfault or call to other vcl function.


modify(2012-08-01)
when you vcl reloaded, hook method be off.


5月 252012
 

VMODを作る上で何かしらの後処理や前処理を、ユーザの処理前にやりたいケースが存在します。
たとえば僕が以前作ったvmod_redirectの場合です。

Varnishのリダイレクトはかなりめんどくさくて以下のように記述する必要があります。


sub vcl_recv {
  if (req.http.user-agent ~ "iP(hone|od)") {
    error 750 "Moved Temporarily";
  }
}

sub vcl_error {
  if (obj.status == 750) {
    set obj.http.Location = "http://www.example.com/iphoneversion/";
    set obj.status = 302;
    return(deliver);
  }
}

このように一回エラー関数でvcl_errorに飛ばして判断する必要があります。
しかしvmod_redirectではvcl_errorに記述することなくredirectを実現しています。


import redirect;
sub vcl_recv {
   if (req.http.user-agent ~ "iP(hone|od)") {
      error(redirect.location(302,"http://www.example.com/iphoneversion/") , "Moved Temporarily");
   }
}

これはvcl_errorの関数ポインタをフックすることにより
オリジナルのvcl_errorが呼び出される前に独自の関数を呼び出すためです。

ではフックするためのポインタは何処に定義されているでしょうか?
vmodで使うsturct sess構造体(sp)に含まれています。


vcl_errorの場合
 sp->vcl->error_func

vcl_deliverの場合
 sp->vcl->deliver_func
...

ここのポインタを書き換えることでVarnishが呼び出すアクションを変更することが可能です。

そのことでオリジナルのアクションを呼び出す前後で任意の処理を行うことができ非常に便利です。

実際に書き換える場合はこのようなコードで行います


static vcl_func_f         *vmod_redirect_Hook_vcl_error = NULL;
static pthread_mutex_t    vmod_redirect_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned           hook_done = 0;

static int vmod_Hook_vcl_error(struct sess *sp){

    ....//任意の処理を書く

    //オリジナルのvcl_errorを呼び出してリターン
    return(vmod_redirect_Hook_vcl_error(sp));

}


int 
vmod_location(struct sess *sp, int status, const char*p,...)
{
        //VCLのreloadでフックが外れた場合の検知
        if(hook_done == 1
           && sp->vcl->error_func != vmod_redirect_Hook_vcl_error ) hook_done = 0;

        if(hook_done == 0){
            //別のスレッドが同時に書き込みをしないようにロックする
            AZ(pthread_mutex_lock(&vmod_redirect_mutex));
            if(hook_done == 0)
            {
                //退避先にオリジナルのポインタを保存
                vmod_redirect_Hook_vcl_error = sp->vcl->error_func;
                //フックする
                sp->vcl->error_func = vmod_Hook_vcl_error;
                
                hook_done = 1;
            }
            //ロック開放
            AZ(pthread_mutex_unlock(&vmod_redirect_mutex));
        }
    
    ....

    return (status);
}


ポイントとして初回のvmod関数呼び出し時にフックします。
これはvmod_Initではフックをするために必要な構造体へのアクセス方法が無いためです。
またmutexを利用してロックしていますが、これは複数のスレッドが同時にフックを行おうとしてフック関数でループするのを防ぐためです


ロックを行わないと、上図の(6)でオリジナルのポインタとおもって、フック後のポインタを取得してしまいます。
二回0チェックしているのは、ロックをかける間に他のスレッドが書き込んでいないことを確認するためです。

このようにフックを使うことでよりVMODの表現が増えるんじゃないかなと思います。
参考になれば幸いです

注意
varnishadmのvcl.useとvcl.discardを使わないでください。
vcl.useの場合は直前のVCLのメソッドが呼ばれ、vcl.discardはセグフォを起こす危険性があります

—-
2012-08-02追記
VCLのリロードを行った時にフックが外れるので検知用コードを追加(hook_done)


4月 132012
 

How do ACL match to req.http.X-Forwarded-For? (this is string!!)
I tried to make a vmod.

inet_pton(BOOL ipv6 , STRING str , STRING defaultstr)

example 1


import campur_xcir;

set resp.http.v6 = campur_xcir.inet_pton(true,"2001:0db8:bd05:01d2:288a:1fc0:0001:10ee","1982:db8:20:3:1000:100:20:3");
set resp.http.v4 = campur_xcir.inet_pton(false,"1.1.1.1","2.2.2.2");
set resp.http.v6ng = campur_xcir.inet_pton(true,"2001:0db8:bd05:01d2:288a:1fc0:0001:10eeHOGE","1982:db8:20:3:1000:100:20:3");//NG pattern
set resp.http.v4ng = campur_xcir.inet_pton(false,"1.1.1.1HOGE","2.2.2.2");//NG pattern

//result
v6: 2001:db8:bd05:1d2:288a:1fc0:1:10ee
v4: 1.1.1.1
v6ng: 1982:db8:20:3:1000:100:20:3
v4ng: 2.2.2.2

example 2


import campur_xcir;

//acl
acl local {
    "192.168.1.0"/24;
    !"0.0.0.0";
}

sub vcl_recv{
  if(campur_xcir.inet_pton(false , req.http.X-Forwarded-For , "0.0.0.0") ~ local){
      //acl ok
      ...
  }
}

I hope that this code is of help to you.
libvmod-campur_xcir

this module is my motley function.
Others, get varnish generated hash etc…

this vmod’s function is increase at times. 🙂


4月 132012
 

ちょっとdai_yamashitaさんに聞かれたのでつくってみたものですがせっかくなので記事にします。

よくクライアントのIPアドレスをACLと突き合わせて処理をする・しないをやるかと思いますが
上位に他のProxyがいるなど(たとえばNginx)で、X-Forwarded-ForをACLと付き合わせてみたいときはどうすればよいでしょうか?
通常の方法ではできないのでVMOD作ってみました。

campur_xcir.inet_pton(ipv6かどうか , 変換したいIPアドレスな文字列 , 失敗した場合のデフォルトのIPアドレスな文字列)


import campur_xcir;

set resp.http.v6 = campur_xcir.inet_pton(true,"2001:0db8:bd05:01d2:288a:1fc0:0001:10ee","1982:db8:20:3:1000:100:20:3");
set resp.http.v4 = campur_xcir.inet_pton(false,"1.1.1.1","2.2.2.2");
set resp.http.v6ng = campur_xcir.inet_pton(true,"2001:0db8:bd05:01d2:288a:1fc0:0001:10eeHOGE","1982:db8:20:3:1000:100:20:3");//失敗パタン
set resp.http.v4ng = campur_xcir.inet_pton(false,"1.1.1.1HOGE","2.2.2.2");//失敗パタン

//結果
v6: 2001:db8:bd05:1d2:288a:1fc0:1:10ee
v4: 1.1.1.1
v6ng: 1982:db8:20:3:1000:100:20:3
v4ng: 2.2.2.2

実際はこんな感じの使い方を想定しています


import campur_xcir;

//acl
acl local {
    "192.168.1.0"/24;
    !"0.0.0.0";
}

sub vcl_recv{
  if(campur_xcir.inet_pton(false , req.http.X-Forwarded-For , "0.0.0.0") ~ local){
      //acl ok
      ...
  }
}

もしよかったら使ってみてください
libvmod-campur_xcir

ちなみにこのモジュールは僕が試しに作ったものを突っ込んでるものです
他にはMLで質問してた人向けに作ったVarnishのhash値を取得するものとかが入ってます
たまに増えたりすると思います


3月 302012
 

oranieさんが主催しているWebサーバ勉強会という
全員何かしら発表するという勉強会でESIについて発表してきました。

ESIについては結構前にqpstudyでもLTしているのですが
Varnishのバージョンも上がり、いろいろ使いやすくなったので、ここでもう一回とおもいこのテーマで発表しました。
Varnishの利点はやはりその柔軟な設定(VCL)にあると思うので
その利点を生かして、単純に静的ファイルをキャッシュさせるだけではなく
ESIのように複雑なキャッシュをさせるのもいいんじゃないかなと思っています。

また他の方の発表を聞いてfluentdはやっぱり使いたい!と思った所存です。
JSONにどうしてもなるのかなぁと思っていたのですがrawで収集できるようにしたよ!という発表もあり既存とも組み合わせしやすくこれはいいなと考えています。
Apacheについても2.4.1が出た際に僕はEvent MPMばっかに目が言ってたのですが他にも魅力的な機能がありもう一回じっくり見てみようかなと考えています。

非常に刺激を受けた勉強会でした。
また機会があれば是非参加したいと考えています。
参加者の皆様、主催者のoranieさん本当にありがとうございました。


3月 202012
 

To save the split log for every host([hostname].access_log), Apache is easy.
I want the same action in varnishncsa. What should I do?

Use the options -m [tag:regex], -w [file], -a and -D.

-m perform a regex match to the tag’s log entry.
-w write log to a file.
-a append log. Will be overwritten if you do not specify.
-D Daemonize.

exec varnishncsa(host is a.example.net and b.example.net)


varnishncsa -m "RxHeader:^Host: a.example.net$" -a -w /var/log/varnish/a.example.net.access_log -D
varnishncsa -m "RxHeader:^Host: b.example.net$" -a -w /var/log/varnish/b.example.net.access_log -D

after request


cat a.example.net.access_log
192.168.1.199 - - [20/Mar/2012:12:51:50 +0900] "GET http://a.example.net/a HTTP/1.0" 200 280 "-" "Wget/1.12 (linux-gnu)"

cat b.example.net.access_log
192.168.1.199 - - [20/Mar/2012:12:51:59 +0900] "GET http://b.example.net/a HTTP/1.0" 200 280 "-" "Wget/1.12 (linux-gnu)"

work as expected.

If you want to log rotate, please send the SIGHUP.


3月 202012
 

Apacheでvirtualhostを切って複数のホストで運用する場合
よくログファイル名に[hostname].access_logとかつけるとおもいます。
ではvarnishncsaで似たようなことをするにはどうすればいいでしょうか?

-m [tag:regex]と-w [file] -D -aオプションを使うことで可能です。
このオプションはログエントリの指定されたタグ(RxHeaderなど)の内容(Host: c.example.netなど)を正規表現でマッチさせて
引っかかったものを対象にするオプションです
タグについてはvarnishlogを眺めてる時に出てくるのを見ればわかると思います


        ↓ここがタグ      ↓ここが内容
   12 RxHeader     c Host: c.example.net

-wは出力先のファイル名を指定して、-Dを指定するとデーモンとして動きます
-aはログの追記です。指定しないとログが存在する場合上書きされてしまいます。

今回はテストでa.example.netとb.example.netでそれぞれログを出力します
こんな感じで指定します


varnishncsa -m "RxHeader:^Host: a.example.net$" -a -w /var/log/varnish/a.example.net.access_log -D
varnishncsa -m "RxHeader:^Host: b.example.net$" -a -w /var/log/varnish/b.example.net.access_log -D

それぞれwgetを投げてみて出力されたのが以下です。


cat a.example.net.access_log
192.168.1.199 - - [20/Mar/2012:12:51:50 +0900] "GET http://a.example.net/a HTTP/1.0" 200 280 "-" "Wget/1.12 (linux-gnu)"

cat b.example.net.access_log
192.168.1.199 - - [20/Mar/2012:12:51:59 +0900] "GET http://b.example.net/a HTTP/1.0" 200 280 "-" "Wget/1.12 (linux-gnu)"

きちんと分かれているのがわかります。

またvarnishncsaはSIGHUPを受け取るとファイルを開き直すので
ローテーションする場合はSIGHUPを使ってください


3月 122012
 

varnishlogtrans version up(mapping from vcl_trace to vcl source)

Past Articles:convert varnishlog output to easy

Changes

  • Feature: mapping from vcl_trace to vcl source(line).(request from @perbu)
  • Feature: show backend status.(request from @dai_yamashita)
  • Change: Add run option.
  • Experimental: mapping from vcl_trace to vcl source(all).
  • mapping from vcl_trace to vcl source(line).

    if you set to -f option, replace vcl_trace to VCL code
    before.

    
             | trace  | vrt_count:1 vcl_line:55 vcl_pos:1
    
    

    after(use -f and -e option)

    
             | trace  | set req.http.X-TEST1=campur_xcir.gethash();
             |        | ^ (vrt_count:1 vcl_line:55 vcl_pos:1 src_name:input)
    
    
    

    show backend status.

    show backend status.
    past status is window 4times.

    
    backend status
    ------------------------------------------------------------
    
    name    | status  | past status
    default | healthy | YY
    
    

    Add run option.

    -f [vcl file]

    if you set, you can mapping from vcl_trace to vcl source.

    -e [varnishd]

    if you set, you can mapping from vcl_trace to vcl source.

    -cc_command [cc_command]

    if you set in the -f option and varnish’s cc_command option ,please setting.

    –(backend|action|variable) [on|off]

    if you want to disable (backend|action|variable) info , set to off. (default is on)

    mapping from vcl_trace to vcl source(all).

    this function is unstable.
    if you want use this function.
    it need change value and set option(–src=on -f=[vclfile] -e=[varnishd]).

    before

    
    define('experimental' , false);
    
    

    after

    
    define('experimental' , true);
    
    

    output

    
    ############################################################
    vcl trace
    [exec]      /* "input"*/
    [exec]  /*
    
    ...
    
    [exec]  sub vcl_recv {
    [exec]      if (req.restarts == 0) {
    [exec]          if (req.http.x-forwarded-for) {
                        set req.http.X-Forwarded-For =
                            req.http.X-Forwarded-For + ", " + client.ip;
    [exec]          } else {
    [exec]              set req.http.X-Forwarded-For = client.ip;
    [exec]          }
    [exec]      }
    [exec]      if (req.request != "GET" &amp;amp;amp;&amp;amp;amp;
    [exec]        req.request != "HEAD" &amp;amp;amp;&amp;amp;amp;
    [exec]        req.request != "PUT" &amp;amp;amp;&amp;amp;amp;
    [exec]        req.request != "POST" &amp;amp;amp;&amp;amp;amp;
    
    ...
    
    

    I hope that this code is of help to you.

    download here.
    varnishlogTrans