8月 032017
 

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

今回のリリースはセキュリティ対策によるもので、なるだけ迅速にアップデート、もしくは回避VCLを入れるべきです。
詳しくは公式情報(VSV00001)を参照してください。

脆弱性の内容

書くか少し迷ったんですが、テストコードがまんまコミットされてるので解説します・・
クライアントがTransfer-Encoding: chunkedかつchunk-sizeの指定が内部の変数でMSBが立つぐらい大きな指定でリクエストを行った場合、AssertでVarnishが再起動します。(DoS)


$ sudo varnishadm panic.show|head
Panic at: Wed, 02 Aug 2017 16:45:40 GMT
Assert error in v1f_pull_chunked(), http1/cache_http1_vfp.c line 172:
  Condition((vfe->priv2) == 0) not true.
version = varnish-5.1.2 revision 6ece695, vrt api = 6.
0
...

対象バージョン

Transfer-Encoding: chunkedに対応したのは4.0.1からなのでそれ以降のすべてのバージョンになります。

バージョン系 影響を受けるバージョン 修正バージョン パッケージDL先
4.0.x 4.0.1~4.0.4 4.0.5 link
4.1.x 4.1.0~4.1.7 4.1.8 link
5.0.x 5.0.0 5.1.3まで上げる必要があります link
5.1.x 5.1.0~5.1.2 5.1.3 link

4.0.0及び3.0.xは先に述べた通り、そもそも今回の機能がないので対象外です。

回避方法(VCL)

VCLでの暫定的な回避方法があります。
これらはchunkedでリクエストしてきたものを503で落とすことで回避します。
大抵のブラウザの場合はTransfer-Encodingを指定したリクエストは行わない事が多いのですが、念のため確認すると良いでしょう。
varnishlog -cq ReqHeader:Transfer-Encoding -i ReqMethod -i ReqURL
これで何かしら出てきて、正規のリクエストの場合はVCLでの回避は不可能ですのでバージョンを上げる必要があります。

なおここで紹介している回避コードは公式そのままなので公式も参考にしてください(VSV00001)
4.0.x向け
インラインCを利用します。
そのためvcc_allow_inline_cをtrueに設定する必要があります
起動パラメータの場合は
-pvcc_allow_inline_c=true
varnishadmで指定する場合は
varnishadm param.set vcc_allow_inline_c true
で可能です
VCLは以下の通りです。


sub exploit_workaround_4_0 {
        # This needs to come before your vcl_recv function
        # The following code is only valid for Varnish Cache and
        # Varnish Cache Plus versions 4.0.x
        if (req.http.transfer-encoding ~ "(?i)chunked") {
                C{
                struct dummy_req {
                        unsigned magic;
                        int restarts;
                        int esi_level;
                        int disable_esi;
                        char hash_ignore_busy;
                        char hash_always_miss;
                        void *sp;
                        void *wrk;
                        int req_step;
                        struct {
                                void *a;
                                void *b;
                        };
                        int req_body_status;
                };
                ((struct dummy_req *)ctx->req)->req_body_status = 6;
                }C

                return (synth(503, "Bad request"));
        }
}

sub vcl_recv {
        # Call this early in your vcl_recv function
        call exploit_workaround_4_0;
}

4.1.xと5.0.0向け
同じくインラインCを利用しますので4.0.xで有効にしたように指定が必要です。


sub exploit_workaround_4_1 {
        # This needs to come before your vcl_recv function
        # The following code is only valid for Varnish Cache and
        # Varnish Cache Plus versions 4.1.x and 5.0.0
        if (req.http.transfer-encoding ~ "(?i)chunked") {
                C{
                struct dummy_req {
                        unsigned magic;
                        int step;
                        int req_body_status;
                };
                ((struct dummy_req *)ctx->req)->req_body_status = 5;
                }C

                return (synth(503, "Bad request"));
        }
}

sub vcl_recv {
        # Call this early in your vcl_recv function
        call exploit_workaround_4_1;
}

5.1.x向け
インラインCは使用しません。


sub vcl_recv {
        if (req.http.transfer-encoding ~ "(?i)chunked") {
                return (fail);
        }
}

その他

