読者です 読者をやめる 読者になる 読者になる

Linuxカーネルの新機能 XDP (eXpress Data Path) を触ってみる

Linux Network

先日netdev 1.2に参加してみたところ,XDP(eXpress Data Path)の話題で持ち切りといった感じだった. というわけで,XDPについて一通り調べつつ,実際に触ってみた.

XDPとは何か?

誤解を恐れずに一言で言うと,「Intel DPDKのような高速パケット処理基盤をLinuxカーネル自身が用意したもの」であると理解している.このスライドでは

A programmable, high performance, specialized application, packet processor in the Linux networking data path

と言っている.

DPDKはユーザランドアプリケーションがNICを直接叩く(=カーネルのネットワークスタックをバイパスする)ことで高速処理を実現している.一方XDPは,カーネル内の最もNICドライバに近い場所でフックしてeBPFを実行することによりパケット処理を実現する.

f:id:yunazuno:20161010181639p:plain (iovisor.orgより)

eBPFはカーネル内でプログラムを実行するための汎用的な仕組みで,パフォーマンス計測やセキュリティ方面で使用されている,らしい.これまでもtc-bpf(8)のようにeBPFでパケット処理を行う仕組み自体は用意されていたが,XDPによってより早い段階でパケット処理を行えるようになった.

XDPの概要についてはiovisor.orgの説明ページや冒頭で触れたスライドが詳しい.

XDP | IO Visor Project

bpf-docs/Express_Data_Path.pdf at master · iovisor/bpf-docs · GitHub

パフォーマンス

パフォーマンスは現時点において,受信したパケットを全てドロップするケースで20Mpps, L3フォワーディングでも10Mpps程度を達成するとされている.

youtu.be (6:46あたりから)

Next steps for Linux Network stack approaching 100Gbit/s by Jesper Dangaard Brouer @ Netfilter Workshop, June 27, 2016

動作環境

XDPを利用するには,カーネル自体に加えてNICドライバでもサポートが必要となる. 先日リリースされた4.8でXDPサポートおよびMallanox mlx4ドライバサポートが取り込まれたので,少なくともこれ以降のカーネルが必要となる.

開発用としてe1000をサポートするパッチも出ているが,まだリリースされていない.

xdp-vagrantでXDPを手軽に試す

ここまでに書いたように,XDPが動作する環境を準備するのは若干敷居が高い.そこで便利なのがxdp-vagrant.

github.com

これを使うと,XDPサポート済みカーネル*1 + e1000サポートパッチ + BCC導入済みな環境がvagrantで簡単に手に入る.

BCC(BPF Compiler Collection)はユーザランドで動作するツール群で,XDPプログラムの読み込みやカーネル側のデータの操作を補助してくれる.Pythonバインディングが用意されているので,XDPプログラムはCで書きつつ*2ユーザランド側のマネジメント廻りをpythonで書く,ということが容易に実現できる.

github.com

ちなみに,ユーザランドカーネルの両方をCで書く場合のサンプルコードは下記にある.

linux/xdp1_user.c at master · torvalds/linux · GitHub

linux/xdp2_kern.c at master · torvalds/linux · GitHub

vagrant upまで

Vagrantfileがlibvirt providerを前提に書かれいているので,vagrant-libvirtプラグインが利用可能な環境を事前に作っておく必要がある.更に,vagrant-restartプラグインも必要. 加えて,オリジナルのVagrantfileだと何故か ubuntu/trusty64 boxが指定されているが,このboxはlibvirt pluginに対応していない.自分でlibvirt対応boxを用意するか,他のlibvirt対応boxで代用する必要がある.ここでは s3than/trusty64 を使うことにする.

$ git clone https://github.com/iovisor/xdp-vagrant
$ cd xdp-vagrant
$ cat <<EOF | patch -p1
diff --git a/Vagrantfile b/Vagrantfile
index 2ee7327..c4392c6 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -5,7 +5,7 @@
 end

 Vagrant.configure('2') do |config|
-  config.vm.box = "ubuntu/trusty64" # Ubuntu 14.04
+  config.vm.box = "s3than/trusty64" # Ubuntu 14.04
   config.vm.network "private_network", ip: "192.168.50.4"

   # fix issues with slow dns https://www.virtualbox.org/ticket/13002
EOF
$ vagrant plugin install vagrant-libvirt vagrant-reload
$ vagrant up
$ vagrant ssh -c "uname -a"
Linux vagrant-ubuntu-trusty-64 4.7.0-07282016-torvalds+ #28 SMP Thu Jul 28 11:52:33 PDT 2016 x86_64 x86_64 x86_64 GNU/Linux

特定ポート宛パケットをドロップする例

bccに含まれるサンプルコード xdp_drop_count.py を参考に,80/tcpと8080/tcp宛てパケットを全てドロップしつつ,ドロップした総パケット数をポート毎にカウントするプログラムを書いてみた.

XDP: drop 80/tcp and 8080/tcp

パケットが到着するたびに drop_80_8080 が呼ばれる.この関数でXDP_PASSを返せばパケットは通常のネットワークスタックに送られる.XDP_DROPでドロップ,XDP_TXでTX queueに直接送り込まれる.

