Hetzner Cloud ネットワークスタック
Hetznerがクラウド仮想マシンを支えるネットワークスタックを詳しく解説し、Open vSwitchと自社開発のFlusskrebsオーケストレーション層を採用した理由から、各ホスト上のパケットフィルタリングルールまでを説明し、このアーキテクチャがどのように進化してきたかを明らかにしている。
日本語
コピー

これはシリーズ記事の第1回で、Hetzner Cloud を支えるネットワークスタックの変遷とアーキテクチャを紹介する。ここではこれまでの開発を振り返り、現在の Open vSwitch ベースのネットワークスタックがどのように動いているのかを見ていく。Cloud サーバーと Load Balancer のネットワークスタックは概ね似ているが、提供するサービスと機能が多い Cloud サーバーと仮想化ホスト(VM host)に焦点を当てる。
簡潔にするため、Cloud Control Plane(すべてのサーバー、Load Balancer、Private Network などを管理する)には触れず、ホスト上のネットワークスタックだけを扱う。
私たちのやり方
クラウド製品はどれも、多様で信頼できる接続手段を必要とする。私たちのネットワーク上で動くサービスに、顧客——そして顧客の顧客——がアクセスできるようにするためだ。
Hetzner では、自分たちで所有し自分たちで運用するソリューションを、まず積み木を用意してその上に組み上げていくという考え方を全般的に取っている。ネットワーク、とりわけクラウド製品のネットワーク接続も例外ではない。
長年にわたり技術は変わり続けてきたが、私たちは可変な部分をホストに置くことを好み、賢く複雑な物理ネットワークを組むことは避けてきた。だから物理ネットワークはホストに安定した IP 接続を提供するだけにとどめ、ファイアウォール、Private Network、その他の基盤サービスといった上位のネットワーク機能はホスト側で提供する設計にしている。
こうしたサービスやネットワーク機能をホスト上で動かすことで、製品の設計と構築に大きな自由と柔軟性が生まれ、ハードウェアベンダーが提供できるものに縛られずに済む。チームと技術の責任範囲が明確になり、(ほとんどの場合)障害が起きても影響を単一ホスト内に閉じ込められる。つまりこの方式を完全に自前で持ち、自分たちの要件に合わせて調整しなければならないということでもある——まあ、それは私たちの DNA みたいなものだ。
少し歴史を
Hetzner はこれまで、クラウド製品のネットワーク接続にさまざまな技術を使ってきた。
初期の例では、2011 年に登場した Hetzner vServer が HDD、ネイティブの Linux bridge、静的ルーティングを使っていた。デュアルスタック接続を提供し、ホストあたり最大 1 Gb/s。最終的にこのアーキテクチャ上で約 25,000 のインスタンスが動いていた。
次の製品は 2015 年 9 月に登場し、Ceph ベースのハイパーコンバージドアーキテクチャを採用した。BGP による動的ルーティングへ移行し、Linux bridge と IPv4 の 1:1 NAT、そして完全にルーティングされた IPv6 プレフィックスを組み合わせた。これにより IP の移動性が向上し、顧客に影響を与えずにホストのメンテナンスができるようになった。2016 年にはホストのリンクを 2x 10 Gb/s にアップグレード。このアーキテクチャは 2018 年まで使われ、インスタンスは約 50,000 まで増えた。
2018 年に Hetzner Cloud がローンチしたとき、私たちは NAT をやめて直接 IPv4 をルーティングする方式に切り替えた。2019 年 7 月には Open vSwitch ベースのデータプレーンを導入し、VXLAN カプセル化によるプライベートクラウドネットワークのサポートを追加した。2020 年 11 月からパブリック側を Open vSwitch へ移行し始め、そのおかげで 2021 年 3 月に Open vSwitch のフローと netfilter を使ったステートフルなクラウドファイアウォールをリリースできた。
VM ホストの構成要素
ネットワークスタックの重要な部分が VM ホスト上で動いていることはすでに述べた。では、クラウドサーバーが正常に動くために、ホストは何を提供しなければならないのか。
ほとんどのサーバーはインターネットにアクセスできる必要がある。自宅や海外からシステムとそのサービスに到達するためだ。一部のサーバーはプライベートネットワークを使う。これはパブリックの代わりにも、パブリックと併用してもよく、あなたのシステムだけがアクセスできるプライベートな仮想ネットワークを通じて、サーバー、Load Balancer、場合によっては専用サーバーを接続する。プライベートネットワークのトラフィックは Virtual eXtensible LAN(VXLAN)でカプセル化し、各プライベートネットワークには一意の Virtual Network Identifier(VNI)を割り当てる。ホストのネットワークスタックは、対応するプライベートネットワークに接続しているメンバーだけがトラフィックを送受信できるようにする。
インターネットからサービスへのアクセスを制御するために、クラウドファイアウォールを提供している。サーバーの特定のポートやプロトコルへのアクセスを制御し、サーバーがどのトラフィックを送信できるかも制限できる。こうした制御はサーバーのできるだけ近くにあるべきなので、ホスト上で行う。中央でステートフルファイアウォールを組むと、余分なハードウェアとネットワークオーバーレイが必要になって「クリーンな」トラフィックをサーバーまで届けなければならず、高可用で集中型のコネクショントラッキングには大量の複雑さがつきまとう。
デフォルトでは、私たちのクラウドサーバーイメージは Dynamic Host Configuration Protocol(DHCP)を使って IPv4 アドレスとルートを動的に設定するよう構成されている。したがって、サーバーがパブリックおよびプライベートインターフェースの IPv4 ネットワーク設定を要求できるよう、DHCP サーバーを提供しなければならない。
ほとんどのイメージでは、cloud-init がシステムの一部を設定する。たとえば IPv6 ネットワークの設定、プライベートインターフェースでの DHCP の起動、クラウド Volume のマウントなどだ。これらの処理の際、cloud-init はローカルシステムの情報やユーザー固有の設定項目をメタデータサーバー(アドレスは http://169.254.169.254/)に問い合わせる必要がある。メタデータサーバーは cloud-init 以外にも多くの連携で使われている。私たちの CSI driver、hc-utils、Flatcar Linux の Afterburn や Ignition などのディストリビューションツール、そして Talos Linux などが含まれるが、これらに限らない。
DHCP サーバーとメタデータサーバーは、最大限の冗長性と可用性を確保するため、各 VM ホスト上でローカルに動作している。
こうしたユーザー向けサービスとは別に、VM ホストは内部サービス群も動かしている。これらはサーバーを正常に稼働させ、ネットワークスタックに接続性を構成するための正しい情報を渡し、私たちの監視に何が起きているかを伝える役割を担う。
ただし本記事ではネットワークスタックに焦点を絞り、その内部動作を詳しく見ていく。
Open vSwitch
ネットワークデータパスの大部分は Open vSwitch(OVS)で実装されている。これはオープンソースの多層仮想スイッチだ。データパスはすでに Linux カーネルに含まれており、ユーザー空間/コントロールプレーンのパッケージも主要な Linux ディストリビューションすべてで提供されている。
一部の仮想化環境ではこれがデフォルトのネットワークスタックであり、Proxmox VE、OpenStack、oVirt といったよく知られた選択肢をはじめ、多くの既製ソリューションが OVS をサポートしている。ただ、私たちはそのどれも使っておらず、KVM とその周辺コンポーネントを自社ツールで管理している。
Open vSwitch は多機能なネットワークソリューションで、機能は非常に豊富だが、私たちのユースケースとデプロイではその一部しか使っていない。OVS は OpenFlow を使う。これはネットワークデバイスのデータプレーンを設定し、パケットをどう転送するかを定めるオープンなプロトコルだ。通信の方向ごとに flow が 1 本必要で、パケットをどう処理し、どこへ送るかを指定する。
flow は完全一致で指定できる。たとえば ARP、NDP、ICMP などでゲートウェイと通信する場合、同じプライベートネットワーク内の他のメンバー宛てのトラフィック、あるいはインターネット上のサービスへの接続などだ。
Open vSwitch のオーケストレーション——Flusskrebs
サーバーと Load Balancer にネットワーク接続を提供し、サーバーに適用する各種 Firewall を設定するには、Open vSwitch がその接続と機能を提供できるよう、対応する flow をインストールしなければならない。
Open vSwitch をオーケストレーションする一般的な方法は、Open Virtual Network(OVN)コントロールプレーンプロジェクトだ。OVS 本体と同時期に始まり、Open vSwitch を動かすノード群を管理することを目的としている。論理ルーティングとスイッチを設定できる抽象化レイヤーを提供し、それを OpenFlow に変換する。
Hetzner Cloud の技術スタックに Open vSwitch を導入するにあたり、私たちはこの道を選ばず、Flusskrebs(直訳すると「河蟹」、つまり「flow crab」とも読める🦀)という自社開発の仕組みを作ることにした。Python で書かれており、Cloud コントロールプレーンのうちホストローカルな部分が呼び出す REST API を提供し、各 VM や Load Balancer が正常に動作するために必要な flow を設定する。ユーザーや私たちのバックエンドが設定した Firewall ルール(たとえばデフォルトでアウトバウンド SMTP をブロックするなど)を実現する flow も含まれる。
このコントローラーは同時に、各ホスト上のプライベートネットワークインターフェース向けの中央 DHCP サーバーとしても機能する。
VM host のネットワークスタック
全体のアーキテクチャ方針とこれらの基本コンポーネントを押さえたところで、VM host のネットワークスタックの詳細を見ていこう。下図は VM host ネットワークスタックの内部動作を簡略化して示したものだ。
ほとんどのホストは 2 本の 10 Gb/s アップリンクを使い、Linux 上で LACP によりリンクアグリゲーショングループ(LAG)または bond として束ね、仮想 chassis を構成する 2 台の物理スイッチに接続している。信頼性をさらに高めるため、今年の初めから BGP を使う 2 本のネイティブルーティングアップリンクへ移行を始めた。それぞれ別々のスイッチに接続する。なお、アップリンクは OVS ブリッジに直接接続されておらず、Linux システムがアップリンクと OVS の間でルーティングする。こうすることでホストの到達性が OVS に依存しなくなり、ホストのインストールと保守も簡単になる。
中央の Open vSwitch ブリッジは、ネットワーク接続と機能を提供する主要コンポーネントであり、ホスト上のすべてのクラウドサーバーがここに接続する。各サーバーはパブリックインターフェースを 1 つ持てるほか、プライベートネットワークインターフェースを最大 3 つまで持てる。以下は、サーバーがネットワークへ出口トラフィックを送ることを許可する flow ルールを生成するためのテンプレートだ:
クリップボードにコピーしました
# Only process packets with correct source MAC address
table=0, priority=9999, in_port={vm_port_no}, eth_src={mac}, actions=set_field:1->reg0,set_field:{vm_port_no}->reg5,resubmit(,1)
# Only accept packets with an IPv4/IPv6 address associated with the server
table=3, priority=9999, in_port={vm_port_no}, ip_src={ip}, ip, actions=resubmit(,4)
table=3, priority=9999, in_port={vm_port_no}, ipv6_src={ip}/64, ipv6, actions=resubmit(,4)
table=3, priority=9999, in_port={vm_port_no}, arp, arp_op=2, arp_spa={ip}, actions=resubmit(,4)
サーバー宛ての入口トラフィックは、以下の flow ルールで転送する。IPv4 または IPv6 のプレフィックスごとに 1 本ずつだ。ホストのネットワークスタックは、よく知られた「仮想ゲートウェイ MAC」を使って VM に自身を識別させる。このアドレスは常に d2:74:7f:6e:37:e3 だ。
クリップボードにコピーしました
table=22, priority=9999, in_port={internet_port_no}, ip, ip_dst={ip}, actions=mod_dl_src:d2:74:7f:6e:37:e3,{vm_port_no}
table=22, priority=9999, in_port={internet_port_no}, ipv6, ipv6_dst={ip}/64, actions=mod_dl_src:d2:74:7f:6e:37:e3,{vm_port_no}
DHCP や metadata サーバーといったインフラサービスも Open vSwitch に接続し、各サーバーとともにそれぞれ専用の Linux Network Namespace 内で動作する。
なお、上図の udhcpd DHCP サーバーはサーバーのパブリックインターフェースのみを担当する。前述のとおり、Flusskrebs コントローラーには中央 DHCP サーバーが含まれており、すべてのプライベートネットワークインターフェースを処理する。パブリック DHCP サーバーに接続する flow ルールも同じく単純だ:
クリップボードにコピーしました
table=1, priority=9999, in_port={vm_port_no}, udp, udp_dst=67, actions={service_port_no}
table=1, priority=9999, in_port={service_port_no}, udp, udp_src=67, actions={vm_port_no}
ステートフルなクラウドファイアウォールの実現には、Open vSwitch だけでなく Linux netfilter のコネクション追跡も使われている。コネクション追跡テーブルはグローバルに共有されるため、あふれさせないようにし、すべてのサーバーと顧客が公平に使えるようにしなければならない。この役割を担うのが ctcount だ。これも社内プロジェクトで、コネクション追跡エントリを記録し、サーバーごとにアクティブな同時接続数を最大 80,000 に制限する。サーバーがこの上限に達すると、既存の接続を 1 本閉じない限り新しい接続は張れなくなる。
現状
この Open vSwitch ベースのデータプレーンはこれまでよく機能してきた。100 万台を超えるクラウドサーバーを抱える今でも、おおむね問題はない。ただ、いくつか限界にぶつかっており、より専用のネットワークスタックでスケーラビリティ、耐障害性、柔軟性を高めたいと考えている。ここ数年、SDN チームはそのための仕組みを構築してきた。自分たちの要件に合わせて作り込みつつ、運用しやすく、プライベートネットワークの IPv6 といった新機能の土台にもなるものだ。
続報と、完全自社開発の最新ネットワークスタック、そしてその仕組みについては、本シリーズの次の記事で紹介する。お楽しみに。
Hetzner Summit では「The new Hetzner Cloud network stack - A technical deep dive into how we deliver your packets」と題した講演を行い、現在のネットワークスタックと私たちが構築してきたものについて話した。録画はまもなく Summit ページで公開される予定だ。