11月 242021
 

前の記事で紹介したパラレルESI(pesi)など、様々なVMODがあるのですが、残念なことにパッケージで提供されているVMODはほとんどありません。

自分がメンテしているカスタムでカウンタを作るもの(xcounter)とかは、勉強がてらGithub actionsでdebを作るようにしてみたのですが、なかなかめんどくさいのが実情でこれは提供したがらないよなぁと思ってました。

なんなら公式のvarnish-modulesも公式ではパッケージ提供されていません。

では、VMODを使いたい場合はどうしてたのかというと、自分でパッケージを作るか.so配布するかとか、利用者・作者共ににも辛い状態だったわけです。

誰かが作るんじゃないかなと思ってたんですが、だれも作らなかったのと急に思い立ったのでえいやでdebとかrpmが作れるツールを作りました。

https://github.com/xcir/vmod-packager

使いかたは簡単で、docker, curl, jqが使える環境で

xcir@build01:~/git/vmod-packager$ cd src
xcir@build01:~/git/vmod-packager/src$ git clone https://github.com/varnish/varnish-modules.git
xcir@build01:~/git/vmod-packager/src$ cd ..
xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -e 0.19 varnish-modules
...
##################################################
        docker image: vmod-packager/focal:7.0.0-1
                Dist: focal
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: varnish-modules
        VMOD Version: 140.0.19
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ ls pkgs/debs/varnish-modules/
varnish-modules_140.0.19~focal-1_amd64.build      varnish-modules_140.0.19~focal-1_amd64.changes  varnish-modules-dbgsym_140.0.19~focal-1_amd64.ddeb
varnish-modules_140.0.19~focal-1_amd64.buildinfo  varnish-modules_140.0.19~focal-1_amd64.deb

こんな感じでパッケージを作れます。

もちろんrpmも

xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -d centos8 -e 0.19 varnish-modules
...
##################################################
        docker image: vmod-packager/centos8:7.0.0-1
                Dist: centos8
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: varnish-modules
        VMOD Version: 140.0.19
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ ls pkgs/rpms/varnish-modules/
varnish-modules-140.0.19-1.el8.src.rpm  varnish-modules-140.0.19-1.el8.x86_64.rpm

作れます。

もちろん、このように簡単にビルドできるのは

  • ./autogen.sh(もしくは./bootstrap) + ./configure + makeで作れるもの
  • 依存パッケージがないもの

上記を満たす必要がありますが、追加パッケージが必要だったり、ちょっと特殊なビルドが必要なものでも
カスタムスクリプトでパッケージにすることが可能です。

実際に先の記事で紹介したlibvdp-pesiをパッケージにしてみましょう。

xcir@build01:~/git/vmod-packager$ cd src/
xcir@build01:~/git/vmod-packager/src$ git clone https://gitlab.com/uplex/varnish/libvdp-pesi.git

xcir@build01:~/git/vmod-packager/src$ cd libvdp-pesi/
xcir@build01:~/git/vmod-packager/src/libvdp-pesi$ git checkout -b 7.0 remotes/origin/7.0
xcir@build01:~/git/vmod-packager/src$ cat libvdp-pesi_init.sh 
#!/bin/sh

cp -rp ${VMP_ROOT_DIR}/src/m4 ${VMP_WORK_DIR}/src/m4

xcir@build01:~/git/vmod-packager/src$ cat libvdp-pesi_config.sh 
#!/bin/sh

./autogen.sh
./configure VARNISHSRC=/tmp/varnish/src

xcir@build01:~/git/vmod-packager$ ./vmod-packager.sh -f  src/libvdp-pesi
...

##################################################
        docker image: vmod-packager/focal:7.0.0-1
                Dist: focal
     Varnish Version: 7.0.0
         Varnish VRT: 140
           VMOD name: libvdp-pesi
        VMOD Version: 140.0.1
   Enable fixed mode
              Status: SUCCESS

xcir@build01:~/git/vmod-packager$ dpkg --info pkgs/debs/libvdp-pesi/libvdp-pesi_140.0.1~focal-1_amd64.deb 
 new Debian package, version 2.0.
 size 56760 bytes: control archive=816 bytes.
     309 bytes,    11 lines      control              
     711 bytes,    10 lines      md5sums              
 Package: libvdp-pesi
 Version: 140.0.1~focal-1
 Architecture: amd64
 Maintainer: libvdp-pesi <example@localhost>
 Installed-Size: 141
 Depends: libc6 (>= 2.14), varnish (= 7.0.0)
 Replaces: libvdp-pesi (<< 140)
 Section: web
 Priority: optional
 Homepage: https://github.com/xcir/
 Description: packed by vmod-packager

ビルド時に事前準備が必要な場合は[VMODの名前]_init.shにその内容を書きます。
今回はm4をコピーしていますが、ここでビルドに必要なパッケージをインストールすることも可能です。

この例では使っていませんが、dependsに追加が必要な場合は[VMODの名前]_env.shという別のファイルに入れる形になります。

また、libvdp-pesiはconfigureでvarnishのソースパスを指定する必要があるので [VMODの名前]_config.shで記述できるようにしています。

ちなみに-fオプションというのを使っていますが、これはlibvdp-pesiがVarnishのバージョンが厳密に一致する必要があるためでバージョンを固定するオプションになります。

こんな感じで割と複雑な手順が必要なVMODでも簡単にパッケージ化が可能です。

とはいえ、細かい指定はまぁいろいろできるんですが、自分は今のところsrc以下に使ってるvmodをcloneした後に

for i in `find src/  -mindepth 1 -maxdepth 1 -type d` ; do echo $i;cd $i ;git pull; cd ..;cd .. ; done

./vmod-packager.sh -v 7.0.1 -t -e `date +%Y%m%d` `find src/  -mindepth 1 -maxdepth 1 -type d`

find pkgs/debs/ -type f -name *.deb|grep `date +%Y%m%d`| xargs -i cp -p {} [コピー先]

みたいな感じで、更新した後に一気にバージョンを日付でビルドしてあとはよしなにするといった感じです。

何はともあれ、使いかたはREADMEを見てほしいです。
今のところよく使われてそうなものはビルドを試しており、大体いけるんじゃないかなと考えています。
が、もしビルド出来ないVMODや気になる点があったら指摘してもらえると嬉しかったりします。

なお、このツールでパッケージにしたものは外部に配布することを想定していません。

これはcopyrightとかdescriptionとかまでカスタマイズできるするのが面倒だったというのがあるので、そのうちなんとかするかもしれないです。
が、現時点では自身の環境にインストールするパッケージを作るのに使うぐらいが良いでしょう。


9月 252021
 

VarnishのEnterprise版で利用できるパラレルESIがcommunity版でも使えるものをUPLEXが公開してくれました。

ヘビーにESIを利用する人はそこまでいない気もするのですが、これがまたなかなか便利なので紹介します。

ビルド方法

このvmodはVarnishの内部関数に深く依存しているため、ビルド時にはVarnishのコードが必要でビルドに癖があります。

また、動作時にはVarnishとビルドにつかったVarnishのコミットハッシュ値が一致している必要があります。

なお以下の環境で確認しています

OSUbuntu 20.04 LTS
Varnish7.0.0 (454733b82a3279a1603516b4f0a07f8bad4bcd55)
vmod_pesimaster (52cd44e3d8944825d1d84a01c70002a509048c74)

pesiは最新のコードを使えばよいかなと思いますが、もしビルドができない場合はもしブランチで7.0があればそちらを(現時点ではないですが)、もしくはコミットログを眺めて適度に巻き戻るとよいでしょう。

とりあえずVarnishとpesiのコードをダウンロードします。

ubuntu@proxy:~/tmp$ ll
total 16
drwxrwxr-x  4 ubuntu ubuntu 4096 Sep 25 21:38 ./
drwxr-xr-x 12 ubuntu ubuntu 4096 Sep 25 21:37 ../
drwxrwxr-x  3 ubuntu ubuntu 4096 Sep 20 23:33 libvdp-pesi-master/
drwxrwxr-x 15 ubuntu ubuntu 4096 Sep 24 02:18 varnish-cache-master/

一旦こんな感じのパスで説明します。

事前準備

まずはVarnishをautogen.sh / configure / makeしておきます。

ubuntu@proxy:~/tmp$ cd varnish-cache-master/
ubuntu@proxy:~/tmp/varnish-cache-master$ ./autogen.sh
ubuntu@proxy:~/tmp/varnish-cache-master$ ./configure
ubuntu@proxy:~/tmp/varnish-cache-master$ make

なお、make installは不要です。

次にpesiの直下にvarnishのソースツリーにあるm4フォルダをコピーします。

ubuntu@proxy:~/tmp$ cd libvdp-pesi-master/
ubuntu@proxy:~/tmp/libvdp-pesi-master$ cp -rp ../varnish-cache-master/m4 ./

configure.acにあるVarnishのバージョン指定をtrunkからインストールしているVarnishのバージョンに変更します。(今回は7.0.0)

ubuntu@proxy:~/tmp/libvdp-pesi-master$ diff configure.ac ../org/configure.ac
56c56
< VARNISH_PREREQ([7.0.0])
---
> VARNISH_PREREQ([trunk])

次にsrc/Makefile.amに1行追加します。(こちらは今PR投げてるのでもしかしたら直るかもしれないです)

ubuntu@proxy:~/tmp/libvdp-pesi-master$ diff src/Makefile.am ../org/src/Makefile.am
72,73d71
< vmod_pesi_debug.lo: vcc_pesi_debug_if.h
<

とりあえずここまででビルド準備ができました。

ビルド/インストール

ここで注意がいるのはVarnishのソースの場所をフルパスで指定することです。

ubuntu@proxy:~/tmp/libvdp-pesi-master$ ./autogen.sh
ubuntu@proxy:~/tmp/libvdp-pesi-master$ ./configure VARNISHSRC=/home/ubuntu/tmp/varnish-cache-master

ubuntu@proxy:~/tmp/libvdp-pesi-master$ make
ubuntu@proxy:~/tmp/libvdp-pesi-master$ make check
ubuntu@proxy:~/tmp/libvdp-pesi-master$ sudo make install

これでインストール完了しました。

使ってみる

使いかたは簡単で

import pesi;

sub vcl_backend_response {
  set beresp.do_esi=true;
}

sub vcl_deliver{
  pesi.activate();
}

これだけです。(細かい使いかたは README に)

とりあえずこの状態で通常のESIとパラレルESIを比較してみましょう。

$ cat esi1.html
<html>
<body>
        <esi:include src="./time.php?t=1&s=1"/>
        <esi:include src="./time.php?t=2&s=2"/>
        <esi:include src="./time.php?t=3&s=3"/>

</body>
</html>


$ cat time.php
<?php
$s=(int)$_GET['s'];
if($s >10)$s=10;
if($s==0) $s=1;
echo $_GET['t'];
echo "&nbsp;";
echo "start:".date(DateTime::ISO8601);
echo "&nbsp;";
sleep($s);
echo "end:".date(DateTime::ISO8601);
echo "<br>\r\n";

コードはこんな感じで1,2,3秒スリープするPHPをincludeします。

通常のESI

