Necoのネットワーク - アーキテクチャと設計編

こんにちは。「Neco」の @ueokande です。

サイボウズでは、cybozu.comのアーキテクチャ刷新プロジェクト「Neco」を実施してます。 その思いについては以下の記事からどうぞ。

今回は、Necoにおけるネットワーク設計についてお話します。

ネットワークの方針

ネットワークの耐障害性は必須の条件です。 Necoでも同様で、ネットワーク機材の単一障害点が無いネットワーク構成にする必要があります。 具体的には以下のような要件です。

  • ホストマシンのNICを二重化して、片方のNICが故障しても通信が行えること
  • ネットワークスイッチを冗長化して、スイッチが故障してもスイッチ下のホストが停止しないこと

スイッチを冗長化する方法は、以下のような選択肢があります。

  1. STP (Spanning Tree Protocol)
  2. MC-LAG (Multi-Chassis Link Aggregation)
  3. BGP (Border Gateway Protocol) + BFD (Bidirectional Forwarding Detection)

STPは他の2つに比べて利点が少ないので、Necoでも採用しませんでした。 MC-LAGは、2台のスイッチを論理的に1つにする技術で、現在のcybozu.comで採用しています。 BGPは動的ルーティング・プロトコルの1つで、古くから使われている信頼性のあるプロトコルです。BFDはネットワーク機器間で障害検知する仕組みです。

Necoではマルチベンダー対応も目標の1つに入っています。 現在のcybozu.comではMC-LAGを採用していますが、スイッチ側の実装はベンダーの独自技術となり、トラブルシューティングが難しいという問題がありました。 BGPとBFDはベンダーに依存せず、L3レイヤーで経路を制御するので、トラブルが発生したときも従来のIPのトラブルシューティングが適用できます。

NecoではBGPによる経路制御を行い、BFDによる障害検知とECMP (Equal-Cost Multi-Path routing) による冗長経路で構成しました。 大規模ネットワークで広く用いられてるリンク・アグリゲーションではなく、ベンダー非依存の技術を使って経路を制御しているのも、Necoの大きな特徴です。 経路制御をスイッチ側に任せるのではなく、ホストマシンにL3リンクを2つ持たせて、ホストマシン側にもBGPサーバーを実装して経路を制御します。

ネットワークのアーキテクチャ

Necoのネットワークアーキテクチャ
Necoのネットワークアーキテクチャ

NecoではCLOSアーキテクチャの1つであるLeaf-Spineアーキテクチャを採用しました。 CLOSアーキテクチャは、各ホストマシン間の通信は同一ホップで到達できます。 このメリットは水平方向にスケールできる構成で、East-West間の通信が多いネットワークでは有利な構成となります。 将来Kubernetesを導入したとき、Pod間の通信が増えて不特定多数のホストマシン間で通信が発生しても安心できるアーキテクチャです。 詳しい説明は以下のリンクからどうぞ。

https://eos.arista.com/ja/cloud-clos-architecture/#CLOS

各ラックにはLeafスイッチとなる2つのToR (Top of Rack) スイッチがあり、それぞれのToRスイッチは冗長化されたSpineスイッチに接続されます。 そして各ホストマシンは両方のToRスイッチに接続します。 これで、仮に片方のNIC、あるいはスイッチごと故障したとしても、ホストマシンがネットワーク的に到達不能ということはありません。 MC-LAGを利用しないので、各ToRスイッチはそれぞれ異なるL2サブネットを持つことになります。 このため、各ホストマシンは2つのNICがそれぞれ異なるL2サブネットのアドレスを持ちます。

それぞれのホストマシンはBGPサーバーを実装して、IPアドレスをToRスイッチに広告します。 各スイッチ間で経路を交換するので、ラックをまたいでも通信できるようになります。 ホストマシンがToRスイッチに広告するIPアドレスは、NICが持つIPアドレスではなく、各ホストに仮想IPアドレスを持たせて、そのIPアドレスをToRに広告します。 この仮想IPアドレスを、Necoでは「代表IPアドレス」と名付けています。 つまりラックをまたぐホスト間の通信は、NICのアドレスではなく代表IPアドレスを使います。 したがって各ホストマシンは、代表IPアドレスと、それぞれのNICのIPアドレスの、計3つのIPアドレスを持つことになります。

