xcir

\いわなちゃん/

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

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


12月 022016
 

4.1系の最新版の4.1.4がリリースされました。 [changelog] [公式のリリース文]
主にバグ修正とドキュメントの改善ですが
1件すごくタイムリーなバグ修正が存在します。
また、今回のリリースで4.0.xがEOLになりました。

バグ修正

バックエンドの現在の接続数のカウンタが機能していないのを修正 #2011

ラウンドロビンDirector利用時にクラッシュすることがあるのを修正 #2024

フェッチ時にワークスペース・ストレージ確保失敗するとリークする可能性があったのを修正しました

return(pipe)時にstd.cache_req_bodyが機能しないのを修正 #1881

vmod_named利用時にVBE_Deleteでpanicすることがあるのを修正しました #2008
コードの修正を見た感じnamedに限らずに動的にバックエンド定義をUpdateしていくようなDirectorの場合に問題が起きそうです。

エラーなどで起動できなかった場合にvsmファイルが残っていたのを修正しました #2115

keepalive時に複数のrangeリクエストが来た場合に先頭リクエスト以外をしばらくブロックすることがあるのを修正しました #2035

バックエンド選択時に稀にpanicするのを修正しました #2027

時刻が逆行(STEP)した場合に子プロセスが再起動する場合があるのを修正しました #1874
うるう秒もありますのでこのあたり注意しておいたほうが良いと思います。
条件としてはセッションがクローズ(close)されるタイミングの時刻がセッション開始時刻(open)が早くなっていた場合です。
Varnishのこの時間の精度はナノ(clock_gettime/CLOCK_REALTIME)もしくはマイクロ秒(gettimeofday)なので
うるう秒で23:59:58->59->59->00のように59を繰り返す場合(STEP)でリクエスト自体を1秒未満で処理していると引っかかる可能性があります。
そのため、今回のうるう秒でSTEP動作をする可能性がある場合は強くアップデートを勧めます。
なお5.0.0も影響を受けます。今のところ5.0.1がリリースされる気配はないのでSLEWで動かすか独自で現時点のmasterのものをビルドするかをおすすめします。(4.0.xは影響なさそうです)
また、この問題の解決のためにclock_stepといパラメータが追加されています。

機能強化

varnishhistの表示にy軸が追加されました
5.0.0のhistと同じ表示です。

仕様変更・追加

varnishtestで外部のコマンドを実行するfeature cmdが追加されました。
すでにあるshellコマンドとの違いですが、shellの場合はその実行結果でテストが成功・失敗しますが
feature cmdの場合はテスト自体が実行されるかされないかです。
混在環境(OSやCPU)で特定環境のみのテストを行う際に便利に使えると思います。

varnishtestのresp.msgはresp.reasonに変更されました

clock_stepが追加されました
時刻同期で一気に時刻が変更(STEP)された場合の許容秒数です。
デフォルトは1秒です。

thread_pool_reserveが追加されました
重要なタスクのために予約されるスレッド数です。
例えば、クライアントの処理でスレッドを使い尽くした場合でもreserveされているスレッドがあればバックエンドの問い合わせが可能です。
この値は引き上げるとクライアント処理で使われるスレッド数が少なくなるため注意する必要があります。
あくまで一気にリクエストが来てスレッドが足りなくなった場合に発動するものなので、普段から適切なスレッド数を指定していればそこまで問題無いと思います。
デフォルトは0(auto)で、thread_pool_minの5%が自動的にセットされます。

パッケージ構成が変更されました
5.0の構成とおなじになりました。(varnish = varnish + libvarnishapi + varnish-doc)
そのためそのままapt-get installで更新しようとすると失敗します。
一度4.1.3までのバージョンをremoveしてアップデートするのをおすすめします

その他変更

ドキュメントの改善やRaceコンディションで起きる幾つかのバグが修正されています。


10月 032016
 

