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

はじめに

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

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

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

しかし、最近「宣言的」という言葉をよく聞くようになったのはなんと言ってもKubernetesの影響でしょう。Kubernetesが宣言的であると言われるのは、Kubernetesでは各種リソースの望まれる状態(Descired 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的な宣言的特性や自己修復性を持たせるのは簡単なことではないように思います。ただ、このような困難に立ち向かおうとしている人はきっといるはずです。非常に有望な「研究課題」と言えるでしょう。

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

上記のようなネットワークの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のコンパイラ自身はHaskelで書かれています。OVNはCで書かれているので、Differential Datalog版のovn-northdの動きを調べる際には、CやDifferential DatalogだけではなくRustや場合によってはHaskelの知識も求められることになります。これらの言語およびそこで使われているツールチェーンを一通り学習するのはそれなりの時間を要するでしょう。また、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

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

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

2019年総括

今年も一年を簡単に振り返ってみようと思います。

主な出来事

1月 台北出張、コサイン同窓会、JANOG@山梨、チームオフサイト@館山
2月 アセンド新年会、韓国出張(寒かった!)、次女の舞台観劇@Victoria(感動)、CTOA Conference
3月 SKO@ラスベガス、ランボルギーニ初体験、ハナモクレン綺麗、長女就職祝い
4月 令和となる、Cloud Native Day Fukuoka登壇、Blockchainの闇を垣間見る
5月 初Airbnb体験、RADIO初参加、CIO Forum Singapore
6月 次女高校卒業式@Victoria(再び感動)、INTEROP基調講演、VMバンドライブ
7月 Cloud Native Day Tokyo登壇、JANOG@神戸、一人鉄板焼き
8月  ナノオプト10周年、コンピュータ関連古い本断捨離、VMworld@SF
9月 長女&彼ピーと釣りに行く、アスレチックにてこずる、上海で誕生日を迎える
10月 台風凄かった、ONIC@軽井沢(浅羽邸のBBQが本番?)
11月 チームオフサイト@館山、Cloud Native Day&vFORUM、母永眠
12月 VMバンドライブ、理事会任期終了、Serverless Day@Fukuoka、ONIC打ち上げ@月島「凛」

新たな取り組み

  • LingoLive(英語レッスン)
  • Swiftの勉強(w/ 長男)
  • マーベル「インフィニティーサーガ」シリーズ(アベンジャーズシリーズ)23本完全制覇
  • ANAマイル修行(無事プラチナ獲得)
  • セッションなるものに初挑戦
  • 嵐のおかげでNetflixに加入

Facebook Most Likes

3位 娘の卒業式に行ってきます(136件)
2位 VMworldで玉置さんとのツーショット(167件)
1位 サンパウロ像的なプロフィール写真(182件)

残念ながらこれ(↓)は117件でランクインせず。

映画

65本とかなり多め。マーベルのアベンジャーシリーズ制覇が効いている。洋画ベスト3は「50回目のファースト・キス」、「ある日どこかで」、「運び屋」。邦画は見たのが少なかったものの、「こんな夜更けにバナナかよ」は割と良かったかな、と。

ライブ

エド・シーラン、最高! しばらく活動休止らしいが、早く戻ってきて欲しい。その他にも洋楽邦楽を楽しんだ。

バイタル

体重は去年と比べて0.4Kg増、高値安定。平均睡眠時間は5時間54分(去年は5時間40分)でさらに改善。今年は特に病気もせず過ごすことができた。相変わらずKONAMIには行っていない。来年こそは頑張ろう!

読書

相変わらず少なくて10冊程度。技術書が8割。いかんわ。「蜜蜂と遠雷」は面白かった。

仕事

今年はVMware Cloud Native Dayを行ったのが大きかった。なにぶん他の国に先駆けて日本で最初にやったため、全てが手探り状態。プログラムの組み立てを一から行う必要があり大変だったが、多くの方が献身的に手伝ってくれたので、無事に終えることができた。感謝!

北京・上海で行われたChina Innovation Week(VMwareの社内イベント)での基調講演とSingaporeでのCIO Forumのメディア対応は大変であったが、得るものも大きかった。また、INTEROPでの基調講演も良い経験となった。

ちなみに今年は304人の方と名刺交換。

Blog

この投稿を含めて6本(うち1本は英語)。去年よりマシになったが、もう少し書きたい。。

コミュニティへの貢献

これが今年は壊滅的にできなかった。コミュニティ活動として、VMware DevOps Meetupの立ち上げを行いはしたものの、コードでの貢献は全くできなかった。反省。

フライト

  • JAL系 0回、0 FLY ONポイント(2018年は6回、16,804 FLY ONポイント)
  • ANA系 40回、50,189ポイント(2018年は8回、20,040ポイント)

今年はANAのマイル修行の年。なんとかプラチナを獲得。これでJAL、ANAどちらに乗っても大丈夫^^。

今年新規に開発した肉屋

  • 명인등심 @ Seoul
  • Armadillo Willy’s Barbecue @ Los Altos
  • Harvest @ Bellagio
  • Joe’s Seafood, Prime Steak & Stone Crab
  • 東京燻製劇場 浜松町・大門店
  • 恵比寿焼肉kintan
  • たれ焼肉のんき浜松町店
  • 一石家(三宮)
  • Rio Grande Grill
  • 神戸牛炉釜炭焼ステーキ IDEA 銀座
  • 熟成和牛焼肉エイジングビーフTOKYO新宿三丁目店
  • モリタ屋ルクアイーレ店
  • 焼肉だいもん

今年のベストは「神戸牛炉釜炭焼ステーキ IDEA 銀座」かな。でも、お値段もベストw

2020年の目標

  • 英語はもう少しなんとかしたい。ハンデと感じないようになるまで、継続して頑張っていこうと思う。
  • コミュニティへの貢献。コードでの貢献をしたい。
  • せっかくSwiftの勉強をしたので、iOSアプリに取り組みたい。
  • 体重を3キロ(できれば5キロ)減らす。体力落ちてはいけないので、KONAMIでの筋力トレーニングも併せて行いたい。
  • 読書。non技術書を10冊読みたい。

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

日頃から仕事でお世話になっている方からこんな記事を教えてもらった。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

Blogお引っ越し

長年の懸案であったこのBlogサイトの引っ越しを行いました。2011年よりさくらインターネットのVPSインスタンス上に立てたWordpressを使ってきましたが(さくらインターネットさん、長い間ありがとうございました!)、メモリ1GB、ディスク20GBという小さなインスタンスだったたので、(攻撃を受けた時など)度々Out of Memoryで落ちたり、運用的に苦労もしてました(最近のさくらさんは、ping監視してメールやSlackで通知してくれるサービスを提供してくれているようですね。知らなかったw)。

ただ、いまどき自分でBlogをマネージするのもイケてないなと思い、今回他のサービスへの乗り換えも検討しました。カスタムドメインをサポートしていたMedium.comが筆頭候補だったのですが、残念ながらMedium.comは最近カスタムドメインのサービスを廃止してしまったようです。その他、note、Qiitaなどのサービスも検討はしたものの、現サイトのコンテンツに対する他のサイトからのリンクなども結構あるので、できることなら記事のURLを変えたくなく、結局自宅のESXi上の仮想マシンにWordpressを立てる、という技術的にはあまり代わり映えのしないお引越しとなってしまいました。

なお、今回の引っ越しにはAll-in-One WP Migrationというプラグインを使いました。これを使うとほぼ全てのWordpressのデータとプラグインなどを含む環境を簡単にマイグレーションすることができて大変便利です。前の環境がPHP5系、新しい環境がPHP7系だったので少し心配でしたが、特に問題なく移行する事ができました。

ESXi環境になりスナップショットを気軽に取れるようになったので、いろいろ設定変更しやすい環境にはなりました。しばらくはこの構成で運用して行こうと思います。

という記事を新環境でポストしてみるテスト。

追伸:Simple Tweetが動かなくなってしまったようです。原因調査中。

(Photo by Harshil Gudka on Unsplash)

2018年総括

主な出来事

1月 大雪にやられる。
2月 Cisco退社。
3月 スペイン&ポルトガル旅行。
4月 VMwareへ2度目の入社、Tesla試乗して加速にビビる。
5月 KONAMIに通い出す(のち挫折)。
6月 CIO Forum、INTEROP Tokyo、香港出張、VMバンド応援、ユッケ丼にやられる。
7月 シンガポール出張、立教キャンパス巡り、猛暑。
8月 Palo Alto出張、VMworld@Las Vegas。
9月 HBDメッセージ107件、オフサイト@湯沢、コサインreuion、sd-wan.jp退役、ServerlessConf、まり古墳ツアー。
10月 ONC@軽井沢、盛況。
11月 vForum Tokyo、登録者10,000人超え。靭帯損傷。
12月 JKD v18.12、vForum Osaka、VMバンドライブ。

新たな取り組み

  • 浜松町で働き出す(昨年も同じような事言ってて済みません)

Facebook Most Likes

3位 琴音の習字(大穴)  225件
2位 VMware入社  307件
1位 Cisco最終日  335件

映画

約30本。洋画ベスト:「Three Billboards」、邦画ベスト:「カメラを止めるな」。出張が少なめだったため、本数もかなり少なめ。Facebookの映画記録アプリがあまりにウ◯コなため、来年からはFimarksで記録を取っていくことにした。

バイタル

体重ほぼ横ばい(前半は減少、後半は増加)。平均睡眠時間は5時間40分(だいぶ改善)。一度食中毒に苦しんだ以外は至って健康。なぜか足をグキッとやり靭帯損傷。KONAMIに通い始めるも挫折。

読んだ本

10冊程度。ほとんど技術書。いかんわー。

コミュニティーへの貢献

  • DockerMachineのFusion Driverのbug fix
  • Dispatch CLIのBash Completion

フライト

  • JAL 6回、16,804 FLY ONポイント(2017年は 8回、23,779 FLY ONポイント)
  • ANA 8回、20,040ポイント(2017年は8回、11,816ポイント)

今年も出張は少なめ。来年はANAでマイルを貯める予定。

開拓した肉屋

  • 炭火焼肉 壱牛
  • 焼肉京城 水道橋店
  • とんかつ檍 大門店
  • まるたけ 近江 西川
  • 焼肉やよい
  • 蕃 YORONIKU
  • 徳壽
  • 焼肉ヒロミヤ 3号店
  • 東京食肉市場直送 肉焼屋 D-29
  • USHIHACHI 品川港南口店
  • 和牛焼肉KIM 茅場町
  • みやび 六本木店

思いの外開拓できた。歓送迎があった、という要因が大きいかもw

2019年の目標

  • KONAMI再開
  • 英語頑張る
  • コミュニティーへの貢献
  • ライブ
  • non-技術書を読む
  • 少し体重を落とす

2018年にやり残したものが多いなー。ま、継続は力なり。諦めずに頑張って行きたいと思います。

画像クレジット:unsplash-logoSteven VanDesande Jr