CLOVER🍀

That was when it all began.

VarnishのVCLのデフォルト蚭定を芋぀぀、蚭定を倉曎しお遊んでみる

これは、なにをしたくお曞いたもの

少し前に、Varnish 6.0をむンストヌルしおみたした。

Varnish 6.0をUbuntu Linux 18.04 LTSにインストールする - CLOVER🍀

この時は、ずりあえずむンストヌルしただけだったので、今回はもう少し蚭定を扱っおみたしょう。

環境

今回の環境は、こちら。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.3 LTS
Release:    18.04
Codename:   bionic

Varnishのバヌゞョンは、こちらです。

$ varnishd -V
varnishd (varnish-6.0.4 revision 14d4eee6f0afc3020a044341235f54f3bd1449f1)
Copyright (c) 2006 Verdens Gang AS
Copyright (c) 2006-2019 Varnish Software AS

Varnishおよびバック゚ンドサヌバヌが皌働しおいるサヌバヌのIPアドレスおよび、アクセスするクラむアントのIPアドレスは、
以䞋ずしたす。

  • Varnish and オリゞンサヌバヌ 
 192.168.33.10
  • クラむアント 
 192.168.33.1

デフォルトのVarnishの蚭定

たずは、ベヌスずなるVarnishの蚭定を確認しおみたす。コメントアりトず飛ばすず、こんな感じでした。

$ grep -vE ' *#' /etc/varnish/default.vcl

vcl 4.0;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {
}

sub vcl_backend_response {
}

sub vcl_deliver {
}

バック゚ンドサヌバヌテスト甚のサヌバヌ

今回は、バック゚ンドサヌバヌを簡単にPythonのHTTPサヌバヌで甚意したす。

カレントディレクトリず、サブディレクトリにファむルをひず぀甚意しお、ポヌト8080で起動。

$ echo 'Hello Varnish!!' > hello.txt
$ mkdir sub-dir
$ echo 'Hello Cache Server!!' > sub-dir/cache.txt

$ python3 -m http.server 8080

Varnishぞは、ポヌト6081越しにアクセスすれば、Varnishを経由しおキャッシュがなければバック゚ンドサヌバヌぞのアクセスずなりたす。

$ curl 192.168.33.10:6081/hello.txt
Hello Varnish!!

ビルトむンの蚭定

ずころで、甚意されおいるデフォルトの蚭定ファむル/etc/varnish/default.vclの䞭には特になにも蚭定がなさそうでしたが、
キャッシュはすでに有効になっおいる感じでした。

どうなっおいるんでしょう

ビルトむンの蚭定ファむルがあるようです。

https://github.com/varnishcache/varnish-cache/blob/varnish-6.0.4/bin/varnishd/builtin.vcl

$ curl -s https://raw.githubusercontent.com/varnishcache/varnish-cache/varnish-6.0.4/bin/varnishd/builtin.vcl | grep -vE '*#' 
/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2015 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This is the builtin VCL code
 */

vcl 4.0;


sub vcl_recv {
    if (req.method == "PRI") {
        /* This will never happen in properly formed traffic (see: RFC7540) */
        return (synth(405));
    }
    if (!req.http.host &&
      req.esi_level == 0 &&
      req.proto ~ "^(?i)HTTP/1.1") {
        /* In HTTP/1.1, Host is required. */
        return (synth(400));
    }
    if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "DELETE" &&
      req.method != "PATCH") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

    if (req.method != "GET" && req.method != "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 (hash);
}

sub vcl_pipe {
    return (pipe);
}