なぜ経路広告に仮想IPアドレスを使うかというと、Kubernetesでネットワークを制御するCalicoやRomanaの設定で、指定できるノードのIPアドレスが1つだったからです。 なのでCalicoやRomanaで片方のNICのIPアドレスを指定してしまうとECMPがうまく働きません。 したがってNecoでは、仮想的なIPアドレスを代表IPとして持たせ、それをBGPに広告するような設計にしました。 この「ノードには単一のIPアドレスで到達できる」という設計は、結果的に他のアプリケーションの実装も楽になりました。

ネットワークの検証

さて、Necoのネットワークで上記のアーキテクチャに至るまで、実験と再検討の繰り返しでした。 Necoでは、クラスタを実マシンではなく仮想マシンで実験できるよう、開発環境の構築と自動化にも力を入れています。 結果的にVM用に書いたネットワーク構成やBGPの設定を、そのまま実マシンに投入することができます。 開発環境の構築方法について、書き始めるとこれもまた長くなるので、また日を改めて記事を書きたいと思います。

以下の情報は、ホストマシンに別のラック内の経路が広告されているという確認です。 ホストマシンは node0 という名前の仮想インターフェイスに 10.69.0.4/32 のIPアドレスを持っています。 そして別のラックにあるホストは代表IPアドレスである 10.69.1.133/32 を、クラスタ内に広告しています。 代表IPアドレスへの経路は/32のIPアドレスで広告されており、それがOSのルーティングテーブルに反映されてるのが確認できます。

cybozu@rack0-worker4 ~ $ ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 10.69.0.68/26 brd 10.69.0.127 scope link eth0
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 10.69.0.132/26 brd 10.69.0.191 scope link eth1
       valid_lft forever preferred_lft forever
4: node0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 10.69.0.4/32 scope global node0
       valid_lft forever preferred_lft forever

cybozu@rack0-worker4 ~ $ ip -4 route
...
10.69.1.133 proto bird metric 32
        nexthop via 10.69.0.65 dev eth0 weight 1
        nexthop via 10.69.0.129 dev eth1 weight 1
...

もちろんpingも届きます。

cybozu@rack0-worker4 ~ $ ping 10.69.1.133
PING 10.69.1.133 (10.69.1.133) 56(84) bytes of data.
64 bytes from 10.69.1.133: icmp_seq=1 ttl=61 time=0.499 ms
64 bytes from 10.69.1.133: icmp_seq=2 ttl=61 time=0.698 ms
64 bytes from 10.69.1.133: icmp_seq=3 ttl=61 time=0.593 ms
^C
--- 10.69.1.133 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 0.499/0.596/0.698/0.086 ms

以下はホストマシンがダウンした時の経路収束の様子です。 2つのホスト「rack0-worker4」と「rack1-worker4」はそれぞれ別のラックに属するホストマシンです。 rack0-worker4は 10.69.0.4/32 という代表IPアドレスを持っており、それをBGPで広告しています。 隣のラックにあるrack1-worker4のルーティング情報にも経路が反映されてます。

障害を再現するために、rack0-worker4の仮想インターフェイスを一時的にDOWNにします。 すると次の瞬間に、rack0-worker4の経路が無くなった事がクラスタ全体に通知され、rack1-worker4のルーティング情報からも削除されました。

BGPによる経路広告と障害の再現
BGPによる経路広告と障害の再現

おわりに

Necoでは、ベンダーに縛られないプロトコルのBGPとBFDを使って、高信頼かつスケール可能なネットワークを設計・構築しました。 今のcybozu.comのインフラとは全く違う構成なので、新しい事ばかりで、実験と調査の繰り返しでした。 しかし本番環境を想定したネットワークを各開発者の手元で再現できたことは、非常に開発をスムーズに進めることができました。

この記事ではNecoではアーキテクチャについて説明しました。次回はNecoのネットワークの実装について説明したいと思います。

Necoでは今後、Kubernetesクラスタのデプロイや、その上のアプリケーションのデプロイなどをやっていく計画です。 まだまだやることは山積みですが、Necoプロジェクトでは一緒に働ける人を募集しています。

www.wantedly.com