上記のプログラムを実行した状態でホスト側からvagrant VMのeth1宛にncやcurlでパケットを送ると,ドロップカウンタがカウントアップしていくことが分かる.ドロップカウンタのインクリメントはカーネル側で行い,それをユーザランドpythonプログラムから参照して出力している.

vagrant$ $ ls -l 
total 8
-rw-rw-r-- 1 vagrant vagrant 2034 Oct 10 06:10 drop_80_8080.c
-rwxrwxr-x 1 vagrant vagrant  807 Oct 10 06:06 loader.py

vagrant# ./drop_packet.py eth1
In file included from /virtual/main.c:4:
In file included from include/linux/if_ether.h:23:
In file included from include/linux/skbuff.h:34:
In file included from include/linux/dma-mapping.h:6:
In file included from include/linux/device.h:24:
In file included from include/linux/pinctrl/devinfo.h:21:
In file included from include/linux/pinctrl/consumer.h:17:
In file included from include/linux/seq_file.h:10:
include/linux/fs.h:2658:9: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
        if (id < 0 || id >= READING_MAX_ID)
            ~~ ^ ~
1 warning generated.
XDP loaded.
80: 1
80: 2
80: 2
8080: 1
80: 2
8080: 2
80: 2
8080: 2
80: 2
8080: 2
host$ echo | nc 192.168.50.4 80    # timeout as no response
host$ echo | nc 192.168.50.4 8080  # timeout as no response
host$ echo | nc 192.168.50.4 81
Ncat: Connection refused.

まとめ

Linuxカーネルに最近追加された高速パケット処理基盤XDPを少しだけ触ってみた.まだリリースされて間も無いということもあって利用可能な環境や関連資料は少ないものの,MellanoxをはじめとしたNICベンダも(eBPF hardware offload含め)サポートに積極的であるように見えるので,今後徐々に盛り上がっていくと思われる.

DPDKと比較してどうか,という部分に関しては,既存のネットワークスタックとの共存やeBPF関連ツールの流用が効く,といった点がアプリケーションを書く側にとって嬉しい点になりそう.

何れにしても,今後の展開が楽しみ.

*1:4.8ではなく4.8 rc

*2:clangでeBPFにコンパイルされる

ロードバランサのアーキテクチャいろいろ

Network Server

少し前に,Facebookのロードバランサが話題になっていた.

blog.stanaka.org

このエントリを読んで,各種Webサービス事業者がどういったロードバランスアーキテクチャを採用しているのか気になったので調べてみた. ざっくり検索した限りだと,Microsoft, CloudFlareの事例が見つかったので,Facebookの例も併せてまとめてみた.

アーキテクチャ部分に注目してまとめたので,マネジメント方法や実装方法,ロードバランス以外の機能や最適化手法といった部分の詳細には触れないことにする.

事例1: Microsoft Azure 'Ananta'

MicrosoftのAzureで採用されている(いた?)ロードバランサのアーキテクチャは,下記の論文が詳しい.

Parveen Patel et al., Ananta: cloud scale load balancing. SIGCOMM '13 (スライド)

2013年10月に発表された論文.'Ananta' と名付けられたこのロードバランサは,論文執筆時点でbing.com等の実環境において約2年(すなわち2011年頃から)の運用実績があると述べられている.

一般的なアプライアンス型ロードバランサやLVS+keepalivedの環境が1+1構成でスケールアップが基本戦略になるのと対照的に,AnantaはN+1構成でスケールアウトが容易な点が特徴である.また実装面では,ハイパーバイザのレベルで諸々の処理を完結させることで,VM側にシステムの存在を意識させないようになっている.

Architecture

Anantaのおおまかな構成は下図の通り.図中には記載していないが,全体の動作を統括するAnanta Manager(AM)も存在する.

f:id:yunazuno:20160225191128p:plain

ロードバランスはRouter, Layer-4 LB, Host Agentの3階層で行われる*1.従来型のロードバランサがLayer 3からLayer 7までを(内部的な実装はともかく)単一の筐体でこなしていたのとは対照的である.なお上図だと各層間を同一L2で繋ぐように描いてしまったものの,当然その必要は無い.

Router

Per-flow Equal Cost Multi Path (ECMP) によるLayer 3レベルでのトラフィック分散を行う.

Routerは,次段のL4 LBからVIPの/32経路をBGPで受信している*2.このとき,コスト(Local PreferenceやMED)が同一であれば,routerはECMPでのパケットフォワーディングを行う. Per-flow ECMP*3を使用すれば,経路に変化が無い限り,すなわちL4 LBが増えたり死んだりしない限り,同一TCPセッションは常に同一のL4LBにフォワーディングされる.これにより,Routerではセッションを管理する必要が無い.

L4 LB (Multiplexer, MUX)

Consistent HashによるLayer 4レベルでのトラフィック分散を行う.論文中ではMultiplexer 'MUX' と呼ばれている.MUXは,自身が管理しているVIPの/32経路をBGPでrouterに広報する.これにより,VIP宛のトラフィックがMUXに引き込まれるようになる.

MUXはアプケーションが稼動しているVMトラフィックを分散する.このとき単純にConsistent Hashだけでフォワード先を決定してしまうと,VMやHyperVisorの増減が発生した際に既存セッションが誤ったVMにフォワードされてしまう.これを防ぐため,MUXは既存セッションの情報をセッションテーブルで保持・管理する.ただし,この情報はMUX間では共有されていない*4ため,場合によってはセッションが失われてしまう可能性がある.ちなみに,実装はWindows Filtering Platform(WFP)を用いている.

