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)で終わるようにしましょう。

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


 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください