Software-Defined Networks: A Systems Approach 日本語版+書き下ろし発売

この度、Larry PetersonとBruce Davie が中心となって書いているネットワーク関する書籍シリーズ “Systems Approach” の SDN 編 “Software-Defined Networks: A Systems Approach” という本の邦訳版「Software-Defined Networks ソフトウェア定義ネットワークの概念・設計・ユースケース」を出すことになり、その翻訳をやらせていただきました。6/22に発売開始予定です。

Software-Defined Networks: A Systems Approach

この翻訳本を出すことになったきっかけは本書の「訳者まえがき」のところでも書いたのですが、簡単に言うと、私の Nicira 時代の同僚で、VMware では私の直属の上司でもあった Bruce Davie から「”Software-Defined Networks: A Systems Approach” の日本語版を出したいので協力して欲しい」と相談されたのがキッカケです。原書を読んでみるととても興味深い内容だったので、ぜひ邦訳の手伝いをさせて欲しい、と返事をしました。ただ、私の知見だけでは不十分と思われたので、SDNやP4 に関して豊富な知識と経験を持たれている海老澤(通称 ebiken)さんにお声がけをし、ebiken さんがファブリックネットワークにも大変お詳しい小林さんに声をかけてくれました。お二人とも翻訳作業をご快諾いただき、3人で分担して翻訳作業にあたることになりました。

出版社は以前からお付き合いがあった翔泳社さんに相談をし、こちらもすぐに話がまとまり、出版に関して不慣れな我々を最後まで手厚くサポートしてくださいました。また、今回は単なる翻訳ではなく、日本オリジナルのコンテンツを加えたものにしたい、という意向もあったため、APRESIA Systems(株)、NTTネットワークイノベーションセンタ、東京大学中尾研究室の方々から新しい SDN のユースケースに関する情報を提供いただき、掲載いたしました。また、ヴイエムウェア(株)のRasmus Rusz Holtetに協力をしてもらって行った日本の SDN 市場に関するインタビュー記事も収録しています。

以下、本書の章立てになります。

第1章 SDNの概要と定義
第2章 ユースケース
第3章 基本アーキテクチャ
第4章 ベアメタルスイッチ
第5章 スイッチOS
第6章 ネットワークOS
第7章 リーフスパインファブリック
第8章 ネットワーク仮想化
第9章 アクセスネットワーク
第10章 SDNの未来
第11章 日本におけるSDN*
付録A [補足] ネットワークファブリック*
付録B [活用例] SDNとNFV*
付録C ハンズオンプログラミング — 演習問題
(*は日本語版オリジナルコンテンツ)

OpenFlow をきっかけに、世界的に SDN ブームが起こってから約10年が経過したわけですが、この10年間で SDN は大きく進化しました。本書がその理解への一助になれば、著者、訳者一同大変嬉しく思います。ご興味を持たれた方は、是非 Amazon でポチっとしていただければ幸いです(笑)

Autcraft: 自閉症を持つ子供のためのマインクラフト – Minecraft for kids with autism

昨日聞いた TED Radio Hour のエピソード “Work, Play, Rest – Part 2” の中の話がとても感動的だったのでご紹介をしようと思います。

紹介されていたのは “Autcraft” という自閉症を持った子供たち専用(大人ももちろんOK)のマインクラフト環境です。Stuart Duncanという人が 2013 年に立ち上げました。自閉症を患っている彼のご長男がマインクラフトに出会い、最初はとても楽しんでいたのですが、マインクラフトの中で馬鹿にされたりいじめられたりしたのが原因で、マインクラフトから徐々に遠ざかるようになってしまいまったそうです。Duncanさんは親として何かできないか、と考え、自閉症を持つ人のためマインクラフト環境を立ち上げたのが Autcraft です。虐めたり揶揄ったりしない、作っているものを壊したりしない、殺したり呪いをかけない、下品な行為をしない、などのルールを設け運用されています。2013 年に簡単な環境を作って公開したところ、2 日間で 750 通のメールを受け取る大反響だったそうで、現在でも1万人ほどの人々に利用されています。

対談の中で Duncan さんがこんなエピソードを話してくれました。Autcraft 立ち上げ当初からのユーザーで当時 12 歳だったある少年は、無事に高校、大学へ進学し、その間もAutcraft を楽しんでいたそうなのですが、大学卒業後は Autocraft から離れてしまったそうです。きっと忙しくなったのだろう、と Duncan さんは思っていたのですが、その彼から昨年のクリスマスにメールをもらいました。そして、彼からのメールにはこう書かれていました。「まだ Autcraft がきちんと動いていて多くの人に利用されていて嬉しいです。Autcraft が無かったら僕は高校にも行けてなかったと思うし、ましてや大学に進学して、やりたいクラブ活動に参加するなんてこともできなかったと思います。そして、今年長年の夢だった LEGO 社本社での採用が決まりました。ありがとうございます!」

ここで私は思わずウルっときてしまいました。技術の力をこんな風に使えたら素晴らしいですよね。興味がある方は是非このエピソードを聞いてみていただければと思います。Duncanさんのと話は 30:38 あたりから始まります。また、Duncanさんが以前に TED のステージで話された時の映像もあります。日本語字幕もありますすのでこちらも是非。

Photo by Nina PhotoLab on Unsplash

2021年総括

今年も無事にこれが書けることに感謝です。まさかコロナが2年続くとは思いもしませんでしたが、もうこれがノーマルになるような気もします。今年はオリンピックもありましたが、もう随分前な気がしますね。それでは、ここ数年やっている一年の振り返りをしてみたいと思います。

主な出来事

1月 clubhouse始める、でもすぐ飽きる
2月 特筆すべきことなし
3月 OCTO-GFI Conference(社内)、CNDOなどに登壇、長女結婚式
4月 次女が大学生に。G検定合格、食当たり?(原因不明)
5月 英会話レッスン再開、coursera開始、家族LINEMOへ乗換、ダニにやられる
6月 父永眠
7月 オリンピック開催、血尿出てビックリ
8月 初孫誕生、膀胱鏡検査悶絶、ワクチン1回目、たくろーさん永眠
9月 ワクチン2回目、56歳誕生日
10月 歯医者に通い始める、ONIC開催
11月 VMworld開催
12月 COSTCOで爆買

新たな取り組み

    • 1Password
    • Coursera
    • 英会話レッスン再開
    • LINEMO
    • メルカリ出品デビュー
    • おうちlab刷新

いろいろなサービスでパスワード漏洩のニュースが流れるたびに、サービスごとにパスワードを変えねば、と思いつつも、300 以上のサービスを使っているとなかなかそれも難しく、実行に移せていませんでした。が、今年ようやく重い腰を上げる決断をし、パスワード管理システムを使い始めることにしました。ブラウザのパスワード管理機能や Mac の Keychain を使う、という選択肢もありましたが、クロスブラウザ、クロスマシンで動くものを使いたかったので、1Password を使うことにしました。他にも LastPassBitwarden なども検討しましたが、まずは一つ使ってみようということで。結果、とても幸せになりました!

今年は Coursera というオンライン学習も始めてみました。手始めに University of Colorado Boulder 校の “The Little Stuff: Energy, Cells, and Genetics” というクラスを取ってみました。とても面白かったです。マウスに特定の匂いを嗅がせてから電気ショックなどのストレスを与えることを繰り返すと、匂いを嗅いだだけで怯えるようになる、というのはいわゆる「パブロフの犬」の実験でよく知られている条件反射なわけですが、なんとこのように訓練されたマウスの子孫にもそれが遺伝する、というのです。これにはびっくり! このように訓練されたマウスの遺伝子の塩基配列には変化が無いので、塩基配列以外のところで遺伝が起っているということになります。このような遺伝の仕組みを調べる学問は「エピジェネティクス(後成学)」と呼ばれ、近年注目を浴びているとのことでした。ふーむ、面白い!

おうちLabについては、機材は調達したものの、ここ数ヶ月忙しくて、残念ながら戯れる時間が取れませんでした。これについては,来年早いうちに記事を書きたいと思います。

Facebook Most Likes

3位 10/3 176, ONIC(お肉)打ち上げ(176 Likes)
2位 誕生日祝いのお礼(221 Likes)
1位 VMware 新オフィス@田町 (312 Likes)

コロナでオフィスを縮小もしくは完全に撤退する会社が多い中、VMwareは新しいオフィスを田町にオープンし、大幅な増床をしました。もったいないくらい素晴らしいオフィスです。このオフィスに負けないくらい素晴らしい仕事をしなければ、という気になりますね。

ONIC-2021
ONIC 2021 の打ち上げ
VMware Office at Tamachi
VMware の新オフィス@田町

映画

今年も出張がほとんどなかったので、映画も22作品と少なめです。(2019年は65作品、2020年は28作品)。ただ、今年は「鬼滅の刃」、「アルハンブラ宮殿の思い出」、「全裸監督2」、などのシリーズものも見たので、時間的には去年とさして変わっていないかもしれません。

今年見た映画のベスト3:

    1. コンフィデンスマンJP <ロマンス編>
    2. ANNA/アナ
    3. シティーハンター THE MOVIE史上最香のミッション

コンフィデンスマンJP、面白いですね。なかなか良くできています。ANNA/アナ、はどんでん返しに次ぐどんでん返しで、これも面白かったです。コンフィデンスマンJPといい、私、どんでん返しモノが好きなのかも。シティーハンターは言わずと知れた日本漫画の名作ですが、これはそれをフランスで実写化したもの。漫画の実写化というとガッカリすることが多いのですが、これはかなりいい感じです。原作に対するリスペクトが満ち溢れててとても良かったです。

Confidenceman JP
コンフィデンスマンJP <ロマンス編> (画像ソース: Amazon)
ANNA
ANNA / アナ(画像ソース: Amazon)
City Hunter
シティーハンター THE MOVIE史上最香のミッション(画像ソース: Amazon)

読書

今年は10冊ちょっと。まだまだ少ないですね。。うち、洋書が1冊。今年はディープラーニング(G検定)の勉強をしたので、技術系ではディープラーニング関係の本が多かったです。non-技術では「三体IIおよびIII」が圧倒的に面白かったです。新入社員の方に教えてもらった「動的平衡」も興味深い話でした。

Three Body Problem 2
三体II(画像ソース: Amazon)
Three Body Problem 3
三体III(画像ソース: Amazon)

Best YouTube Video

今年見た YouTube ビデオでベストだったのは、

でした。これは会社の同僚が教えてくれたもので、長年日本語言語学界隈で物議を醸してきた「象は鼻が長い」という文をどう解釈するか、ということについて話しているものです。この文、一見単純な日本語の文章に見えますが、さて、この文の主語は何だかわかりますか? 「象」なの? それとも「鼻」なの? あれれ、分からなくなってきましたよね(笑) この YouTube 動画はそれを解き明かしてくれます。

バイタル&健康

昨年は10 Kg以上の減量になりましたが、今年はそこからリバウンドしないことが目標でした。結果、1.6 Kg の体重増ということで、まあ、上出来だったのではないかと思います。体脂肪率もちょっと上がっちゃったのはご愛嬌。

Weight and Fat in 2021
体重と体脂肪率の推移 in 2021

今年の平均睡眠時間は5時間42分。去年より若干増えましたが、ほぼ同じペースです。

Sleep Meister Stat in 2021
Sleep Meisterでの統計 in 2021

NRC (Nike Run Club) でのランニングは引き続きやっています。今年は 1,200 km 以上走りました。「ランニングする前に読む本」という本を読み、スロージョギングをするようになったので、スピードは去年より落ちましたが、そのぶん距離を走るようになりました。通常、1 kmを 8 分くらいのペースで 5〜7 km、長いときは 10 kmくらい走っています。NTC (Nike Traning Club) でのワークアウトも続けています。

Nike Run Club in 2021
Nike Run Club in 2021

おかげさまで今年も大きな病気を患うことはありませんでした。4月に原因不明のムカムカと下痢を経験をしたくらいです。ただ、今年は何回か血尿を経験しました。いずれも走った直後の尿だけだったので、病気の可能性は低いと思いつつも、念の為に病院で調べてもらうことにしました。そしらた徹底的に調べてくれて(詳しくは「膀胱鏡検査」を参照)、結論としては問題ないだろう、ということになりました(ふー)。膀胱鏡検査はもう二度とやりたくないですw

ファイザーのワクチンを2度接種しました。副反応は全くと言っていいほど何も無く、なんだか肩透かしに合った感じでした。来年ブースターが打てるようになったらすぐに打とうと思います。Vaccination Certificate

仕事

    • VMware Japan Field Innovation Program 開始
    • 澤円さんとの対談

今年は Field Innovation Program という新しい試みの立ち上げに注力しました。その名の通りで、フィールドからイノベーションを加速させることを目的にしています。現在7つほどイニシアチブが立ち上がっており、参加してくれているメンバーが積極的に活動をしてくれています。

また、澤円さんと行った対談もとても良い経験でした。テーマは「マルチプラットフォームやクラウドネイティブ時代を生き抜くエンジニアのキャリアとは」。さすがに澤さん、お話が大変面白い。1時間ほどの対談でしたが、もっとお話をしていたかったです。その時の話が記事になっていますので、ご興味ある方は読んでみてください。

Fireside Chat with Sawa-san
澤円さんとの対談

プライベート

今年はプライベートでもいくつか大きな出来事がありました。

6月に父が亡くなりました。88歳でした。いまは、2年ほど前に先に天国に行った母と楽しく過ごしていることでしょう。

3月末に長女の結婚式を行いました。ちょうどコロナの合間のタイミングで、無事にできたのはラッキーでした。また、4月からは次女が大学生となり、北陸での生活が始まりました。そして、8月に長男夫妻のところに女の子が生まれ、初孫となりました(名実ともにおじいちゃんとなりましたw)

Wedding
長女の結婚式
The First Grand Daughter
初孫誕生

そして同じ月に、長年大変親しくさせていただいていた歌仲間の「たくろーさん」がご逝去されました。あまりに突然のことで心の整理がつかず、涙が出なくなるくらい泣きました。今頃は天国で大好きだった “You Don’t Have To Be A Star” を思い切り歌われていることでしょう。沢山の楽しいい思い出をありがとうございました。

買ってよかったもの

今年買ってよかったなと思ったものは、

Sticker
画像ソース: Amazon

です。実家への投げ込み広告防止の為に買ったのですが、それなりの効果はあったようです。350円投資の効果は十分にあります(笑)

Blog

今年はこの記事を含めて外向け3本、社内向け2本、英語のものはなし、という少々寂しい結果となってしまいました。外向けの技術に関するものも1本だけ。それも「このままだと今年のBlogは膀胱鏡検査話だけになってしまう!」という危機感から、慌てて書いた一本という体たらく。来年はもっと頑張りたいと思います。

コミュニティへの貢献

相変わらず VMware DevOps MeetupOpen Networking Conference (ONIC) は継続してやっています。また、Cloud Native Days Spring 2021 OnlineOpen Policy Agent Rego Knowledge Sharing Meetup などでもお話をさせていただきました。

オープンソースへの貢献は今年も殆どできず、簡単な pull req をいくつか送っただけでした。

今年前半、COVID-19によりインドが大変厳しい状況に陥ったため、2021 India COVID-19 Recovery Fund に寄付を行いました。また日本ユニセフにもわずかながら寄付をさせていただきました。

また、オープンソースというわけではないですが、今年の後半取り組んだプロジェクトがあります。来年前半にはご報告できるのではないかと思います。

フライト

今年は国内、国際ともに一度も飛行機には乗らずじまいでした。物理的に移動をしなくても概ね支障なく仕事ができるのが分かった一方、やっぱり物理的に移動する重要さも痛感しました一年でした。来年は少し移動できるようになるといいなー、と思います。

今年新規開拓した肉屋

外出が減ったせいで、新しい開拓は少なめです。

もちろんこれしか肉を食べに行かなかったわけではありませんよ。リピートした店もそこそこあります^^

2022年の抱負

まず去年掲げた2021年の抱負を振り返ってみます。

    • 筋肉つける(未達
    • 1,000キロ走る(達成
    • 読書20冊(技術書10冊、non技術書10冊)(未達
    • コードの貢献(未達
    • 月1料理を月2料理くらいに(未達
    • お家ネットワークの10G化(未達

ひどい、ひどすぎる。ま、全部自分の怠惰が原因なわけですが。。。

というわけで2022年の抱負は(できるかぎり定量的に)、

    • 筋肉つける(筋肉量50Kgを目指す)
    • 読書20冊(技術書10冊、non技術書10冊)
    • Blog 4本(できれば英語でも)
    • 1 Project にコードの貢献
    • TOEIC 950、IELTS Academic 7.5
    • ハーフマラソンに参加
    • せめて月1料理を

を上げたいと思います。来年の振り返りでまた惨敗となる可能性も大きいですが、目標は高く持つべし!とうことで。自らを追い込むタイプ。

それでは、皆さんまた来年もよろしくお願いいたします。

宣言的ネットワーキングとインクリメンタル処理

はじめに

2020年1月に札幌で行われたJANOG45で、Cisco Systemsの河野美也さんと共同で「宣言的(Declarative)ネットワーキング」というセッションをやらせていただきました。その時の資料はこちらです。残念ながら30分と時間が限られていたため、十分にお伝えすることができなかった部分もあるため、こちらに補足記事としてまとめておくことにしました。

そもそも宣言的(Declarative)って何?

最近「宣言的」や「Declarative」という言葉をよく耳にするようになっていると思います。実は、宣言的/Declarativeという言葉はそう新しいものではなく、実はかなり前から使われている言葉です。プログラミング言語の世界では関数や述語論理に基づいたプログラミング言語などが従来型の命令的プログラミング言語との対比として「宣言的プログラミング言語」と呼ばれていました。比較的最近では、Ansibleなどの構成管理ツールやJenkinsなどのCI/CDツールにも宣言的な側面があると言われていますし、FlutterやSwiftUIなどのU/Iフレームワークも「宣言的U/I」などと呼ばれ注目されています。このように「宣言的」という言葉はさまざまな文脈でいろいろな意味で使われており、決定的な定義があるわけではありません。宣言的という言葉が持つ多面性については河野さんがこちらの記事で言及してくださっていますので是非ご一読ください。

しかし、最近「宣言的」という言葉をよく聞くようになったのはなんと言ってもKubernetesの影響でしょう。Kubernetesが宣言的であると言われるのは、Kubernetesでは各種リソースの望まれる状態(Desired State)をManifestとして定義する事ができて、現在の状態(Current State)とDesired Stateの間にズレがある場合は、コントローラがそれを検知し自動的にそのズレを修復するように動くようになっているからです。「どのようにしてそのズレを解消するかは問わない。ともかくDesiredな状態にしてくれ!」と指示をするので、Kubernetesは宣言的であると考えらています。Desired Stateと現在のStateを常に比較し、差異があればそれをなくそうとする動作はReconciliation Loopと呼ばれており、各コントローラに実装されています。Kubernetesに自己修復(Self-Healing)性があると言われるのは、Kubernetesの各種コントローラにこのようなReconciliation Loopが実装されているためです。

宣言的(Declarative)ネットワーキング

JANOG45のセッション後に幾つかのフィードバックをもらいました。その中の一つは以下のようなものでした。

非常に妥当なご意見だと思いますし、他にも同じように感じられた方も多かったようです。なぜ今日のネットワークはKubernetes的な宣言性を持ち得ていないのか、どのようにしたらそれを実現できるのか、という点に興味を抱いている人は多いんですね。これは大変興味深い話であり、私自身も大きな関心を持っています。

個人的には、今日のネットワークがKubernetes的な宣言性を持てていない理由の一つとして「Desired Stateを定義するのが難しく、Reconciliationするのも難しい」という点があるのではないかと思っています。KubernetesのDeploymentであれば、どのようなコンテナがいくつ動いているかを把握したり、アプリケーションが応答するかをlivenessProbeなどで監視して、Desired Stateとのズレがあったら基本的に再度コンテナを立ち上げ直す事でReconciliationを図ります。では、ネットワークのDesired Stateって何でしょう? おそらく「ホストAからホストBにパケットが届く事」あるいはL4まで考えれば「ホストA上のサービスCからホストB上のサービスDへパケットが届く事」ということになるのではないかと思います。このようなDesired Stateを検出するのはなかなか困難です。コンテナは一次元的な要素なので比較的シンプルですが、ネットワークはsourceとdestinationがある二次元的な要素ですので、N^2問題に直面します。ホストがN台あったとすると、全ての到達性を確認するためにはN*Nのprobeをしなければいけません。Network Policy(ACL)も含めてネットワークのDesired Stateを考えると複雑性はN*Nでは済まず、サービス数Mも含めて考えてN*M*N*Mでの到達性を把握する必要があります。パケットの到達性はsourceに依存するので、このようなprobeをどこからしたら良いのか、という点も悩ましいです。また、仮にこのようなProbeできてネットワークのDesired Stateと現在のStateにズレをうまく検出できたとしても、それをどのようにReconcileするかも難しいです。KubernetesのDeployment/Podの場合は、単純にコンテナを作り直す、という戦略を取っているわけですが、ネットワークの場合は、単純にネットワークを実現しているコンテナを再起動しても解決しない場合が多いと思います。ネットワークのエンドポイントに問題はなく、途中の経路に何か問題があって疎通性が阻害されている場合には、いくらエンドポイントを再起動してもおそらく問題は解決しないでしょう。

このように、現在のネットワークにKubernetes的な宣言的特性や自己修復性を持たせるのは簡単なことではないように思います(少しドメインを限定すればネットワークにも宣言性を持たせることは可能という考え方もあります[3])。ただ、上記のような宣言的ネットワーキング / Declarative Networkingの困難さに立ち向かおうとしている人はきっといるはずです。非常に有望な「研究課題」と言えるでしょう。

もう一つの宣言的ネットワーキングの側面

上記のようなネットワークのKubernetes的な宣言性は非常に興味深いトピックではありますが、私がJANOG45でお話をさせていただいたのは、これとはちょっとまた別の観点からの宣言的ネットワーキングの話で、SDNコントローラに宣言的なエンジンを用いることによっていかに堅牢で効率的なコントローラを実現できるか、という点でした。

ネットワークは非常に複雑な分散システムです。設定している最中に障害が起こり得ます。一般的にネットワーク設定は冪等ではありません(例えばポートを作った後にどこか別のノードで設定が失敗し、再度最初から同じ設定を入れようと思っても、すでにポートが作成済であればエラーになってしまうでしょう)。したがって、設定の途中で障害が起こったら単純に最初からやり直すだけではダメで、綺麗にロールバックしてやらなければいけません。ロールバックしている最中にまた別の障害が起こる、など多重障害が起こる可能性もあります。このようにさまざまなFailureシナリオにきちんと対応するするのは簡単ではありません。堅牢なSDNコントローラを作るのは非常に難しいことなのです。

Niciraの創業者の一人であるMatin Casadoから聞いた話なのですが、「Niciraでは幾つかのSDNコントローラを試作したが、上記のようなネットワークの複雑性からどれもなかなかうまくいかなかった。最終的に最も堅牢に動いたのはDatalogをベースにした宣言的なエンジンを使ったコントローラだった」とのことでした。この話を聞いて以来、私はずーっと宣言的なSDNコントローラに興味を抱いていました。

たまたまCiscoの河野さんも宣言的ネットワークに興味関心をお持ちである、ということを知り、それでは共同でJANOGでセッションをやってみましょう、ということになった次第です。河野さんからは宣言的ネットワークに関するさまざまな観点と考察を、私の方からは宣言的なSDNコントローラエンジンの適用事例の一つとして、OVNのコントローラにおけるDatalog技術の適用のお話をさせていただきました。OVN の概要とアーキテクチャに関してはこちらこちらを参考にしてください。

SDN コントローラとテーブル更新

一般的にSDNコントローラは多数のテーブルを扱います。その中でもフローテーブルは非常に巨大かつ動的に更新される必要があるものです。VMやコンテナは別のホストに移動する可能性があるので、それらのイベントに追従する形でフローテーブルを動的に更新してやらなければいけません。これを大規模な環境でやるのは思いの外大変です。

仮に2台のハイパーバイザ(Host1とHost2)と仮想マシンが3台(VM1、VM2、VM3)があり、VM1とVM2がHost1上で動いていて、VM3がHost2上で動いていたとしましょう。

SDNコントローラはそのような状況を表す以下のようなテーブルを持っているはずです。

Controller:

VM IP Host Port
VM1 10.10.10.101 Host 1 2
VM2 10.10.10.102 Host 1 3
VM3 10.10.10.103 Host 2 2

SDNコントローラはこのテーブルから各ハイパーバイザ(Host1、Host2)のフローテーブルを以下のように設定することになります。

Host1:

Match Action
dst=10.10.10.101 output:2 (local port)
dst=10.10.10.102 output:3 (local port)
dst=10.10.10.103 output:1 (tunnel port)

Host2:

Match Action
dst=10.10.10.101 output:1 (tunnel port)
dst=10.10.10.102 output:1 (tunnel port)
dst=10.10.10.103 output:2 (local port)

このようなフローテーブルの設定を行うには、概ね以下のようなコードをコントローラに実装すれば良いでしょう。

for h in Hosts:
  for vm in VM:
    if vm.host == h:
      h.add_flow(match="dst={vm.ip}", action="output:{vm.port}")
    else
      h.add_flow(match="dst={vm.ip}", action="output:{tunport}")

ここで、仮にVM2がHost1からHost2に移動(vMotion/Live Migration)したとしましょう。

コントローラはこのようなVMの移動を把握することができますので、自分が持っているテーブルを以下のように更新します。

Controller:

VM IP Host Port
VM1 10.10.10.101 Host 1 2
VM2 10.10.10.102 Host 2 3
VM3 10.10.10.103 Host 2 2

当然この変更に合わせてハイパーバイザ上のフローテーブルも更新しなければなりません。一番簡単な方法は、上にあげたロジックを再度実行することです。こうすれば適切なフローテーブルが新たに各ハイパーバイザに設定されるはずです。

Host1:

Match Action
dst=10.10.10.101 output:2 (local port)
dst=10.10.10.102 output:1 (tunnel port)
dst=10.10.10.103 output:1 (tunnel port)

Host2:

Match Action
dst=10.10.10.101 output:1 (tunnel port)
dst=10.10.10.102 output:3 (local port)
dst=10.10.10.103 output:2 (local port)

しかしこれはあまりに非効率です。テーブルが小さいうちはこのような実直な方法でも良いかもしれませんが、テーブルサイズが大きくなってきた場合、この方法は明らかにCPUやネットワーク帯域の無駄になります。大規模環境では設定にかかる時間も無視できないほど大きくなってしまうでしょう。もう少し良い方法はないでしょうか?

誰もがすぐに思いつくのはおそらく「変更があったところだけ更新をする」という方法でしょう。いわゆるインクリメンタルなアップデート処理をすれば良いわけです。アルゴリズム的にはこんな感じになるでしょう。

for h in Hosts:
  if old_vm.host == h:
    h.del_flow(...)
  else
    h.del_flow(...)
  if new_vm.host == h:
    h.add_flow(match="dst={new_vm.ip}", action="output:{new_vm.port}")
  else 
    h.add_flow(match="dst={newvm.ip}", action="output:{tunport}")

一見簡単そうな変更に見えるかもしれませんが、実は結構面倒な処理になります。このようなインクリメンタル処理のアルゴリズムはテーブル毎に個別に実装してやらなければいけません。OVNコントローラには数十個のテーブルがありますし、NSX-Tでは百を超えるテーブルがありますので、すべてのテーブルにこのようなインクリメンタル処理のアルゴリズムを個別に実装していくのはそう簡単な作業ではありません。また、テーブル間には依存関係がある場合があるので、一つの変更に対して、整合性を保つように複数のテーブルを注意深く更新していかなければなりません。設定の途中でエラーになった際のリカバリー処理なども考えると、テーブルごとにインクリメンタルな処理を実装する方式はかなり難度が高いことで、場合によってはコントローラの堅牢性を失いかねません。

宣言的コントローラエンジンとDatalog

OVNは大規模環境でも堅牢に動くProcution ReadyなSDNコントローラを目指していますので、まさに上記のような課題に直面することになります。

OVNではovn-northdとovn-controllerという2種類のコントローラが動いています。前者がネットワーク全体を管理するコントローラ、後者が各ハイパーバイザで動いているローカルのコントローラです。現在、ovn-northdコントローラにDatalogを使った宣言的な仕組みを適用する試みが行われています。

Datalogは一階述語論理に基づいた宣言的なプログラミング言語です。文法的にはPrologに似ていますが、Prologとはいくつかの点で異なり、演繹的データベースの問い合わせに使われることが多い DSL (Domain Specific Language) の一つです。

例えば、

Parent(Anakin, Luke).
Parent(Anakin, Leia).
Sibling(c1, c2) :- Parent(p, c1), Parent(p, c2), c1 != c2.

というDatalogのプログラムからは

Sibling(Luke, Leia).
Sibling(Leia, Luke).

という結果が導出されます。”:-” を含んだ行は「ルール」と呼ばれ、”:-” の右側の項が全て成り立つ時に”:-” の左側が成り立つ、という意味になります。”:-” を含まない行は「事実(Fact)」と呼ばれます。上記のDatalogプログラムをざっくりと日本語にすると、

Lukeの親はAnakinである(事実)。Leiaの親はAnakinである(事実)。c1とc2が共通の親を持っており、c1とc2が異なれば、c1とc2は兄弟である(ルール)。

ということになり、そしてそこから導かれる結果は、

LukeはLeiaと兄弟である。LeiaはLukeと兄弟である。

となります。

Datalogを使うと、先に紹介した手続き的なフローテーブルの書き換え処理を宣言的に記述する事ができます。例えば、

Host(1),
Host(2);
VM("VM1", "10.10.10.101", 1, 2),
VM("VM2", "10.10.10.102", 1, 3),
VM("VM3", "10.10.10.103", 2, 2);
Flow(h, "dst=${ip}", "output:${p}") :- Host(h), VM(_, ip, h, p).
Flow(h, "dst=${ip}", "output:1") :- Host(h), VM(_, ip, other, _), other != h.

というルールとFactから

Flow(1, dst=10.10.10.101, output:2).
Flow(1, dst=10.10.10.102, output:3).
Flow(1, dst=10.10.10.103, output:1).
Flow(2, dst=10.10.10.101, output:1).
Flow(2, dst=10.10.10.102, output:1).
Flow(2, dst=10.10.10.103, output:2).

という結果を自動的に演繹的に導出することができます。

また、VM2がHost 1からHost 2のポート3に移動したとすると、

VM("VM2", "10.10.10.102", 2, 3),

のようにFactをアップデートをすれば、それを反映した正しい結果、

Flow(1, dst=10.10.10.101, output:2).
Flow(1, dst=10.10.10.102, output:1).
Flow(1, dst=10.10.10.103, output:1).
Flow(2, dst=10.10.10.101, output:1).
Flow(2, dst=10.10.10.102, output:3).
Flow(2, dst=10.10.10.103, output:2).

という結果を得ることができます。このように、Datalogを使うとフローテーブルがどのようなルールに則っているべきかを宣言的に記述するだけで良くなり、SDNコントローラを実装する人間はどのようにハイパーバイザのフローテーブルを設定すべきか、そのロジックを考えなくて済むようになります。これはとてもありがたいことです。

Differential Datalog

Datalogを使うと、フローテーブルなど、SDNコントローラが扱わなければいけない各種テーブルの更新処理を宣言的に記述することができるようになります。手続き的に書くよりはるかにメンテナンス性の良い記述をすることができるようになるので、コントローラの堅牢性の向上に大きく貢献すると考えられます。

ただし、Datalogを使うだけでは、先ほど見たような「インクリメンタルな更新処理をいかに効率よく行うか」という問題を解決することにはなりません。そこで登場するのがDifferential Datalogです(DDlogと呼ばれることもあります)。

Differential DatalogはDifferential Dataflowという仕組みの上に作られていますので、まずDifferential Dataflowについて簡単に説明をしておきましょう。Differential Datalowはいわゆるビックデータ処理システムの一つと考えられますが、インクリメンタルな更新をとても効率よく処理することができるのが特徴です。

例えばTwitterのツイートにつけられているハッシュタグ間の相関関係を調べるために、ハッシュタグをノード、mentionをそれらのノードを繋ぐ辺として見立て、ハッシュタグの「グラフ」を作ることを考えてみましょう。いわゆるグラフから結合成分(Connected Component)を抽出する問題です。現在、Twitterで1日につぶやかれるツイートの数はおよそ5億個です。このような大量のデータからグラフの結合成分を抽出する処理はビッグデータ処理システムの代表格の一つであるMapReduceの得意分野の一つです。ただ、Twitterのように常に新しいツイートが行われる(すなわち入力が随時変化していく)ようなケースはMapReduceはあまり得意ではありません。入力が変化するたびに初めから再度計算をし直さなければいけないからです。一方、Differential Dataflowは入力の変化に応じてアウトプットを効率よく計算することができるため、入力の変化に応じた結果をリアルタイムに得ることができます。

Differential Datalogは、Differential Dataflowをバックエンドに用いたDatalog処理系で、VMware Research Groupで開発されました。Differential Dataflowをベースにしているので、優れたインクリメンタル更新処理性能を持ちつつ、Datalogによる宣言的なプログラミングが可能です。ユーザはどのようにインクリメンタル処理が行われるのかを意識する必要はなく、基本的にDatalogでルールやFactを記述すれば、入力の変化に追従して素早く出力を計算することができます。

Datalogの宣言性とDifferential Dataflowの優れたインクリメンタル更新処理性能を兼ね備えているDifferential Datalogは、SDNコントローラにおけ各種テーブルの更新処理にとても向いている仕組みです。

実際にDifferential Datalogの差分更新がどれくらい高速なのか、簡単な実験をしてみましょう。

仮に100台のハイパーバイザー(Host0〜99)上に10,000台のVM(VM0〜9999)があり、VM0がHost0からHost1に移動した際のフローテーブルを計算するとします。まずは、フローテーブルのあるべき姿を定義するルールをDifferential Datalogで宣言的に定義します。

$ cat flowtable.dl
input relation Host(id: bigint)
input relation VM(vm: string, ip: string, host: bigint, port: bigint)
output relation Flow(host: bigint, matches: string, action: string)
Flow(h, "dst=${ip}", "output:${p}") :- Host(h), VM(_, ip, h, p).
Flow(h, "dst=${ip}", "output:1") :- Host(h), VM(_, ip, other, _), other != h.

このルールを使用するためには、これのコードをDifferential Datalogのコンパイラ(ddlog)にかけます。そうすると、このルールが埋め込まれたRustのコードが生成されます。

$ ddlog -i flowtable.dl -L ~/differential-datalog/lib/

ddlogによって生成されたRustコードをDifferential DataflowのライブラリとともにRustコンパイラでビルドすると、Differential Datalogで記述されたルールエンジンを備えた静的ライブラリとCLIツールが生成されます。

$ (cd flowtable_ddlog/ && cargo build --release)
Finished release [optimized] target(s) in 0.49s
$ ls -l flowtable_ddlog/target/release/
total 59100
drwxrwxr-x 34 shindom shindom 4096 Mar 17 09:23 build
drwxrwxr-x 2 shindom shindom 57344 Mar 17 09:36 deps
drwxrwxr-x 2 shindom shindom 4096 Mar 17 09:23 examples
-rwxrwxr-x 2 shindom shindom 15984984 Mar 17 09:36 flowtable_cli
-rw-rw-r-- 1 shindom shindom 2325 Mar 17 09:36 flowtable_cli.d
drwxrwxr-x 10 shindom shindom 4096 Mar 17 09:35 incremental
-rw-rw-r-- 2 shindom shindom 42098680 Mar 17 09:35 libflowtable_ddlog.a
-rw-rw-r-- 1 shindom shindom 2260 Apr 12 07:14 libflowtable_ddlog.d
-rw-rw-r-- 1 shindom shindom 219 Mar 17 09:23 libflowtable_ddlog.la
-rw-rw-r-- 2 shindom shindom 2350122 Mar 17 09:35 libflowtable_ddlog.rlib

今回の例だと、flowtable.dlに記述されたルールエンジンを持つライブラリlibflowtable_ddlog.a、libflowtable_ddlog.rlibと、CLIコマンドのflowtable_cliが生成されます。

本来であれば、このlibflowtable_ddlog.(a|rlib)を使ったコードをRust、C/C++、Java、Goなどで書くべきなのですが、今回は生成されたCLIツールを使って処理性能を簡易的に調べてみることにします。

100台のホスト上に10,000台の仮想マシンが動いている初期状態の作成と、そのうちの一台の仮想マシン(VM0)がホスト0からホスト1に移動をDifferential Datalogで書くと以下のようになります。これで、VMが一台ホストを移動した際のフローテーブルの更新を模擬することができます。また、かかった時間を調べるために、各処理の途中で時刻を表示するようにしてあります。

start;
insert Host(0);
insert Host(1);
      :
insert Host(98);
insert Host(99);
insert VM("VM0", "10.10.0.0", 0, 0);
insert VM("VM1", "10.10.0.1", 1, 1);
insert VM("VM2", "10.10.0.2", 2, 2);
      :
insert VM("VM9997", "10.10.99.97", 97, 9997);
insert VM("VM9998", "10.10.99.98", 98, 9998);
insert VM("VM9999", "10.10.99.99", 99, 9999);

echo initial setup started:;
timestamp;
commit;
echo initial setup finished:;
timestamp;
start;
delete VM("VM0", "10.10.0.0", 0, 0);
insert VM("VM0", "10.10.0.0", 1, 0);
echo update started:;
timestamp;
commit;
echo update finished:;
timestamp;

これをflowtable_cliに食わせてやると以下のような結果になります。

$ ./flowtable_ddlog/target/release/flowtable_cli --no-print < flowtable.dat
initial setup started:
Timestamp: 490458325506557
initial setup finished:
Timestamp: 490467825165438
update started:
Timestamp: 490467825243578
update finished:
Timestamp: 490467829061228

これを見て分かる通り、最初のフローテーブルの作成には約9.5秒かかっていますが、VMが移動した後のフローテーブルの更新には0.00381765秒しかかかっていません。フローテーブルの更新を実直に再計算するとフローテーブルの初期作成とほぼ同じ時間がかかると考えられるので、Differential Datalogのインクリメンタルアップデートの素晴らしい処理性能をご理解いただけるのではないかと思います。

Differential Datalog の OVN への適用

上で述べたように、現在OVNのovn-northdコントローラをDifferential Datalogを使って再実装する努力が行われています。この作業はOVNのGithubリポジトリのddlog-dev-v2というブランチで開発作業が進められています。

Differential Datalogを使ってテーブル処理を書く主なメリットは、

  • よりメンテナンス性の高いコードになる
  • インクリメンタルな更新による性能向上

の2点です。まず、メンテナンス性の高いコードになる、とう点をみてみましょう。以下は、OVNのmeterおよびmeter_bandテーブルを管理する部分のコードです(OVNはC言語で書かれています)。

struct band_entry {
    int64_t rate;
    int64_t burst_size;
    const char *action;
};

static int
band_cmp(const void *band1_, const void *band2_)
{
    const struct band_entry *band1p = band1_;
    const struct band_entry *band2p = band2_;

    if (band1p->rate != band2p->rate) {
        return band1p->rate > band2p->rate ? -1 : 1;
    } else if (band1p->burst_size != band2p->burst_size) {
        return band1p->burst_size > band2p->burst_size ? -1 : 1;
    } else {
        return strcmp(band1p->action, band2p->action);
    }
}

static bool
bands_need_update(const struct nbrec_meter *nb_meter,
                  const struct sbrec_meter *sb_meter)
{
    if (nb_meter->n_bands != sb_meter->n_bands) {
        return true;
    }

    /* A single band is the most common scenario, so speed up that
     * check. */
    if (nb_meter->n_bands == 1) {
        struct nbrec_meter_band *nb_band = nb_meter->bands[0];
        struct sbrec_meter_band *sb_band = sb_meter->bands[0];

        return !(nb_band->rate == sb_band->rate
                 && nb_band->burst_size == sb_band->burst_size
                 && !strcmp(sb_band->action, nb_band->action));
    }

    /* Place the Northbound entries in sorted order. */
    struct band_entry *nb_bands;
    nb_bands = xmalloc(sizeof *nb_bands * nb_meter->n_bands);
    for (size_t i = 0; i < nb_meter->n_bands; i++) {
        struct nbrec_meter_band *nb_band = nb_meter->bands[i];

        nb_bands[i].rate = nb_band->rate;
        nb_bands[i].burst_size = nb_band->burst_size;
        nb_bands[i].action = nb_band->action;
    }
    qsort(nb_bands, nb_meter->n_bands, sizeof *nb_bands, band_cmp);

    /* Place the Southbound entries in sorted order. */
    struct band_entry *sb_bands;
    sb_bands = xmalloc(sizeof *sb_bands * sb_meter->n_bands);
    for (size_t i = 0; i < sb_meter->n_bands; i++) {
        struct sbrec_meter_band *sb_band = sb_meter->bands[i];

        sb_bands[i].rate = sb_band->rate;
        sb_bands[i].burst_size = sb_band->burst_size;
        sb_bands[i].action = sb_band->action;
    }
    qsort(sb_bands, sb_meter->n_bands, sizeof *sb_bands, band_cmp);

    bool need_update = false;
    for (size_t i = 0; i < nb_meter->n_bands; i++) {
        if (nb_bands[i].rate != sb_bands[i].rate
            || nb_bands[i].burst_size != sb_bands[i].burst_size
            || strcmp(nb_bands[i].action, sb_bands[i].action)) {
            need_update = true;
            goto done;
        }
    }

done:
    free(nb_bands);
    free(sb_bands);

    return need_update;
}

/* Each entry in the Meter and Meter_Band tables in OVN_Northbound have
 * a corresponding entries in the Meter and Meter_Band tables in
 * OVN_Southbound.
 */
static void
sync_meters(struct northd_context *ctx)
{
    struct shash sb_meters = SHASH_INITIALIZER(&sb_meters);

    const struct sbrec_meter *sb_meter;
    SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) {
        shash_add(&sb_meters, sb_meter->name, sb_meter);
    }

    const struct nbrec_meter *nb_meter;
    NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) {
        bool new_sb_meter = false;

        sb_meter = shash_find_and_delete(&sb_meters, nb_meter->name);
        if (!sb_meter) {
            sb_meter = sbrec_meter_insert(ctx->ovnsb_txn);
            sbrec_meter_set_name(sb_meter, nb_meter->name);
            new_sb_meter = true;
        }

        if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) {
            struct sbrec_meter_band **sb_bands;
            sb_bands = xcalloc(nb_meter->n_bands, sizeof *sb_bands);
            for (size_t i = 0; i < nb_meter->n_bands; i++) {
                const struct nbrec_meter_band *nb_band = nb_meter->bands[i];

                sb_bands[i] = sbrec_meter_band_insert(ctx->ovnsb_txn);

                sbrec_meter_band_set_action(sb_bands[i], nb_band->action);
                sbrec_meter_band_set_rate(sb_bands[i], nb_band->rate);
                sbrec_meter_band_set_burst_size(sb_bands[i],
                                                nb_band->burst_size);
            }
            sbrec_meter_set_bands(sb_meter, sb_bands, nb_meter->n_bands);
            free(sb_bands);
        }

        sbrec_meter_set_unit(sb_meter, nb_meter->unit);
    }

    struct shash_node *node, *next;
    SHASH_FOR_EACH_SAFE (node, next, &sb_meters) {
        sbrec_meter_delete(node->data);
        shash_delete(&sb_meters, node);
    }
    shash_destroy(&sb_meters);
}

かなりの量のコードで、それなりに複雑です。

一方、これと同じ処理をDifferential Datalogで記述すると、

/* Meter_Band table */
for (mb in nb.Meter_Band) {
    sb.Out_Meter_Band(.uuid_name = uuid2name(mb._uuid),
                      .action = mb.action,
                      .rate = mb.rate,
                      .burst_size = mb.burst_size)
}

/* Meter table */
for (meter in nb.Meter) {
    sb.Out_Meter(.name = meter.name,
                 .unit = meter.unit,
                 .bands = set_map_uuid2name(meter.bands))
}

のようになります。非常にシンプルに記述できますので、可読性に優れ、バグの混入の可能性も低くすることができると考えられます。これがDifferential Datalogを使う一つ目のメリットです。

Differential Datalogを適用することのもう一つのメリットは、インクリメンタルアップデートの性能向上です。eBayのHan Zhouが、Cで実装されたovn-northdを使った場合とDifferential Datalog版のovn-northdを使った時のスケールテストの結果を共有してくれています [1]。結果は以下の通りです。

Cバージョン 67分47秒
DDlogバージョン 7分39秒

Differential Datalogを使うことで、およそ10倍程度の性能向上があったことになります。残念ながらこのテストの詳しいテストシナリオははっきりしないのですが、OVNを使ったOpenStack環境でスケール試験を行なったようです。

また、RedHatのMark Michelsonらが別の性能評価をしています。Markが行ったテストは、1つの論理ルータの下に159個の論理スイッチを作り、それぞれの仮想スイッチに92個の論理ポートを作った上でACLを適用する、というものです [2]。その結果をグラフしたのが以下になります。

View post on imgur.com

 

このテストは約15,000論理ポートを順次作成していく、というシナリオなので、インクリメンタルな処理性能を測るのにあまり適したシナリオではありませんが(インクリメンタルな処理性能が最も顕著に現れるのは、すでにある大規模な環境に若干の更新をした場合です)、それでもCでの実装に比べるとDifferential Datalog (DDlog)の実装の方がかなりいい性能が得られていることが分かります。

今後に向けて

テクノロジーとしては非常に有望なDifferential Datalogですが、課題もあります。

Differential Datalogは従来の手続型言語とは大きく異なります。今までCで書いてきた人が「明日からDatalogで書いてね!」と言われてもパラダイムが全く異なりますのでなかなか難しいのではないかと思います。ツールチェーンも少々複雑です。上述の通り、Differential DatalogのコンパイラRustのコードを吐き出します。また、Differential Datalogのコンパイラ自身はHaskellで書かれています。OVNはCで書かれているので、Differential Datalog版のovn-northdの動きを調べる際には、CやDifferential DatalogだけではなくRustや場合によってはHaskellの知識も求められることになります。これらの言語およびそこで使われているツールチェーンを一通り学習するのはそれなりの時間を要するでしょう。また、Differential Datalogにはデバッグの機能も盛り込まれてはいるものの、皆さんが普段使い慣れているデバッガに比べるとまだまだプリミティブです。

ovn-northdのDifferential Datalog実装は現時点ではまだmasterとは別ブランチとなっており、masterブランチではCを使った開発が引き続き行われています。したがって、Cで書かれた新機能がmasterブランチに追加されたら、その機能をDifferential Datalogで実装して、追従していかなければなりません。これをタイムリーにやっていくのものなかなか大変です。完全にDifferential Datalogに移行ができればこのような悩みは無くなりますが、それにはもっともっとテストをしなければなりません。具体的にはCバージョンで生成されるテーブルとDifferential Datalogバージョンから生成されるテーブルがいかなる場合(障害時も含めて)で差異がない、という状態にならないとCバージョンからDifferential Datalog版に移行することはできません。これにはまだ時間がかかると思われます。また、現時点でDifferential Datalogが適用されているのはovn-northdだけです。理論的にはローカルコントローラであるovn-controllerの方にもDifferential Datalogを適用することができますが、まだ未対応です。

これらの課題がありつつも、Differential Datalogは定性的には素晴らしい特性を持っていますので、今後はOVNだけではなく、大規模なSDN環境における宣言的かつインクリメンタル処理を効率よく行う仕組みとして広く使われていくようになるのではないかと思います。NSX-TのコントローラはNiciraのコントローラと同様、Datalogベースの宣言的なエンジンを備えていますが、そこにDifferential Datalogによる差分更新機能を適用することも検討されているようです。また、Differential Datalogは非常に汎用的な仕組みですので、今後はSDNだけでなく広く分散システムに対する宣言的なアプローチとして使われていく可能性もあります。引き続き注視をしていきたいと思います。

参考リンク

[1] https://mail.openvswitch.org/pipermail/ovs-dev/2019-July/360604.html
[2] https://mail.openvswitch.org/pipermail/ovs-dev/2019-July/360889.html
[3] https://codeout.hatenablog.com/entry/2020/07/06/130233

Photo by Irvan Smith on Unsplash

自宅のインターネット回線品質をVeloCloudで測ってみた

こちらの記事で書いた通り、現在我が家には3つのインターネット回線が引かれている状態です。VeloCloudのEdgeは回線のlatency、jitter、packet lossを測って可視化してくれる機能を持っていますので、今回はこの機能を使って各回線の品質を調べてみました。

結果は以下の通りです(グラフをクリックすると拡大されます)。

ちなみに、”So-net” はNURO光、”NTT” はフレッツ光(OCN)、”ARTERIA Networks Corporation” はUCOMを表しています。VeloCloud EdgeにWAN回線を繋ぎこむと、WAN側のアドレスを認識して、そのアドレスから自動的にその回線の通信サービスロバイダ名をWANリンクのNameとして設定してくれるようになっていますので便利です(EdgeがNATの内側にあってもこの機能は動きます)。

フレッツ光(OCN)- Latency –

フレッツ光(OCN)- Jitter –

フレッツ光(OCN)- Packet Loss –

NURO光 – Latency –

NURO光 – Jitter –

NURO光 – Packet Loss –

UCOM – Latency –

UCOM – Jitter –

UCOM – Packet Loss –

これらの結果をみて分かる通り、フレッツは想像通りインターネットが混雑する時間帯(21:00から0:00くらい)はLatency、Jitter共に大きくなっており、Packet Lossも若干見られます。前回のテスト結果で見られたような通信速度の低下を裏付ける形になっています。一方、NUROとUCOMは全ての時間帯で安定しており、Jitter、Packet Lossもほとんどありません。なお、現時点ではVeloCloud EdgeはIPv6のサポートをしていないので、これらの測定結果はIPv4のものです。IPv6で測れればフレッツ(OCN)はもっと良好な結果になったと思われます。

それぞれのグラフの縦軸のスケールが異なるので、パッと見比較しにくいと思いますので、Latency、Jitter、Packet Lossについて全ての回線を重ね合わせたグラフ(Stackグラフ)も参考のため載せておきます。色が全て青系の色なのでちょっと分かりにくいですが・・・。

3回線重ね合わせ – Latency – 

3回線重ね合わせ – Jitter –

3回線重ね合わせ – Packet Loss –

Photo by Joe Neric on Unsplash

我が家にNUROがやってきた!

先日、自宅に無事にNUROの回線が開通しました。これで、UCOM、フレッツ光(OCN)に加えNURO光が使えるようになり、「トリプルホーム」という少々贅沢な環境となりました。なんでこんな(無駄に贅沢なw)ことになっているか不思議に思われるかもしれませんが、これには若干歴史的な経緯もありまして。。備忘録も兼ねてこれまでの経緯を記録すると共に、これらのインターネットサービスの現時点での快適度について調べてみました。

我が家のインターネット環境の歴史

今から約20年前の1999年、当時インターネットアクセスの手段としてはアナログ電話やISDNやが一般的だった中「家を買うなら光が欲しい」と思いまして(笑)、今のマンションを購入する際に「部屋まで光を引き込むこと」というのを入居の条件にしたのでした(新築マンションだったのでそのような交渉が可能でした)。ただ、当時はマンション施工業者も「ファイバーって何? PD?なんじゃそりゃ!」みたいな状態だったので話が全く噛み合わず埒があかなかったので、結局入居時に「INS1500 (PRI) を使います!」と宣言し、それでやっと光を通してもらえることになりました。というわけで、無事に入居時にファイバーが部屋まで引き込まれました。ただ、それをちゃんとPRI回線として使ったのは、2000年問題対策として自宅にMAX6000を置いて(当時、私はアセンドに勤務していました)、会社のリモートアクセス環境のバックアップ環境を作った時くらいで、それ以外の時は休止にしていました。ちなみにMaxを自宅に持ち込んでいた時にテストでMultilink PPPしてみたのですが、自宅で24Bが順に繋がっていくのを見るのはなかなか壮観でした(笑)。

2001年、NTTのフレッツ光のサービスが始まりましたが、すでに引き込んであったファイバーがあったおかげでBフレッツに入ることができました。私一人でBフレッツ回線を使っていたので、極めて快適なネットワーク環境でした。

2003年になって、うちのマンションにもにもようやくインターネットサービスが提供されることになりました。その際、二つの選択肢が提供されました。一つはケーブルTV系のインターネットサービス、もう一つはUCOMが提供するインターネットサービスでした。当時ケーブルTV系のサービスは宅内まではVDSLで引き込む形だったので、私は部屋まで100Mbps Ethernetが来るUCOMのサービスに入ることにしました。それに伴い、Bフレッツのほうは解約しました。UCOMの方が安かった、というのもあるのですが、なんと言ってもUCOMのサービスはグローバルアドレスを/29でくれることが大きな魅力でした!

UCOMのサービスは非常に高速かつ安定していて、しかもグローバルIPを/29でくれるという気前の良さだったのでとても満足しており、10年以上我が家のインターネット環境に変化はありませんでした。次の変化が訪れたのは2016年、私がSD-WANのスタートアップであるViptela(後にCisco Systemsが買収)に入社したタイミングでした。Viptelaは日本にオフィスがなかったので、自宅がオフィス兼ラボになっていました。SD-WANの検証やデモをするためには、複数回線ないと話にならないので、このタイミングでもう一本インターネット回線を部屋まで引くことにしました。検証を兼ねていたので、その時に最も広く使われてたであろうフレッツ光ネクストを選択しました。ファイバーは以前Bフレッツで使っていたものを使い回すことができました。IPv6のテストもしたかったので、プロバイダはOCNを選びました。これで、無事に自宅がデュアルホームな環境になりました(正確にはこの時LTEもWAN回線に使っていたので、すでにトリプル回線になっていたのですが、通信費を抑えるためにLTEは必要のない時は落としておくことが多かったので、常時トリプルホームというわけはなかったです)。

2018年、Ciscoを退社する際にViptelaのラボ機材も返却しましたが、なんとなく未練があってフレッツ光(OCN)は解約はせずにデュアルホームな環境を維持することにしました。ただ、デュアルホームと言っても別に高度なことをしていたわけではなく、普段の生活にはフレッツを使い、DNSやBlogのサーバをUCOM側の回線から外部に公開をする、という感じで使っていました。

この環境に特に不満はなかったのですが、今年になってマンションで利用できるインターネットサービスとして新たにNURO光が加わることなり、前から興味があったので話を聞いてみることにしました。一番気にしていたのはIPv6でしたが、こちらは普通に使える、ということでした。費用的にはフレッツ光よりもNUROの方がだいぶお得になります。気になる速度ですが、フレッツ光の回線速度は1Gbpsに対してNURO光は2Gbpsです。あくまでこれはリンク速度ですので、実効の速度がどれくらいになるかはやってみないとわかりません。とりあえず試してみるか、ということでNUROに申し込みを行いました。

で、先日無事にNUROの回線が開通した次第です。すでに入居時に引いた光ケーブルとUCOMのためのUTPケーブルが通っていたので、新たに光のケーブルを通すのに若干苦労しましたが、無事に工事をすることができました。

我が家のインターネット環境の快適度比較

さて、気になる速度ですが、今回はfast.com を使って調べてみました。fast.comははNetflixが提供しているもので、Netflixが快適に使えるネットワーク環境なのかどうかをユーザが判断できるようにするために提供されています。クライアントがデュアルスタックの際、IPv6で繋がればIPv6で、そうでなければIPv4で測ってくれるので便利です。他のブロードバンド速度測定サイトのように余計な広告が出てこないのもいいですね!

測定方法は3回測って一番結果が良かったものを採用することにしました。時間帯的にはインターネットがあまり混んでいないと思われる時間帯(朝の5:30頃)と混んでいると思われる時間帯(深夜0:00頃)で測りました。また、フレッツ光(OCN)とNUROに関してはIPv4、IPv6についてそれぞれ測っています。ちなみにフレッツ光(OCN)のIPv6はIPoEです。UCOMはIPv6が来ていないので、IPv4のみ測っています。結果は以下の通りです。

IPv4-フレッツ光(OCN)

フレッツ光(OCN) IPv4 早朝
フレッツ光(OCN) IPv4 深夜

IPv6-フレッツ光(OCN)

フレッツ光(OCN) IPv6 早朝
フレッツ光(OCN) IPv6 深夜

IPv4-NURO光

NURO IPv4 早朝
NURO IPv4 深夜

IPv6-NURO光

NURO IPv6 早朝
NURO IPv6 深夜

IPv4-UCOM

UCOM IPv4 早朝
UCOM IPv4 深夜

考察

これからみて分かる通り、フレッツのIPv4 (PPPoE)はよく言われているように、夜間等のインターネット混雑時はかなり遅くなります。IPoEのIPv6はこの影響を受けないので今のところ比較的安定して快適な環境が得られています。NUROはIPv4は混雑時には若干速度が低下するものの概ね良好、IPv6は終始快適な状況です。UCOMは100Mbpsのサービスなので、昼夜を問わず安定して90Mbps近く出ているのは素晴らしいと思います。

今後の予定

この結果からすると当初の目論見通り、フレッツ光を解約してNURO光にすることになるかなーと思います。UCOMは手放せないので、NUROとUCOMのデュアルホーム環境ですね。ただ、フレッツの解約はいつでもできるので、VeloCloud使ってもう少しこのトリプルホーム環境で遊んでみようかな。

Photo by Sincerely Media on Unsplash

Antrea – Yet Another CNI Plug-in for Kubernetes

Antreaとは

2019年11月にSan Diegoで行われたKubeConのタイミングで、Kubernetesのためのネットワークプラグインとして、新たにAntreaというプロジェクトが公開されました。CNI (Container Networking Interface) に準拠したプラグインで、現在はVMwareが中心となって開発をしていますが、オープンソースのプロジェクトですので、今後は徐々にコミュティーからのサポートが得られていくのではないかと思います。GitHubのリポジトリはここにあります。

“Antrea”という名前ですが、実はAntrea Netから来ています。Antreaはフィンランドに近いロシアの街で、世界最古の漁網が見つかった場所として知られており、その網は見つかった場所にちなみんで「Antrea Net」と呼ばれています。Kubernetesという名前は「操舵手」というギリシャ語から付けられたものですが、これに倣ってKubernetes関連のプロジェクトには船や海にちなんだ名前(例えばhelm/tillerなど)がつけられることが多くなっています。一方、CNIのプラグインであるCalicoやFlannelはどちらも繊維(ファブリック)に関係のある名前になっています(「ネットワーク」と「ファブリック」をかけている??)。この「海」と「ファブリック」の二つの要素を兼ね合わせた「漁網」であるAntrea Netに因んでAntreaという名前がつけられました。

Antreaの特徴は以下のような点になります。

  • Open vSwitchによるデータプレーン
  • Network Policyのサポート(iptablesからの脱却)
  • LinuxとWindowsサポート(予定)
  • 幅広いプラットフォームサポート
  • Octantとの連携
  • rspanやIPFIXのサポートなど(予定)

Antreaは仮想スイッチとして広く使われているOpen vSwitch (OVS)をデータパスに使用しているのがもっとも大きな特徴です。OVSをデータパスに使用することでいくつかのメリットが得られます。KubbernetesのNetwork PolicyやServiceのロードバランスを実現するために、他のCNIプラグインの多くはiptablesを使っています。iptablesは古くからある実績豊富な機能ではありますが、性能はあまりよろしくありません。iptabblesはルール数が多くなると、CPUに対する負荷、遅延、設定時間が非常に大きくなることが知られています。複雑なNetwork Policyや多数のServiceを使っているKubernetes環境では、iptablesベースのCNIでは期待した性能が得られないことが予想されます。一方、AntreaはOVSをデータプパスに使っているため、iptablesに起因する性能的問題を回避することができます。

OVSはLinux以外のOS(例えばWindows)もサポートしているので、多様な環境にまたがったKubernetesネットワーク環境を実現することができます。現時点のAntreaはLinuxのみのサポートですが、将来的にはWindowsもサポートする予定です。KubernetesはすでにWindowsをサポートしていますので、AntreaがWindowsをサポートするようになれば、LinuxとWindowsにまたがったネットワークを作って、そこに統一的なNetwork Policyを適用することができるようになります。また、OVSは仮想スイッチの機能だけではなく、他にもさまざまな機能(NetFlow/sFlow/IPFIXやRSPANなど)をサポートしています。将来的にはこれらの機能をAntreaから利用できるようにしていく予定です。

Antreaは幅広いプラットフォームで実行されれることを想定しています。オンプレの仮想環境はもちろん、ベアメタルや各種パブリッククラウドで実行することを想定して作られています。AntreaはKindで作ったKubernetesクラスタもサポートしていますので、簡易的にクラスタを用意してテストをすることもできて便利です。ただし、現在はLinuxの上のKindのみのサポートになります。MacOSのKindはまだサポートされてません。Kindで作ったクラスタでAntreaを動かす場合は、OVSのカーネルモジュールは使われず、OVSのユーザ空間のデータパスが使われます(ブリッジ作成時にdatapath_type=netdevが指定される)。

EncapsulationはデフォルトではVXLANが使われますが、GeneveやGRE、STTを使うこともできます。またこれらのEncapsulationとIPsecを組み合わせて使うこともできます。将来的には、Encapsulationをしないモードもサポートが予定されています。

VMwareはNSX-TのためのCNIプラグインとしてNCPを提供しています。それなのになぜ別のCNIプログインを開発しているのか疑問に思われる方もいるでしょう。NCPはすでに多くのプロダクション環境で使われており、実績面ではAntreaよりもはるかに成熟しています。また、NCPはiptablesに依存しないServiceのロードバランシングを既に実現しているので、スケーラビリティ的にも優れています。一方、Antreaはまだ出来立てのホヤホヤで商用の実績はありません。ただ、Antreaは100%オープンソースで手軽に始めることができるのは魅力です。幅広いプラットフォームやOSで利用できるようになる潜在的能力に魅力を感じる人も多いでしょう。Antreaが機能面、堅牢面においてすぐにNCPのレベルに達するとは考えにくいですが、コミュニティのサポートが得られればかなりのスピードで進化していく可能性を秘めていると思います。現時点でAntreaをプロダクション環境で使うことはお勧めしませんが、将来的には何かしらの形でNSX/NCPとの連携のようなこともできるようになるかもしれません。その頃にはAntreaも十分に成熟しているはずですので、ユーザにとって環境やユースケースによって使い分けられる選択肢が広がることは喜ばしいことでしょう。

アーキテクチャ

Antrea はKubernetesに特化したネットワークプラグインです。例えばCalicoはKubernetesだけでなくOpenStackなど他のオーケストレーションシステムをサポートしていますが、AntreaはKubernetesのみを対象としています。そのぶんAntreaはKubernetesの持つ仕組み(API、Tool、Operator)を最大限に利用するようになっています。

アーキテクチャーは以下通りです。

Antreaはいくつかのコンポーネントによって構成されます。Antrea Conrollerはいわゆるコントローラで、Deploymentとして作成さられます。Kubernetes APIを使って関連するオブジェクトの追加や削除を監視しています。一方、Antrea AgentはDaemonSetとして作られ、各ノードで動作します。Antrea agentはOpenFlowの設定をOVS に対して行います。antrea-cniはKubeletからのCNIコマンドをAntrea Agentに引き渡します。antctlコマンドはAntrea Controllerに対するCLIインターフェースを提供するもので、現在絶賛開発中です。

現時点ではkube-proxyはKubernetes標準のものを使用しています(したがって、Serviceのロードバランシングにはiptablesを使います)が、将来的にはこれはOVSベースのロードバランシングを使ったkube-proxyに置き換えられる予定です。Network Policyは既に完全にOVSベースで、iptablesは使われていません。なお、Antreaの最初のリリース(v0.1.0)では、OVSのOpenFlowテーブルを設定するのにovs-ofctlコマンドを使ってしましたが、現在はlibOpenflowライブラリを使うように変更されています。

OctantはKubernetes Clusterの状況を可視化してくれるダッシュボードです。AntreaはOctant用のプラグインを用意しており、Octantから簡単にAntreaの状況を確認することができます。

インストールと動作確認

今回はオンプレの環境でAntreaを動かしてみます。Kubernetes環境はESXiの上にCluster APIで2つのWorker Nodesを作った素のKubernetes環境を用意することにします。ESXi向けCluster API(CAPV; Cluster API Provider for vSphere) はCentOS 7、Ubuntu 18.04、Photon3用のOVAイメージを提供していますが、今回はUbuntu 18.04を使うことにします。AntreaはKubernetes 1.16以降を要求しますので、v1.16.3のOVAイメージを使用することにします。

標準的な方法でCluster APIでKubernetes環境を作ります。ノードが上がってきたら、各ノードでOVSのカーネルモジュールを入れます。Cluster APIで作られるノードは再構築される可能性があるため、本来ならばCluster APIで使うOVAテンプレートに予めOVSを組み込んでおくべきですが、今回はテストなのでそのステップは省略することにします。各ノードで 以下のコマンド

sudo apt update ; sudo apt -y install openvswitch-switch

を実行して、OVSをインストールします。次にカーネルモジュールが正しく読み込まれたか確認しましょう。

$ lsmod | grep openvswitch
openvswitch           131072  0
nsh                    16384  1 openvswitch
nf_nat_ipv6            16384  1 openvswitch
nf_defrag_ipv6         20480  3 nf_conntrack_ipv6,openvswitch,ip_vs
nf_nat_ipv4            16384  2 openvswitch,iptable_nat
nf_nat                 32768  5 nf_nat_masquerade_ipv4,nf_nat_ipv6,nf_nat_ipv4,xt_nat,openvswitch
nf_conntrack          131072  12 xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_ipv6,nf_conntrack_ipv4,nf_nat,nf_nat_ipv6,ipt_MASQUERADE,nf_nat_ipv4,xt_nat,openvswitch,nf_conntrack_netlink,ip_vs
libcrc32c              16384  4 nf_conntrack,nf_nat,openvswitch,ip_vs

この時点ではKubernetesのノードは以下のような状態になっているはずです。

bash-3.2$ kubectl get nodes
NAME                                       STATUS     ROLES    AGE   VERSION
workload-cluster-1-controlplane-0          NotReady   master   30m   v1.16.3
workload-cluster-1-md-0-78469c8cf9-5r9bv   NotReady   <none>   21m   v1.16.3
workload-cluster-1-md-0-78469c8cf9-tlkn9   NotReady   <none>   21m   v1.16.3

STATUSが “Not Ready” になっていますが、これはCNIプラグインがまだ何もインストールされていないためで、期待された動きです。

Antreaのインストールは非常に簡単です。以下のコマンドを実行するだけで必要なコンポーネントが全てインストールされます。

kubectl apply -f https://raw.githubusercontent.com/vmware-tanzu/antrea/master/build/yamls/antrea.yml

ここではAntreaの最新のものを使うようにしていますが、Antreaの開発のペースは早いので、一時的に最新版ではうまく動作しないケースもあるかもしれません。その場合には、

kubectl apply -f https://github.com/vmware-tanzu/antrea/releases/download/<TAG>/antrea.yml

の<TAG>の部分を適当なものに置き換えてください(現時点で最新のリリース版タグは “v0.2.0” です)。

インストールが無事に終了するとkube-system Namespaceに以下のようなPodができて、全てRunningのSTATUSになっているはずです。

bash-3.2$ kubectl get pods -n kube-system
NAME                                                        READY   STATUS    RESTARTS   AGE
antrea-agent-dvpn2                                          2/2     Running   0          8m50s
antrea-agent-qqhpc                                          2/2     Running   0          8m50s
antrea-agent-tm28v                                          2/2     Running   0          8m50s
antrea-controller-6946cdc7f8-jnbxt                          1/1     Running   0          8m50s
coredns-5644d7b6d9-878x2                                    1/1     Running   0          46m
coredns-5644d7b6d9-pvj2j                                    1/1     Running   0          46m
etcd-workload-cluster-1-controlplane-0                      1/1     Running   0          46m
kube-apiserver-workload-cluster-1-controlplane-0            1/1     Running   0          46m
kube-controller-manager-workload-cluster-1-controlplane-0   1/1     Running   2          46m
kube-proxy-4km5k                                            1/1     Running   0          38m
kube-proxy-cgxcp                                            1/1     Running   0          38m
kube-proxy-h4css                                            1/1     Running   0          46m
kube-scheduler-workload-cluster-1-controlplane-0            1/1     Running   2          46m
vsphere-cloud-controller-manager-gfgbp                      1/1     Running   1          45m
vsphere-csi-controller-0                                    5/5     Running   0          45m
vsphere-csi-node-6mvpt                                      3/3     Running   0          7m4s
vsphere-csi-node-gwr67                                      3/3     Running   0          7m4s
vsphere-csi-node-hb6rk                                      3/3     Running   0          7m4s

DaemonSetも確認してみましょう。

bash-3.2$ kubectl get daemonset -n kube-system
NAME                               DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                     AGE
antrea-agent                       3         3         3       3            3           beta.kubernetes.io/os=linux       9m1s
kube-proxy                         3         3         3       3            3           beta.kubernetes.io/os=linux       47m
vsphere-cloud-controller-manager   1         1         1       1            1           node-role.kubernetes.io/master=   46m
vsphere-csi-node                   3         3         3       3            3           <none>                            46m

antrea-agentがDeamonSetとして実行されているのを確認することができます。

ConfigMapを確認してみましょう。

bash-3.2$ kubectl get configmap -n kube-system
NAME                                 DATA   AGE
antrea-config-tm7bht9mg6             3      10m
coredns                              1      48m
extension-apiserver-authentication   6      48m
kube-proxy                           2      48m
kubeadm-config                       2      48m
kubelet-config-1.16                  1      48m
vsphere-cloud-config                 1      47m
bash-3.2$ kubectl get configmap antrea-config-tm7bht9mg6 -n kube-system -o yaml
apiVersion: v1
data:
  antrea-agent.conf: |
    # Name of the OpenVSwitch bridge antrea-agent will create and use.
    # Make sure it doesn't conflict with your existing OpenVSwitch bridges.
    #ovsBridge: br-int
    # Datapath type to use for the OpenVSwitch bridge created by Antrea. Supported values are:
    # - system
    # - netdev
    # 'system' is the default value and corresponds to the kernel datapath. Use 'netdev' to run
    # OVS in userspace mode. Userspace mode requires the tun device driver to be available.
    #ovsDatapathType: system
    # Name of the interface antrea-agent will create and use for host <--> pod communication.
    # Make sure it doesn't conflict with your existing interfaces.
    #hostGateway: gw0
    # Encapsulation mode for communication between Pods across Nodes, supported values:
    # - vxlan (default)
    # - geneve
    # - gre
    # - stt
    #tunnelType: vxlan
    # Default MTU to use for the host gateway interface and the network interface of each Pod. If
    # omitted, antrea-agent will default this value to 1450 to accomodate for tunnel encapsulate
    # overhead.
    #defaultMTU: 1450
    # Whether or not to enable IPSec encryption of tunnel traffic.
    #enableIPSecTunnel: false
    # CIDR Range for services in cluster. It's required to support egress network policy, should
    # be set to the same value as the one specified by --service-cluster-ip-range for kube-apiserver.
    #serviceCIDR: 10.96.0.0/12
  antrea-cni.conf: |
    {
        "cniVersion":"0.3.0",
        "name": "antrea",
        "type": "antrea",
        "ipam": {
            "type": "host-local"
        }
    }
  antrea-controller.conf: ""
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"antrea-agent.conf":..<snipped>..}}
  creationTimestamp: "2020-01-06T06:57:51Z"
  labels:
    app: antrea
  name: antrea-config-tm7bht9mg6
  namespace: kube-system
  resourceVersion: "4819"
  selfLink: /api/v1/namespaces/kube-system/configmaps/antrea-config-tm7bht9mg6
  uid: e6115864-4179-471d-a382-a1bb4afdeca1

これをみて分かるように、ConfigMapでは、

  • Integration Bridge名(default: br-int)
  • DataPath タイプ(default: system)
  • ホストと通信するインターフェース名(default: gw0)
  • トンネル(Encapsulation)タイプ(default: vxlan)
  • MTU値(default: 1450)

などを設定することができます。

それでは何かDeploymentを作ってみましょう。今回はお手軽にnginxのDeploymentをreplicas=2で作って、port-forwardでPodにアクセスしてみましょう。

bash-3.2$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
bash-3.2$ kubectl apply -f nginx.yaml
deployment.apps/nginx-deployment created
bash-3.2$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-54f57cf6bf-fx4mz   1/1     Running   0          44s
nginx-deployment-54f57cf6bf-mg74x   1/1     Running   0          44s
bash-3.2$ kubectl port-forward nginx-deployment-54f57cf6bf-fx4mz 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

無事にnginxにアクセスするすることができました。

次にantrea-agentの内部がどう動いているのか見てみましょう。

bash-3.2$ kubectl exec -ti antrea-agent-dvpn2 -n kube-system /bin/bash
Defaulting container name to antrea-agent.
Use 'kubectl describe pod/antrea-agent-dvpn2 -n kube-system' to see all of the containers in this pod.
root@workload-cluster-1-md-0-78469c8cf9-tlkn9:/# ovs-vsctl show
2fa33701-4b83-4016-a070-08fc90921b2d
    Bridge br-int
        Port "coredns--f4e323"
            Interface "coredns--f4e323"
        Port "tun0"
            Interface "tun0"
                type: vxlan
                options: {key=flow, remote_ip=flow}
        Port "nginx-de-70ebff"
            Interface "nginx-de-70ebff"
        Port "vsphere--7be010"
            Interface "vsphere--7be010"
        Port "gw0"
            Interface "gw0"
                type: internal
    ovs_version: "2.11.1"

Pod用のインターフェースが作成されて、Integrationブリッジ(br-int)に接続されている様子が分かると思います。

最後にOctantとの連携を見てみましょう。

AntreaにはOctant用のプラグインが用意されていて、Octant側からAntreaの状況を確認することができるようになっています。AntreaのOctantプラグインはPodとして動かす方法とプロセスとして動かす方法と2つあります。今回はPodとして動かしてみます。

まず、必要なSecretを作ります。

kubectl create secret generic octant-kubeconfig --from-file=/Users/shindom/work/cluster-api/out/workload-cluster-1/kubeconfig -n kube-system

次にbuild/yamls/antrea-octant.ymlを使用したkubeconfigに合わせてファイル名を書き換えます。

# Change admin.conf to the name of kubeconfig file in your set up.
- name: KUBECONFIG
  value: "/kube/kubeconfig"

Antrea用のOctantプラグインはAntreaのレポジトリにソースがあるので自分でビルドすることもできますが、私のDockerhubにビルド済みのものを置いておきましたので、こちらをお使いいただくのが簡単です。build/yamls/antrea-octant.ymlを以下のように書き換えて、

containers:
 - name: antrea-octant
   image: mshindo/octant-antrea-ubuntu:latest

(アップデート2020/10/25アップデート:  AntreaのDockerhub Repositoryに適切なバージョンのAntrea用Octantプラグインが置かれるようになりましたので、上記の私のイメージは不要になりました。したがって、私のRepositoryからイメージも削除しいています)。

Podとしてデプロイします。

kubectl apply -f build/yamls/antrea-octant.yml

上記のyamlファイルはNodePortを作るようになっていますので、そこに接続するとOctantのUIにアクセスすることができます。

今後の予定

Windowsの対応、No Encapsulationモードの対応、OVSベースのService Load Balancing、RSPAN/IPFIXなどのDay 2 Operation機能への対応などは既に述べた通りですが、他にも

  • IPv6でのPod Network対応
  • DPDKやAF_XDPなどの高速データパス技術への対応

などがロードマップに上がっています。

本当はAntreaが採用しているデータパスのパイプラインについても説明したかったのですが、力尽きたので今回はここまでで(笑)。

Photo by Manuel Sardo on Unsplash

オーバーレイネットワークと私

日頃から仕事でお世話になっている方からこんな記事を教えてもらった。2004年の記事だが、当時IntelのCTOだったPat Gelsinger(現VMwareのCEO)がオーバーレイネットワークの構想を持っていたのには驚かされる。Patが思い描いていた世界は(当時彼がIntelにいたということを考えると)全てのデスクトップやノートブックPCにまでオーバーレイネットワークが伸びていき、そこで色々なネットワークサービス面に接続して使う、というような姿だったのではないかと想像する。今日のオーバーレイネットワークは我々の手元のPCまで届いているとは言い難い状況だが、クラウドやデータセンターにおいてはそれに近い姿が実現されているので、2004年にこのような世界観を持っていたPatの先見性には驚かされるばかりである。

Patのような先見の明を持ち合わせていたわけではないが、なぜか私もこれまでオーバーレイネットワークに携わることがとても多かった。今回はその辺を少し振り返ってみようと思う。

私は1997年にAscend Communicationsに入社したのだが、当時Ascendはアクセスサーバーとして圧倒的なシェアを持っていて、どのISPもAscendを使っていた時代だった。そんな中、ネットワーク業界の経験がない私がAscendのSEとして飛び込んでいったわけで、最初の1年は「自分は本当に役に立っているのだろうか?」と自問自答する日々だった。PPPの16進ダンプを見るだけですらすらデバッグができる人たちがゴロゴロしている中で、自分は何をしたらいいのかと悩んでいたのである。であれば、他の人とは少し違ったことをやろう、と思い、当時Ascend MAXが持っていたAscend Tunnel Management Protocol (ATMP) を触り始めたのが、私にとってオーバーレイネットワークとの最初の出会いであった。当時はダイアルアップ系のトンネリングプロトコルとしては、ATMP、PPTP、L2Fなどベンダー色が強いものが乱立していたため、IETFとして標準的なトンネリングプロトコルを作ろうということでL2TP (Layter 2 Tunneling Protocol) が生まれた。AscendがL2TPの仕様策定にも関わっていたこともあり、私も比較的早い段階でL2TPの実装に触ることができた。ちょうどその頃、VPN周りの検証や運用に関する知見などの共有を目的とするグループvpnops(発起人は当時IRIにおられた松本直人さん)でL2TPの相互接続性の試験をしようということになり、幸い私にもお声がけを頂き検証に参加することとなった。この相互運用試験時のデバッグのためにtcpdump用のL2TP dissectorを書いて持って行ったのだが、このコードをのちにitojunさんがtcpdumpのtrunkにマージをしてくださったのは良い思い出である。

こんなことをしているうちにそこそこL2TPに関する知見が溜まっていったわけだが、丁度そんなタイミングでNTTが初のインターネット定額サービス(フレッツISDN)を始めるにあたりL2TPを使うかもしれないということで、Ascendで一番L2TPと戯れていたと思われる私がこのプロジェクトに引き込まれることになった。このプロジェクトは私にとって人生の転機となるプロジェクトであり、その際にお世話になった方々には本当に感謝しても仕切れない。そんなこんなでL2TPどっぷりな日々を送ることになり、気がつけば私もL2TPプロコルのコントロールメッセージを全てそらで言えるようになっていた(笑)

Ascendでの心残りはIPsecができなかったことだ。プロトタイプの製品はあったものの、残念ながら市場に出る製品にはならなかった。個人的にはIPsecにとても興味があったので、2000年にAscendからCoSine Communicationsという会社に移った(IPsecだけが理由ではないが、IPsecは大きな動機の一つであった)。CoSineがやっていたのは、完全な仮想ルータをIPsecでトンネルすることでオーバーレイ型のVPNを実現する、というものであった。当時は仮想ルータという概念はまだ一般的ではなく、VRFが出てきたばかりという時代だったので、CoSineがやろうとしていたことは当時としてはかなり先進的であったと思う(一方、ソフトウェアの品質はお世辞にも高くはなかったので、色々ご迷惑をおかけしました。すみません)。

当時はVPNに関しては2大派閥があった。Peerモデル派とオーバーレイモデル派である。Peerモデル派の代表はBGP/MPLS VPN(いわゆるRFC2547/RFC4364)。それに対してCoSineがやっていたのはオーバーレイモデルなVPNである。注意したのいのは、ここでいう「Peer vs オーバーレイ」というのはデータプレーンでのオーバーレイ(トンネル/カプセル化)をするかどうかの話ではなく、サービスプロバイダが顧客の経路情報に関与するかどうか、という話である。Peerモデルは顧客の経路情報とサービスプロバイダの経路情報を同じように扱うモデルである。顧客経路とサービスプロバイダのバックボーンの経路を対等(peer)に扱うのでこのように呼ばれている。一方、オーバーレイモデルではサービスプロバイダは顧客の経路情報には関与せず、顧客側の経路制御は顧客側で行う。典型的には、トンネル上でルーティングプロトコルを動かして顧客経路を交換するモデルだ。

Peerモデル vs オーバーレイモデルに関してはIETFのMailing Listでもしばしば宗教論争が起こった。当時はPeerモデル派が優勢で、Peerモデル派からは「オーバーレイモデルなどスケールするはずがない」など、こてんぱんに言われることも多く、悔しい思いをしていたのを良く憶えている。真っ向から対立していた両陣営の主張の正当性はさておき、商業的に成功したのは圧倒的にBGP/MPLS VPNであったのはみなさんよくご存知の通りである。

ひょんなことから2011年にNiciraと出会い、Niciaに行くことになった経緯についてはこちらをご参照いただきたいが、はからずもNiciraもまたオーバーレイ技術をベースとする会社であった。Niciraが開発していた製品「NVP」は、ネットワーク機能をハードウェアから切り離しネットワークの抽象化を行う、というアーキテクチャとなっており、STT(Stateless Transport Tunneling)を使って仮想スイッチ間をトンネルして仮想ネットワークを作り出す、というものであった。Niciraは2012年にVMwareに買収され、NVPはNSXとして製品に取り込まれ、年間1000億円の市場に成長した。

Nicira/VMwareのあと、2016年にViptelaというSD-WANのスタートアップに行ったが、SD-WANもやはりオーバーレイ技術をベースにしたものである。多くのSD-WAN製品はIPsecトンネルで作られるオーバーレイネットワークだ。SD-WANの柔軟性と普遍性はコントローラの存在だけでなく、オーバーレイアーキテクチャによるところも大きい。先の「Peerモデル vs オーバーレイモデル」的な観点で見ると、CPE同士で経路を交換するSD-WANはオーバーレイモデル的なVPNということになる。2000年の頃には「スケールしない」などと散々非難をされたオーバーレイモデルのVPNが、CPEの性能向上やクラウド上のコントロールプレーンの利用により、15年の時を経て実際に大規模環境で動いているのを見るとなんだか嬉しい気分になる。

オーバーレイにはオーバーヘッドは付き物である。この性能的なオーバーヘッドを理由にオーバーレイ技術が批判されるケースをしばしば目にするが、これは非常に近視的なものの見方ではないかと思う。新しいオーバーレイネットワーク技術が生まれると、それは通常ハードウェアが想定していないカプセル化であるため、性能が劣化する。しかし、そのような問題はハードウェアの進化とともに解決されていくことが多い。特にカプセル化などはハードウェアで扱いやすい問題に属すると思うので、多くの場合時とともに性能的な問題は解決されていく。長期的にはオーバーレイによる「抽象化」がもたらす技術の進化の方がはるかに大きな意味を持つと思う。

ネットワークの話ではないが、いくつか例をあげてみたい。近代的なコンピュータとオペレーティングシステムは仮想メモリシステムを使っている。仮想アドレス空間を物理アドレス空間にマップしてメモリを使っているわけだが、これはCPUが持つMMUやTLBなどの仕組みによって大きな性能劣化なく仮想メモリシステムが使えているわけである。今日、性能的なオーバーヘッドを理由に仮想メモリシステムを否定する人はいないだろう。それよりもアドレス空間の仮想化によって得られるメリットのが方が遥かに大きいのは明らかだからだ。もう一つ別の例を挙げよう。x86 CPUの仮想化も当初は性能的なオーバーヘッドが大きく実現困難と思われていたが(それを現実的な速度でやって見せて世の中をびっくりさせたのがVMwareだったわけだが)、その後Intel VTやAMD-VなどのCPUによる仮想化支援機能によって、大きな性能の劣化なくx86の仮想化ができるようになった。今日、CPUの仮想化のメリットはおそらく誰も否定しないだろうし、性能的オーバーヘッドは相対的に無視できるほど小さくなっている。オーバーレイネットワークによるネットワーク抽象化もこれらと同じ話であると思う。イノベーションは抽象化から生まれ、抽象化する事で発生するオーバーヘッドは、多くの場合ハードウェアがのちに解決してくれるのである。

私自身、初めからこのようなことを考えて約20年間一貫してオーバーレイ・ネットワークに携わってきたわけではない。「たまたま」といえばそうなのだが、きっとオーバーレイによる抽象化とそこから生まれるワクワク感がきっと本質的に好きなのだろうと思う。

今までネットワーク機器は自分たちの手の届くところにあったので、ネットワーク機器を直接「触る」ことでネットワークを構築、管理をしてきた。しかし、これからはクラウドの時代である。クラウドにも当然物理ルーターや物理スイッチはあるが、通常我々はそれらを直接触ることはできない。物理ネットワーク機器に直接触れない環境でどのようにネットワークをエンド to エンドで構築・管理していけば良いのだろうか? クラウド時代においては、オーバーレイでネットワークを作りエンド to エンドで管理していくのは必然の事のように思える。そんなわけで、これからも大いにオーバーレイネットワークに絡んでいきたいと思う次第である。

Photo by Joshua Forbes on Unsplash

Kubernetes Cluster API – Part 1 –

Cluster APIはKubernetesスタイルのAPIを使ってKubernetesのクラスタの管理するための仕組みで、Kubernetes SIG (Special Interest Group) のうちの1つであるCluster Lifecycle SIGで仕様の策定が行われています。

Kubernetes環境の構築ツールにはkubeadm、kops、Kubesprayなど多数存在していますが、それぞれスコープが異なります。kubeadmはKubernetesのコンポーネントのインストールをしてくれますが、それを動かすためのプラットフォームの面倒は見てくれません。kubeadmの実行時にはすでにKubernetesを動かすための環境(サポートされているOSが動いているベアメタルや仮想マシン、container runtime、kubeletやkubectlなどのツール、など)が整っていることが前提になっています。一方、kopsはKubernetesを動かすプラットフォーム(Cluster)まで含めて構築をしてくれますが、プラットフォームを個別にサポートしていく必要があります。2019年9月時点でオフィシャルにサポートされているプラットフォームはAWSのみ、GCEとOpenStackがbetaサポート、vSphereとDigitalOceanがalphaサポートとなっています(コードを見るとAliCloudやベアメタルもサポートしようとしているように見えます)。

今後もこのようなKubernetesクラスタ環境設定ツールが他にも出てくる可能性がありますが、今のままだとそれぞれのツールで個別にプラットフォームをサポートしていかなければいけません。また、Kubernetesを動かしたいプラットフォームも、AWSやGCPだけでなく他のパッブリッククラウドやベアメタルなど、多様な環境をサポートしたくなるに違いありません。さらに、クラスタを構築する(Day1 Operation)だけでなく、クラスターを大きくしたい/小さくしたい、クラスタノードのOSのバージョンアップをしたい、といったいわゆるクラスタのDay2 Operationも必要になりますが、これらもプラットフォームごとに考えなければいけないことになります。これはKubernetesのユーザーにとっても環境構築ツールのエコシステムにとっても大きな苦痛です。

このような問題を解決するために、クラスタを含むKubernetes環境全体を管理するための汎用的な仕組みを提供することを目的に生まれたのがCluster APIです(CAPIと呼べばれることもあります)。”API” という名前が付いていますが、どちらかというとフレームワーク的な色合いが強いと思います。

「汎用的」と言っても、当然プラットフォームごとに必要な処理は異なりますので、Cluster APIではプラットフォームごとに個別の “Provider” を用意してプラットフォームの差異を吸収し、どのようなプラットフォームに対しても統一的なのAPIでコントロールできるように作られています。現在さまざまなProviderが用意されており、これらのProviderがサポートしているプラットフォーム上であればCluster APIを使ってKubernetes環境を簡単に構築・管理することができます。2019年9月時点でサポートされているProviderは以下の通りです。

  • AWS
  • Azure
  • Baidu
  • Baremetal
  • DigitalOcean
  • Exoscale
  • GCP
  • IBM Cloud
  • OpenStack
  • Packet
  • Talos
  • Tencent Cloud
  • vSphere

一般的にはKubernetesはコンテナ管理のプラットフォーム(オーケストレータ)として知られていますが、実はKubernetesはコンテナ以外のものも管理することできる枠組みが備わっています。Kubernetes のアーキテクチャの根本をなす部分として、システムをあるべき状態(Desired State)に保つ、という機能があります。Kubernetesでは、さまざまなリソースのあるべき状態を宣言的に定義しリソースの作成を行うと、それらのリソースの状態は常に監視され、あるべき状態と現在の状態(Current State)に差が生じている事を検出すると、あるべき状態に戻そうという動きをします。これはKubernetesのReconciliation Loopと呼ばれるメカニズムによって実現されています。Reconciliation Loop

例えば、Podが3つ動いているはずなのに実際にはPodが2つしか動いていなければ、Kubernetesは自動的にPodを1つ新たに立ち上げて、常に3つのPodが動いているように調整(修復)をしてくれます。このようないわゆる自己修復(Self Healing)機能が働くのはReconciliation Loopのおかげです。

KubernetesではDeployment、Replicaset、Pod、Node、Secret、StorageClassなど50種類ほどのリソースがあらかじめ定義されていますが、Kubernetesはこれらの定義済みのリソース以外のものを定義する仕組みが備わっており、Custom Resource Definition(CRD)と呼ばれています。CRDを定義しそれに対応するコントローラを書けば、自由にさまざまなリソースをKubernetesで扱うことができるようになります。これらのCRDで定義されたリソースにもReconciliation Loopによる自己修復機能を働かせることができるため、Kubernetesは変化に富む分散システムのライフサイクル管理をする汎用的なフレームワークとしても使うこともできるわけです。

Cluster APIはこのようなKubernetesの特性を活かし、クラスタなどのプラットフォームを規定するリソースをCRDとして定義して、それらをKubernetes自体で管理することで、KubernetesをKubernetesで管理する、というアーキテクチャになっています。

ClusterAPIでは以下の5つのCRDを用意しています。

  • Cluster
  • Machine
  • MachineSet
  • MachineDeployment
  • MachinClass

Cluster

Clusterリソースはクラスター全体の設定を規定します。例えば、ServiceやPodで使用するCIDRブロックの指定やドメイン名を指定します。またproviderSpecアトリビュートでクラスタ全体に関わるProvider固有の設定も行えます。

apiVersion: "cluster.k8s.io/v1alpha1"
kind: Cluster
metadata:
  name: workload-cluster-1
spec:
  clusterNetwork:
    services:
      cidrBlocks: ["100.64.0.0/13"]
    pods:
      cidrBlocks: ["100.96.0.0/11"]
    serviceDomain: "cluster.local"
  providerSpec:
    ...

Machine

Machineリソースは、Kubernetesを動作させるノードの設定を規定します。例えばkubeletやKubernetesのコントロールプレーンのバージョンなどを指定します。また、providerSpecアトリビュートではノードが使用するメモリ量やCPU数、インスタンスのタイプなどを指定します。

apiVersion: cluster.k8s.io/v1alpha1
kind: Machine
metadata:
  name: "workload-cluster-1-controlplane-1"
  labels:
    cluster.k8s.io/cluster-name: "workload-cluster-1"
spec:
  providerSpec:
    ...
versions:
    kubelet: "1.13.6"
    controlPlane: "1.13.6"

MachineSet

MachineSetリソースではMachineリソースがいくつ動いているべきかをreplicasアトリビュートで指定します。

apiVersion: cluster.k8s.io/v1alpha1
kind: MachineSet
metadata:
  name: "workload-cluster-1-machineset-1"
  labels:
    machineset-name: "workload-cluster-1-machineset-1"
    cluster.k8s.io/cluster-name: "workload-cluster-1"
spec:
  replicas: 3
  selector:
    matchLabels:
      machineset-name: "workload-cluster-1-machineset-1"
      cluster.k8s.io/cluster-name: "workload-cluster-1"
  template:
    metadata:
      ...
    spec:
      ...

MachineDeployment

MachineDeploymentリソースはMachine、MachineSetのアップデートポリシーを規定します。現在サポートされているアップデートポリシーはRollingUpdateのみです。

apiVersion: "cluster.k8s.io/v1alpha1"
kind: MachineDeployment
metadata:
  name: "workload-cluster-1-machineset-1"
  labels:
    cluster.k8s.io/cluster-name: "workload-cluster-1"
spec:
  replicas: 3
  selector:
    matchLabels:
      cluster.k8s.io/cluster-name: "workload-cluster-1"
      set: node
  template:
    metadata:
      ...
    spec:
      ...

MachineClass

MachineClassリソースは、どのProviderSpecを使用するのかを規定します。Machine、MachineSet、MachineDeploymentリソースは、どれもproviderSpecアトリビュートでプロバイダに固有な情報を設定することができますが、プロバイダ情報は共通であることが多いので、何箇所も同じ設定をするのは面倒です。そこでMachineClassを使ってプロバイダ固有の情報を記述し、他のリソースからそれを参照するようにすることで、よりシンプルにMachine関係のリソースを記述することができるようになります。

apiVersion: "cluster.k8s.io/v1alpha1"
kind: MachineClass
metadata:
  name: "workload-cluster-1-machine"
providerSpec:
  value:
    apiVersion: ...
    kind: ...

お気付きの方も多いかと思いますが、Cluster APIのCRDは、標準でサポートされているリソースと類似性があります。

Machine — Pod
MachineSet — ReplicaSet
MachineDeployment — Deployment
MachineClass — StorageClas

このような類似性を踏まえると、Kubernetesをすでに使っている人にとってはClusterAPIは理解しやすいのになるのではないかと思います。

次回はCluster APIとvSphere用のProviderを使ってvSphere上にKubernetesのクラスタを立てる解説し、、、ようと思ったのですが、ちょうど@masanaraさんがQiitaにこのトピックについて記事をあげて下さいましたので、皆さんこちらをご参照ください。

Photo by apoorv mittal on Unsplash

RADIO 2019

VMwareが行なっているRADIOというイベントに参加するためにサンフランシスコに来ています。RADIOはResearch And Development Innovation Offsiteの略で、年に一度VMwareのエンジニアリングチームが集まり、4日間かけて先進的な取り組みついて発表をする場となっています。RADIOでセッションをするためには社内論文を出して採択される必要があり、かなり狭き門となっています。そのためRADIOでの発表はエンジニアリングにとって大きなモチベーションになっていて、これに向けて1年(あるいは複数年に渡って)頑張ってリサーチに取り組んでいるエンジニアも多いです。ここで発表されものから多くの特許が生まれ、また実際に製品となったりします。特に優秀と認められたものには全員の前で発表するResearch Talkが認められ、その他にも各種Breakoutセッション、BoF、JIT BoF(その場でテーマが決められて行うBoF)、Posterセッション(ブース出展)、など様々な機会が与えられています。VMwareのExecutiveたちも参加しますので、Executiveたちからの話を聞けるのはもちろんのこと、毎年著名なGeust Speakerを呼んで話をしてもらうのが通例となっているようです(今年誰が来るのかはまだ分かりません)。

2000名近くのエンジニアが一堂に会して、かつ、キャンパスででなくオフサイトで行われるイベントですのでコストもそれなりにかかっていると思われますが、「会社が元気 -> R&Dに投資できる -> イノベーションが生まれ新製品が出てくる -> 収益が上がり会社が元気になる」という正のフィードバックループがうまく回っているな、と感じます。

原則エンジニアリング向けのイベントなのでフィールドから参加することはできないのですが、若干名フィールドからの参加が認められており、今回日本からも数名参加をしています。実は私もRADIOに参加するのは今回が初めてです。今まで何度か参加の機会はあったのですが、色々な理由で実際に参加することはできていませんでした。同僚からはRADIOの素晴らしさはよく聞くのでとてもワクワクしています。RADIOでされる話は製品化前の話がほとんどですので、どんな話があったかは基本的に非公開でお話しすることはできないのですが、一部はメディアに公開される予定ですのでご期待ください。

(Photo by Kristopher Roller on Unsplash