Host Agent (HA)

IPIPのデカプセル化やNATといった処理を透過的に行い,適切なVMトラフィックが屆くようにする役割を持つ.

VMやその上で動いているアプリケーションのヘルスチェックもここで行う.ヘルスチェックの結果はAnanta Managerに送られたのちMUXに反映される.実装はHyper-Vの仮想スイッチ*5を用いている.

トラフィックフロー

あるアプリケーションApp 2がVIP 192.0.2.2を使うとき,VIP宛の通信は下記のような流れで処理される:

f:id:yunazuno:20160225191129p:plain

  1. RouterにVIP 192.0.2.2宛のパケットが屆く.このとき,router上には192.0.2.2/32の等コスト経路が複数存在するので,そのうちいずれか1経路を選択してパケットをフォワードする.
  2. MUXにパケットが屆く.MUXは自身のセッションテーブルを参照し,セッションが既に存在するか確認する.存在すればそれに従ってフォワード先のVMを決定する.存在しなければhashによって新たにフォワード先のVMを決定し,セッションテーブルに記録する.フォワード先のVMが決定されると,パケットをIPIPでカプセル化してVM宛にフォワードする.
  3. HAを通過してパケットがVMに屆く.このとき,HAは透過的にIPIPのデカプセル化を行う.
  4. VMが応答を返す.なお,このときのパケットのソースIPアドレスVMの実IPアドレスになっている.
  5. HAを通過してパケットがクライアントに送られる.このとき,HAはsource NATを行い,パケットのソースIPアドレスVMの実IPアドレスからVIPに書き換える.また,L3DSRなので,レスポンスのパケットはMUXを通過しない.

事例2: Facebook

このエントリの冒頭で挙げたエントリでも詳解されていたもの.

Building a Billion User Load Balancer | USENIX

MicrosoftのAnantaと同様,複数レイヤ分割によってスケールアウトが容易な構成になっている.

Facebookのシステムが優れているのは,単一IDC内におけるロードバランスだけでなく,PoP間トラフィックの最適化やGSLBによるユーザトラフィックの品質向上まで含めて考えられている点である.また実装面では,Microsoftのものと違って特定の環境に依存する要素が無く,よりポータブルになっている*6

Architecture

PoP間通信やGSLBに関する部分は置いておいて,単一IDC内だけに着目すると,おおまかな構成は下図の通り.Microsoftの例と同じく階層構造である.ロードバランシングはRouter, Layer 4 LB, Layer 7 LBの3階層で行われる.例によって階層間は同一L2である必要は無い.

f:id:yunazuno:20160225191126p:plain

Router

Per-flow Equal Cost Multi Path (ECMP) によるLayer 3レベルでのトラフィック分散を行う.Microsoftの例におけるrouterと全く同じ役割と動作を行う.

Layer 4 LB

Consistent HashによるLayer 4レベルでのトラフィック分散を行う.これもMicrosoftの例におけるMUXとほぼ同じ役割と動作を行う.実装はLVSExaBGPを用いている.

Layer 7 LB

コネクションを終端し,アプリケーションに対するLayer 7レベルでのトラフィック分散(reverse proxy)を行う.実装はProxygenを用いている.

上で挙げた各層の要素はコンテナ化され,Kubernetesを使って任意の場所にデプロイされる. 余談だが,発表動画中では気軽に「任意の場所にデプロイ」と言って図まで描いている。だがしかし、ファブリック的ではないネットワークアーキテクチャでこれをそのまま真似すると,router-L4LB間の帯域が足りなくて辛い思いをすることになると予想される.Facebookのデータセンターは末端まで広帯域を用意していることが知られているが,こういうことをやってこそ出来る芸だと思う.

トラフィックフロー

VIP 192.0.2.1があるとしたとき,このVIP宛の通信は下記のような流れで処理される.

f:id:yunazuno:20160225191127p:plain

  1. RouterにVIP 192.0.2.1宛のパケットが屆く.このとき,router上には192.0.2.1/32の等コスト経路が複数存在するので,そのうちいずれか1経路を選択してパケットをフォワードする.
  2. L4 LBにパケットが屆く.L4 LBは自身のセッションテーブルを参照し,セッションが既に存在するか確認する.存在すればそれに従ってフォワード先のL7 LBを決定する.存在しなければhashによって新たにフォワード先のL7 LBを決定し,セッションテーブルに記録する.フォワード先のL7 LBが決定されると,パケットをIPIPでカプセル化してL7 LB宛にフォワードする.
  3. L7 LBにパケットが屆く.L7 LBはIPIPをデカプセル化した上でコネクションを一旦終端し,upstreamのアプリケーションに対してリバースプロキシを行う.
  4. アプリケーションがL7 LBにレスポンスを返す.
  5. L7 LBを経由してパケットがクライアントに送られる.L3DSRなので,レスポンスのパケットはL4 LBを通過しない.

事例3: CloudFlare

CDN事業者であるCloudFlareは,世界中にPoPを設置してコンテンツ配信を行っている. そんなCloudFlareのPoPにおけるロードバランス手法が下記の記事で紹介されている.

blog.cloudflare.com

CloudFlareのアーキテクチャで特徴的なのは,ネットワークレベルで負荷分散と障害時の切り離しが完結している点である.またインターネットレベルでのIP Anycastによる耐耐障害性確保も実現している.

Architecture

おおまかな構成は下図の通り.

f:id:yunazuno:20160225191124p:plain

この構成では,ロードバランサにおける負荷分散機能をECMPで行い,分散先の追加・削除機能をBGP自身で行う.MicrosoftのMUXやFacebookのL4/L7 LBに該当するような明確なロードバランサは存在しない.

Router

ECMPによるLayer 3レベルでのトラフィック分散を行う. Routerは,後段のedge serverにインストールされたBGP speakerからの/32経路を受信している.この経路をそのままインターネット(他のAS)に広報することで,edge serverは外部からアクセス可能になる.

Edge server

CDNのキャッシュやプロキシ機能を提供する. 各Edge serverにはBGP speaker*7がインストールされており,routerに対して自身のVIPを広報する.各edge serverが同一のコストで広報を行えば,routerからECMPによって負荷が分散される. また,VIPは複数のPoPで同一のものを使用している.従って,インターネットに対するIP anycastが行われている.

冒頭でも述べた通り,構成の面白い点は障害時の動作である. たとえばEdge serverが死んだ場合,そのサーバからrouterに対する経路広報が止まるため,そのサーバは自ずと負荷分散対象から外される. また,電源障害や上流回線全断などによってPoP自体が死んだ場合,そのPoPからインターネットへの経路広報が止まるため,そのPoPへはアクセスされなくなる.クライアントはIP Anycastによって他のPoPに自動的に迂回される.

トラフィックフロー

VIP 192.0.2.3があるとしたとき,このVIP宛の通信は下記のような流れで処理される.MicrosoftFacebookのものに比べると非常にシンプル.

f:id:yunazuno:20160225191125p:plain

  1. RouterにVIP 192.0.2.3宛のパケットが屆く.このとき,router上には192.0.2.3/32の等コスト経路が複数存在するので,そのうちいずれか1経路を選択してパケットをフォワードする.
  2. Edge serverにパケットが屆く.パケットは特にカプセル化等はされておらず,通常のものと変わらない.
  3. Edge serverがレスポンスを返す.
  4. ルーティングテーブルに従ってパケットがクライアントに送られる.

余談: Google

GoogleのロードバランスといえばGFEだと思うが,明確な資料は見当たらなかった. 見付けられた範囲だと、下記の動画で簡単に言及がある程度である.

www.youtube.com

少し前にSeesawというものが公開されていたが,これがGoogle内部ではどういう位置付けでどの程度使われているのかよく分からないので何とも言えないところ.

github.com

追記 (2016-02-29 19:25)

Googleのロードバランサに関する情報を@rrreeeyyyさんに教えていただいた:

論文は下記のページに掲載されている.こんどのNSDIで発表される予定

Maglev: A Fast and Reliable Software Network Load Balancer

Abstractと図をざっくり眺めた程度だと,こちらも複数レイヤ分割構成でECMPやconsistent hashを活用している様子.あとで論文を詳しく読む予定. 情報ありがとうございました!

まとめ

Microsoft, Facebook, CloudFlareにおけるロードバランサのアーキテクチャを調べて簡単にまとめてみた. 最近のクラウド環境であればロードバランサ機能は当然用意されているが,それらの内部では(Azureに限らず)ここで挙げたような技術が活用されていると考えられる.クラウド様々である. また,ロードバランサに限らず,機能ごとのレイヤ分けやBGP+ECMPの活用といった考え方は,様々な局面で参考にできそう.

*1:実際にはHost Agentは負荷分散の機能は無い.

*2:実際には,Routerの負荷を考慮してもう少し集約された経路を使用している,と論文中では述べられている

*3:source ip, source port, destination ip, destination port等のhashによりパスを選択する方式; これに対してパケット毎にランダムにパスを決定するのがper-packet ECMP

*4:少なくとも論文執筆時点では未実装; DHTのようなアプローチで解決できるよ,と筆者らは言っている

*5:恐らくHyper-V Extensible SwitchをWFPで拡張したもの思われる

*6:もちろん,AnantaもHAを必ずしもハイパーバイザに組み込む必要は無くて,MUX-App間のどこかに同じ機能を持たせれば同様のことは実現できそうに見える.

*7:Birdを使用している

OS X 10.11のDNS64らしき動きをするDNSサーバを作る

Network

OS X 10.11 El Capitanのインターネット共有機能を使うと,IPv6インターネット接続性が無い環境であってもIPv6 only networkを作ることができる,とAppleは言っている.どういう仕組みなのか少し調べてみたところ,AAAA filterに似た仕組みをDNS64に組み込んでいるらしい.

具体的には,あるドメイン名に対するAAAAクエリがDNS64サーバに来た際,AレコードをNAT64形式に変換してAAAAレコードに詰め直したものを返す,という動作をするようである.通常のDNS64ではAAAAレコードが実在すればそれを返すが,こちらはAAAAレコードが実在してもそれを無視してAレコードを参照する,という違いがある.

この仕組みにより,ルータとなるMacIPv6接続性を持っていなくても,インターネット共有機能を利用している端末に対してIPv6接続性を提供できる.