シーケンシャルに処理しているため1+2+3で6秒かかっています。

パラレルESI

パラレルで処理しているため、ベースページ(esi1.html)が読み込まれた後に子ESIのfetchが一気に走っています。

ちなみにESIをネストしても結果は期待通りです。

ESIネスト

<html>
<body>
        <esi:include src="./time.php?t=C%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D1%26s%3D1%22%2F%3EC%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D1%26s%3D1%22%2F%3E&s=1"/>
        <esi:include src="./time.php?t=C%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D2%26s%3D2%22%2F%3EC%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D2%26s%3D2%22%2F%3E&s=2"/>
        <esi:include src="./time.php?t=C%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D3%26s%3D3%22%2F%3EC%3Cesi%3Ainclude+src%3D%22.%2Ftime.php%3Ft%3D3%26s%3D3%22%2F%3E&s=3"/>
</body>
</html>

urlエンコードしてる部分はこんな感じで。

C<esi:include src="./time.php?t=1&s=1"/>C<esi:include src="./time.php?t=1&s=1"/>
C<esi:include src="./time.php?t=2&s=2"/>C<esi:include src="./time.php?t=2&s=2"/>
C<esi:include src="./time.php?t=3&s=3"/>C<esi:include src="./time.php?t=3&s=3"/>

要は子ESIでさらに2つincludeしています。イメージは以下の通りです。

+ sleep=1
|+ sleep=1
|+ sleep=1
+ sleep=2
|+ sleep=2
|+ sleep=2
+ sleep=3
|+ sleep=3
|+ sleep=3

通常ESI

パラレルESI

子ESIにおいてもパラレルで動いていることがわかります。

使うときの注意点

きわめて魅力的なパラレルESIですが、注意点があります。

ESIをネストしていて、パラレルESIを有効にした場合は子要素もパラレルESIを有効(pesi.activate)する必要があります。

パラレルESIを有効にしたリクエストにおいて子要素で通常のESIを行うとpanicを起こしてVarnishがrestartします。

ここら辺のことはREADMEに書いてますのでしっかり読んでおいてしっかり検証したほうがいいでしょう。

あ、あとVarnish自体のアップデート記事はそろっと書きます(本書いててサボってたけどもう書き終わってしまったので)


4月 292021
 

キャッシュなどのクソ分厚い本は書いたのですが、流石にあれだけ分厚いと読んでくれる人も少なそうな気がするので、キャッシュを使う上で知っておきたい基本的なポイントを解説したいと思います。
目的としてはとりあえずこの辺押さえておけば、MDNのキャッシュCache-Controlなどの記事が読みやすくなるんじゃないかなというのと(記事読み終わったらMDN読んでみてほしい)、興味を持ってもらえれば本を手に取ってもらえるかなぁといった感じです(割と本から内容持ってきてる)。

なお、キャッシュといっても様々なものがありますが、この記事ではHTTPにおけるキャッシュをキャッシュとしています。また、同様にProxyはReverseProxyのことを指します。

そもそもキャッシュとは何をどうしようとしているのか

そもそも一口にキャッシュといっても文脈によって異なります。
一般的にわかりやすいところだとブラウザCDNだと思いますが、この両者はキャッシュは行いますが特性はまったく違います

インターネット上の記事、本、雑誌どれでもそうなのですが、その記事が指しているのはブラウザなのか、CDNなのか、はたまた両者なのかを理解したうえで読まないと混乱しますし、キャッシュってクソ難しいよねと感じてしまいます。

ひとつずつ整理していきましょう。

キャッシュの性質と置き場所

キャッシュの性質

キャッシュはいくつかの軸で考える必要があり、その一つに再利用を誰がするのかという性質があります。

  • 特定のクライアントのみが参照可能なキャッシュ(private cache)
  • 複数のクライアントから参照可能なキャッシュ(shared cache)

この2つの性質はきちんと区別して考える必要があり、ごっちゃにすると事故の元となります。
(ちなみにcache-controlのpublic/privateとは全く別物です。それとごっちゃにしないようにしましょう)

例えばAPIのレスポンスをキャッシュするとします。
都道府県のリストを返すようなAPIであればsharedとして問題ないでしょう。
ところが、ログイン後のユーザの情報が含まれるものは複数のクライアントで共有してはいけないのでprivateです。

sharedのものをprivateとして扱うのは範囲が狭まるわけですから問題ないのですが、逆を行うと事故るのが目に見えるでしょう。
多くのキャッシュ事故はここを意図せず・適切なケアをせずに跨いでしまって起きています。

また、private/sharedですが、厳密にこの2種に収束するかというとそうではありません。

若干応用的な考え方になるので簡単にですが、例えばユーザの住んでる地域(都道府県)という属性によってAPIの結果が変わる場合について考えてみましょう。
これは複数のクライアント向けではあるものの、すべてのクライアントが対象ではないということが言えます。
特定の属性(追加のキー)の集合体(クラスタ)向けのキャッシュでprivateとsharedの中間といえるでしょう。
これはキャッシュキーがとかそういう話にはなり、キャッシュを高度に使うための話になるのですが、これについてはCDNとの付き合い方を読んでみてください。
あくまで、ここではprivate/sharedで分かれて、場合によってはその垣根を意図して跨ぐことで高度に使うことができるといったことを知っておけばよいでしょう。

キャッシュの置き場所

先ほどブラウザとCDNを例にあげましたが、Webサイトを見る上でキャッシュはどこにあるのだろうかというと、ローカル・ゲートウェイ・経路上の3か所にあります。

キャッシュの置き場所(ローカル[ブラウザ]・ゲートウェイ[Proxy]・経路上[CDN])

ゲートウェイと経路上というのはあまりキャッシュでは聞きなれないとは思います。

ゲートウェイキャッシュはオリジンとインターネットをつなぐ部分においてあるProxyで行うキャッシュで、
経路上のキャッシュはインターネットの経路上にあるCDNのエッジサーバーで行うキャッシュです。