二年ぶりぐらいにメジャーバージョンが更新されました。[公式リリース] [ダウンロード]
今回の目玉機能としてHTTP/2対応とDirectorのConsistent Hash対応があります。
なにはともあれ、とりあえずHTTP/2を使うセットアップをしてみましょう。

Hitch1.4.0+Varnish5でHTTP/2を使う

Varnishはよく知られてるようにHTTPSに対応していません。
HTTP/2に対応したこのバージョンにおいても、やはり本体では対応をしていません。
そのため通常のブラウザからVarnishに対してHTTP/2にアクセスするにはTLSを解かなくては行けません。
今回はVarnishSoftwareが提供しているHitchを利用します。
なお、HTTP/2を利用するには1.4.0以降のバージョンが必要です。
また、一部ディストリビューションではパッケージが提供されていますが、私が利用しているUbuntu 16.04向けのパッケージはなかったのでソースからbuildしていきます。

Hitchのインストール
公式手順を参考に行います


sudo apt-get install libev-dev libssl-dev automake python-docutils flex bison pkg-config
git clone https://github.com/varnish/hitch.git
cd hitch
./bootstrap
./configure --prefix=/opt/hitch/
make
sudo make install

これだけです。(prefixはお好きなところにでも)
なお、HTTP/2を使うにはNPNもしくはALPNのサポートが必要です。
NPNはOpenSSL1.0.1、ALPNの場合は1.0.2が必要ですが、既にChromeでは今年の6月ごろにNPNでのHTTP/2サポートは打ち切られALPNのみでのサポートとなりました。
そのため実質HTTP/2を利用するにはALPNをサポートしている必要があります。
とりあえず確認する程度であれば、FireFox49はNPNでも接続できるので1.0.1でも問題はないです。

Hitchの設定


HTTPS:443           HTTP:80
   ↓               ↓
Hitch:443--(localhost:81)-->Varnish:80
                 ↓
                Origin

今回はこんな感じで構成します。
まずはHitchの設定です。


#/etc/hitch/hitch.conf
frontend       = "[*]:443"
ciphers        = "ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:EECDH+HIGH:EDH+HIGH:HIGH:+3DES:!RC4:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5:!DH"
backend        = "[127.0.0.1]:81"
write-proxy-v2 = on
workers        = 2
backlog        = 1024
keepalive      = 3600
syslog         = on
user           = "nobody"
daemon         = on
alpn-protos    = "h2,http/1.1"
pem-file       = "/etc/hitch/******.pem"

ciphersとかは割と適当ですがきちんと設定したい人はこちらで考えると良いかと思います。
なお、hitchのpem-fileは
秘密鍵 + 証明書 + 中間CA証明書 + dhparam
です。
Let’s Encryptでやる場合は
privkey.pem + fullchain.pem + dhparam
になります。
また、どうやって複数のvhostsに対応するのかというとpem-fileを列挙します。
ちなみにデフォルトのものは最後に定義されたものになります。
Hitchの起動


/opt/hitch/sbin/hitch --config=/etc/hitch/hitch.conf

次はVarnishの設定です。
Varnishの設定
まずはListenの指定をしましょう。
ポート80で通常のHTTPを受付して81でPROXYプロトコルで受付します。
そのための設定はこのような感じです。


-a :80 \
-a 127.0.0.1:81,PROXY \

また、HTTP/2はデフォルトでは有効にされていません。
そのためfeatureでhttp2を指定する必要があります。
varnishadmから設定する場合は


root@proxy02:~# varnishadm param.set feature +http2

root@proxy02:~# varnishadm param.show feature
feature
        Value is: +esi_disable_xml_check,+http2
        Default is: none

        Enable/Disable various minor features.
           none                       Disable all features.

        Use +/- prefix to enable/disable individual feature:
           short_panic                Short panic message.
           wait_silo                  Wait for persistent silo.
           no_coredump                No coredumps.
           esi_ignore_https           Treat HTTPS as HTTP in
                                      ESI:includes
           esi_disable_xml_check      Don't check of body looks like
                                      XML
           esi_ignore_other_elements  Ignore non-esi XML-elements
           esi_remove_bom             Remove UTF-8 BOM
           https_scheme               Also split https URIs
           http2                      Support HTTP/2 protocol