これと同様の環境をMac以外でも構築できないかと思い,まずはOS XのDNS64と同じような挙動をするDNSサーバを作ってみた.

Code

AAAAクエリが来たら問答無用でそれを握り潰してAクエリを投げ,応答をNAT64形式のアドレスに変換した上でAAAAレコードに詰めて返す. 実際の名前解決廻りの処理にはPythonのTwistedに含まれるNamesを使用している.起動部分のベースはTwistedのサンプルコードから.

A DNS64 resolver with AAAA filter-like behavior (s ...

Example

上で挙げたDNSサーバが127.0.0.1:10053で動いているとする.

$ dig @127.0.0.1 -p 10053 www.facebook.com. A +short
star.c10r.facebook.com.
173.252.120.6
$ dig @127.0.0.1 -p 10053 www.facebook.com. AAAA +short
star.c10r.facebook.com.
64:ff9b::adfc:7806

普通のDNSサーバの応答と比べると、実在するAAAAレコードが無視されていることが分かる.

$ dig @8.8.8.8 www.facebook.com. A +short
star.c10r.facebook.com.
31.13.79.246
$ dig @8.8.8.8 www.facebook.com. AAAA +short
star.c10r.facebook.com.
2a03:2880:f00d:1:face:b00c:0:1

あとはTAYGA等でNAT64を行えば,簡易的なテスト用環境として使えるレベルにはなりそう.

Norikraでそこそこ手軽にNetFlow解析

Network

去年のJANOGで別の方が発表された内容とダダ被りではあるものの,折角なのでメモ程度に書いておく.

概要

  • データセンタネットワークやバックボーンネットワークの運用をやっていると,インターネットに出入りするトラフィックの内訳 (どのISP向けのトラフィックが多いのか,どの回線にトラフィックが乗っているか,等) を見たいことがよくある
  • NetFlowをNorikraで解析し,その結果をGrowthForecastに流し込むと,トラフィックの内訳をそこそこ手軽に見ることができる
  • 定常的なモニタリング用途以外に,突発的なトラブルシューティングにも使えていろいろ便利

背景

自前でAS (Autonomous System) を運用している事業者では,トラフィックのコントロールの観点から,「どのAS向けのトラフィックがどのくらいあるのか」「このASとはどの回線を使って通信しているか」といった情報を知りたいケースがよくある (と思う). このような場面では,NetFlowに代表されるトラフィック統計情報を収集する機能が用いられる.NetFlowを使うと,ある機器を通過した通信フローに関する情報 (src/dst IP, src/dst ASなど) が取得できるので,これらの情報を解析すればトラフィックの内訳を知ることができる.

NetFlow解析を行う場合,一般的には次のような選択肢がある.

回線ごとのトラフィック集計を行いたいケースや複数ASを運用しているようなケースでは,柔軟性の観点から自前での構築を選びたくなる.が,一から全てやるのは結構大変なので,解析はNorikraに,結果のグラフ化はGlowthForecastにやってもらうことにする.

実現方法

構成はNorikra+Fluentd+GrowthForecast.NetFlowコレクタはfluent-plugin-netflowを使用する.

f:id:yunazuno:20150331024123p:plain

Fluentdの設定はこんな感じ.fluent-plugin-forest使ってるので、AS数や回線数によってはメモリの消費に注意.

## <- NetFlow
<source>
  type netflow
  tag  netflow.flow

  bind 0.0.0.0
  port 2055
</source>

## -> Norikra
<match netflow.*>
  type     norikra
  norikra  127.0.0.1:26571

  remove_tag_prefix  netflow
  target_map_tag     true
</match>

## <- Norikra
<source>
  type    norikra
  norikra 127.0.0.1:26571
  <fetch>
    method     sweep
    tag_prefix norikra.query
    tag field  _gf_key
    interval   1m
  </fetch>
</source>

## -> GrowthForecast
<match norikra.query.**>
  type forest
  subtype growthforecast
  remove_prefix norikra.query

  <template>
    gfapi_url http://127.0.0.1:5125/api/
    graph_path netflow/as${tag_parts[0]}/${tag_parts[1]}_bps_${tag_parts[2]}
    name_keys traffic_bps
  </template>
</match>

この構成でNorikraに下のようなクエリを登録した後NetFlowを流し込めば,ひとまずAS毎の集計結果がNorikraで出力されるはず (ただしこの時点では全ての回線の合計値が出力される).

select
    src_as, dst_as,
    (SUM(in_bytes * sampling_interval) * 8) / 60 as traffic_bps,
from
    flow.win:time_batch(60 sec, 0L)
group by
    src_as, dst_as

次は回線毎の集計.SNMP ifIndexと回線の対応関係やフローの向きのチェックを全てEPLで書くのは辛いので,簡単なUDFを作った.

github.com

norikra-udf-netflowでは,NetFlowのデータを扱い易い形式に変換するためのいくつかの関数を定義している.内容は下の表のような感じ.変換に必要な情報はインストール前にあらかじめdefinition.yamlに書いておく必要がある.

Function Description
NFDirection(src_as, dst_as) フローの向き ("in" or "out")
NFOppositeASN(src_as, dst_as, ipv4_src_addr, ipv4_dst_addr) 対向のAS番号
NFRouter(host_ipaddr) NetFlowの生成元のルータの名称
NFCareer(host_ipaddr, ifindex_in, ifindex_out, flow_direction) フローが通過した回線の名称

これらの関数を使って回線毎の集計をやる場合,こんな感じのクエリになる.

Query name: flow_aggregator, Group: LOOPBACK(aggregated_flow)

select
    NFOppositeASN(src_as, dst_as, ipv4_src_addr, ipv4_dst_addr) as opposite_as,
    NFCareer(host, input_snmp, output_snmp, NFDirection(src_as, dst_as)) as career,
    (SUM(in_bytes * sampling_interval) * 8) / 60 as _traffic_bps,
    NFDirection(src_as, dst_as) as flow_direction
from
    flow.win:time_batch(60 sec, 0L)
group by
    OppositeASN(src_as, dst_as, ipv4_src_addr, ipv4_dst_addr),
    NWPoPCareer(host,(code) input_snmp, output_snmp, FlowDirection(src_as, dst_as)),
    FlowDirection(src_as, dst_as)

Query name: traffic_as_per_career

select
    opposite_as,
    career,
    case LAST(_traffic_bps) when null
        then 0
        else LAST(_traffic_bps)
    end as traffic_bps,
    (opposite_as || "." || career || "." || flow_direction) as _gf_key
from
    aggregated_flow.win:time_batch(60 sec, 0L)
group by
    opposite_as,
    career,
    (opposite_as || "." || career || "." || flow_direction)

一度LOOPBACKで集計済みデータを別target(上の例だとaggregated_flow)に流しているのは,後で突発的に解析用のクエリを投入する時にデータを再利用しやすくするため.NorikraのクエリでFluentdのタグを作ってる点がちょっとアレ. ここまで全て上手く動いていれば,GrowthForecastでグラフが作られているはず.

これまでの運用状況など

Norikra

去年末に投入後,約3ヶ月稼働中.単純な時間平均で 10000 event/sec (=10000 flow/sec) 弱程度のeventを流し込んでいる.JVMが数回突然死したり,CPUコアが多い環境で起動しないトラブルに遭遇した*1以外は概ね安定している.現在heapに30GB弱割り当てていて,GCのタイミングで結構な時間止まっているので何とかしたい.

Flow collector

fluent-plugin-netflowが手元の環境だと1プロセスあたり8000 flow/sec 近辺でsocket bufferが埋まる速度に追い付けなくなった.順当に行けばfluent-plugin-multiprosessでポートを分けて負荷を分散させる場面だと思われる.が,これをやるとルータによってNetFlowの送り先が変わってしまうので後々大変そう,ということになり*2nfcollectという超簡易的なNetFlow collectorを書いて凌いだ.

github.com

nfcollectはフローの到着順を保持しない代わりにそこそこのスループットが出るようになっていて,flow-genで試したところ同じ環境で30000 flow/secぐらいまでは何事も無く捌いてくれた.こちらを使った場合でもある時点でfluent-plugin-netflowと同じ問題が発生することに変わりは無いものの,現状30000 flow/secを越えることをはまず無さそうということで一旦目を瞑っている.

その他諸々

当初は単純な集計用途だけを想定していたが,使い始めてから暫くすると,トラブルシューティング的な用途にも使えそうなことが分かった.例えば本来存在するはずの通信が見えない時や,特定ISPから特定アドレス帯への通信が上手くいかないといった時に,その問題に合わせたクエリをその場で書いてNorikraに投入してしばらく待てば,パケットキャプチャより手軽に調査ができる*3.ちょっとしたクエリのサンプル集のようなものを事前に書いておけば,大抵の人は問題無く扱えると思う.

まとめ

NetFlow解析,割と難しいもの扱いされているという噂を耳にしますが,やってみると色々便利なのでまずは試してみると良いと思います.

*1:単純にエラーメッセージちゃんと読んでなかったのが原因.現在はNorikra側でも対策が行われている.

*2:BGPルータの設定変更,あまりやりたい仕事ではないので

*3:ただし対象の通信が平常時からある程度の量流れている場合に限る

モダンなcipher suiteに対応していないHTTPSクライアントを搭載したロードバランサが存在する問題について

Operation Network

実は一般的に知られている問題なのかもしれないものの,最近知ったのでメモ.

概要

  • 一部のロードバランサアプライアンスにおいて,搭載されているHTTPSクライアントが所謂"モダンな"暗号スイートに対応していないものが存在する
  • このため,特定条件下において意図せずヘルスチェックに失敗し,場合によってはVIPがdownする
  • 現時点では直ぐに影響が出る訳では無いものの,気に留めておかないと近い将来痛い目を見そう

背景

DSR構成のロードバランサでSSL(TLS)を使用する場合,SSLの終端はロードバランサではなくリアルサーバの仕事になる*1

この環境下においてリアルサーバの死活監視にLayer 7方式を使用するパターンを考える.このパターンでは,ロードバランサからリアルサーバにSSLで接続し,HTTP GETないしHEADリクエストを投げて意図したレスポンスが返ってくれば,リアルサーバは正常動作しているとみなされる*2

f:id:yunazuno:20150227225611p:plain

問題となるケース

リアルサーバ上で稼働しているWebサーバ(Apache, Nginx等)において,セキュリティの要件上Mozillaが言うところの所謂"モダンな"cipher suiteのみ使用可能にする (=RC4や3DESに依存したCipher suiteを使用不可にする) ケースを考える.

この時,ロードバランサがヘルスチェックに使用するHTTPS Clientが先に述べたモダンなcipher suiteに非対応だと,リアルサーバとのHandshakeに失敗してしまう.その結果,実際は正常動作しているリアルサーバが死んでいると誤認識されてしまい,意図せずリアルサーバないしVIPがdownする.

どう対応するか

ぱっと思い付く対応策はこんな感じ:

  1. Layer 7ヘルスチェックを諦めてLayer 4ないしLayer 3でのヘルスチェックに切り替える
  2. 要件を曲げてCipher suiteの制約を緩める
  3. ロードバランサ側をモダンなCipher suiteに対応させる
    • ベンダに機能追加してもらう,対応OSにバージョンアップする,対応LBに入れ替える,など

商用サービスでモダンもの限定にするケースはまだレアだとは思うものの,気に留めておく必要がありそう.特に,現在使用しているCipher suiteがある時点から安全でなくなる可能性を考慮すると,上述したケースでどういう対応を取るか,ぐらいは考えておいたほうが良さそう.

また,今回のHTTPS Client側cipher suiteの問題に限らず,ロードバランサでSSLを終端した場合のCipher suiteやTLSバージョンの対応状況等,ロードバランサ+SSLには諸々のハマりどころが存在するようなので,運用している人は頑張りましょう.

*1:一般的なプロキシ構成下では,SSLはロードバランサで終端する

*2:プロキシ構成下では,リアルサーバへの接続にSSLは不要

第3回「さくら石狩DC見学ツアー」に行ってきたレポート

気付けば石狩から帰ってきて1週間も経ってしまったものの、まだなんとか記憶に残っているうちに「さくら石狩DC見学ツアー」に参加してきたレポートを。

今回のツアーの応募理由

「他所のデータセンターの中がどうなっているか見てみたい」というのがツアーに応募した最大の理由。 DC内・DC間ネットワーク(完全オンプレ)の面倒を見る仕事を今年の4月に始めてから約8ヶ月経った今、DC内での機器設置や配線をやったりDC調達の話を聞き齧ったりしたことで、DC絡みの諸々をまずは一通り眺めたような状態ではあった。が、それはあくまでこれまで見てきたDCに関してのことで、それらが一般的に見てどうなのかとか、他にどういう考え方・方法があるのか、といった点がここ最近の疑問だった。データセンターその他の低いレイヤのインフラ技術については中々知見が公開されないこともあって他者との比較が難しく、割と悶々としていたような状況だった。

そんな中で、はてブに上がってきていた募集の記事を見かけて応募したら運良く当選した、という次第。普通、DCの中を見せてくれる組織はなかなか無いので、今回当選して本当にラッキーだった。

全体を通しての感想

石狩DCの第一印象

石狩DCの建屋。「あれ、思ってたより小さい・・・?」というのが、バスでの到着時の最初の感想。実際はラック数(500ラック/棟)を考えれば妥当、というかむしろ少し大きめぐらいのサイズだが、何故か超広い土地に超デカい建物がドカンと建っている、みたいなものを勝手に想像してしまっていたので拍子抜けだった・・・。

(写真は特に断りが無い限りはてなさんにご提供。)

石狩DC全景 (後ろから)

後から冷静に考えてみると、地方のDCであればわざわざ建物を大きく作る必要が無いのだなぁ、と。都心のDCだと土地が狭いが故、縦に建物を広げるしかないので、最初にデカい建物をドカンと建てる方法になる。一方地方であれば横に広げることができるので、小さい単位で徐々に改善しつつ作ればよい、ということで納得。この「小さい単位で色々試しつつ改善する」というのは、今回の見学を通して随所で感じられた。1号棟->2号棟と順に見て廻ると、空調方式や電源まわりの構成であったり建物自体の設計であったりの改善がはっきりと分かるのは面白かった。さくらの方も仰っていたような気がするが、このあたりは自前DC+自前サービス向けだからこそできることで、借り物のDCであったりハウジング向けだったりすると中々難しそう。

DC内部の諸々

DC内部について。配線の処理とか機器の選定とか惹かれる箇所が多々あった。が、一番惹かれたのはサーバルーム入口の引き戸タイプの自動ドア!

自動ドア!段差無し!

DCで作業したことがある人であればご存知かと思うが、普通のサーバルームの入口は観音開きになっていて、かつ消化用ガスが漏れるのを防ぐために床部分が盛り上がっている。ここを荷物を載せた台車で乗り越えるのは割と神経を使う作業で、滑り落ち防止のベルトを掛け忘れて台車からサーバが飛び立ったりSFPが舞い落ちたりと、場合によっては割と笑えないことになる。この自動ドアは2号棟から導入したとのことだったが、現場の作業者の声がきちんとフィードバックされている感があって非常に好印象だった。これも一気にドカンと建てるのではなく小さく改善しつつ作ることの強みだと思う。

休憩スペース。過去に見てきたDCの休憩スペースはもっとこう、殺伐とした感じだったのでなんとなく落ち着かなかったものの、冷静に考えてこれが正常なような。

殺伐としていない休憩スペース

地理的な距離や立地

東京23区内から石狩DCまで約4時間弱?といったところ。もっと時間が掛かるかと思っていたが、意外と近かった。DCの近所には海底ケーブルの陸揚げ局があってそこから直にケーブル引けたり、新しく発電所が建つとのこと。DCの立地としてはかなり良さそう。

何の変哲も無い陸揚げ局

さくら田中社長のこと

噂には聞いていたが、さくらの田中社長が技術について嬉々として話す姿は衝撃的だった。仮想化環境でのIO云々の話をしたかと思えばサーバラックの熱処理の話をしてみたりと、ただひたすら凄い、の一言に尽きる。また、「DCがどうこうではなく、あくまで提供しているサービスの中身で勝負する。サービスに直結しないノウハウの部分は公開する。」といった旨の事を仰っていたのが非常に印象に残っている。特に前半部分については、インフラの特に低いレイヤの部分をやっているとつい忘れがちなように思う。

嬉々として喋る田中社長

その他諸々

新千歳に到着して早々、沿岸バスの洗礼を受ける。

車体に描かれていた3人の中ではこの子がよい

お昼ごはん。初日分、蓋を開けたところの写真も撮るつもりが開けた瞬間にそのことを忘れて食べ始めてしまったらしく、蓋付きの写真しか残っていなかった。もちろん非常に美味。

1日目のおひるごはん

流石に2日目ともなると冷静で、写真がちゃんと残っていた。

懇親会でLTしたらMackerel Tシャツを頂いた。はてなさんありがとうございます!(これだけ自分で撮影)

はてなさんありがとうございます

ツアー前日も1日中DCに籠って作業していた*1ら、そのダメージで初日夜は体力が残っておらず、懇親会2次会に参加しそびれた。逃した蟹は大きい。

おわりに

今回石狩DCツアーに参加して、当初の目的が果たせたのはもちろん、思っていた以上に色々なものを見たり聞いたりすることができた。見せてもらいっぱなしなのもアレなので、今後何らかの形で還元できればなぁ、といったことを考えている。

もし今後同じようなDC見学ツアーが開催されるのであれば、普段データセンターに関わっている人であっても、普段やっていることを客観的に振り返るという意味で参加すると良いと思う。普段DCに出入りしている人であっても、ベンダの保守やってるような人で無い限り複数の組織のサーバルームに出入りする機会は殆ど無いだろうし、そういう人こそ参加してみると意外な発見があるような気がする。 もちろん、データセンター見たことも入ったことも無い、という人も是非一度は見ておくべきだということは言うまでもない。

最後に、今回このような機会を作っていただいたさくらインターネットはてなの皆様、ご案内頂いた石狩開発さん、JTBさん、本当にありがとうございました!

*1:障害対応とも言う

VyOS+fluent-plugin-netflowでNetFlowを取得する

Network

NetFlowまわりの諸々を触りたいとき,VyOSだとハードウェアを別途用意しなくても手軽に色々試せて便利.

VyOSの設定

PlixerManageEngineの記事を見つつ,下の設定さえあればとりあえず動く.Timeout値まわりは環境に合わせて要調整.バージョンはVyOS 1.0.4.

set system flow-accounting netflow version 5
set system flow-accounting interface eth0
set system flow-accounting netflow server 192.168.56.1 port 2055

# system flow-accounting netflow timeout以下は必要に応じて設定
set system flow-accounting netflow timeout expiry-interval 60

なお,BGPのOrigin ASやnext-hopも併せて取得したい場合,/opt/vyatta/bin/sudo-users/quagga_gen_as_network.plあたりを実行してやる必要があるように見える (未調査).

現在のフロー情報はshow flow-accountingで確認できる.

vyos@router1:~$ show flow-accounting
flow-accounting for [eth0]
Src Addr        Dst Addr        Sport Dport Proto    Packets      Bytes   Flows
172.16.0.2      10.1.1.100      0     0      icmp        106       8904       1

Total entries: 1
Total flows  : 1
Total pkts   : 106
Total bytes  : 8,904

NetFlowコレクタで確認

ここでは fluent-plugin-netflow をNetFlowコレクタとして使用する.ひとまず動作確認として,受け取ったNetFlowエントリをそのままstdoutに出力させる.

<source>
    type netflow
    tag netflow.event
    
    bind 0.0.0.0
    port 2055
</source>

<match netflow.*>
    type stdout
</match>

このconfigでFluentdを起動し,VyOS側でフローの収集対象にしたインタフェース(上の例ではeth0)に適当なトラフィックを流せば,fluentd側でフロー情報が収集できていることが確認できる.

collector$ fluentd
(snip)
2014-10-14 22:22:22 +0900 netflow.event: {"version":"5","flow_seq_num":"0","engine_type":"0","engine_id":"0","sampling_algorithm":"0","sampling_interval":"0","flow_records":"1","ipv4_src_addr":"172.16.0.2","ipv4_dst_addr":"10.1.1.100","ipv4_next_hop":"0.0.0.0","input_snmp":"2","output_snmp":"0","in_pkts":"92","in_bytes":"7728","first_switched":"2014-10-14T13:21:44.789Z","last_switched":"2014-10-14T13:22:02.789Z","l4_src_port":"0","l4_dst_port":"0","tcp_flags":"0","protocol":"1","src_tos":"0","src_as":"0","dst_as":"0","src_mask":"0","dst_mask":"0","host":"192.168.56.90"}

手軽に試せて便利なので,もう少し色々と触ってみたい.