sub vcl_pass {
    return (fetch);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_purge {
    return (synth(200, "Purged"));
}

sub vcl_hit {
    if (obj.ttl >= 0s) {
        // A pure unadulterated hit, deliver it
        return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        // Object is in grace, deliver it
        // Automatically triggers a background fetch
        return (deliver);
    }
    // fetch & deliver once we get the result
    return (miss);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_deliver {
    return (deliver);
}

/*
 * We can come here "invisibly" with the following errors: 500 & 503
 */
sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    set resp.http.Retry-After = "5";
    set resp.body = {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + resp.status + " " + resp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + resp.status + " " + resp.reason + {"</h1>
    <p>"} + resp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + req.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};
    return (deliver);
}


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

sub vcl_backend_response {
    if (bereq.uncacheable) {
        return (deliver);
    } else if (beresp.ttl <= 0s ||
      beresp.http.Set-Cookie ||
      beresp.http.Surrogate-control ~ "(?i)no-store" ||
      (!beresp.http.Surrogate-Control &&
        beresp.http.Cache-Control ~ "(?i:no-cache|no-store|private)") ||
      beresp.http.Vary == "*") {
        set beresp.ttl = 120s;
        set beresp.uncacheable = true;
    }
    return (deliver);
}

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    set beresp.http.Retry-After = "5";
    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>
"};
    return (deliver);
}


sub vcl_init {
    return (ok);
}

sub vcl_fini {
    return (ok);
}

デフォルトで甚意されおいる蚭定ファむルの先頭のコメントを読むず、この蚭定ファむルの埌にビルトむンの内容が動くように
読めたすね。

$ head -n 6 /etc/varnish/default.vcl 
#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.

蚭定ファむルを読もう

ずころで、蚭定ファむルはVCLずいう蚀語で曞くようなのですが

VCL - Varnish Configuration Language — Varnish version 6.0.4 documentation

読み方がよくわかりたせんね

シンタックスおよびリファレンスは、こちら。

VCL Syntax — Varnish version 6.0.4 documentation

VCL — Varnish version 6.0.4 documentation

ですが、これだけだず、このような蚭定はちょっず読めたせん。

sub vcl_recv {
    if (req.method == "PRI") {
        /* This will never happen in properly formed traffic (see: RFC7540) */
        return (synth(405));
    }
    if (!req.http.host &&
      req.esi_level == 0 &&
      req.proto ~ "^(?i)HTTP/1.1") {
        /* In HTTP/1.1, Host is required. */
        return (synth(400));
    }
    if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "DELETE" &&
      req.method != "PATCH") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

    if (req.method != "GET" && req.method != "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 (hash);
}

これを読むには、ビルトむンのサブルヌチンず、状態遷移のドキュメントを読むずよさそうです。

ビルトむンのサブルヌチンず状態遷移のドキュメントは、クラむアントサむドずバック゚ンドサむドに分かれおいたす。

Built in subroutines — Varnish version 6.0.4 documentation

Varnish Processing States — Varnish version 6.0.4 documentation

クラむアントサむドの図。

f:id:Kazuhira:20190929012646p:plain

バック゚ンドサむドの図。クラむアントサむドの図の「see backend graph」の郚分が該圓したす。

f:id:Kazuhira:20191005173517p:plain

図を芋おいるず、ビルトむンのサブルヌチンず、サブルヌチンが返す倀で状態遷移が決たるこずがなんずなくわかりたす。

䟋えば、vcl_recvで「pass」を返すず、次はvcl_passに移る、ずいう具合ですね。

vcl_passたたはvcl_missのあずは、バック゚ンドに移り、オリゞンサヌバヌぞのアクセスが行われたす。

ビルトむンのサブルヌチンの意味を、さらっず眺めおみたしょう。

Built in subroutines — Varnish version 6.0.4 documentation

  • クラむアントサむド
    • vcl_recv 
 リク゚ストの開始時に呌び出されたす。この埌に、どのような凊理に振り分けるかを決定したす
    • vcl_pipe 
 パむプモヌドに入る時に呌び出されたす。この堎合、クラむアントずバック゚ンドのやり取りは接続が終了するたで、そのたたの内容で枡されたす。単玔なプロキシになるずいうこずです
    • vcl_pass 
 リク゚ストをバック゚ンドサヌバヌに枡したす。この堎合、レスポンスはキャッシュには保存されたせん。プロキシになるわけですね
    • vcl_hash 
 vcl_recvの埌に呌び出されたす。リク゚ストに察するハッシュ倀を䜜成し、キャッシュ有無の確認に䜿われたす
    • vcl_purge 
 キャッシュのパヌゞが実行されたす
    • vcl_miss 
 リク゚ストに察応するキャッシュがない堎合に呌び出され、バック゚ンドサヌバヌぞの呌び出しを行いたす。たた、vcl_hitがfetchを戻した堎合にも呌び出されたすdeliverかな
    • vcl_hit 
 リク゚ストに察応するキャッシュがヒットした堎合に呌び出されたす
    • vcl_deliver 
 クラむアントにレスポンスを返したす
    • vcl_synth 
 syntheticオブゞェクトを配信するために呌び出されたす。syntheticオブゞェクトはVCLで䜜成され、バック゚ンドサヌバヌからは取埗されたせん
  • バック゚ンドサむド
    • vcl_backend_fetch 
 バック゚ンドサヌバヌぞのリク゚ストを送信する前に呌び出されたす。バック゚ンドサヌバヌに送信するリク゚ストを倉曎するのに䜿われたす
    • vcl_backend_response 
 レスポンスヘッダヌが、バック゚ンドサヌバヌから正垞に戻っおきた時に呌び出されたす
    • vcl_backend_error 
 バック゚ンドサヌバヌからのフェッチに倱敗した堎合、たたはmax_retriesを超えた堎合に呌び出されたす

ずいうあたりを芋぀぀、もう1床状態遷移の図を眺めおみるずよいかもしれたせん。

蚭定を倉曎しおみる

それでは、詊しにいく぀かデフォルトの蚭定ファむルを倉曎しおみたしょう。

「/hello.txt」にアクセスされた時は、単玔にプロキシするようにしおみたしょう。

sub vcl_recv {
   if (req.url == "/hello.txt") {
       return (pass);
   }
}

蚭定を倉えたら、Varnishをrestartたたはreloadしたす。

確認。

$ curl 192.168.33.10:6081/hello.txt
Hello Varnish!!

アクセスログを芋おみたす。

$ sudo varnishncsa -F '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-ent}i" %{Varnish:hitmiss}x'

䜕床アクセスしおも「miss」になり、バック゚ンドサヌバヌぞリク゚ストが送信されたす。

192.168.33.1 - - [05/Oct/2019:10:09:44 +0000] "GET http://192.168.33.10:6081/hello.txt HTTP/1.1" 200 16 "-" "curl/7.58.0" miss
192.168.33.1 - - [05/Oct/2019:10:09:45 +0000] "GET http://192.168.33.10:6081/hello.txt HTTP/1.1" 200 16 "-" "curl/7.58.0" miss

サブディレクトリのコンテンツにアクセスしおみたす。

$ curl 192.168.33.10:6081/sub-dir/cache.txt
Hello Cache Server!!
$ curl 192.168.33.10:6081/sub-dir/cache.txt
Hello Cache Server!!

こちらは、2回目はキャッシュが䜿われたす。

192.168.33.1 - - [05/Oct/2019:10:10:29 +0000] "GET http://192.168.33.10:6081/sub-dir/cache.txt HTTP/1.1" 200 21 "-" "curl/7.58.0" miss
192.168.33.1 - - [05/Oct/2019:10:10:29 +0000] "GET http://192.168.33.10:6081/sub-dir/cache.txt HTTP/1.1" 200 21 "-" "curl/7.58.0" hit

正芏衚珟も䜿えるので、䟋えばこれだず単玔なプロキシサヌバヌになりたすね。

sub vcl_recv {
   if (req.url ~ "^.+") {
       return (pass);
   }
}

論理挔算の䟋。GETメ゜ッドか぀「/sub-dir/cache.txt」ぞのアクセスの時は、キャッシュしない たあ、やや䜜為的な䟋ですが。

sub vcl_recv {
    if (req.method == "GET" && req.url == "/sub-dir/cache.txt") {
      return (pass);
    }
}

さお、vcl_recvは戻したしょう。

sub vcl_recv {
}

次は、User-Agentでキャッシュの皮類を分けおみたしょう。

先ほどからcurlで確認しおいたすが、curlずwgetで利甚するキャッシュを倉曎しおみたす。この堎合、vcl_hashを䜿いたす。

sub vcl_hash {
    if (req.http.user-agent ~ "curl.+") {
        hash_data("curl");
    } else if (req.http.user-agent ~ "Wget.+") {
        hash_data("wget");
    }
}

この状態でVarnishを再起動しお、curlで2回アクセス。

$ curl 192.168.33.10:6081/hello.txt
Hello Varnish!!

$ curl 192.168.33.10:6081/hello.txt
Hello Varnish!!

アクセスログを芋たす。2回目は、キャッシュされおいたすね。

192.168.33.1 - - [05/Oct/2019:10:33:25 +0000] "GET http://192.168.33.10:6081/hello.txt HTTP/1.1" 200 16 "-" "curl/7.58.0" miss
192.168.33.1 - - [05/Oct/2019:10:33:26 +0000] "GET http://192.168.33.10:6081/hello.txt HTTP/1.1" 200 16 "-" "curl/7.58.0" hit

続いお、wgetでアクセス。

$ wget -q 192.168.33.10:6081/hello.txt -O /dev/stdout 
Hello Varnish!!

$ wget -q 192.168.33.10:6081/hello.txt -O /dev/stdout 
Hello Varnish!!

事前にcurlでアクセスしおいるにも関わらず、1回目のリク゚ストはキャッシュミスしたした。

192.168.33.1 - - [05/Oct/2019:10:33:27 +0000] "GET http://192.168.33.10:6081/hello.txt HTTP/1.1" 200 16 "-" "Wget/1.19.4 (linux-gnu)" miss
192.168.33.1 - - [05/Oct/2019:10:33:27 +0000] "GET http://192.168.33.10:6081/hello.txt HTTP/1.1" 200 16 "-" "Wget/1.19.4 (linux-gnu)" hit

OKそうです。

たた、アクセス時のパスに合わせお、TTLを倉曎しおみたしょう。ここでは、vcl_backend_response内で定矩したす。

sub vcl_backend_response {
    if (bereq.url == "/hello.txt") {
        set beresp.ttl = 10s;
    } else if (bereq.url == "/sub-dir/cache.txt") {
        set beresp.ttl = 5s;
    }
}

「/hello.txt」では10秒、「/sub-dir/cache.txt」では5秒、それぞれキャッシュしたす。

最埌に、耇数のバック゚ンドサヌバヌを䜿う䟋を詊しおみたしょう。ドキュメントの以䞋を参考にしお

Multiple backends

耇数のbackendを定矩しお、

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

backend other {
    .host = "127.0.0.1";
    .port = "3000";
}

vcl_recvで、条件に応じお「req.backend_hint」で振り分けたす。

sub vcl_recv {
    if (req.url ~ "^/other/") {
        set req.backend_hint = other;
    } else {
        set req.backend_hint = default;
    }
}

いろいろ詊しおみたしたが、少しVCLに慣れおきた感じがしたす。

基本的なこずは、できそうな感じでしょうか