です。
起動パラメータで指定する場合は


-p feature=+http2 \

を追加すれば良いです。
これで動作します。

なお、後述しますが絶対本番環境では行わないでください。

それでは、HTTP/2での動作のさせかたを紹介したところで、いつも通り新機能などの紹介を行います。

全体的な話

過去のメジャーバージョンアップを振り返ってみると
2.1->3.0

  • VMODのサポートとVCL文法の整理

3.0->4.0

  • リクエスト・フェッチ・レスポンスといった一連の動作をClient/Backendスレッドに分割
  • VCLにバージョン番号付与とスレッド分割に対応するための変更

4.0->4.1(マイナーバージョンアップですが)

  • PROXYプロトコルのサポート
  • 内部処理にフックできるようなしくみの追加

4.1->5.0

  • HTTP/2の実験的サポート
  • shard director(consistent-hash)対応
  • バックエンドフェッチがPROXYプロトコルサポート
  • 別VCLを動的リンクして使うlabel機能のサポート

こうしてみると4.0の頃からHTTP/2の準備を着実にすすめていたのがわかるかと思います。
また、嬉しいことに5.0では3.0->4.0のときのような破壊的なVCLの変更はありません。
もちろん幾つかのVCLの変更はあります。
しかし大抵の場合は変更せずとも動き、もしくは少しのreplaceで済んでしまいます。
また、statsの項目も削られたものはあるものの出力フォーマットは変わっておらず、
LOGについてもHTTP/2関連の追加はあるものの大きな変更はなく、
大抵のVMODもリビルドすれば動き、累積バグは修正されています。
そのため、4.1で動いていた監視や周辺ツールはほぼ変更なしで動き既存の資産を使いやすいと思います。
10/03時点で知り合いのサイトの本番に投入して、20日程度経過していますが大きな問題もなく動作しています。
当然メジャーバージョンアップのため慎重な検証は必要ですが、3.0->4.0のときのような苦しみはなく
HTTP/2を使わないのであれば、どちらかと言うと4.0->4.1のときのような変更と思ってもらえればよいかと思います。

新機能

HTTP/2のサポート(experimental)

先程も紹介しましたがHTTP/2の極めて実験的(Very Experimental)なサポートを行っています。
有効にする場合は起動パラメータ、もしくはvarnishadmでfeatureパラメータを+http2に指定します。
なお、現時点で私が確認しているバグだと回避不能でリクエストで容易にクラッシュを起こすものが1件、回避可能なものが1件、仕様なのか判断できないが実装漏れっぽいのを1件見つけてます。(また今度泣きながら英語書きます)
そのため決して本番に投入することはおすすめしません。
しかし実際に触ってみていろいろバグを見つけて報告するとよいかと思いますので触ってみてほしいところです。

vmod-directorでsharedをサポート