なお、これらの区分け(ローカル・ゲートウェイ・経路上)はあまり一般的ではなく、定義でも揺れてる感があるんで整理したものです。(参照

また、これらのキャッシュはすべてのサイトで使われているわけではありません。
CDNを未導入のサイトもありますし、ブラウザキャッシュをうまく使えていないサイトも多数あります。
とりあえず、3か所あるということは知っておきましょう。

ローカルキャッシュ(ブラウザキャッシュ)

ローカルキャッシュはいわゆるブラウザキャッシュで非常にわかりやすいものです。

そのクライアントのローカルにあるので、当然他のクライアントから参照することができません。
そのため特定のクライアント向けのprivate cacheと複数のクライアント向けのshared cacheのどちらも格納することが可能です。
複数のクライアント向けだからといって必ず他クライアントに共有しなくてはいけないというものではなく、あくまで性質であるということを覚えておきましょう。

ゲートウェイキャッシュ(Proxy)・経路上のキャッシュ(Proxy/CDN)

ProxyとCDNですが、どちらも複数のクライアントからリクエストを受けます。

そのため、格納できるのはshared cacheのみです。

Proxyもインターネットの経路上にあるわけなので経路上のキャッシュではと感じた方は鋭いです。
そのとおりでProxyも経路上のキャッシュではあるのですが、CDNと一緒にするには性質が異なるため分けています。
ここでは詳しく触れませんが、いったんはProxyとCDNの違いはいくつかあり、両者を併用することでメリットもあるのですが、いったんはsharedを格納する場所と覚えておきましょう。

まとめるとこんな感じです

種類クライアントとの対応格納できる場所
private1:1ローカル
shared1:nローカル・経路上・ゲートウェイ

ローカル・経路上のキャッシュを併用しよう

キャッシュは再利用されるほどいいものです。

サイトの規模にもよるのですが、ローカルと経路上のキャッシュはそれぞれ性質が異なるため、ブラウザキャッシュだけ適切に設定しておけば経路上では不要というわけではありません。


ローカルキャッシュはキャッシュを持つクライアント自身がサイトを再訪する場合は有効ですが、キャッシュを持っていない新規クライアントには無効です。

経路上のキャッシュは新規クライアントに対してもキャッシュを返すことができるため、例えばサイトへの流入が突然増えるといった事態でも対処がしやすいです。

そのためコンテンツ次第ではありますが、ブラウザキャッシュのように特定のクライアントでしか使えないprivate cacheにするよりも、 効率を考えてローカル・経路上のどちらでもキャッシュができ、多数のクライアントで共有できるshared cacheに積極的にすべきでしょう。

ここまでのまとめ

  • 一口にキャッシュといってもその性質でprivateとsharedの2種類がある
  • キャッシュの格納する場所としてローカルと経路上(+ゲートウェイ)がある
    • ローカルにはprivateとsharedが格納できる
    • 経路上にはsharedが格納できる

とりあえずこの辺りを抑えたうえでキャッシュの各記事を読んでみると、CDN(経路上)の話をしているのか、ブラウザ(ローカル)の話をしているのかとわかりやすくなると思います。

Cache-Controlとはなにを行うものか

そろっと新しいRFCが出そうですが、現状のキャッシュの仕様はRFC7234で、そのセクション2において

Although caching is an entirely OPTIONAL feature of HTTP,
it can be assumed that reusing a cached response is desirable 
and that such reuse is the default behavior when no requirement
or local configuration prevents it.

キャッシュはHTTPのオプション機能ではあるが、キャッシュの再利用は望ましいものであり、それを防止するような要件・設定がない場合は「デフォルトの動作」である。

RFC7234#2

と触れられています。

まず最初に知っておきたいのは条件さえそろえばCache-Controlを指定しなくてもキャッシュされるということです。
条件は様々なものがありますが、重要な条件にTTLが計算できるということがあります。
TTLはCache-Controlのmax-ageで指定するものでは?と思われるかもしれませんが、ヒューリスティックなTTL計算(RFC7234#4.2.2)というのがあります。

Last-Modifiedというコンテンツの最終更新日と現在時刻を表すDateヘッダがありますが、この2つのヘッダを使うことで、ヒューリスティックなTTLの計算を行います。

すごい乱暴に言えば最終更新日から1日経過してるんでまぁ2時間ぐらい(10%程度)キャッシュしてもいいでしょといった感じです。

ヒューリスティックなTTL = (Date – Last-Modified) / 10

つまり、キャッシュされたくないコンテンツはCache-Controlを正しく設定する必要がありますし、キャッシュをするにしても意図した動作を行うように正しく設定する必要があります。

キャッシュを格納する、そして使う

ローカル・経路上のどちらの場合でもキャッシュというものは格納する使うという2つのステージがあります。

例えばTTLの範囲内かといった計算は利用するタイミングで行うわけですから、当たり前といえば当たり前なのですが、案外この辺りの意識が抜けているとことが多いです。

Cache-Controlには様々なディレクティブがありますが、それがどのステージに効くのかというのを考えるとわかりやすいことがあります。

勘違いされやすさで有名なno-cacheとno-storeですが、no-cacheはキャッシュを利用するときのディレクティブでno-storeはキャッシュを格納するときのディレクティブとなります。

no-cacheは利用するときのディレクティブなので、キャッシュは格納されます。で、利用時に再検証を行い最新であることを確認して利用します。

no-storeは格納するときのディレクティブなので、そもそもキャッシュに格納させないわけです。

レスポンスだけではないCache-Control

これはexample.netに対してF5でリロードをしたときのリクエストおよびレスポンスヘッダとなります。

レスポンスヘッダにCache-Control: max-age=604800とある以外にリクエストヘッダにCache-Control: max-age=0とあることに気づきます。(ちなみにctrl+F5だとリクエスト側がno-cacheに変わります)

リクエストを行う際にもCache-Controlがつきますがこれは何を意味しているのでしょうか?

レスポンスヘッダのmax-ageはそのコンテンツのTTLを指定しますが、リクエストヘッダは指定したTTL以上のコンテンツを受け入れる気がない、つまりmax-age=0なので0秒経過したコンテンツは欲しくないので新しいのくださいってことですね。

これだとちょっとわかりづらいと思うので雑に言うとキャッシュに格納されているレスポンス由来のmax-ageをリクエスト由来のmax-ageに一時的に書き換えて評価するといった感じでしょう。

さて、これを踏まえて先ほどのno-cache/no-storeをもう一度考えてみましょう。

no-storeは格納するときの指定なわけですから、リクエスト中に含まれていた場合はキャッシュに格納しないでねといった動きになります。
あまりレスポンスの時の動きと変わらないような気がしますが、既にキャッシュがある状態を考えてみましょう。
no-storeは利用時には評価されないため、キャッシュがレスポンスされます。

とはいえ、CDNやProxyの経路上のキャッシュに対してF5でリロードしてもオリジンに毎回検証してくれるわけではありません。
当然といえば当然なのですが、律儀に全部通していたらあまりCDNの意味がありません。

じゃあどこに効くのかというとブラウザなどのローカルキャッシュです。

ローカルキャッシュ自体をサーバーと考えてそこに対してCache-Controlを通じて問い合わせを行っていると考えるとしっくりくるかもしれません。

なのでxhrでの問い合わせの時にキャッシュされるとか困ってる場合はこの辺りを思い出してみるとよいかと思います。

Cache-Controlの解釈は実装によって異なる

リクエスト・レスポンスに含まれるCache-Controlはあくまで指定でしかなく、その解釈は受け取った側に委ねられています。

例えばno-storeを指定したとしても本当にキャッシュを格納しないのかというと受け取った実装依存となるわけなので、CDNを使う場合はこの辺りもきちんと調べておくとよいでしょう。

ちなみに今まで見た中で一番思い切りがいいなとおもったのはステータス200ならCache-Control一切見ないでX時間キャッシュというものです。こういうのもあります

TTLは経路上とローカルで共通

たとえばsharedキャッシュでmax-age=3600と指定した場合、経路上・ローカルで1時間キャッシュできます。
この1時間はそれぞれではなく合算となります。

たとえば経路上で30分キャッシュ(Age: 1800)したものはローカルでは30分しかキャッシュできません。

案外この辺りを失念しやすいのか、サイトを眺めているとAgeヘッダ > max-ageとなっていて毎回検証リクエストが飛んでるといったことがあります。

先ほどローカル・経路上のキャッシュを併用しと触れましたが、併用すれば当然TTLの割り振りも考える必要があります。

ここまでのまとめ

  • キャッシュは条件を満たしていればCache-Controlを指定しなくてもされる
  • Cache-Controlの解釈は実装によって異なる可能性があるのでCDNを使う場合はきちんと調べよう
  • キャッシュは格納と利用の2ステージがある

最後に

この辺りの話は重要なのですが、案外触れている記事がない(もしくは知っているものとして書かれている)ので、
押さえておくと、様々な記事についても読みやすくなると思います。

また、この辺りの話も含めてめっちゃ詳しく書いてますので、よかったら読んでもらえるとうれしいです。


 Posted by at 9:32 PM
3月 032021
 

twitterでなんどもつぶやいてるので多分知られているとは思うんですが、Web配信の技術という本を書きました。
せっかくなんで、なんでまたこんな本を書いたのかとかどういう流れだったのかみたいなのを簡単に書いてみようかなと

 そもそもどういう本なのか

非常にタイトルを決めるのが難しい本でした。
サブタイトルに「HTTPキャッシュ・リバースプロキシ・CDNを活用する」とあるようにいわゆるHTTPキャッシュの本なわけですが、コンテンツ配信の技術といえばCDNの印象が強く出ますし(本書はCDNの使いかたというわけではないです)、Web配信といえば動画ストリーム配信(VTuberの配信とか)を思い浮かべる人も多いと思います。
今考えればWebコンテンツ配信の技術とすればよかったかもと思いつつ、今度は長くなりすぎるのでなかなか難しいです。

ということでHTTPキャッシュを使ってWebサイトを高速化したり安定化させようといった趣旨の本です。
以前と比べるとキャッシュを使ってWebサイトの負荷対策を行うというのは広く広まってきているんじゃないかなーと思います。
これが日本で広まったのは日経電子版のリニューアルでの全面的なCDNを利用が大きく寄与していると考えています。
あくまで自分の体感ですが、日経電子版のリニューアル以降に雑誌でもCDNの特集が組まれることが増え、また書籍においてもCDNに触れるの増えた気がします。

ところが雑誌の特集は長くても数十ページ、書籍ではそもそもキャッシュの本は(少なくとも自分は)見たことなく、数~数十ページでCDNを使ってパフォーマンスを向上しましょうと触れられている程度です。
では、インターネットの記事で見られる事例はどうなのかというと、あくまでもキャッシュの知識があることを前提とした記事であって、なぜキャッシュが効果的なのか、キャッシュといってもどういう種類があるのかといった基本的な事から触れらている本や記事は自分はあまり見たことがありませんでした。

キャッシュというのは非常に便利なのですが、適切に扱わないと牙をむくものです。

このコロナ渦で急遽負荷対策を行うためにCDNを導入し、設定ミスで個人情報を漏洩といったニュースになったサイトもありましたが、1年に1度はこのようなキャッシュ関連の事故と思われるニュースを見かけます。
このようなニュースは、キャッシュに対して二の足を踏む人を増やす結果となると思っています。

キャッシュはたしかに難しいものではありますし、事故ると非常に怖いものですが、本来の難しさ以上に恐れられていると考えています。
そもそも、正しく理解して設定すればキャッシュは安全に利用することができるものです。
ところが、そのために必要なドキュメントがあるのかというと存在せず、キャッシュについての入門書が必要だと考えました。

だからこそ、キャッシュと関連技術だけで456ページという気が狂ったようなページ数の本が出来上がりました。
本を書く上で似たような本がないかもamazon.comも含めて調べたのですが、無かったので恐らく世界初に近いキャッシュの入門書ではないかと考えています。

あれ、タイトルは入門ではなく技術としているじゃないかと思った人もいると思いますが、個人的には「Web配信入門」のつもりでも書いています。
ではなぜ入門ではなく技術なのかというと表紙のデザイナーさんから「入門」というよりも、現場で活躍する実践的な知恵というニュアンスが強いので「技術」とかそういうフレーズのほうがマッチするのではという提案があったからだったりします。

もちろんどっちつかずにならないようには書いたつもりで、本の最初のほうに

本書は主に中小規模サイトを例に、配信に従事したことのないエンジニアでもわかるよう基礎から解説しています。もちろん、大規模サイトでもやるべきことの基本は変わりません。
配信を業務で行っているエンジニアにとっても基礎の再確認や見落としのチェックなどに役立つ内容になっています。

第一章より引用

と触れています。

配信に触れたことがないエンジニアにはなぜ必要なのかという基礎から、配信を既に意識しているエンジニアでも物足りなりなさを感じないように自分が今まで踏んできた様々な障害やミスも多数書きました。むしろ書いてる途中に踏んで追加したコラムもいくつかあります。

また本を書く時の工夫として、参照したRFCはなるだけ書くようにしました(これは他の本も追随してほしいなとか)
そもそもHTTPキャッシュの多くの誤解はRFCを読んでいないというところから来ていると考えており、実際にRFCの参照部分を示すことで突き合わせてみることができるのかなと考えています。

なので章を進めるごとに実践的な知識を得られるようになっています。
章立ては

  • 配信の基礎知識(2章)
  • Proxy/CDNでキャッシュを行うために必要なオリジン側の地ならしを行う(3章)
  • Proxy/CDNでのキャッシュの行い方(4~6章)
  • 実際に自前で配信環境をつくってみる(7章)

となっています。
とりあえず目次も読みながら以降を読んでほしいのですが

2章はネットワークの話をメインにここにキャッシュを置けたら効果あるよねだったり、キャッシュの種類の話をしています。
割と多くの人がWebサイトでキャッシュといえばCDNという印象があると思いますが、それよりも速いブラウザキャッシュもあるよね、とかどう使い分けるのかといった知識が得られると思います。

3章はかなり分厚い章です。ここではHTTPそのものの話やHTTPキャッシュの仕様を多く触れている章です。
RFCの参照も一番多い章で、この章までを読むことでブラウザキャッシュをうまく使うことができるはずです。
またちょっと面白いところで画像のフォーマットについても触れています。Web配信において重要な要素はいくつかありますが、その一つに配信コンテンツのサイズを小さくすることです。
ちらほらjs/cssなどのテキストコンテンツの圧縮漏れやサムネイルなのになぜか原寸大の画像をスタイルシートで小さくしてるだけだったりするサイトを見かけます。
恐らく配信にかかわったことがあるインフラエンジニアであれば首がもげるほど同意してもらえると思うのですが、CDNを使って配信する以前に対策ができることが多い(サムネ使って・・・とか)と思います。そのようなことを詰め込んだ章です。

4章はProxyやCDNでキャッシュを行い負荷対策をする際の注意事項を多数入れています。
3章とうって変わってこの章はRFCの参照が少ないです(これ以降の章もですが)要は実践的な内容といった感じです。
とはいっても4章は5章のより実践的な内容に移るまでの基礎的な内容とも言えます。特に冒頭に実際にキャッシュで助かったサイトの例を取り上げており、結構楽しいんじゃないかなと思います。

そして分厚い5章で、恐らくこの本で一番難しい章となっています。
恐らく実際に読んだ人だとこれはProxy向けの話が多いんじゃ?と思う人も多いとおもいますが、決してそうではありません。
例えば多段Proxyの話はCDNにおいても多段エッジの話とも通じてきます。特に細かい制御が可能なFastlyの場合はこの辺りを理解しておく必要があるでしょう。

そして6章はCDNの話です。本書を買った人は割とCDNの使いかたを目的としていると思います。
サンプルとしてFastlyを取り上げていますが、恐らくそのボリュームにはがっかりする人も多いと思います。
CDNの業者はたくさんありますし、共通する設定はあるものの多くの設定は各CDNで異なるため、正直なところそういう操作は各CDNのマニュアルを読んだほうが良いでしょう。(特定CDNに特化してがっつり記事書いても面白い気もしますがまぁ・・)
そもそもCDNを使うために必要な知識は5章までで完成できていると考えています。
では、この章で目的としているのはなにかというと、CDNで障害が起きたらどうやって切り分けるのかとか、プロダクトからなんかキャッシュ壊れてるとか言われたりなんか遅いけどと言われた時にどう考えるのかといったあまり見ない切り口書いています。

そして最後の7章は個人的に賛否両論がある章だと考えています(正直すごくどうするか悩んだ章です)
自作CDNと銘打っていることもあり、当初は中規模程度の配信環境について書こうかなと考えてはいたのですが、恐らく中規模・大規模トラフィックを扱う人はだいたいCDNを使うはずで、自前で用意するのは(失礼ながら)お金がないサイトのはずだからそういうサイトに向けて書きました。
純粋に中規模サイトに触れる場合は細かいチューニングや監視に触れる必要があり、その内容は何かしら特化した内容となるためニッチになるだろうと判断したというのもあります。
個人的には実を取り実際に手を出しやすいようになったとは考えていますが、これを自作CDNと呼ぶとまぁなんか言われそうだなとドキドキしています。

最後の付録はVarnishについて書いています。付録というには割とコアなところも触れてると思います。残念ながらVarnish本ではないのでかなり端折っていますが重要だったりハマりポイントは触れられたと思います。

さて、各章について説明したのですが、なんとなく途中の章から読んでもダメそうというのを感じ取ってもらえたかと思います。

例えばCDNを今すぐ使いたいという理由で6章だけを読んだとしても恐らく役に立たないでしょう。

この本の特徴としては知識を積み上げていっています。
そのためCDNを使うのであれば例えば3章のCache-Controlなどの知識が、5章の各イベントの知識など、それまでの章の知識があること前提に、CDNを使うのであれば追加でこの知識が必要であるといった感じで書いてます。
なので、もし買って最初から読んだ人はちょっと後ろから読んでみてもらえるとスカスカで内容が全く足りない頭が悪そうな本だと思えて面白いと思います。

あと、対象読者はエンジニアなのですが、キャッシュを行うというのはエンジニアだけで済む話ではないことも多々あります。

5章で動的コンテンツはよく見ると複数のコンテンツの集合体であり、キャッシュするには異なるライフサイクルを持つコンテンツを混ぜない(=分割する)といいったことに触れたのですがページを分割するには例えばデザインレベルで検討する必要があるかもしれません。
他にもCDNを使うとなればその導入メリットを説明する必要もあるかもしれません。ただ、もしこの本を読んでもらえればなぜ重要なのか、というのを制作に携わる人で共有できると考えています。ですので、エンジニアだけではなくWeb制作にかかわる全ての人に読んでほしいです。

幸いなことに本屋にも割と並んでるみたいですので、飛ばし読みにはなるのですがまず4章の冒頭をさらっと読んでみてこういう効果があるぞと理解した後に最初からさらっと読んでもらえれば立ち読みでもきっとキャッシュのニュアンスは伝わるかと思います。
もちろん買ってはほしいですが、まずキャッシュというものを頭の片隅に入れてもらえればと思うわけです。

 本を読まないにせよ知っておくと便利なキャッシュの知識

せっかくなので、あまり他では見かけないけど意識しておくとキャッシュの考え方が変わる知識を一つ紹介します。
no-cacheはその名前からハマる人が多く、キャッシュをしないという意味ではないというのがだいぶ浸透してきたのかなと思います。

正確にはキャッシュを使うには検証が必要というディレクティブです。

よくセットで説明されるno-storeはキャッシュを格納してはいけないというディレクティブです。

このno-cacheのキャッシュを使うには検証が必要というのとno-storeのキャッシュを格納してはいけないというニュアンスの違いはどこからくるのでしょうか?

これは両者が評価するステージが異なるところから来ています。
そもそもキャッシュというのは「格納する」というのと「利用する」というのは異なるステージで行われます。

no-storeは「格納する」際に格納させないという指定であり、no-cacheは「利用する」際にオリジンに検証を行い最新であれば利用するという指定です。

「利用する」というステージはあまり普段意識されることはないのですが、TTLを評価するタイミングは当然「利用する」時に行うものなので、あーなるほどと納得いく人も多いと思います。

さて、ここで面白いのがno-cacheやno-storeはサーバーからのレスポンスヘッダ(cache-control)に含まれるだけではなく、クライアントからのリクエストヘッダ(cache-control)に含めることも可能です。

両者とも意味は同じなのですが(max-ageはリクエストとレスポンスにある場合で意味合いが異なってきますが)、CDNやProxyに対してno-cacheを指定したところで無視されます。
もし律儀に解釈してオリジンに問い合わせたらF5のやりたい放題です。ただ、これはブラウザキャッシュはこれを解釈します。no-storeを指定してリクエストすれば、ブラウザキャッシュには格納されません。

では、既にキャッシュがある状態でno-storeを指定した場合はどうなるのでしょうか?答えはキャッシュがレスポンスされます。

これはno-storeが「格納する」際に評価されるためで、この場合に指定すべきなのはno-cache、もしくはmax-age=0です。ちなみにmax-age=0はリクエストで含まれた場合は指定秒数以上のキャッシュを受け入れる気がないという意味となります。

これは考えてみればなるほどと思うんですが、割と気づかない人おおいんじゃないかなぁとか思います

本を書く経緯

この本を書くまではそもそも技術書を書くとかどういう経緯から行うかとか全くしりませんでした。もしかしたら興味ある人もいると思うのでちょっと書いてみます。

自分とあったことがある人は「エイプリルフールで落ちるサイトを減らしたい」みたいなことを勉強会やうどん会で聞いたことあるかもしれないです。他にもこんな感じでエイプリルフール当日つぶやいてました。

この本を書く前から、このような問題意識を持っていたのですが、2017年にとあるサイトのキャッシュ事故でCDNが話題になった際にCDNとの付き合い方という記事を書きました。

実はこれは元々社内向けに書いていて満足していたのですが(結構何か起きたりすると社内向けに記事を書いたりすることが多くて他にも割と出したら面白そうなネタがいくつか)TwitterかQiitaで、動的コンテンツをキャッシュするなんて・・・みたいな投稿を見かけていやそうじゃないとカッとなって書いた記憶があります。

カッとなってやったことなのですが、翌年に本書きませんかというメールが来たわけです。

はじめまして。出版社、技術評論社の野田と申します。
書籍企画へのご協力、執筆をお願いしたく連絡差し上げました。

現時点ではまだVarnish-Cache入門のような書籍になるか、
あるいはCDN含め配信全体を俯瞰的に解説する本になるかも未定なのですが、
Webサービスやシステムのための「リバースプロキシ」や「キャッシュ」「配信最適化」の入門書を企画しております。

PDF版のVarnish
Cache入門や貴ブログのCDNに関する記事(****)を拝読し、
ぜひ執筆お願いできないかと今回連絡差し上げました。

もしご興味お持ちいただけるようなら、
企画の狙い、最終的にはどういう形の書籍になるかの複数のプランなどお伝えにお訪ね(もしくは企画書の送付)いたします。


ご検討よろしくお願いいたします

メール来た時めっちゃビビったと思います。(というかどこからメアド知ったんだろうというのがまずありましたw)
このあと打ち合わせをした際の議事録がこんな感じです

議事録
 大まかな方向性
 進めるならWebサイトのコンテンツやアプリケーション向けアセット「配信本」。目的意識に近い。
 キャッシュ本は説明しきれないし、varnish-cacheは商業書籍にするほど人気があるものではない。
 ユーザーに近いところから徐々に配信を最適化する手法を解説していく。
 CDNを使えば万事解決というものではなく、むしろその前の段階が重要なのでCDN以前についてもしっかり解説し最終的にCDNを使う方法について解説するような構成。
 ブラウザー、リバースプロキシ、アプリ、CDN…というような構成で進める。
 内容補足
 CDNをちゃんと使う前の最低限から始める 1:nのキャッシュ
 CDNは最後に…
 どのCDNを紹介するか GREEはAkamaiを使っているが読者向けにはFastlyだろう Akamaiは想定読者がそもそも使えない
 Akamai/Fastly/CloudFront
 レビューしてくれるか、内容をヒアリングできるか。
 大規模ならではという話があると面白いかも varnishのマイグレーションとか大変な話は大規模
 キャッシュさせるサーバーについて UbuntuとRHEL系(CentOS)
 varnish, nginx, apacheの三択。書籍としてはたぶんvarnish中心。 
 読者の課題とこの本の意義。
 小規模、中規模だと適切にキャッシュを使えていないことがある。そういうところが適切に使えるようになるために、配信やキャッシュについて一冊にまとめる本を出すことには意義があるだろう。大規模は概ね適切に使えているはず(例外もあるが…)。
 eg. 4/1、エイプリルフールで落ちる中小サイト…。
 eg.
 メディア掲載などでPV増が起きた肝心なときに落ちてしまい機を逃すサイト…。
 こういった代表的な例が考えられるが、適切に配信できていない中小サイト(の運営企業の担当者)が読者対象。大企業は配信を見る人がいるが、そういう人がいないところ。
 本のつくり
 192p ~ 256pぐらい。技術書としては短め。

元々~256pぐらいだったのが倍ぐらいまで膨れるとか驚きますねこんな感じのやり取りをしつつ、後は原稿を書いていったわけです。ちなみに執筆はmarkdownで、図はcacooで、やり取りはgithubのissueで行っています。

締め切りとは何だったのか

2018年末には原稿は仕上がるはずだったのですが、締め切りを2年ぶっちぎってしまって本当に申し訳ないという・・書きたいことはかなり早期に固まっていたのですが、実際に書いてみると恐ろしく時間がかかる。

いくつかの理由はあるのですが、キャッシュ自体が順を追って説明するのが難しい要素が多々あるからです。
例えばVaryがその典型なのですが、これは3,4,5章で説明をしています。3章までで触れた内容で解説できるVaryと5章までで触れた内容で解説できるVaryは違うのです。
こういうのが結構あったことや、自分の中で整理しきれてない部分を調べなおしたりで時間がかかりました。

あと、原稿が一気に加速したのは2019年末ぐらいからで、それまでは数日なにも書かずにやり取りしてるgithubのissueを見るのが怖いというのもざらだったんですが、とにかく毎日何かコミットをする(句読点の修正でもいいので)というのをルーチンにしたところ一気に進みました。
多分これやってなかったらまだ書き終わってないでしょうし、もし最初からやっていたら半年~1年ぐらいは速く仕上げることができたんじゃないかなと思います。
何か本を書く人はまじでルーチン化するのをお勧めします。
ありがたいことに催促はほぼされなかったので、割と納得いくところまでかけたかなと思います。

少し期待していた「進捗どうですか?」「進捗だめです」のやりとりはしていないはずです(というかだめですでふざけられるレベルではないので申し訳ない)

出版社で出すという意義

最近では技術書典のような個人で技術書を出すということもやりやすくなったため、以前に比べると気軽にまとまった形で技術の情報発信ができるようになってきています。

以前何かで見かけたのですが(twitterだった気がします)技術書を出版社で出す意味はあるのだろうかです。
あくまで今回書いてみての感想ですが、目的によっては意味はあるです。

この目的ですが、自分の場合は「HTTPキャッシュの入門書を書いて、少しでも落ちるサイトを減らしたい」というまぁ大それた目標だったりします。

後者はまだわかりませんが、前者については出版社で出したからこそ達成できたと考えています。

正直なところこんなニッチな本売れないだろうと思ってましたが、ありがたいことに漫画でもないのに発売日前に出版社の在庫切れ+増刷と非常に売れているのでお金もついてくるとは思うのですが、打合せで聞いた際の見込み額は自分のかけるコストに対して低いなと正直思いました。
技術書は漫画のように売れるわけでもないですし、仕方ないところはあるとは思います。
なので自分はこれは分厚い同人誌であってお金を目的としていないと考えながら書きました。なので目的次第なのかなといった感じです。

じゃあ何が出版社でのメリットなのかというと編集がつくということです。
あまりこういうのを書くと自意識強すぎではと思われるかもしれないんですが、

一般的に著者は書こうとしている分野においてそれなりに知識を持っているはずです。
その中であくまで著者の考えでわかりやすく書いていくのですが、結局のところ、それはできる人の考えるわかりやすいでその知識を持ってない人から見て本当にわかりやすいかといえばそうではないです。

そのため原稿を書いてる途中に何度もこれはわかりづらいという指摘を受けて修正しました(特にVaryとか本当になんど直したんだろうと)
指摘を受けてもなんでこれでわかりづらいのかすぐには理解できないこともあり、もし編集抜きで一人で書いていたのであれば間違いなくキャッシュを元々知っている人ならわかるすごいニッチな本になったと思います。
間違いなく編集がいなければこの本は別のものとなっていたでしょう。

後は読みやすさも改善されます。この記事を見てもらえば分かるように割と地の文は読みづらいよねとは思うわけですが(流石にもう少し意識して書いてますが)、本は綺麗に直してもらっており、恐らく編集で相当なコストを費やしており、本当に有難うございますとしかいえないわけです。

最初の読者は編集者であるというのは聞いた事があると思いますが本当にそうだと思います。

ですので、技術評論社の野田様には非常に感謝しています。

後はまぁ自分の本がISBNついて紀伊国屋とか並んでるのを見るとなんかすごいなと思ったりもすることもありますが(発売日に眺めに行きました)、それはおまけかなとも思います。

追記

編集すごいと再認識したのと、この本ができたのは自分と編集の見ている問題意識が一致していたからだろうなと思いました(めっちゃ驚いた

さいごに

ということでキャッシュと関連技術のクソ分厚い本書きました。
ぜひぜひ買ってくださいね!あと感想とかレビューもらえるとすっごくうれしいです。


 Posted by at 11:40 AM
9月 042019
 

Varnish6.0.4/6.2.1が公開されました。
今回の更新はセキュリティ上の問題の修正(VSV00003/CVE-2019-15892)です。
Varnishに存在するヘッダのパース処理の問題により細工されたHTTPのリクエスト(keep-alive)によりassertを引き起こさせ結果としてVarnishを再起動させることが可能です。

影響を受けるバージョンについてと修正

  • 6.1.0~6.1.1および6.2.0
    • 6.2.1で修正
  • 6.0.0~6.0.3
    • 6.0.4で修正

なお6.0.0未満のバージョン(以前のLTSだった4.1も含む)についてもパース処理に同様の問題はあるもののassertがなかったため影響を受けません。

VCLによる緩和策について

すぐにアップデートできない環境向けにVCLでの緩和が可能で公式で紹介されています。
実際のコードは公式を参照してください。

単純な緩和策

今回の攻撃の成立条件としてkeepaliveが必須なので、単純な方法としてはクライアントにレスポンスする際にConnection: Closeを返却する方法がありますが、当然パフォーマンスにインパクトがあります。

複雑な緩和策

なるだけパフォーマンスに影響を与えない形で緩和するためのVCLも提供されています。
ただしこれはインラインCを使っている関係でサポートしているバージョンが

  • 6.2.0
  • 6.1.1
  • 6.0.3

と限られています。

その他変更について(6.0.4)

6.2.1の変更は今回のセキュリティ修正のみですが、6.0.4についてはもともと積んでいて6.0ブランチにコミットされていたバグ修正や改善も同時に入っています。(かなり少ないですが)
その中で一部を紹介します

std.ipのデフォルトポートを指定可能に

std.ipは文字列をIPアドレスに変換するメソッドですが、もともとポート番号は80固定となっていましたが、pというパラメータが追加されて指定可能となりました。
デフォルト値は今まで通り80なのは変更ありませんので今まで使っていても新規に指定が必要というわけではありません。

そのほか

また、今回の更新については以前紹介したVML購入者向けのVIVU-listに事前に通知されました。そう高くもないですし使っている人は検討してもよいかと思います。
あともともとの次のリリース予定日が9/15だったんですが、どうなるんだろ・・


5月 092019
 

Vanrish6.2.0がリリースされました
マイナーバージョンアップにとどまることからわかる通り大きな機能の追加はなく、公式のリリース2019年4月3日メールでも触れられていますが、HTTP/3が見えてきているのでその前段階の改善といったところでしょうか(最近のbugwashでもh3の話題を見かけます)
割と重要なバグ修正や機能改善が含まれています。

開発者向けの仕様変更

インストールやビルドに必要なPythonが2.7から3.4以上に変更されました

今回の公式パッケージの提供が遅れた原因です。
6.2.0のリリース自体は3/15にあったのですが、公式パッケージは3/29まで提供されていませんでした。(で、記事が今頃になったのは書きかけで放置してた感じです・・
なおPython2.7.xは来年EOLになるみたいです。

vmodの仕様変更

対応していないvmodはほぼ間違いなく動作しません。
変更ポイントは多いのですが、多くのvmodで引っかかるのはvccで定義した各種func/eventは必ず先頭にvmod_がつくようになりました。
今までは

$Event event_function

という定義であれば

int v_matchproto_(vmod_event_f)
event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)

でよかったのですがこれが

int v_matchproto_(vmod_event_f)
vmod_event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)

とする必要があります。
また次に引っかかるのが多そうなのがVCL_BLOBで使う構造体が変わったことです。
今まではvmod_privだったのですが専用のvrt_blob構造体が用意されました。
ほぼrenameで対応できるのでこれも容易だと思います(あとは->privを->blobに変えれば大体動くはず)
VRT_APIが9に変更されてるので

int v_matchproto_(vmod_event_f)
#if VRT_MAJOR_VERSION > 8U
  vmod_event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
#else
  event_function(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
#endif

こんな感じで両対応も可能です。

VUTの仕様変更

通常の利用者は問題ないと思うのですが、自前で作ったスクリプトとかでVUTをたたいている人はおそらく動かなくなります。
VUT構造体が変更されていますので追随する必要があります。
また、signalのハンドリングも変わっているので気を付ける必要があります。

仕様変更

タイマー系のログ出力はミリ秒精度に統一されました。

ExpKillとExpRearmは元々ナノ秒だったんですがこれがミリ秒に変わりましたが
これを見てる人ほぼいないと思うので影響する人はいないでしょう。

非推奨のパラメータが削除されました。

shm_reclen/vcl_dir/vmod_dirが削除されています。
それぞれvcl_reclen/vcl_path/vmod_pathを利用してください。

パラメータの追加/変更

thread_pool_stackのデフォルトサイズが大きくなりました。

VCLの変更

req.is_hitmiss/req.is_hitpassが追加されました

vcl_hit内でreturn(miss)が削除されました。
そもそも期待通りうごかなかったみたいです

機能追加/改善

起動パラメータのdebugにvcl_keepが追加されました。

VCLをCコードに変換した結果とsoを保存するようになっています
出力は/var/lib/varnish/[instance-name]/[vcl-name]/以下にvgc.cで出ます。
varnishtestを行う際に便利かもしれません。

ban時にttlなどの有効期限での比較が使えるようになりました。

obj.age/ttl/grace/keepは時間の経過とともに変わる値ですがこの値を使ってbanができるようになりました
なので例えば5時間経過しているオブジェクトを消したいといったときは

ban obj.age > 5h

みたいな指定が可能になりました。

パラメータをデフォルトに戻るparam.resetが追加されました(varnishadm/varnish-cli)

varnishadmでのjson出力サポート

6.0.2と6.1.0からサポートするようになったjson出力ですが
6.1.1の記事でも触れた通り実は6.0.2のほうがjson出力に対応しているコマンドが多かったのですが、6.2.0では違いがなくなっています。
また同時にいくつかのコマンドの列幅がターミナルのサイズに合わせて可変長となったので
もしツールなどでcliの結果を処理しているのであればjsonを使うことを検討すべきでしょう。

statの強化

これも6.0.2のみで対応していたstatが6.2.0でも出るようになりました。

lookup処理の改善(#2854)

lookup時のmutexを減らしています。
高RPSな環境では割と効果があると思います。

resp.filtersの追加

6.1.0の際に増えたberesp.filtersのresp版です。
最終的なレスポンスを行う際に使用できるVDP filterの並び替えができるようになりました。
ただ、バグがあるのですぐに使う人もいないと思いますが一旦次バージョンを待ったほうがいいです(masterは修正済み

vmod_stdの型変換関数の改善

型変換は今までreal->integerの時はreal2integer、time->integerの時はtime2integerと分かれていてめんどくさく、fallbackの指定も必要でしたがこれがすっきりしました。
例えばほしいものがintegerであればこんな形です。

std.integer(real=1.23)

とりあえずduration/bytes/integer/real/timeが用意されており、
以前のreal2integer/real2time/time2integer/time2realは非推奨となり、将来削除されます。

vmod_directorsにlookupが追加されました。

vcl_init/finiで呼び出し可能で、個人的にはいまいち使い道が思いつかないのですが

new l2_cache = directors.hash();
l2_cache.add_backend(cache01, 1.0);

みたいな指定を

new l2_cache = directors.hash();
l2_cache.add_backend(directors.lookup("cache01"), 1.0);

といった感じで書けます。

varnishncsa/varnishlogにratelimitオプション(R)を追加しました。

-R 10/2m

こんな指定が可能です。

varnishstatのリフレッシュレートが変更できるようになりました。
表示中に+/-を入れると0.1秒単位で増減します。

バグ修正

http/2のactive streamのカウント周りのバグが修正されました(#2916,2923)

一つはコントロール用のstream=0をactiveなストリームとしてカウントしてたのでmax_concurrent_streamsの判定が1ずれてたものと
もう一つはストリームのカウントの増減をスレッドの割り当て時・解放時に行っていたので、解放時に後処理してたら次のstream openの要求が来てrefuseするといった感じです。
とりあえずこれでストリームを多く使うようなサイトでも問題なく使えるようになったと思います。


10月 282018
 

6.1.0が出たときにすぐ記事を書かなかったのは、リリース後に一部デグレ(varnishhist表示)がIRCで話題になって6.1.1をリリースする?
みたいな話がでていたので実際に6.1.0を動かしながらはまりポイントがないかを探しながら6.1.1のリリースをまってました。
思ったより期間があいたので、先に書いとけばよかったなと反省しています。すいません。
また、すでにPRが用意されていることからさほど時期を開けずにLTS(多分)である6.0.2もリリースされると思われます。

6.0.0/6.0.1から6.1.1の変更は新機能もあるのですが、それ以上にバグ修正が多いので基本的にバージョンアップをおすすめします(6.0.2でもいいですが)

特に以下の機能を使っている場合は関連するバグ修正がいくつかあるのでバージョンアップを強くお勧めします。
・HTTP/2 (6.1.1/6.0.2のみの重要な修正があるので6.1.0/6.0.1を利用している場合もバージョンアップ必要)
・proxy protocol
・UDS
・pipe

ダウンロードはこちらから

6.1.xと6.0.xの違い

6.1.xはいくつかの新機能やコア部分の変更はありますが、バージョンアップに苦労するような変更はほぼありません。
例えばVCLについては削除された変数などがないため、6.0で動いていたものをそのまま動かすことができます。
ただしvmodについてはdirector APIの変更があったため、一部については再コンパイルだけでは動作させることができません。(要対応)

では、今回はどのような大きな変更があったかというと
backendをフェッチしてstorageに格納する際に呼ばれるVFPと呼ばれるスタックをVCLから入れ替えなどの操作ができることになったことです。
今までもVFPスタックに処理を追加することはできたのですが(Varnishでテストコードを書こう!~実践編~+Bodyを読もう!)、デフォルトの順番を動かすことや差し替えを行う事が難しかったのです。
例えば今回の変更で、デフォルトのgzip処理をZopfliに変更するようなこともできるといえば可能です。(他にもESI処理の置き換えも)
ちなみにVarnishにはクライアントにレスポンスする直前に呼ばれるVDPスタックもあるのですが、ここの同様な操作は6.1.xでは実装されていません。
しかしtrunkではVDP関連のコミットがあるのでおそらく次期バージョン(2019/03)では来ると思います。

VarnishのLTS

Varnishは古いバージョンのサポートを早々に打ち切ります。
しかし一部のバージョンについては長くサポートされています。
例えば4.1.xは5.xがサポートが打ち切られているのにも関わらずサポートされ続けています。
Releases & Downloads

特に明言されているわけではないのですが(MLとかIRCでなんどかそのような発言は見かけましたが)
VarnishSoftware(VS)がリリースしているVarnishCachePlusのベースとしているバージョンはLTSとしてサポートされるようです。
(用はスポンサーがついてる Please backport #2077 to 5.1.x
現在のところPlusは6.0.1r2と4.1.10r5がリリースされていますので6.0.xと4.1.xはLTSです。
とはいえ6.0ベースのPlusが出たので、さすがに4.1.xは収束していくのではと考えています。
もし、現在4.1.xを使っている方は6.0.xまで上げるのも検討してもよいかと思います。

6.1.1と6.0.2の位置づけについて

今回のリリースで6.0.2だけに追加されている機能があります。(もちろんVFPスタック操作については6.1限定です)
理由を聞いたところ6.1.1はbugfixリリースで新機能を入れることで新しいバグが入り込むのを避けたいということでした。

動作変更について

HostヘッダなしのHTTP/1.1のリクエストを400で返すように変更(#2631)
そもそもHTTP/1.1はHostヘッダが必須なので特に気にすることはないでしょう。
builtin.vclでの実装なので、どうしても困ることがあるのであれば処理を上書きすればよいかと思います。(望ましくないですが)
For HTTP/1.1 requests, Host is mandatory

パラメータの追加

max_vcl/max_vcl_handling
varnishの設定を適用する際にreloadをしていると以前のvclが残ります。
当然これらはディスク容量を圧迫しますし、また一部の操作(vcl.listなど)は当然遅くなります。
max_vclはvcl数の閾値でデフォルトは100です。で、閾値を超えた場合の動作を定義するのがmax_vcl_handlingです。
0は警告も何もしない、1でwarning表示(デフォルト)、2でVCLの追加を拒否します。
デフォルトの状態で使い続けても特にVCLの追加ができなくなることもないので(warnはでる)
warnが出たらvcl.discardしていくのもよいかと思います。

backend_local_error_holddown/backend_remote_error_holddown
バックエンドに接続を行う際に、自身もしくは接続先のリソース不足などで接続失敗することがあります。
この場合で即時再試行しても結局リソース不足で再度失敗することが多いので、少し待ってみるのも効果的です。
このパラメータはその秒数を指定するものです。
backend_local_error_holddownはEADDRNOTAVAIL・EACCESS・EPERMが起きた際に待機する秒数で
backend_remote_error_holddownはECONNREFUSED・ENETUNREACHが起きた際に待機する秒数です。
どちらもデフォルトでよいかなと思います。
また、これらのエラーが起きた際は後述するカウンタが上がりますので、その際はチューニングや構成見直しを行うと良いでしょう。

thread_pool_watchdog
h2の場合依存関係や制御情報をやり取りするだけのストリームがあるのですが、Varnishの場合はそれもスレッドを割り当てます。
ですので、スレッド不足で実際のリクエストに対してスレッド割り当てができない場合、そこでリクエストの処理を待ってしまいデッドロックする可能性があります。
これらのデッドロックを検知した場合に子プロセスを再起動するのですが、その設定時間がこれです。
基本的にデフォルト値で問題ないでしょう。
もし、これ起因でrestartが走るようであれば、スレッド関連の設定を見直すのがよいでしょう(thread_pool_maxなど)
また、h2起因で追加されたパラメータですが、別にh2以外でもデッドロックが起きた場合は発動します。

VCL変更

削除されたいくつかの変数が復活
req.ttl・req.ttl・req.graceが復活しています。

すでにimportしたvmodを再度importしようとしてもエラーにならないように変更
地味にうれしい変更です。
例えばvmod_stdはよく使われているvmodなのですが、複数のvclをincludeしている際にimport std;を複数のvclに記載してエラーになることがあります。(きちんとdefine.vclみたいなものにまとめておけばいいのですが)
この変更でエラーにならずに2回目以降のimportは無視されるようになります。

vcl_init中のreturn failでメッセージを入れられるようになりました
vcl_initで何かしらのエラーが起きて終了させたいときに使えるreturn(fail)ですが
なんのエラーで落ちたかとメッセージで出す方法がありませんでした。
今回return(fail(“foo”))といった指定が可能になったことで失敗した理由を通知することが可能です。


//vcl
sub vcl_init{
  return(fail("foo"));
}

//output
VCL "boot" Failed initialization
Message:
        foo

このような形で出力されます。

beresp.filtersの追加
後述します

新機能について

vmod_stdにfnmatchを追加


BOOL fnmatch(STRING pattern, STRING subject, BOOL pathname=1, BOOL noescape=0, BOOL period=0)

shellでパスを指定するような(ex:/var/log/*)形で指定することが可能です。
pathname/noescape/periodはそれぞれfnmatchのflagsの該当するものと同じ意味です。
FNMATCH

サンプルにもありますが、


if (std.fnmatch("/foo/*", req.url)) { ... }

といった感じでurlマッチに使えると思います。
これの便利なところとしては、VCL中で使う正規表現と違いpre-compileが必要無いのでマッチパタンを変数にするといったことも可能です。

VFPスタックの呼び出し順をVCLから変更可能に(6.1.x)

先ほども述べた通り現状ですぐに役に立つものではありません。
VFPを提供するVMODが増えてくれば有用でしょう。
とりあえず現状どのようなものが適用されているかを確認するのであれば


varnishlog -graw -i filters

で見ることができます。

また、レスポンスヘッダにどのようなフィルタが使われているかをだすには


sub vcl_backend_response {
  set beresp.http.x-filters = beresp.filters;
}

このようにすればヘッダに出力されます。

また設定するには


sub vcl_backend_response {
  set beresp.filters = "esi" // gzipじゃない時のberesp.do_esi = true;と同等;
}

とすればよいでしょう。
とはいえ、理解せずに弄るといろいろ崩れたりしますので注意しましょう。(多分VMODで指定されると思うのでその通り使えばOKかと)
かなり上級者向けの設定です。

varnishadm(varnish-cli)でのjson出力(6.0.2/6.1.0)

json出力がサポートするようになりました。


# sudo varnishadm backend.list -j

[ 2, ["backend.list", "-j"], 1540196843.165,
  {
    "reload_20181022_074741_10060.***": {
      "type": "backend",
      "admin_health": "probe",
      "probe_message": "5/5 good",
      "last_change": 1540194463.057
    },

...

    "reload_20181022_074741_10060.***": {
      "type": "hash",
      "admin_health": "probe",
      "probe_message": "healthy",
      "last_change": 1540194463.054
    }
  }
]

6.1.1では一部、6.0.2は大部分のコマンドで出力をサポートしています。
varnishadmの結果をスクリプトで処理する際には非常に便利だと思います。

6.1.0/6.0.2の両方で対応しているもの
– ping
– backend.list
– help

6.0.2のみで対応しているもの
– status
– vcl.list
– param.show
– ban.list
– storage.list
– panic.show

statの強化
varnishstatで表示されるstatがかなり追加されています。
こちらも一部は6.0.2のみ対応の項目があります

6.1.0/6.0.2の両方で対応しているもの
– MAIN.sess_fail_econnaborted
– MAIN.sess_fail_eintr
– MAIN.sess_fail_emfile
– MAIN.sess_fail_ebadf
– MAIN.sess_fail_enomem
– MAIN.sess_fail_other
– [backend名].unhealthy
– [backend名].busy
– [backend名].fail
– [backend名].fail_eacces
– [backend名].fail_eaddrnotavail
– [backend名].fail_econnrefused
– [backend名].fail_enetunreach
– [backend名].fail_etimedout
– [backend名].fail_other
– [backend名].helddown

以下6.0.2のみ対応
– wrk.client_resp_500
– wrk.ws_backend_overflow
– wrk.ws_client_overflow
– wrk.ws_thread_overflow
– wrk.ws_session_overflow

例えばfail_eaddrnotavail(EADDRNOTAVAIL)であればエフェメラルポートの確保に失敗してるんだろうということで設定の見直しなどができると思います。
また、overflowのカウンタも便利です。
これらはそれぞれ起動パラメータのworkspace_clientなどと一致するので、カウンタが上がっていたらチューニングを行うと良いでしょう。

その他変更について

director周りにAPIが変更されているため一部vmodは対応が必要
通常のVMODであれば再コンパイルだけで動くと思いますが、director周りを触るvmodの場合は対応が必須です。
アップデートする前に対応しているか確認すると良いでしょう。

バグ修正について

6.0.2/6.1.1は大量のバグ修正が含まれます。
また、毎回言ってるのですがHTTP/2で重要なバグフィックスが2件あります(他にもあるんですが特に重要・・・)

HTTP/2利用時にスレッドリークが起こるの修正(#2623)

閉じられたストリーム宛てにPRIORITYフレームが届くとプロトコルエラーとして扱っていたのを修正(#2775)

http/2のexperimentalはまだ外れてないのですが、さすがにもう強烈なバグは出尽くしたんじゃないかなとか・・・考えてます。

そのほか

公式パッケージでUbuntu18.04(bionic)向けのパッケージが追加
varnishtestでhaproxyを扱うことが可能に
おそらくvarnishtestは将来的にプロジェクトが分離され汎用的になるでしょう(Divorcing varnishtest from varnish-cache)
既にレポジトリもあります
VTest


4月 092018
 

Varnish6.0.0がリリースされました。[changelog] [公式のリリース文] [パッケージDL] [ソースDL]
今回のリリースはメジャーバージョンアップということで機能追加や累積バグFixなど来ています。
目玉機能はUnixDomainSocket(UDS)対応で、個人的に注目しているのがshard directorにおけるlazyモード追加です。
また新しいVCLバージョン(4.1)も追加されていますが、既存が使えなくなったわけではないので影響は少ないでしょう。
なお、アップデートする場合はいくつか注意が必要な点があります。

現状で適用する場合は十分に注意検討する必要がある環境

http/2環境
h2利用時にスレッドがリークしているような動きをしています。(#2623)
最初は自分のテスト用環境だけかなと考えていたんですが、他に踏んでる人を確認したので現状のままでh2を有効にするのはかなり注意が必要です。
h2を使いたいのであれば、きちんと検証して自環境で起きないことを確認しつつ行うか、既存バージョンのままで使いつつFixを待つと良いと思います。
ただしh1のみの場合は現状問題ないため、h2を使わないのであればバージョンアップをしても良いかと思います。

公式パッケージの対象バージョンが少なくなった
公式で提供してるパッケージの対象OSは以下の通りです
– RHEL7(CentOS7)
– Debian9
– Ubuntu16.04
Ubuntu14.04やRHEL6は対象外となりましたので注意が必要です。
ただ、これはあくまで対象バージョンを絞ったというだけなので、自前でpkg-varnish-cacheを利用してパッケージを作って適用は可能です。

VMODを利用している場合
VMOD側で対応しないといけない変更が入っているため、いろんなVMODを利用している場合は対応状況を確認したほうが良いです。
必要な変更はたいしたことないので、メンテされてるものについては対応されるんじゃないかなとか思います。

なお、VSMに直接つなぐアプリについては5.2.0のときのVSM/VUTインタフェース変更に対応できていればほぼ動くと思います。
一部定数と戻り値の型が変わったものがあるためそこを使っていて運が悪いと踏むかもしれません。


VCL

vclを記述する際には、先頭に「vcl 4.0;」と記述していたかと思いますが、今回「vcl 4.1;」が追加されました。
既存の4.0が使えなくなったわけではないため、そのまま動かすことも可能ですし、大抵の場合は先頭行だけ4.1に書き換えても(大抵の場合は)動くでしょう。
あとで紹介するUDSは4.1のみでサポートしている記法が必要なので、もし使う場合は4.1に変更しましょう。
またincludeですが親のVCLバージョンより小さい場合は可能です。
つまり

parent vcl ver child vcl ver result
4.0 4.0 OK
4.0 4.1 NG
4.1 4.0 OK
4.1 4.1 OK

こんな感じです。
VCLの記載の仕方は環境それぞれだとおもうのでなんともいえないのですが、
default.vclを起点として、includeしていくやり方であればdefault.vclを一旦4.1にしておくと良いのかなと思います。
とはいえ、4.0から4.1へのアップデートはそんなに難しくないのでやってしまったほうがいいんじゃないかなとは思います。

VCL変更点

4.0での変更点と4.1での変更点があります。
まずは共通の変更点から

変更点(4.0/4.1共通)

return(fetch)がvcl_hitで使えなくなりました
個人的にはまだ残っていたのかといった感じですが(結構前からdeplicatedになってた)
return(miss)を使いましょう

req.storage / hash_ignore_busy / hash_always_missをclientスレッド側でアクセスできるようになりました。

obj.storageが追加され、vcl_hit/vcl_deliverでアクセスできるようになりました

restart時の動作が変わりました
req.restarts,xid以外のすべてのreq変数は維持されます。
restartを利用している場合は動作の確認をしたほうが良いでしょう。

vcl_recvでもreturn(restart)が呼べるようになりました

変更点(4.1のみ)

backendの定義に.pathが追加
あとで触れるUDSで使うパスです。
.hostと同時に定義することは出来ません。

local.socketの追加
接続に利用したソケットの名前を返します。
-afoo=:81と名前が指定されてる場合はfooを返しますが、もし定義されていない場合は自動的につけられた名前(a0,a1)が返却されます。

local.endpointの追加
接続に利用したソケットの定義を返します。
-afoo=:81の場合は:81が返却されます

sess.xidの追加
セッションのユニークなID(xid)を返します。
今まで使用していたreq.xid/bereq.xidはそのリクエスト、バックエンドリクエストを示すものでした。
しかしKeepaliveやHTTP/2では1セッション中に複数のリクエストが含まれます。


*   << Session  >> 433160262
-   Begin          sess 0 PROXY
-   SessOpen       ***
-   Proxy          ***
-   Link           req 423002616 rxreq
-   Link           req 423002617 rxreq
-   Link           req 423002618 rxreq
-   Link           req 423002619 rxreq
-   Link           req 423002620 rxreq
-   ReqAcct        72 22 94 1251 6 1257
-   End

これはとあるHTTP/2.0でのログの抜粋ですが、これが示しているのは
xid:433160262のセッション(sess.xid)にxid:423002616~423002620のリクエスト(req.xid)がぶら下がっているということです。
今回sess.xidがvcl中から簡単に取れることで、異なるリクエスト中でも同一セッションであることがわかるので、なにかに使えるかもしれないです(ぱっと思いつかないですが・・)

resp.do_esiの追加
beresp.do_esiの指定でフェッチ時にパースした場合でも無効にすることができます。
vcl_deliver/vcl_synthで呼び出し可能です。

(req|bereq|beresp).protoがreadonly
そんなに変更することもなかったと思いますが.protoがreadonlyになりました。

req.esiが削除された
まぁ使う人はそんなにいないかと・・・beresp.do_esiを使いましょう

beresp.storage_hintが削除されました
beresp.storageで代用できます。
例えば今まで


set beresp.storage_hint = "foo";

としていたのであれば


set beresp.storage = storage.foo;

と変更すればよいです。

beresp.backend.ipがが削除されました

IPアドレスが0.0.0.0:0のものはUDS経由のものになります。


Unix Domain Socket(UDS)対応

上下流どちらも対応しています。
これを利用することにより高CPS環境において、特に同一サーバに置いてることがほとんどだと思われるTLS終端を行うミドルウェアとの通信をより効率的に行えるようになります。(上流側)

なお、今回の例ではNginxを組み合わせてやってみます。

LISTEN側でUDSを使う場合
起動パラメータの-aで以下のような指定をします


-a /var/run/varnish-uds.sock,user=vcache,group=varnish,mode=666

今回はproxyプロトコルを指定していませんが指定できます。(PROXYを追加しましょう)
また今回はお手軽にNginxとつなぐために666にしていますが、適切な設定にするべきでしょう。

Nginx側


upstream backend {
    server unix:/var/run/varnish-uds.sock;
}
server {
        listen 80 default_server;
        listen [::]:80 default_server;
        location / {
            proxy_pass http://backend;
        }
...
}

これだけです。
なお、LISTEN側でUDSを使う場合はVCL4.1が必須です。
が、現状試してる限りでは起点となるvcl(default.vcl)が4.1であればinclude先は4.0でも平気な動きをしています。 
しかし、ドキュメント上は

When UDS listeners are in use, VCL >= 4.1 will be required for all VCL programs loaded by Varnish.

と記述されており、もしかしたら塞がれる可能性もありますので、UDSを使う場合はすべてのVCLを4.1にするのが無難でしょう。

なお、UDSは当然ながらIPアドレスはなく、UDS経由のアクセスは0.0.0.0:0として扱われます。
なので、client.identity(デフォルトではclient.ipが使われる)を使って振り分けを行う場合は常に同一backendが選択されるので注意が必要です。
適切に修正を入れるか、そもそも上流はPROXYプロトコルが使えるミドルウェアにするなどの対策が必要でしょう。

バックエンド側でUDSを使う場合
すごく単純です
Nginx側


server {
  listen unix:/var/run/nginx-uds.sock;
  access_log off;
  root /var/www/html;
  index index.html index.htm index.nginx-debian.html;
}

Varnish側


vcl 4.1;
backend default {
    .path = "/var/run/nginx-uds.sock";//絶対パス
}

これだけです。
特に難しくないと思いますが、1点注意なのがbackend内での.pathプロパティはvcl4.1のみなので4.0だと出来ません。
なおListenの時と違いバックエンドのみ使用する場合は4.0混在でも動きます。とはいえ4.1の書き換えは難しくないので書き換えたほうが無難でしょう。


その他新機能・変更

varnishncsa

制御文字・バイナリを含む可能性があるタグの-Fを拒否するようになりました
対象はH2RxHdr H2RxBody H2TxHdr H2TxBody Debug HttpGarbage Hashですが、たぶん使ってる人はいないかなと

起動パラメータの変更

esi_iovsの追加
ESI処理で使うiovec構造体の割り当て数ですが通常の場合はいじる必要がありません。
ESIを使っていてどうしてもtuneしたい場合はsystemcallでwritevの実使用数をみながら調整するとよいでしょう
またこの値を変更する場合はworkspace_threadを同時に調整する必要があるでしょう(割り当て元がworkspace_threadからなので)

h2関連パラメータの追加
h2_header_table_size
h2_max_concurrent_streams
h2_initial_window_size
h2_max_frame_size
h2_max_header_list_size
このあたりはh2のRFCを読んでもらえればわかるかなって感じです。
ちなみにh2_max_concurrent_streamsは100なんですが、chromeのストリームはデフォで1000なので、そのあたりで気になる人もいるかもしれないです。(とはいえそんなに弄る必要ないと思います)

feature_bitの追加(http_dete_postel)

日付の形式はrfc7231#section-7.1.1.2で定義されてるんですが、たまにこれに準拠しないやんちゃなDate,Last-Modified,Expiresを返してくるサーバがいます


Fri, 2 Mar 2018 14:26:02 GMT  (まちがい)
Fri, 02 Mar 2018 14:26:02 GMT (ただしい)

この間違いのパタンを許容するようになります
有効にしたい場合は-p feature=+http_dete_postelにします。
個人的にはオリジン側直したほうがよいかなーとは思いますが・・・

cli_bufferの削除
まぁ以前からアナウンスあったので・・・

カウンタの追加

cache_hit_grace
そのまんまですがgrace状態でレスポンスできた件数になります。
パフォーマンスを稼ぐ場合は一旦古い状態のキャッシュを返しつつ裏で更新リクエストを行うのを重視しますが(うまくいけばキャッシュ更新時に遅くならない)
このカウンタを見てチューニングを行うことが出来るかなとも思います。 

n_lru_limited
nuke_limitに達した件数を示します。
当然ですがストレージがいっぱいの状態で新しいオブジェクトをキャッシュを行おうとする場合は古いものを押し出す必要があります。(LRUです)
Varnishではこの処理をNukeと呼んでいますが、実はこの処理にはnuke_limitというパラメータがあり、一つのオブジェクトを確保するためにNukeして良いオブジェクトのLimitを定義しています。
オブジェクトサイズがだいたい似たようなところならデフォルト値(50)でも問題ないのですが、平均50KBのところに10MBのでかいオブジェクトがたまにやってくるとかだと足りないことがあります。
当然Limitを超えた場合はストレージを確保できないためエラー(503)となるのですが、問題はストリームをしている場合です。
ストリームでレスポンスをしている場合は、当然ながらオリジンから帰ってきたレスポンスコード(200)を返してしまっているので503を返すことができません。
そのため200なのにレスポンスボディが中途半端なサイズになるということがあるのですが(割とmlとか見てるとそれっぽい問い合わせ多い)
このカウンタを見ることでそのような状態に陥ってることがわかります。
とりあえず1でもあがるようであれば、nuke_limitを引き上げる、ストレージの見直しをする(サイズあげるとか、ある程度のサイズ毎に分けてしまうとか)とかすると良いと思います。

vmod_directors

shard directorにかなりの修正が入っています。
shard_param
新しくshard_paramというクラスが定義されています。
基本的に今までのshardの各種パラメータ(rampupとか)が定義されており、単純に外出しにされたものです
使い方は


sub vcl_init{
	new vd = directors.shard();
	vd.add_backend(hoge);
	new p = directors.shard_param();
	p.set(by=URL, warmup=0.7);
	vd.associate(p.use());
}


sub vcl_init{
	new vd = directors.shard();
	vd.add_backend(hoge);
	new p = directors.shard_param();
	p.set(by=URL, warmup=0.7);
}
sub vcl_backend_fetch{
	set bereq.backend=vd.backend(param=p);
}

こんな感じです。

ハッシュアルゴリズムについて
今までハッシュアルゴリズムを選択できたのですが(SHA256(default),CRC32,RS)
これがSHA256だけになりました。これに伴いalgを指定できていたメソッドにおいてalg引数が削除されました。
しかし、わざわざSHA256以外を使ってる人も少ないと思うのでそこまで影響ないと思います。
引数がなくなる影響を受けるのは.reconfigureと.keyになります。

resolve={now,lazy}の追加
個人的に注目している機能です。
lazyモードは実際にバックエンドへの接続を作るタイミングで解決されるものです。
これが何が嬉しいのかというと、Directorを入れ子にすることができます。
例えばfallbackと組み合わせてdirectorが全滅したらfallbackするみたいなのも書けます。


probe healthcheck {
    .url ="/check";
    .timeout           = 1s;
    .window            = 4;
    .threshold         = 2;
    .interval          = 1s;
}
backend ws01  {.port="80";.host = "XX";.probe=healthcheck;}
backend ws02  {.port="80";.host = "YY";.probe=healthcheck;}

sub vcl_init {
	new sdp = directors.shard_param();
	sdp.set(warmup=0.5, healthy=ALL);

	new sd1 = directors.shard();
	sd1.associate(sdp.use());
	sd1.add_backend(ws01);
	sd1.reconfigure();

	new sd2 = directors.shard();
	sd2.associate(sdp.use());
	sd2.add_backend(ws02);
	sd2.reconfigure();

	new fb = directors.fallback();
	fb.add_backend(sd1.backend());//lazy
	fb.add_backend(sd2.backend());//lazy

}
sub vcl_recv {
	set req.backend_hint = fb.backend();
	return(pass);
}

サンプルでshardに登録してるバックエンドはそれぞれ一つですが感覚は掴んでもらえるかと思います。(ついでにshard_paramもサンプル的に入れてみました)

vmod_std

std.log/syslogにおいてworkspace枯渇した場合でもfailしないようになりました

vmod_unix

今回追加されたvmodです
接続してきたプロセスのuser/group/uid/gidを取得できます。
厳密にガードをかけたいときに使うとよいかもしれないです。
man vmod_unixそのままですが


sub vcl_recv {
      # Return "403 Forbidden" if the connected peer is
      # not running as the user "trusteduser".
      if (unix.user() != "trusteduser") {
              return( synth(403) );
      }

      # Require the connected peer to run in the group
      # "trustedgroup".
      if (unix.group() != "trustedgroup") {
              return( synth(403) );
      }

      # Require the connected peer to run under a specific numeric
      # user id.
      if (unix.uid() != 4711) {
              return( synth(403) );
      }

      # Require the connected peer to run under a numeric group id.
      if (unix.gid() != 815) {
              return( synth(403) );
      }
}

こんな感じです

vmod_proxy

今回追加されたvmodです
Proxy Protocol v2にはType-Length-Value(TLV)というフィールドに例えばどんなCipherを使用しているかみたいな情報を埋め込むことが可能です。
残念ながらHitchはalpn(PP2_TYPE_ALPN)しか埋め込んで来ないようなので、フル機能を使いたい場合はHAProxyの比較的新しいバージョンを使う必要がありますが
何かしら情報を取得してログに出力したい場合は有用だと考えています。
また、残念ながら取れる値は固定になっているので、カスタムTLVの範囲へは現状アクセスできません。(NLBのVPCエンドポイントIDとかl)
とはいえコードを見る限りでは対応は容易なのでp-rしてみるのもおもしろいかもしれないです。
使い方は単純で


vcl 4.1;
import proxy;
import std;
sub vcl_recv{
	if(proxy.alpn() == "http/1.1"){
		//プロトコルはまぁreq.protoで取れるんで適切ではないかもですがサンプルとして
		...
	}
}

こんな感じです

VSL

ReqAcctのバイト数が正確になりました
プロトコルのオーバーヘッド分(chunkedのとか)は今まで入ってませんでしたが、OSから報告される値を使うように変更したので正確になりました。

バグ修正

多くのバグ修正がされているのですが、その中でも特筆したほうがよいものに触れます。
max_restartsの取扱い
return(restart)を行った際のチェックの不備で、max_restrtsで指定した回数より1回少ないrestartでlimitになっていましたが、修正されてmax_restartsの指定した数return(restart)ができるようになりました。


11月 222017
 

最近のVarnishのリリースサイクルは半年に一回で、その間はセキュリティ等のクリティカルな問題がない限りはリリースされません。
当然、半年の間に様々なFeatureやBugfixなどがコミットされており、場合によっては使いたいこともあるかと思います。
今までにそのように最新のVarnishを使うためには、以前紹介したpkg-varnishcacheを利用して自前でビルドする必要がありましたが
それなりに敷居が高く、そんなに使ってる人はいないんじゃないかと思います。
しかし、今月に入ってからweekly-buildがされるようになりました。(rpm/deb)

https://packagecloud.io/varnishcache/varnish-weekly

branchはtrunkになりますので、場合によってはすでに互換性のない変更が上がっている可能性がありますが
気になるfeatureやBugfixなどがあって更新したいときは使ってみるとよいかと思います。
なお、weeklyとありますが、基本的に毎週月曜に行っているbug-washでビルドするか決めているので
開発中のfeatureがあるなどの場合はskipされるみたいです。


11月 162017
 

Varnishの複数のバージョンがリリースされました。 [changelog] [パッケージDL] [ソースDL]

今回のリリースはセキュリティ対策によるもので、なるだけ迅速にアップデート、もしくは回避するために設定を見直す必要があります。
詳しくは公式情報(VSV00002)を参照してください。

脆弱性の内容

以下の条件を満たす際にsegfault/データリークが発生する可能性があります。

  • VCL_backend_errorで生成したsyntheticオブジェクト(beresp.body)を
  • file/persistentストレージに挿入する
原因

バックエンドへの接続が失敗したりなどで表示されるエラー画面はvcl_backend_errorで生成されています。
builtin.vclをみればわかるようにこのアクション内でエラー画面を生成しています。
で、この生成したものを後処理でストレージを確保してからberesp.bodyからストレージへmemcpy(3)するんですが、この際にfile/persistentストレージがターゲットに選ばれた際に問題が発生します。
file/persistentストレージの領域はpage-sizeでalignされており、確保サイズが割り切れない場合はちょっと余分な容量がついてきます。
で、この時にmemcpy(3)の第三引数に指定するサイズをalignされたサイズをそのまま渡してしまったために、コピー元から余分なサイズをreadしようとしてしまいます(なのでだいたいはsegfaultするかと)

では、vcl_backend_errorを通過すると必ず起きるかというとそうでもないです。
このようなエラー画面などは、TTLが短いためtransientというテンポラリストレージに保存するようになっています。
これは明示的に指定していない場合はmallocストレージエンジンが利用されるので今回のケースには当てはまりません。
しかし、transientに挿入する条件はエラー画面だからとかそういうのではなく、純粋にTTL(正確にはttl+grace+keepの合計値)がパラメータのshortlived未満かどうかです。
shortlivedを超える場合はhintで指定された(無指定はdefault)ストレージを使用します。
そして、shortlivedはデフォルトで10秒なので、VCL_backend_errorにおいてttlがそれ以上になるようにしていると引っかかります。(そのような人は珍しい気もしますが・・)
では、そのまま使っているケースであれば踏まないかというと、実は踏むケースが存在します。
ここのコードを見てみると同一リクエストがある状態(waitinglistに入ってる)の場合だとttl+grace+keepの合計が11secになります。
デフォルトのshortlivedが10secなのでここにタッチしますので、この状態でdefault storageがfile/persistentを使用している場合は踏んでしまいます。
なのでまとめるとこんな感じになります

Transientを明示的にfile/persistent指定している場合(ex -sTransient=file…)
vcl_backend_errorに突入した場合は引っかかります。
そもそもTransientをfile/persistentで使うメリットはそこまで無いので再検討するとよいかと思います。

デフォルトストレージにfile/persistent指定している場合
このケースでは2パタンあります。
vcl_backend_errorでTTL/grace/keepを明示的に指定していて、合計値がshortlived以上の場合はvcl_backend_errorに突入した場合は引っかかります。
次にTTLの変更もなく、shortlivedの変更もない(default 10sec)場合で同一リクエストがラッシュしてる状態だとTTL合計値がshortlivedを超えるためvcl_backend_errorに突入した場合は引っかかります。

なお、同様にsyntheticオブジェクトを生成するvcl_synthのほうは影響を受けません。
vcl_synthの場合はここでmemcpy(3)していますが、resp.bodyのlength(szl)を利用していますので問題ありません。

暫定の回避方法

それぞれの設定によって違いますが、説明したとおりとりあえずvcl_backend_errorでfile/persistentストレージが使われなければOKです。
なのでvcl_backend_errorもshortlivedもいじってない場合はshortlivedを12sに設定するかvcl_backend_errorでのTTL/grace/keep指定を明示的に0にすると良いでしょう。
なお、shortlivedを引き上げた場合はメモリの使用状況が変わるのでモニタリングする必要がありますし、TTLを明示的に0にした場合は若干ラッシュ時のオリジンの耐性が弱くなるでしょう。(気にするレベルではないと思います)
まぁどちらにせよ早めにアップデートするのが良いでしょう。

そのほかの変更について

今回はこの脆弱性以外にもいくつか修正があります。

4.1.9/5.2.1共通の変更
バグ修正

クライアントからのリクエストが空っぽの場合にidle_timeoutが正しく使用されなかったのを修正(#2492)

4.1.9のみの変更

基本的に5.2.0にあるもののbackportになります

新機能

bereq.is_bgfetchの追加
req.ttlの評価変更(#2422)

バグ修正

いくつかのカウンタの修正