実は今回の脆弱性について概要と公開予定時刻を事前に教えてもらっていました(攻撃には使用できないぐらいの荒い情報ですが)
お陰で公開後に即対処することが出来たのですが、VIVU – Very Important Varnish Usersで触れられているようにVMLを買うと教えてもらえるようなので(€1000/年)
企業でVarnishを使っているころは買ってみると良いのではないでしょうか?


8月 022017
 

4.1系の最新版の4.1.7がリリースされました。 [changelog] [公式のリリース文] [パッケージDL] [ソースDL]

今回の変更内容は主にstat周りのバグフィックスとちょっとした新機能です。

追加された機能

varnishncsaのLogFormatの%{X}xでrecord-prefixに対応しました(#2077)

もともとは%{VSL:tag[field]}xといった感じで指定できたのですが
Timestampのように同一タグでラベルで情報が違うものの場合、例えばProcessにかかった時間、Fetchにかかった時間を分けて出力することは不可能でした。(最初にマッチしたものだけなのでStartになる)
しかし今回record-prefixに対応したため、vsl-queryのような柔軟な指定が可能になりました。(levelには対応してないですが)


$ sudo varnishncsa -F "fetch:%{VSL:timestamp:fetch[2]}x process:%{VSL:timestamp:process[2]}x"
fetch:0.000377 process:0.000392

バグ修正

workspaceのリセット漏れでworkspaceが足りなくなって落ちるケースがあったのを修正(#2219)

varnishstatでのglobを修正(#2022,2118,2320)
あまり知られていないオプションだと思うのですが -fでstatのキー絞り込みをできるのですが
-1との組み合わせの時にうまく動かないバグがいくつかあったようです。

nuke_limitの効果がなくなっていたのを修正(#1764)

ヘルスチェックの応答でReason-Phraseがない場合に失敗していたのを修正(#2069)

(ちなみに今回記事が遅れたのは前の記事書いたあとあたりに書いたつもりになって忘れてました・・すいません)


7月 022017
 

最近何かと話題なCDNですが、そもそもCDNってなんだろう・・・どんなことに使えるんだろう?的なことを書いてみようと思います。
一応先に言っておくと、私はCDN業者に所属したことないのであくまでも利用者として見た時の話を書きます。
また、私の考えであり、様々なワークロードがあるなかでこれがすべてではありませんので、こんな考えもあるんだなぁぐらいに思ってもらえると助かります。

そもそもCDNってなんだろうか

そもそもCDNはContent Delivery Networkの略であってCache Delivery Networkの略ではありません。
要はコンテンツをクライアントに対して高速・効率的に配信するためのネットワークです。
良くCDNといえばその成り立ちからキャッシュというイメージはありますが、重要な要素の一つではあるもののCDNの全てではありません。
さらに言えばAkamaiのIntelligent PlatformやFastlyのEdge cloud platform、CloudFlareでは特に特有の名前はついていませんがOur platformと表現していますが、要はCDNはプラットフォームなのです。その上にキャッシュや後述する様々な機能などがアドオンされていると思ってもらえると良いかと思います。

CDNの作り方

CDNの正体とはなんでしょうか?
割とブラックボックスのように思えると思いますが、実はProxy(配信サーバ)とGSLB(広域負荷分散)を基本としたものです。
なので言ってしまうと、AWSのRoute53のGeo, Latencyベースのルーティングを使い、Proxyをいろんなロケーションに立ててしまえばあなたもCDN管理人に!
そして、作ったプラットフォーム上にサムネイルサーバを置いてしまえば動的サムネイル生成機能が付いたCDNになりますし、エンコーダーを・・・WAFを・・・みたいな感じにどんどん機能が増えていきます。
誤解を恐れずにすごく乱暴に言ってしまえば、これを大規模にやっているのがいわゆるCDN業者といえるでしょう。
また、既存CDN業者にない機能を使いたい、より柔軟性がほしい、そもそも大トラフィック扱っていて自社のほうがコストメリットがあるなどで自前でCDNを作る人達もいます(DIY CDN)。もちろん全部置き換える人もいればハイブリッドで使う人達もいます。
例えばSpotifyは去年から自社でCDNを立ち上げてハイブリッドで使っているようです。(Spotifyの日本インフラ)

CDNの機能について

さて、ここまででCDNはプラットフォームでそこにキャッシュなどの様々な機能をアドオンしたものとわかったと思います。
では、CDNにはどんな機能があるでしょうか?メジャーなものを幾つか紹介します。
当然ですが、ここで紹介する機能すべてがどのCDNにもあるもものではなく、あったりなかったりします。(なのでうまく選んでいきたいですね)

キャッシュ
やはりCDNといえばやはりキャッシュ!
貴方が例えばスマホゲームのインフラ担当の場合、当然アセットのダウンロードをどうしようか考えると思います。
リリース時に一気にダウンロードが走り、そのトラフィックは数百Gbpsを超えるケースもあります。
リリース時や更新時の一瞬のためだけにインフラを構築して・・・というのはやはり無駄があります。
ここでCDNの出番です。
アセットは一般的には静的なコンテンツなので、キャッシュが可能です。
まずユーザがリクエストしてきたら、最寄りのCDNのエッジサーバにリクエストがされます。
キャッシュがあればそこでレスポンスしますし、なければ自社のサーバ(オリジンサーバ)に対してリクエストが着ます。
このことによりオリジンサーバへのトラフィックを減らすことができ、なおかつユーザーからみるとネットワーク的に近いエッジサーバから取得することでより快適にダウンロードすることが出来ます。
そして一般的にCDNの帯域キャパシティは大きく、また大抵の場合は配信サイズ(1GBあたりいくら)での課金になります。
なので100Gbpsでようが1Gbpsだろうがピークをあまり気にせずユーザにアセットを配信することが出来ます。
(※CDN業者によってはQuotaが設定されていたりするので予め予定がわかってる場合は相談しておくと良いかと思います)
また他にも動的コンテンツのキャッシュもありますがあとで触れます。

Dynamic site acceleration(DSA)
キャッシュ以外での高速化を行う技術群です。
AkamaiFastlyではそれぞれDSAという名称で提供されており、他のCDNでも特に明記はしてないもののなんらかのDSA技術を使っているところがほとんどだと思います。
ざっくり触れていきます

・コンテンツのgzip圧縮
圧縮することで転送サイズを小さくして高速にします。

・エッジでのTLS終端
ユーザに近いエッジ(レイテンシが小さいことが期待できる)でTLS終端を行うことでハンドシェイクを高速に行えます。

・TCPの最適化
モバイルNW向けにリアルタイムにパラメータ調整したりするものや
エッジからオリジンまでを最適化したりなど
様々な手法でいろいろあります。

・コンテンツのプリフェッチ
ベースページを通した場合、その中に含まれる画像などのコンテンツをプリフェッチしてエッジを温めておくことでRTTを小さくします。

・ルーティングの最適化
インターネットは様々な経路でつながっています。
そのため、物理的な直線距離と実際の通信経路での距離は違い、またレイテンシも変わってきます。
身近な例で例えるのであれば、自宅のISPを乗り換えたら経路が変わって特定のサイトが早くなるということも十分考えられます。
その経路を最適化するのがこの技術です。代表例だとAkamaiのSureRouteが有名です。

他にもあると思います。
ちなみにsonotsさんがDSAについてふれて、実際にベンチをとっておられました(CloudFrontをかますとキャッシュなしのAPIコールでも速くなるようだ

Front End Optimization(FEO)
クライアントが実際にページを表示するにはざっくりいって

  1. ベースページのDL
  2. CSSやJSなどのリソースをDL
  3. ページレンダリング

といった流れがあると思います。
DSAの場合ではあくまでも配信を改善することが目的なので1,2でのネットワーク通信部分しか改善することはできません。
コンテンツに対してはほぼ手を加えず、手を加えるにしてもgzip圧縮程度で非可逆的な処置は行いません。(その分安全とは言える)
しかしサイトがリッチになるにつれ、ページレンダリングの処理が重くなっていき、場合によっては秒単位でかかるページも存在します。
じゃぁ、そこに対して何かできることはないか?というのがということで出てきたのがFEOです。
すごくざっくりいうとコンテンツの改変を含む最適化行うことで速度を向上しようと言うものです。
mod_pagespeedのようなことをCDNでやると言った感じです。
ベースページを解析して改変したり、画像をより圧縮効率の高い別フォーマットや端末に合わせた変換をしたり、CSSを調整したりなどいろいろ行います。
近年デバイス数も増えているので(モバイル)、すべての端末に対して最適化を手作業で行うのは難しいですが、CDN業者がその辺をメンテしてくれるのであれば楽ということです。

セキュリティ
最近は様々なサイバー攻撃が存在し、それをガードするためにCDNを入れることもあります。
DDoS対策やWAF、bot対策など様々なメニューがあります。

一つ注意としてですが、オリジンドメインがバレると直接そこにアタックされてどうしようもなくなったりするので
アクセス元を制限したり、類推しづらい名前にしとくのがよいかなとか・・(オリジンがわかりやすい名前でオリジンにアタックきたという事例を聞いたことあるので・・・)

最新技術への対応
最近であればHTTP/2など、様々な技術が出てきます。
しかし自前でやるには難しい環境の場合もあるかと思います。その場合でもCDNがproxyしてやってくれたりすると最新技術を利用でき、またそれで高速化のメリットを享受できます。

その他
他にもいろんな機能があります。
ビデオストリーミングや
機械学習でページ中の動的・静的部分を判別して静的部分をフラグメントキャッシュするもの
エッジ側にコードがおける(FastlyのVCL,CloudFrontのLambda@Edgeなど)などいろいろあります。

ユーザ情報を含むなどキャッシュできない動的なコンテンツをCDNに通すべきか通さないべきか

答えはどちらでもないだと思います
通す・通さないべきではなく、必要であれば通せばよいとおもいます。
先に触れたとおりCDNにはいろんな機能があります。
同一ドメインにある静的コンテンツをキャッシュしたいから通すみたいなケースが多いと思いますが
そもそも仮に全部キャッシュが出来なかったとしても、他の機能(セキュリティ対策など)が有用であれば使えばいいという単純な話だと思います。

しかし、動的コンテンツをCDNに通すのを気にしている人で個人情報が・・セキュリティが・・と気にする人がいます。
実際にCDN業者でもいろんな事件がありました
CloudFlareでのデータ流出バグ
CDNetworksのコンテンツ改ざん
このようなものは外部業者を使う以上一定のリスクは存在しうると考えていますので
結局ここは信用できるところを選ぶといった対策しかできません。
※CloudFlareやCDNetworksの事故を取り上げてますが、この2社が現在もヤバイとは思っていません。例えばCloudFlareはFBIに使用されてますし、CDNetworksはログインドメインは使用していないもののりそな銀行が使用しています。
しかし、これをもってそもそも使うのがNGというのは個人的には賛同できません。
多くのCDNではPCI DSSに対して配慮しているものがありますが(Akamai Fastly CloudFront CloudFlare CDNetworksなど)
そもそも動的コンテンツを流すことが想定されていなかったりすればこのような対応はされていないと思います。
参考程度ですがCitibank・USBank・ジャパンネット銀行・マネックス証券はログインフォームも含めてAkamaiを使用していました。


$ nslookup online.citi.com
Server:         127.0.0.1
Address:        127.0.0.1#53
Non-authoritative answer:
online.citi.com canonical name = online.citibank.com.edgekey.net.
online.citibank.com.edgekey.net canonical name = e11515.b.akamaiedge.net.
Name:   e11515.b.akamaiedge.net
Address: 184.30.150.120

$ nslookup onlinebanking.usbank.com
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
onlinebanking.usbank.com        canonical name = onlinebanking.usbank.com.edgekey.net.
onlinebanking.usbank.com.edgekey.net    canonical name = e3644.ksd.akamaiedge.net.
Name:   e3644.ksd.akamaiedge.net
Address: 104.78.14.64

$ nslookup login.japannetbank.co.jp
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
login.japannetbank.co.jp        canonical name = login.japannetbank.co.jp.edgekey.net.
login.japannetbank.co.jp.edgekey.net    canonical name = e2588.ksd.akamaiedge.net.
Name:   e2588.ksd.akamaiedge.net
Address: 184.26.113.168

$ nslookup mst.monex.co.jp
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
mst.monex.co.jp canonical name = mst.monex.co.jp.akadns.net.
mst.monex.co.jp.akadns.net      canonical name = mst.monex.co.jp.edgekey.net.
mst.monex.co.jp.edgekey.net     canonical name = e4187.g.akamaiedge.net.
Name:   e4187.g.akamaiedge.net
Address: 69.192.239.108


このことからわかるように銀行や証券のようなすごくセンシティブな情報を扱うサイトでもCDNをつかうのはナンセンスではないということです。
結局のところは得られるメリットに対して、どれだけリスクやデメリットの程度、そして見合うコストなのかの話で、そもそもNGというわけではないです。(もちろん正しく使えればという話ですが)
例えば、2017年の今にクラウドはセキュリティが怖いからオンプレミスにするとか言うエンジニアがいたら割と?と思われると思いますが(たまにTwで見かけますが・・)
これはCDNでも同じ話が言えると思います。
CDN業者のセキュリティが怖いから自社でCDN作るぞ!みたいなこと言ったら流石にあなた達はAkamaiやAWSたち以上のことできるんですか・・・と思ってしまいます。
CDNであまりこういう意識がないのは単純な話としてCDNを使うエンジニアの母数が少なく、その辺の声が小さいんだろうなーとか考えています。

キャッシュをする、そしてしない

キャッシュを行うときに注意するべきなのはなんでしょうか?
一番重要なことは、一つのキャッシュするオブジェクトに対して複数の意味を持たせないということです。
例えばhttp://example.net/mypageというページがあって、仮にこれをキャッシュするとします。
名前からわかるようにこれはユーザ情報を含んでいます。
通常CDNなどでキャッシュする際はホスト名とパスが指定されますので
それに基づくとキーは「example.net + /mypage」になります。
しかしこれだとユーザ情報をキーに含んでいないため、Aさんが見た後にBさんが見た場合、Aさんのmypageが出る可能性があります。
この場合はmypageはAさんのページでもあるしBさんのページでもあるという複数の意味をもたせたから起きることです。
正しいキーは「example.net + /mypage + Aさん」といったようになります。
これはCDNでのキャッシュに限った話ではなく、例えばmemcachedやRedisといったKVSでも言えますし、他のキャッシュと呼ばれるものでも共通の考え方だと思います。
ちなみにこれはログイン情報に限った話ではなく、例えば、同じ画像URLでSP向けとPC向けでサイズやフォーマットが違う場合も、同様にキーにSPなのかPCなのかを含める必要があります。
また、クッキーの取扱にも注意が必要です。
2014年にキャッシュ設定の不備からクッキーが他ユーザに共有されてしまったというサイトがありました。
当事者じゃないので推測しかできませんが、おそらくキャッシュしたオブジェクトにSet-Cookieがついてしまってそれが使いまわされてしまったんだろうと考えています。

これは私がキャッシュを行うときの基本的なポリシーを図にしたものです。
必ずしもこれがすべてのワークロードで正しいものではありません。
例えば元々オリジンが静的コンテンツを置いているだけであればここまで複雑なルールは必要ないですが
動的・静的・かつユーザ情報含む全コンテンツを通すケースにおいては、参考にはなると思います

※この図では先程ふれたSP/PCで違う場合といったものはいれてませんが、そういうものがあればキーに含めてください。
※ユーザ情報はクッキーに含まれていることを想定しています。なにか他のものに入ってる場合は適宜消す対象を含めてください(POSTとか注意したいですね)
※他にキーに設定したりするものとしてはCORSのOriginヘッダなどがあります。これはVaryで分けてもいいですが、オブジェクト自体が変わらないのが通常なので、CDNに条件付きでレスポンスヘッダのみの書き換えができるのであればでそちらで行うのが良いでしょう。(AkamaiやFastlyなどはできる)

まず、キャッシュをしない場合は必ずCDN側で抑制すべきと考えています。(もちろんCache-Controlを適切に設定するのも大事です)
CDN業者によって挙動が変わるため、確実なのはCDNの機能で「確実にキャッシュさせない」ことです。
なぜかというと単純な話として出口で対策をかけるのが確実だからです。
また、基本的に静的コンテンツしか載せないのであればブラックリスト方式(基本キャッシュで一部非キャッシュ)でもいいとおもうのですが、
サイトすべてをCDNに流していて、そこにユーザ情報を含むもの(キャッシュ・非キャッシュにかぎらず)があるのであればホワイトリスト方式(基本非キャッシュで一部キャッシュ)で運用すると安心できると思います。

また、私はあまりオリジンのアプリを信用していません。
例えばこのパスはキャッシュが可能でユーザ情報によって変化もしないのが確実だからCookieをオリジンに送信するとします。
しかし実際はアプリはCookieからユーザ情報を読み取って、ユーザ毎にコンテンツを生成していたらどうなるでしょうか? 流出です。
なので、大事なのはキャッシュをする場合はオリジンに対して不要なキー(Cookie)は送信しないということです。
Cookieを消しておけば、仮にアプリがユーザ情報を元になんらかのコンテンツを生成しようとしても情報がないので、ログインページに飛ばされるなどクリティカルではない障害ですみます。

また、同様にSet-Cookieも消すべきです。
これはセッションが共有されるのを防ぐというのが主目的です。
ではなぜキャッシュキーにユーザ情報が含んでる状態でも消すのかというと、cookieのexpiresの問題や何らかのワンタイムトークン的なものが含まれていたりとか合ってよくわからない動作になるのを防ぐためです。

TTLの取扱に気をつけよう
静的コンテンツの場合は、頻繁に同一URLでのコンテンツの差し替えがないのであればTTLはあんまり気にする必要はないのですが(その場合もpurgeすればいい話ですが)
動的コンテンツの場合はモノによっては注意が必要です。
例えば5分毎に更新されるコンテンツが1日もキャッシュされたら困るからです。
静的・動的と言ってますが要はそのURLで別のコンテンツが提供される可能性があるのであれば(差し替えであれ、定期更新であれ)TTLには注意しようと言う話です。
ここはそのコンテンツの特性に応じていろんな考え方があるのでこれで確実といったのは特にないので・・・(例えば必ず毎時0分に切り替わる必要があるとか、多少ぶれてもいいものとかいろいろある)

また、テストも重要です。
CDNのテストってどうやるんだ・・・という話もありますが、大抵の場合はそのオブジェクトがキャッシュ対象かなどをレスポンスヘッダで教えてくれたりします。(Akamai)
なのでその辺を元にテストを行えば問題ないでしょう。
テストパタンを考えるのはそんなに難しくはないと思います。
ユーザ情報を含むページも通してるのであれば、複数ユーザ+非ログインユーザで条件が変わるパス毎にテストをすればよいだけです。
そして意図した動作(Cookie/Set-Cookie消してるとかキャッシュしてるしてないなど)になっているかの確認をしましょう。
先程の図では触れていませんがキャッシュのテストをするときは、割とTTLも重要なので動作確認をしましょう。
TTLの動作(特に切れた場合)もCDN事業者によっていろいろあると思いますし設定可能です。
すべてのパタンではないですが、例をあげると
TTLが切れた場合でオリジンがエラーを吐いている場合はとりあえず古いオブジェクトを使う
という動きをするケースもあります。(Optionでなど)

ここまで言っておいてなんですが、すべてのCDN業者でこれほど自由度のある設定ができるとも限りません。
特にユーザ情報を含むコンテンツをキャッシュする際はかなりキャッシュキーに注意する必要がありますので、CDN業者でここまで設定できないのであればユーザ情報を含むコンテンツのキャッシュは辞めるべきです(そもそもやって意味があるのか的な話は後で触れます)、単純にキャッシュせずに通すだけであれば問題ないです。
他にも、CDN側で抑制出来ないパタンがあるのであれば、オリジン側のProxyでリクエスト・レスポンスヘッダのクレンジングするのも良いと思います。(同様のルールでクッキーの制御を行う)
オリジン側のProxyでクッキーを消していれば仮にCDNでキャッシュされても、やっぱり非ログインページになるのでそこまで痛くないでしょう。(もちろん2重管理にはなるのと完全にはカバーできないのでその辺は注意)
その場合はよりCDNの仕様を注意深く読み・問い合わせ、入念にテストを行うべきです。

あと、この辺の複雑な制御はFastlyがCDNの中でもトップレベルと考えています。
理由としてはVCLというDSLでキャッシュするしないの制御を始め、クッキーからユーザ情報を抽出してそれをキャッシュキーに設定するなど細かい制御が可能なためです。
例えば/admin/というページはキャッシュをしてはならないというルールをVCLで書くと


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

と言った感じになります。

ユーザ情報を含む動的コンテンツを効率的にキャッシュするには

ユーザ毎に生成されている動的コンテンツをキャッシュするのは難しいですし、普通はやりません(ある意味最終手段とも言える)
難しい理由としては

  • キャッシュ混じりなどが起きないようなキーの管理
  • ページを構成する要素のTTLが同期していない
  • そもそも再利用されるのかという話

こんな感じです。
キャッシュを行うのは何度も使用されることを期待して行うものですが、当然ながらユーザ毎にキャッシュしたものはユーザしか使用しません。
TTLが同期していないという話は例えば、ページ中に5分おきに更新される直近売れたものリストとユーザの行動によって変わるレコメンドがあるなど(これならXHRでやればいいという話もありますがたとえなので)
一体どれだけのTTLでキャッシュすればいいかがわからないケースです。
じゃぁどうすればいいかというと、少し発想を変えてみて部品単位で考えてみることです。
そうすると案外再利用されたりするものがあったりします。
もちろんそのための規格もあって、Edge Side Includes(ESI)というものです。
ざっくりいうとフラグメントキャッシュ持っておいてCDN側で結合する感じです。
これにより

  • 部品単位で適切なTTL、更には適切なキーでキャッシュできる(そのページがユーザ毎だったとしても部品がユーザ毎とは限らない)
  • 再利用しやすい

というふうになりますので
うまくすれば通常のサイトの場合は半分以上は実際キャッシュできると思います。
もちろんキーの管理とか難しいですし、バラバラに分解した上でユーザ情報を含まないところだけでキャッシュするだけでもかなり効果があります。
また、ゲームのようなあまり向かないものもありますのできちんとワークロードにあってるかは検討する必要がありますが良いかなと思います。
かなり昔に書いた資料ですが参考になるかもなのでよかったらどうぞ(VarnishではじめるESI)

まとめ

ここまで割とCDN使っていこうぜてきなノリで書いてきましたが、それぞれメリットやデメリット等いろいろあります。そしてもちろんコストもかかりますので、見合う見合わないを適切に判断した上で楽になれば使えば良いと思いますし、そうでないなら使わなければ良いと思います。
しかし動的コンテンツをそもそも流すのはナンセンスではないということは理解していただけたんじゃないかなーとか思います。
CDNも結局一つのレイヤーとして考えてもらって、もし貴方がCDNが解決できるかもしれない課題で悩んでるのであればこの記事が助けになれば良いかと思います。


 Posted by at 2:23 AM
5月 052017
 

4.1系の最新版の4.1.6がリリースされました。 [changelog] [公式のリリース文] [パッケージDL] [ソースDL]

今回の変更内容は基本的にバグフィックスと5.1.1/5.1.2で追加された一部機能の取り込みになります。

追加された機能

すべて5.1.1/5.1.2で追加された機能です。
vmod_stdにgetenv(STRING)の追加
varnishtestにsetenvの追加
vsl-queryでvxid指定が可能に

バグ修正

Ageレスポンスヘッダの数値が実経過時間より大きな数値になるケースがあったのを修正しました(#2216)
Ageレスポンスヘッダの小数点の丸めをsprintfの%.0fで行っていたため、実際の経過時間より大きな数値になることがあったんですが
切り捨て(floor)を行うようにした感じです。

std.querysortでワークスペースが足りない際にpanicしてたのを修正しました(#2233)

稀にVSLを利用するクライアントが初期化に失敗するケースがあるのを修正しました(#2241)
あくまでに初期化時(VSM_Init)のみなので稼働中のクライアントの場合には問題ありません。
自分もVSLを利用するクライアントを作ったり使ってますが遭遇したことがないのでかなり稀だと思います。

クライアントワークスペースの空き容量が少ない場合にチャンク転送を行った場合にpanicしていたのを修正しました(#2275)

動的バックエンドを利用している場合で稀に子プロセスが再起動するのを修正しました(#2295)


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としてます

(2017-08-09追記)なお、記事中で修正箇所の記載がありますが、masterのものを使用する場合はまだpkg-varnish-cacheが追い付いてないことが多く当時は修正が必要だったということです。
バージョンにより修正が不要になったり、また別の修正が必要になったりするので注意が必要です。

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 dh-systemd 
(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の購入を検討してくれると嬉しいなと思います。