consistent-hashのサポートです。
元々はUPLEXが提供していたvslpを公式に持ってきた形になります。
consistent-hashについてはぐぐるといろいろ出てくるとおもうので詳しくは割愛します(参考
今まではbackend数に変動があった場合には全体で再計算が走るのでキャッシュヒット率が全体で落ちる問題がありましたがこれを使うことで解決することが出来ます。
特に複数台のクラスタで複数段のキャッシュを組んでる環境ではかなり効いてくると思います。

VOID shard.set_rampup(DURATION duration=0)
0秒以上を指定した場合、バックエンドがhealthyになった場合にいきなり100%のトラフィックを流すのではなく、指定秒数(duration)の間に徐々にトラフィックを流していくようになります。(slow start)

BOOL shard.add_backend(BACKEND backend, STRING ident=0, DURATION rampup=973279260)
バックエンドを追加します。
identはノードの識別子で、デフォルトではバックエンドの名前を使うようになっています。
通常は使うことはないと思いますが、weight的な指定をしたいときなどに使うと良いかもしれないです。
rampupはサーバ個別に指定する場合に指定します。(973279260はmagic valueでset_rampupで指定された値を使うようになります)

BOOL shard.remove_backend(BACKEND backend=0, STRING ident=0)
バックエンドを削除します。
backendを指定した場合は一致するすべてが削除されます。

BOOL shard.clear(PRIV_TASK)
全てのバックエンドを削除します。

VOID shard.set_warmup(REAL probability=0.0)
指定の確率で次の代替バックエンドにリクエストがいくようになります。(0.5=50%)

warm

要はそのバックエンドが死んでキャッシュが落ちたとしても、
代替バックエンドがキャッシュをある程度持っている状態なのでダウン時の影響をより減らすことができるようになります
(ただし当然ながらキャッシュの二重持ちが起きるので空間効率は悪くなります)
なお、rampup期間中のバックエンドはwarmupの対象外になります。

BOOL shard.reconfigure(INT replicas=67, ENUM {CRC32,SHA256,RS} alg=”SHA256″)
consistent-hashで使うハッシュリングを作ります。
replicasは1つのバックエンドが仮想的に何個のバックエンドになるかです。(consistent-hashの仮想ノード)
algはどのハッシュ関数を使ってハッシュリングに配置していくかです。
なお、shardディレクターを使う場合は必ず一度は呼び出す必要があります。
イメージ的にはcommitと思ってもらえれば良いかなと思います。

BACKEND shard.backend(ENUM {HASH,URL,KEY,BLOB} by=”HASH”, INT key=0, BLOB key_blob=0, INT alt=0, REAL warmup=-1, BOOL rampup=1, ENUM {CHOSEN,IGNORE,ALL} healthy=”CHOSEN”)
バックエンドを選択します。

by=HASH(デフォルト)
vcl_hashで生成したhash値を使用して振り分けサーバを決めます。

by=URL
req.url/bereq.urlを使用します。

by=KEY
keyで指定したINT数値を使って振り分けします。
例えばcookieをもとに振り分けしたい場合は


shard.backend(by=KEY,key=shard.key(bereq.http.cookie))

といった感じです。

by=BLOB
key_blob指定したblobを使って振り分けします。
通常は使うことはないと思います。
また、blob全体を使うのではなく先頭4バイトを使用します。

key
by=keyを指定した場合に指定する数値です

key_blob
by=blobを指定した場合に使用するblobです

alt
指定した場合はn番目の代替バックエンドを選択します。
どのようなときに使うかというと何かしらのエラーが起きて別のバックエンドを選択したい場合(restart/retries時)に使用します。
alt=req.restart or bereq.retriesと指定しているとリスタート/リトライのたびに別のバックエンドを選択することが出来ます。
なお、rampup/warmupはaltが0のときのみ有効で、1以上のときはaltが優先されます。

warmup
代替サーバが選択される確率を指定します。(-1,0~1)
-1の場合はset_warmupで指定した値を使用します。

rampup
rampup機能を使うかどうかです。

healthy=CHOSEN(デフォルト)
healthyなバックエンドを選択します。
altが0以上の場合はバックエンドのhealthyを無視してn番目の代替バックエンドを選択します。
ドキュメントにはthen return the next healthy backend.とあるのですが、コードと実際の動作を見る限りだとalt指定時の代替バックエンド選択ではhealthyかの評価をしていません。

healthy=IGNORE
helathyを無視します。
altを指定した場合はhealthyを無視した状態で代替バックエンドを選択します。
またrampup/warmupは機能しません。

healthy=ALL
基本的にはCHOSENと同じですが、altが0以上のときに代替バックエンドを選択する際にもヘルスチェックをします。

ちょっとややこしいのでまとめてみました

healthy alt==0 alt>0
CHOSEN ヘルスチェックを行う ヘルスチェックを行わない
IGNORE ヘルスチェックを行わない ヘルスチェックを行わない
ALL ヘルスチェックを行う ヘルスチェックを行う

 

INT shard.key(STRING string, ENUM {CRC32,SHA256,RS} alg=”SHA256″)
指定した文字列を指定したハッシュアルゴリズムを通したときのINT値を得ます。
例えばSHA256は256bitなんですが、32bit分だけ返します。
この関数はshard.backendをby=KEY指定で使う場合に使用します。

で、実際のところどうやって使うかというとこんな感じです。



sub vcl_init {
  new ws_cache = directors.shard();
  ws_cache.set_rampup(10s);
  ws_cache.add_backend(ws01);
  ws_cache.add_backend(ws02);
  ws_cache.add_backend(ws03);
  ws_cache.add_backend(ws04);
  ws_cache.reconfigure();
}

sub vcl_recv {
  //x-varnish-hashにはユーザIDとかそんなのが入ってる
  ...
  set req.backend_hint = ws_cache.backend(by=KEY, key=ws_cache.key(req.url + ":" + req.http.host + ":" + req.http.x-varnish-hash));
}

 

INT,BACKEND型がBOOLで評価できるようになりました

こういうことができるようなります。


sub vcl_recv {
  if(1){
    set resp.http.foo = "1";
  }
  if(req.backend_hint){
    set resp.http.bar = "1";
  }
}

INT型で0はfalseになります。
また、BACKEND型では割り当てられていない場合はfalseになります。

別VCLを動的リンクして使うlabel機能のサポート

いままで異なるVCLを読み込む方法としてはincludeがありました。
これは単純にincludeを記載した場所に指定したVCLを展開するものでした。
そして今回追加されたlabelは別のコンパイルされたVCLを動的リンクします。
とりあえず使い方を紹介します

まずラベルを持つVCLをloadします


varnishadm vcl.load a_example_net /etc/varnish/a_examplet_net.vcl
varnishadm vcl.load b_example_net /etc/varnish/b_examplet_net.vcl
varnishadm vcl.load c_example_net /etc/varnish/c_examplet_net.vcl

varnishadm vcl.label vcl_a_example_net a_example_net
varnishadm vcl.label vcl_b_example_net b_example_net
varnishadm vcl.label vcl_c_example_net c_example_net

default.vcl


vcl 4.0;
//最低限1つはバックエンドがないと動かないのでダミー
backend default { .host = "127.0.0.1"; }

sub vcl_recv {
  if(req.http.host ~ "a\.example\.net$"){
    return(vcl(vcl_a_example_net));
  }
  if(req.http.host ~ "b\.example\.net$"){
    return(vcl(vcl_b_example_net));
  }
  if(req.http.host ~ "c\.example\.net$"){
    return(vcl(vcl_c_example_net));
  }
  return (synth(403));
}

a.example.netの場合はvcl_a_example_netというラベルを持つVCL(/etc/varnish/a_examplet_net.vcl)を使うようになります。
これは何に使えるかというとCDNのMWとしてVarnishを使っていたりして外部の人にVCLの記述を解放したいけど
VCLがそれぞれ干渉しないようにしたい場合に使えます。
vcl_a_example_netの中からb.examplet.netに関する処理を書いてもそもそもそういうリクエストが割り振られないからです。
また、今回はreq.http.hostで分けましたが、もちろん他のキーで振り分けを行うことが出来ます。

なお、設定を更新する場合は再度vcl.loadしてvcl.labelを叩く感じです


varnishadm vcl.load a_example_net_1 /etc/varnish/a_examplet_net.vcl
varnishadm vcl.label vcl_a_example_net a_example_net_1

ちなみにvcl.listを見てみるとこんな感じになります


# varnishadm vcl.list
active      auto/warm          0 boot
available   auto/warm          0 a_example_net
available  label/warm          0 vcl_a_example_net -> a_example_net_1
available   auto/warm          6 a_example_net_1 (1 label)

vcl_a_example_netがa_example_net_1になっているのがわかると思います。
また、reloadを行った場合はラベルの先のVCLについてはreloadされませんので注意が必要です。

バックエンドフェッチがPROXYプロトコルサポート

バックエンドの定義に.proxy_header=[プロトコルバージョン];を指定することでproxyプロトコルが使えるようになりました。


backend ws01 {.host = "192.168.1.1";.proxy_header = 1;}//PROXYプロトコルv1
backend ws02 {.host = "192.168.1.2";.proxy_header = 2;}//PROXYプロトコルv2

varnishtestの強化・変更

HTTP/2のテストやproxyプロトコルのサポートが行われました。
また、resp.msgはresp.reasonへ
semaはbarrierに名前が変更されました。

std.integerがREAL型の数値をパースできるようになりました

1.2といったものはもちろん1E+2みたいな表記もパースできるようになりました。
もちろんINT型になるように小数点以下は削除されます。

std.logがvcl_initで動くようになりました

 

varnishhistにy軸の目盛りが付きました

 

varnishstatの-fオプションがglob(7)の形式が使えるようになりました

 

POSTをキャッシュできるようになりました

正確にはVCLを正しく書けばPOSTもキャッシュできるようになったということです。(もちろんデフォルトではされないです)
内部的な変更としてbodyを常にバックエンドに送るように変更されており、これによりGETとbodyを使うPOSTなどと扱いが変わらなくなったためです。
とはいえGETはbodyがいらないのでbuiltin.vclでは削除するようになっています。


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

もし、自身のVCLがbuiltin.vclを通らない(return)している場合はunset bereq.bodyを追加する必要があるでしょう。
また、POSTのキャッシュができるようになったとはいえbuiltin.vclではやはりpass動作になっています。
試していませんがここをhashにすればキャッシュできると思いますが、オブジェクトのハッシュを作る際にbodyを評価していないため
ここらへんはbodyに対してアクセスできるvmodを使って中身を精査してハッシュを作る必要があります。

仕様変更

statsからn_waitinglistがなくなりました

VCL名に指定可能な文字が制限されました
わざわざvcl名を使っている人はそんなにいないと思うんですが
[A-Za-z][A-Za-z0-9_-]*にマッチする名前じゃないとだめになりました。

すべてのVCLオブジェクトは使う前に定義しないといけなくなりました
backendとかaclとかそういうのですね、むしろ今まで意識してなかった(いつも先頭に持ってきてた)ので
え、いけたの?ぐらいです。

pass(uncacheable)されたリクエストではcache-control/ExpiresのTTLはパースされなくなりました
今まではpassした場合でもTTLのパースはしていたのですがされなくなったので、対応するログも出力されなくなりました。

-T/-Sを無効化する場合は明示的にnoneを指定する必要があります

パラメータの変更

追加:ban_lurker_holdoff
効率的でないbanの指定などでban.listが肥大化した場合、ban_lurkerスレッドとの潜在的な競合によりlookup性能が悪化するケースがありました。これを防ぐためにsleepを入れるようになってその値です。
デフォルトは0.1sとban_lurker_sleepと同じです。

追加:session_max
session_maxは削除されました。
もともと4.0のリリースで効果がなくなったものです。
なお、将来的に復活する可能性はあるとのことです。

名前の変更:vcl_dir->vcl_path / vmod_dir->vmod_path
まだどちらも使えますがおそらく将来的に_dirのほうはなくなります。
また、:で分割して複数のpathを指定できるようになりました。
vcl_pathのデフォルトは/etc/varnish:/usr/share/varnish/vclとなっていますが
これはincludeなどでVCLを読もうとした際にまず/etc/varnishを探索し、
存在しない場合は/usr/share/varnish/vclも探すといった動きになります。
また、/usr/share/varnish/vclにはデバイスを判定するためのdevicedetect.vclが入っていています。

builtin.vclの変更点

GET時にbereq.bodyのunset
これは先程「POSTをキャッシュできるようにしました」で説明したとおりです。

hit-for-passの取扱について
今まではreturn(pass)したりそもそもキャッシュできないオブジェクトはhit-for-pass(HFP)としてマークされて
2分間は常にpass扱いとされていましたが、HFPとしてマークしないようにしました。
以下が4.1と5.0のdiffです。


 sub vcl_backend_response {
-    if (beresp.ttl <= 0s ||
+    if (bereq.uncacheable) {
+        return (deliver);
+    } else if (beresp.ttl <= 0s ||
       beresp.http.Set-Cookie ||
       beresp.http.Surrogate-control ~ "no-store" ||
       (!beresp.http.Surrogate-Control &&
         beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
       beresp.http.Vary == "*") {
-        /*
-        * Mark as "Hit-For-Pass" for the next 2 minutes
-        */
+        # Mark as "Hit-For-Pass" for the next 2 minutes
         set beresp.ttl = 120s;
         set beresp.uncacheable = true;
     }

VCLの変更点

backendsの定義に.proxy_headerが追加されました
新機能で触れたものです

vcl_recv:returnにvcl(label)が追加されました
新機能で触れたものです

vcl_recv:rollbackが削除されました

vcl_hit:return(fetch)が削除されました
元々4.1の時点でfetch->missに変更するようにアナウンスが出てたものですが、このタイミングで削除さらえました。

vcl_backend_*:remote.ip, client.ip, local.ip, server.ipにアクセスできるようになりました

vcl_backend_fetch:bereq.bodyが追加されました
新機能で触れたものです

vcl_backend_error:synthetic()がberesp.bodyに変更されました
こんな感じの表記になります


    set beresp.body = {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + beresp.status + " " + beresp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + beresp.status + " " + beresp.reason + {"</h1>
    <p>"} + beresp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + bereq.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"};

vcl_synth:synthetic()がresp.bodyに変更されました

vcl_deliver: obj.ttl, obj.age, obj.grace, obj.keepにアクセスできるようになりました(read)

正規表現でmatchした時の結果をBOOLとしてそのままセットできるようになりました


set req.http.foo = req.http.bar ~ "bar";

このような表記ができるようになりました。

アップデート時の注意

パッケージの構成が変更された
元々は

  • Varnish本体
  • libs
  • libs-dev
  • docs

の4種類でパッケージが別れていたのですが

  • Varnish本体+lib+docs
  • dev

の2種類に変更されました。
そのためインストールするときに失敗する場合は一度既存パッケージをアンインストールしてみてください。

VMODのABIが変更されたのでリビルドが必要
VMODのABIが変更されたので使用しているVMODについてはリビルドが必要です。

環境によってはreloadに失敗するかも
これは私が引っ掛かった問題+私のsystemd力が低いのでパッと解決策が思いつかなかった問題なのですが
VCL名が仕様変更(先頭文字はアルファベットである必要がある)されたけど、reloadスクリプトがそれに追随していないように見えます。
Xenial(systemd)でリロードしようとするとこの部分のLOGNAMEが入らないため先頭がuuidになります。
uuidは数字を含むためreload時にVCL名の仕様変更に引っかかってしまう感じです。
とりあえず先頭にreload_とかつけとけば回避はできるのでもし引っかかった場合は


-    vcl_label="${LOGNAME}${LOGNAME:+_}${uuid}"
+    vcl_label="reload_${LOGNAME}${LOGNAME:+_}${uuid}"

こんな感じで修正すると良いかなと思います。
また、私がRHEL/CentOSの環境を持っていないので試していませんが、おそらくRHELでも同様の問題がおきます。(:は使えない)

その他変更

累積バグが直ってたり、細かい改善があったりします。