自作VMM u-rootベースのinitrd

はじめに gokvm開発 1 2 3 4 5 6 の続き。 前回までに紹介したとおり virtio-blk と virtio-net に対応したことで、仮想マシンが外部とIOを通してやり取りができるようになった。 今回は initrd を busybox ベースから u-root ベースへと変更したので、それについて述べていく。 0d89a47f u-rootベースの initrd の導入 Go言語で作られたVMMには、同じくGo言語で書かれた initrd が相応しいのではないかということで、Pull Requestをもらった。 1コマンドで成果物を生成でき、busyboxと比べると手順が少なく簡単な印象を受けた。 cb504d85 u-rootベースのinitrdをデフォルトとする u-rootによるinitrdをしばらく触ってみると自分のやりたいことはこなせるだろうという感触を持ったので、デフォルトとした。ただ、busyboxでは特に意識せずできていたことが u-root ではできないことがあった。例えば以下のもの。 ctrl-lやctrl-eでシェル内カーソル移動を行うために、clearやticコマンドに加えて terminfo ファイルが必要だった。 ゲストの起動時に、NIC・ファイルシステムの初期化やHTTPサーバの起動のために、それを記載したスクリプトファイルを /bin/uinit に配置したが、デーモンが途中でkillされるような挙動になってしまった。init関連の挙動に対する自分の理解が甘いのだと思う。しょうがないのでワークアラウンドとして .bashrc に記載した。 終わりに この他にもいくつかリファクタリングを実施した。 今回はVMMらしい変更はなかった。今後はマイグレーションをやっていきたい。 KVMを使ったVMMを自作してLinuxを起動するまでの記録 ↩︎ KVMを使ったVMMを自作してLinuxを起動するまでの記録2 ↩︎ KVMを使った自作VMMのSMP対応 ↩︎ 自作VMMの PCI デバイス対応 ↩︎ 自作VMM の virtio-net 対応 ↩︎ 自作VMM の virtio-blk 対応 ↩︎...

June 13, 2022

自作VMM の virtio-blk 対応

はじめに gokvm開発 1 2 3 4 5 の続き。 前回の virtio-net 対応に引き続いて、virtio-blk に対応した。 virt queueのデータ構造や挙動はそのまま流用できる。 この辺り Virtio はうまく設計されているなと感動する。 7389ff59 カーネルコンパイルオプションの調整 ゲストカーネルからファイルシステムを経由してブロックIOを実現するにあたって、以下のオプションを有効にした。 CONFIG_VIRTIO_BLK=y CONFIG_XFS_FS=y CONFIG_EXT3_FS=y CONFIG_EXT4_FS=y 4f4bbb78 virtio-blkの実装 さて、それでは本題である virtio-blk の実装に移っていく。 virtio-blk の挙動は virtio-net のものとほとんど同じなので、もし前回のブログを読んでいなければ、そちらを先に読むことをお勧めする。 差分はキュー数とdescripterテーブルのエントリが指す先のデータ構造だけである。 virtio-net では送受信のため2つのキューを必要としたが、virtio-blk の場合には 1つのキューで読み書きを実現する。 これはディスクへの読み書きはどちらもOS側からの発行となるため、外部割り込みを受ける必要がないためである。 descripterテーブルエントリが指すデータ構造は、以下のように3つのエントリがLinked Listの要領で繋がっている 6 。 1つ目のエントリが指すデータ構造は blkReq であり、typeフィールドが1なら書き込み、0なら読み込みを意味する。 sectorフィールドがディスクの先頭からのオフセットを意味する。 1セクタは512バイトなので、仮想ディスク用ファイルの sector x 512 バイト目から読み書きすることを意味する。 type blkReq struct { typ uint32 _ uint32 sector uint64 } 2つ目のエントリが実データを指す。ここに実際に読み書きしたいデータをバイナリで格納する。 3つ目のエントリがステータスである。エラーが発生した場合には0以外の数値を書き込む。...

April 12, 2022

自作VMM の virtio-net 対応

はじめに gokvm開発 1 2 3 4 の続き。 最近の一連の開発によって、gokvm 上のVMに virtio-net によって仮想NICを提供することができた。 ネットワーキングのサポートは当初の目標の一つだったので、達成感がある。 この対応によって gokvm 上のVMはホスト(あるいはソフトウェアスイッチを経由して外部)との間で通信できるようになった。 WEBサーバを提供したり、SSHでログインできたり、と出来ることの幅が広がる大きな変更だと思う。 例によって、重要なコミットを抜き出して振り返りたい。 c5217550 Virt Queue データ構造の追加 そもそも Virt Queue とは何なのか。 Virt Queue は ゲスト・ホスト間におけるデータのやり取りに使うリング構造のキューを意味する。 例えば送受信でそれぞれ1つのキューを使うナイーブな virtio-net の場合には、 送受信それぞれ1つの Virt Queue (全体で合わせて2つのVirt Queue)が必要になる。 もちろん、マルチキューをサポートする場合やコントロールキューをサポートする場合には、さらに Virt Queue が必要になる。 1つの Virt Queue は Descripter Table、Avail Ring、Used Ring から構成される。 取り扱いたいデータのアドレスと長さを1つのディスクリプタとしてまとめ、それをテーブル状に並べたものが Descripter Table である。 Avail Ring と Used Ring は似ていて、どちらもディスクリプタのIDをゲスト・ホスト間で 伝え合うために利用される。 方向も決まっていて、Avail Ring がゲストからホスト宛、Used Ring がホストからゲスト宛 となる。 ちなみに virtio 仕様 5 の中では、ゲストをdriver、ホストをdevice と表現している。...

March 18, 2022

自作VMMの PCI デバイス対応

はじめに gokvm開発 1 2 3 の続き。 gokvm 上のVMからPCIデバイスを取り扱えるよう開発を進めてきた。 道のりは長いだろうが、最終的には virtio-net を経由して、VMと外部の間でIP疎通を取りたい。 現時点では virtio-net デバイスをゲストカーネルのネットワークインターフェイスとして認識させることができたので、 ひとまずそこまでのログを残しておく。 やったことを大きく分けると、(1) ゲストのLinuxカーネルに対してvirtio-netデバイスをPCIデバイスとして認識させ、 (2) virtio-netデバイス初期化を完了させることでネットワークインターフェイスとして登録させることの2点。 virt queue上の操作やパケットのやり取りについては、この記事には含まれない。 例によって、コミット単位で実装の経過を残しておく。 fc02176d lspciコマンドの追加 busyboxにはlspciコマンドが同梱されているが、pci.ids ファイル 4 が存在しない。 pci.ids はベンダIDやデバイスIDなどの数値と、それに対応する文字列が組になっているようなファイルである。 このファイルがあれば、人間に読みやすいフォーマットで出力できる。 後々のデバッグをスムーズに進めたいので、対応させておいた。 e126392e PCI Config空間に対するIOエミュレーション カーネルがPCIデバイスを認識するための重要なフェーズ。 PCI Config 空間を読む方法はいくつかあるようだが、ここではタイプ1 5 と呼ばれる方法でアクセスした。 ここで使われるIOポートのアドレスは以下の通り。 0xcf8:アドレスレジスタに対応する。バス番号、デバイス番号、Function番号、PCI Config 空間内のオフセットに対応する。 0xcfc ~ 0xcff:データに対応する。 アドレスレジスタは32bit幅で以下のように解釈される。 位置 内容 Bit 31 Enable Bit Bit 30-24 Reserved Bit 23-16 Bus Number Bit 15-11 Device Number Bit 10-8 Function Number Bit 7-0 Register Offset ざっくり PCI Config 空間のあるオフセットにあるデータを読みたいときは次のような手続きになる。...

January 24, 2022

Open vSwitch AF_XDPの背景と使い方

最近、OVS(Open vSwitch)がAF_XDPに対応したとの話を聞いたのでどういう背景があったのか、そしてどうのように使えば良いのか調べてみた。 OVSは、カーネルモジュールとユーザスペースプロセスから構成されている。 その構成の部分で、以下のような課題が見えてきた1ので、最近AF_XDPを使った実装に置き換えが進められているようだ。 カーネル本体の更新やシステム全体のリスタートを要求する修正がある カーネル開発者の方針や実装に影響を受ける DPDKで速度面で劣る バックポートが多すぎる ディストリビューションのサポートが受けられなくなることがある どれも構成変更を推し進めるには妥当な理由に思う。 下図は、バックポートと新規機能それぞれに起因する差分をコード行数によって比較したもの。 バックポートにかかるコストが読み取れる。 ちなみに、カーネルモジュールを使った実装は2022年4月にリリース予定のOVS 2.18で廃止される予定2になっている。 出典:Revisiting the Open vSwitch Dataplane Ten Years Later それならユーザスペースでデータプレーンを実装したDPDK(Data Plane Development Kit)で良いじゃないかいうと、 それはそれで課題がある。 ipコマンドなどカーネルネットワークスタック向けのツールと相性が良くない 特定のNICやCPUを占有してしまう この辺りの課題を解決するアプローチとしてAF_XDPの導入が進められている。AF_XDPを使うと、XDPのフックポイントに小さなeBPFプログラムを仕組んでおき、カーネルネットワークスタックをバイパスした上で、ユーザプロセスへとパケットを転送することができる。 安定した仕様を持つので、将来のカーネルリリースでも継続して使えるはず。 既存ツールとも相性が良い。DPDKからAF_XDPを使おうという話 3 もあるが、DPDKとOVSの間のメンテナンスコストが残る。というわけで、OVS本体でAF_XDPをサポートしようということになったようだ。 ところでAF_XDPを使うとどのようにパケットが転送されるのだろうか。 AF_XDP は fill リングと competion リングの2つのリングを持つ。 その各要素はディスクリプタとなっていて、umem 領域を指している。 パケット受信時の流れを図中の番号に沿って見ていく。 まずアプリケーションはfillリングに空きディスクリプタを登録する カーネルは fill リングからそのディスクリプタを取り出す umem領域にパケット本体を書き込む そのumem領域を指すようなディスクリプタを completion リングに登録する アプリケーションは completion リングからディスクリプタを取り出す そのディスクリプタの指す umem 領域からパケット本体を取り出す 出典:Revisiting the Open vSwitch Dataplane Ten Years Later...

December 7, 2021

KVMを使った自作VMMのSMP対応

はじめに gokvm開発 1 2 の近況報告。 これまでは1つの仮想CPUにしか対応していなかった。 マルチCPUのためSMP(Symmetric Multiprocessing)対応させたいと思い立ってから2~3週間くらい試行錯誤し、無事実装することができた。 自分の知る限り KVM でVMMを作ってみたという取り組みを探す中で、 具体的にSMP対応とはどのような実装なのか解説されている資料がなかなか見つからなかった。 稚拙な記事ではあるけれど、今後自作VMMに挑戦する方にこの記事が役に立てば嬉しい。 例によってコミット単位に開発の経過を紹介していく。もちろん実際にはもっともっと泥臭い実装から始めていて、 何度もgit rebaseを繰り返しながら、最終的に解説できるよう粒度を調整したので、コミットのタイムスタンプはあてに出来ない。 #34 vCPUスレッドを複数生成 プルリクエストをいただいた。まずはioctl(fd,KVM_CREATE_VCPU,...)でvCPUを複数生成できるよう変更する。 その後vCPUごとに個別のスレッドを生成して各vCPUごとに独立してioctl(fd,KVM_RUN,...)を発行する。 vCPUはライフタイム全体を通して、同一のスレッドからioctlを発行する必要がある。 Go言語の場合にはスレッドの代わりにgoroutineを使うことが多いので、 runtime.LockOSThread()を呼び出してgoroutineとスレッドを静的に関連づけた。 ce22a91 struct mpf_intel の実装 vCPUがカーネルに認識されるためにはIntel MultiProcessor Specification 3 に従ったデータ構造を認識させる必要がある。 このデータ構造はLinuxカーネルの中で struct mpf_intel のPhysPtrが指す先 struct mpc_table に対応する。 コード 4 を読むと、チェックサム・バーション・マジックナンバーを読み取ることができたので、 仕様書は斜め読みしかしていない。 このデータ構造はどこに配置すれば良いのか。仕様書を読むとExtended BIOS Data Area (EBDA)の最初の1KB以内とあるのでそこに配置することにした。 EBDAは典型的に 0x0009FC00 に置かれる 5 ようなので、それに倣った。 a. In the first kilobyte of Extended BIOS Data Area (EBDA), or b. Within the last kilobyte of system base memory (e....

November 25, 2021

Understanding Linux Network Internal 1~2部 読書メモ

このブログではネットワークに関する比較的新しい技術について触れてきたが、たまには古きを温めるのも良いだろうということで読んでみた。Linuxカーネルは今後も長きにわたって使われるはずで、カンペキな理解でなくとも、取っ掛かりだけでも掴んでいる意味は大きいと思う。この本は1,000頁超えで、1~7部から構成されているので、一気に読むのはモチベーション維持が難しいと思う。この記事ではとりあえず現時点で読んだところまでをまとめたい。カーネルバージョン 2.6.39 のソースコードを手元に置いて、読み進めていった。ビルド方法などは前回の記事 1 のとおり。 1部 ネットワークに関する重要なデータ構造として struct sk_buff と struct net_device がある。まずはこの2つのデータ構造を掴むことが肝要だと思う。struct sk_buff は(フラグメンテーション云々の話を抜きにすると)1つのパケットに対応する。しばしばそのインスタンスは skb という名前が付けられる。skb->data が処理を担当しているネットワークレイヤのヘッダを指している。例えば、L2の処理を行っている際にはskb->data はL2ヘッダ の先頭を指している。処理の進行に伴って、このポインタは移動していく。実データの前後に余白が設けられている。 +------------+ skb->mac skb->nh | | | | | head-----------> +------------+ | | | | | headroom | v v | data-----------> +------------+ +---------+---------+---------+--- | | | | | L2 | L3 | L4 | | tail | | Data | | header | header | header | ... | | | | | +---------+---------+---------+--- | end | | | | ^ ^ | | +----------> +------------+ | | | | | | tailroom | | | | +-------------> +------------+ +---------+ | | skb->data +------------+ struct sk_buff この構造体にどんなメンバがいるか見ていく。users が参照カウンタに対応していて、sk_getやkfree_skbで操作できる。mac_header など各レイヤに対応するポインタもある。cbはコントロールバッファの略で、48バイトの領域を各レイヤの中でプライベート(他のレイヤを意識せず)に使える。struct sk_buffは双方向リストで管理されていて、リスト全体は struct sk_buff_head に対応する。デバッガを使って、中身を見ていく。送信を担当する関数にアタッチしてみると、struct sk_buff内部に保持されたIPヘッダ の中身を見ることができる。...

August 26, 2021

BusyboxベースのミニマルなLinux環境を作りQEMUで起動

すでに多くの方が似たような取り組みを行っていてブログ記事 1 2 3 として丁寧にまとめられているように、 やはりこういった環境を手元にさっと作れることの意味は大きいと思う。 ここではざっくりとした仕組みを記録しておく。 成果物をスクリプトとしてまとめGithubにあげている。 特徴 CentOS6、CentOS7、Ubuntu20.04などメジャーなディストリビューション向けカーネルのビルドに対応しているので、実務よりの応用ができる Busyboxを使ってユーザランドをメモリ上に展開するので、起動のたびにピュアでミニマルな環境を作れる SSHログインや外部ネットワーク疎通が可能なので、他システムとの連携が絡む動作を検証しやすい GDBを使ったデバッグによってカーネル内部のデータを参照できる 現時点ではx86/64のみに対応している カーネルのビルド カーネルのビルドは端的に言えば、ビルド設定を.config ファイルに記述し、makeコマンドを叩くことに対応する。.config はテキストファイルなので適用なエディタでも編集できるが、専用のコマンド(make oldconfig、make defconfig、make menuconfig など)が用意されているので、それを使うことが多い。カーネルはアップストリームのものと各ディストリビューションが手を加えたものがあるがここでは以下の全てのカーネルをビルドできるよう環境を整えた。 upstream (kernel v2.6.39) centos6 (kernel v2.6.32-754.35.1.el6) centos7 (kernel v3.10.0-1160.13.1.el7) ubuntu20.04 (kernel v5.4.0-65.73) 最近のGCCで古いカーネルをコンパイルするのは難儀なので、カーネルバージョンごとにビルド専用Dockerイメージを用意した。例えばカーネルv2.6.39はCentOS6のビルド環境を使ってビルドすることにした。 FROMghcr.io/buddying-inc/centos68:latestRUN sed -i "s|#baseurl=|baseurl=|g" /etc/yum.repos.d/CentOS-Base.repo \ && sed -i "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-Base.repo \ && sed -i "s|http://mirror\.centos\.org/centos/\$releasever|https://vault\.centos\.org/6.10|g" /etc/yum.repos.d/CentOS-Base.repoRUN yum install -y gcc perl glibc-static kernel kernel-devel \ autoconf zlib-devel zlib-static openssl-static openssl-devel上のDockerfileをもとに buildenv-v2....

July 7, 2021

Cloud Native Data Center Networking 読書メモ

気になるところだけつまみ読みした。よく纏っていて手元においておきたい一冊。 全体を通して KISS(Keep it simple, stupid) の重要性が主張されているように思う。 物理面 Leaf・Spine間に複数のリンクを繋がない。ルーティングの観点から見た時、あるリンク障害となった時に別のリンクが生きていることから、障害前と同量のトラフィックが流れてきてしまう。あるリンクが障害となっていることため期待する帯域を確保できずパフォーマンスが劣化する。代わりにスイッチを増やすほうが良い。 Spineをただの経由デバイスとして扱う。あるSpineを特殊な用途(例えば外部接続)として使ってしまうと、そのSpineにトラフィックが集中してしまう。Border LeafやExit Leafをおけば解決する。例外を排除する。単純さことが強さ。 3層のClosトポロジが望ましい。層の数というよりは巨大かつ高機能なスイッチを使うことが適切でない。仮にSpineスイッチに巨大なスイッチを採用することで無理やり2層のClosネットワークを構築すると、高機能なためにトラブルシュートが複雑になってしまう。LinkedInやDropboxでは、chassis-switchからfixed-form-factorスイッチへ切り替えた(要出典)。 スイッチ障害時に即座に交換できるようスペアを用意する。わざわざサポートに交換を依頼すべきでは無い。 ケーブルやトランシーバーにはNOSベンダでテスト済みのものを使う。 機能リストを比較して選定すべきでは無い。ミニマニストになるべき。 BGP周り 本書のASN番号の割り当てモデルに従う。 Leaf-Spine-SuperSpineの3層構成 Leaf-Spineの集合をPodと呼ぶ LeafはユニークなASN Pod内の全てのSpineは同一のASN(Podごとに別のASN) SuperSpineは同一のASN Unnumbered BGPを使う。 ループバックIPアドレスが正当で、正しく広報されていることを確認する。 マルチパスを有効にする。 複数のアドレスファミリーの到達性に対し、同一のeBGPセッションを使う。 BFDを使う。 不正なPrefixを受け取らないようroute mapを設定する。 Leaf以外ではルートを集約しない。 即時反映のためにBGPの advertisement interval timer を0秒とする。 keepalive timerを3秒、hold timerを9秒、connect timerを10秒とする。 設定を最小化する。大事。 EVPN周り Distributed Symmetric Routingモデルを採用する。 アンダーレイのrouted multicastを避ける。 BUMパケットを使わない。 設定を最小化する。繰り返しになるが大事。 自動化まわり 単純なところから手をつける。ループバックIPアドレスを割り当てるなど。 コードとデータを分ける。 実際に設定を適用する前にバリデーションを設ける。 Gitを使う。 ローリングでアップデートする。Closトポロジでは影響範囲をコントロールできる。 言語やツールを統一する。AnsibleとChef、RubyとPythonを混ぜない。 Ansibleなど巨大なコミュニティを持つツールを使う。

May 28, 2021

EVPN in the Data Center 読書メモ

EVPN in the Data Center を読んだので、 メモを残しておく。 メールアドレスなどを登録するとNVIDIAのページからPDFを 無料でダウンロードできる。 あくまでも調査中の個人的なメモなので間違いも含まれている。 イントロ Closトポロジで構成されたL3ネットワーク上で、L2を前提とするアプリケーションをどのようにデプロイすれば良いか。 例えば、L2のマルチキャストやブロードキャストを使って死活監視やメンバの検出を実現するようなアプリケーションがこれに該当する。 Ethernet VPN(EVPN)は、この課題に対して、L3ネットワークの上にオーバレイによって仮想的なL2ネットワークを提供することで解決する。 ここで、EVPNのコントロールプレーンにはBorder Gateway Protocol(BGP)が使われる。 EVPNとMultiprotocol Lable Switching(MPLS)の組み合わせで成熟した技術であるが、Virtual Extensibe LAN(VXLAN)への応用ができるようになった。 端的に言うと、EVPNはコントローラベースのVXLANに対する新たなアプローチとみなせる。 EVPNはサービスプロバイダの世界を起源に持つので、データセンタネットワークの世界から見ると、馴染みの薄い用語が多く理解しづらい。 この本では、OSSであるFRRを設定例として使いつつ説明していく。 ネットワーク仮想化 仮想ネットワークにおいては、あるユーザは、まるで別のユーザ(あるいはテナント)が存在しないかのようにネットワークを占有できる。 パケットがどの仮想ネットワークに紐づいているかは、多くの場合、パケットヘッダのVirtual Network Identifier(VNI)によって判断する。 VLANやL3VPN、VXLANはこれに該当する。 VLANはインライン仮想ネットワーク、VXLANはオーバレイ仮想ネットワークであり、後者の方がスケーラビリティや運用のしやすさの面で優れている。 なぜなら、上流のスイッチは仮想ネットワークについてのフォワーディングテーブルを持つ必要がなく、管理すべき状態が少なくてすむため。 さらに、仮想ネットワークの追加・削除に伴う影響は、エッジスイッチだけに限定されるため、短時間でユーザへ提供できる。 オーバレイ仮想ネットワークでは、 トンネルのエンドポイント(カプセル化したり、カプセル化をほどいたりするノード)をNetwork Virtualization Edge(NVE)と呼ぶ。 主なL3のトンネリング技術には、VXLANやGRE (IP Generic Routing Encapsulation)、MPLSがある。 VXLANではエンドポイントをVXLAN Tunnel end Point(VTEP)と呼ぶ。 オーバーレイ仮想ネットワークはさらに2つに分類できる。 1つのエンドポイントが唯一のエンドポイントとのみトンネルを張る。L3VPN+MPLSがこれに該当する。 1つのエンドポイントが複数のエンドポイントとトンネルを張る。Virtual Private LAN Switching(VPLS)がこれに該当する。 パケットがトンネル化されていたとしても、アンダーレイのノードはトンネルヘッダしか見ない。 そのため、すべてのパケットは同一の送信元・送信先をもつとみなされ、同一のパスを通ってしまう。 そこで、VXLANやその他のプロトコルでは、UDPのソースポートを書き換えることによって、5タプルのハッシュ値を変更し、別のパスを通すことができる。 サーバノードでは、NICでのTCPセグメントオフロードやチェックサムオフロードによって、 パケット処理にかかるCPUサイクルを削減できる。 しかし、トンネル化されているときにはこれと相性が悪い。 もちろん、VXLANヘッダを理解するNICも存在するが、この相性問題から、 多くの場合サーバノードではなくネットワークノード側でVXLANのカプセリング操作を行っている。 また、トンネル化はヘッダの追加によって実現するので、MTUサイズに気をつける必要がある。 コントロールプレーンは以下を担当する。 パケットの送信先を見て、適切なNVEを見つけ出す。VXLANでは、VNIとMACの組みを見て、NVEのIPアドレスを見つけ出すことに対応する。 全てのNVEに対して、そのNVEに関係する仮想ネットワークの一覧を提供する スイッチのチップは、各社独自のASICから、マーチャントシリコンへと移り変わっている。 本書の執筆時点では、トンネルヘッダとしてIPv6を使うことは多くの場合難しい。...

May 12, 2021

MininetでFRR(BGP Unnumbered)を動かす

https://github.com/bobuhiro11/mininetlab の紹介。 Mininet を使うと単一のマシン上でいくつかのスイッチとホストを動作させることができる。 これを使って仮想的にホストを2つ立ち上げ、その間をFRRパッケージに含まれるBGP(Unnumbered)で接続してみる。 Mininetでは以下のようにPythonでトポロジや各ホストにおけるコマンド実行について記述できる。 一見複雑にみえるFRRもdaemons、vtysh.conf、frr.confの3つのファイルを正しく配置しておけば、 frrinit.sh startで簡単に起動できる。 ホストごとにnetnsは分割されているが、mountns は分割されていない(要確認)ため、 /etc/frrや/var/run/frrが2つのホスト間で衝突してしまいFRRが正常に起動できなかった。 これはprivateDirs = ['/etc/frr', '/var/run/frr'] によって回避できる。 #!/usr/bin/env python from mininet.net import Mininet from mininet.log import setLogLevel import time frr_conf = ''' hostname {name}password zebra ! router bgp {asnum}bgp router-id {router_id}bgp bestpath as-path multipath-relax neighbor h1-eth0 interface remote-as external neighbor h2-eth0 interface remote-as external address-family ipv4 unicast network {router_id}/32 network {network}exit-address-family ! line vty ! end ''' vtysh_conf = ''' service integrated-vtysh-config ''' daemons = ''' bgpd=yes vtysh_enable=yes zebra_options=" -A 127....

May 8, 2021

Running BGP in Data Centers at Scale

NSDI 2021 でのFacebookの論文 1 を読んでみて、間違いをおそれず自分の言葉でまとめてみる。 概要 FacebookのBGPをベースとしたデーターセンターネットワークについてまとめもの。AS番号の割り当て、経路集約、BGPポリシー、独自のBGP実装に対するテストやデプロイ手法など実践的な内容となっている。終盤には実際に過去2年間の運用において経験した事故についても触れられている。 1 イントロ Facebookはいくつかのデータセンターを展開しているが、それらのデータセンターでは共通するAS番号の割り当てスキーマを持っている。つまり、データセンターAのスイッチに相当するデータセンターBのスイッチも同じプライベートAS番号を持つことになる。大量のスイッチでBGPエージェントを動かし全体として階層的な構造を持つClosトポロジーを採用しているが、その全てのレイヤで経路集約を行っており、ハードウェアのFIBを最小限に保っている。BGPはもともとインターネットで使われていた技術で、その歴史の中で収束上の問題、経路の不安定さ、設定ミスによる事故を経験してきたが、データセンターに応用する場合には運用者が全てのスイッチを管理・運用できるため、これらの問題に柔軟に対応することができる。例えば、通信障害に対しては、事前にバックアップ経路を入れておくことで、例えリンクあるいはスイッチに障害が発生したとしても、その障害を伝播させる範囲を限定することで、、迅速に収束させることができる。さらに、ベンダ製BGPエージェントよりも高速な開発スパンを持つミニマムな実装を目指して、Facebook社内で独自にBGPエージェントを開発している。 2 ルーティング設計 スケールするネットワークの構築が目的だが、同時に短期間に構築する必要がある。また、どんなに可用性を追求しようとも事故は起きるので、その影響範囲を設計として縮小したい。当時、中央集権のSDNを開発することに比べ、BGPを使ったルーティング設計を展開する方がスピードと実績の両面で優位にあった。もともとベンダ製スイッチとそのBGP実装を使っていたが、後に独自にハードウェアとBGPエージェント実装を行うようになった。 ルーティングプロトコルとしてOSPFやISISのようなIGPも検討したが、スケール性能が不透明で経路の伝播を制御できそうになった。BGPとIGPを組み合わせたハイブリットなルーティング設計も考えたが運用コストが高くなるので、最終的にBGPを唯一のルーティングプロトコルとして採用することにした。 2.1 トポロジ設計 サーバポッドと呼ばれるラックの集合をモジュールとしてまとめ、それをSpine Planeがまとめ上げる形でデータセンターを設計する。トポロジについては下図から読み取れるが、ここでは言葉でも書いておく。1つのサーバポッドは48個のサーバラックから構成され、16個のFSWに接続される。サーバポッド間は、複数のSpine Planeを経由することで、相互に接続される。Spine Plane自体の数は、1つのサーバポッドの中に展開されるFSWの数と等しい(下図では青黄緑紫の4つ)。Spine Planeとサーバポッド間でリンクを増やすことによって帯域を増大できる。つまり、サーバポッドの追加によってコンピュートリソースを、SSWの追加によってネットワークリソースを増大できる。 出典: Running BGP in Data Centers at Scale 2.2 ルーティング設計の指針 同一性と単純さを求めるというのが2つの大きな柱となる。これらを達成するために、BGPで利用するFeature Setを最小限にし、複数のスイッチへできるだけ同一の設定を適用しようと取り組んでいる。同じTier(RSW、FSW、SWW)の中では、OriginateするプレフィックスやBGPピアアドレスを除いて、同じ設定を投入している。また、ベンダ依存を取り払うために、特定のプラットフォームに依存しない形で、ネットワークトポロジデータを作っている。このデータには、ポートマップ、IPアドレス割り当て、BGPの設定、ルーティングポリシーが含まれる。Robotron 2 と呼ばれるシステムによって自動的にプラットフォームごとの設定へと変換している。 2.3 BGPピアリングと負荷分散 ピアリングは直接繋がれた1ホップのeBGPセッションでのみ行う。マルチホップは使わない。スイッチに複数のリンクがあった場合には、それぞれ個別のeBGPセッションとして扱う。負荷分散はECMPによって実現する。後述する経路集約やルーティングポリシーの仕組みによって、障害発生時・復旧時に発生するネクストホップの追加・削除に伴うFIBの更新は軽量で、運用は簡潔になった。単純さを理由に、経路ごとの重み付けは行なっていない。 2.4 AS割り当て AS割り当ては全てのデータセンターで同一である。例えば、あるデータセンターで1つ目のSSWにAS65001を割り当てたとすると、別のデータセンターでも同じAS番号が再利用される。サーバポッドには、それ自身を示すAS番号が割り当てられ、そのサーバポッドの外からはこのAS番号によって識別される。つまり、サーバポッド内のスイッチ(RSW、FSW)のAS番号はそのサーバポッド内に閉じることになる。この性質から、RSWとFSWのAS番号は全てのサーバポッドで再利用される。Spine Planeにはデータセンター内でユニークなAS番号が割り当てられる。Spine Planeは複数のSSWから構成されるが、それらSSWは同一のAS番号を持つ(SSW間でピアを張ることはないので共通化できる)。 出典: Running BGP in Data Centers at Scale 2.5 経路集約 階層的に全てのTierにおいて、経路集約を行なっている。例えば、RSWは配下のサーバのIPを集約し、FSWは配下のRSWの経路を集約する。経路集約によって、数十万経路から数千経路へと大幅に削減できる。 3 ルーティングポリシー BGPを使うことで、ベストパス選択による高可用性の恩恵を受けることができる。さらに、経路広報に介入できることから、伝播を高精度に操作できる。 3.1 ポリシーのゴール 信頼性、保守性、スケーラビリティ、サービス到達性の4項目を達成したい。 信頼性 経路伝播のスコープを制限し、事前にバックアップ経路を定義していく。バックアップ経路はFSW1->RSW2->FSW2->RSW1のようなもので、BGPコミュニティタグをつけた上で、あらかじめサーバポッドに閉じたスコープで伝播させておく。バックアップ経路があることで、あるリンクがダウンしても、サービスを継続できる。伝播がサーバポッドに閉じるという性質から、収束までの時間が短く、別のサーバポッドへの影響がない。また、実際にはバックアップ経路はECMPによって複数存在することになるので負荷は分散される。 保守性 スイッチにはアップ・ダウンのような2種類の状態ではなく、LIVE・DRAINED・WARMという3種類の状態を持つ。WARMはRIBやFIBが準備完了だが、トラフィックは流れていない状態に対応する。この3状態を持つことで、一日あたり平均242回のオペレーションを行なっているが、パケットドロップは発生していない。 スケーラビリティ FSWではラックレベルのプレフィックスを集約するため、サーバポッド追加による経路数の増加が小さくスケールする。 サービス到達性 サービスはVIPを経由して提供され、そのVIPは複数のインスタンスからBGPによって広報される。広報のため、インスタンスはRSWに対して直接BGPセッションを張っている。 出典: Running BGP in Data Centers at Scale...

April 21, 2021

Use of BGP for Routing in Large-Scale Data Centers

RFC7938を読んだのでメモしておく。 ちょっとかじった程度の話なので、言葉づかいは不適切かもしれない。 概要 サーバが10万台を超えるラージスケールなインフラにおいて、単純かつ高安定性なネットワーク設計手法についてまとめる。BGPを唯一のルーティングプロトコルとして採用する。 イントロ Web検索エンジンのような大規模な分散システムのインフラを、単純かつ高安定性なネットワークによって少ない人数で運用したい。 ネットワーク要件 帯域と遅延が重要な要素。伝統的な木構造のネットワークはnorth-southのトラフィックに対しては対応できる。ただ最近Hadoopのようなサーバ間のトラフィックパターンが増えたことから、east-westのトラフィックが増えることになり、伝統的な木構造のネットワークで対応するには、ポート密度など物理的な制約から難しくなってきた。 データセンタの設備投資額についてみると、データセンタ全体の10~15%をネットワークが占める。削減にあたって、2つのアプローチがある。1つ目は、同一のハードウェアやデバイスを使うなど、全てのネットワーク機器を統合するということ。まとめて購入することで購入コストや管理コストの削減につながる。2つ目は、ネットワークベンダ間で価格を競争させるということ。もしベンダを分散させるなら、ソフトウェア要件を最小にして、柔軟性を持たせることが重要になる。 オペレーションについてみると、L2ネットワークではブロードキャストやユニキャストのストームがそのドメインで大規模なパフォーマンス上の問題や稼働率の低下を引き起こす。これはL3ルーティングを組み込んだ設計を行うと、その影響範囲を縮小できる。一方で、L3ルーティングでは、コントロールプレーンの分散に起因する障害を考慮する必要が出てくる。これはルーティングプロトコルの種類の削減することで対応する。 アプリケーションのロードバランシングも重要な観点になる。これまでは経路上に専用のデバイスを置くことで実現したが、トラフィックの増大へは対応しづらい。そこで、複数台のローバランシング専用ノードを並べることで、水平にスケールできる構成が望ましい。これはAnycast Prefix AdvertisementとEqual Cost Multipath(ECMP) によって実現できる。 要件をまとめると、 REQ1: デバイスのアップグレードではなく、同種のデバイス追加によって、水平にスケールする構成が望ましい REQ2: ソフトウェアのfeatureやprotocolを最小化したい REQ3: 運用コストが小さくてシンプルな実装を持つルーティングプロトコルが良い REQ4: 機器やプロトコル起因の障害範囲を小さくしたい REQ5: プロトコルのfeatureによってトラフィックエンジニアリングを実現したい データセンタトポロジ トラディショナルなtree-basedなトポロジと、Clos-basedなトポロジを比較してみる。前者は、下図のように3層のスイッチ(Coreレイヤ、Aggregation/Distributionレイヤ、Accessレイヤ)から構成され、上位の層では帯域を確保するために、ポート密度や帯域容量を上げることになる。この構成では前述したように、Tier 2を増やしたときに、それを格納できるほどまでTier 1のポート密度を上げることができない。つまり、各スイッチの次数(グラフのある頂点から出る辺の数)の制約でスケールしない。 +------+ +------+ | | | | | |--| | Tier 1 | | | | +------+ +------+ | | | | +---------+ | | +----------+ | +-------+--+------+--+-------+ | | | | | | | | | +----+ +----+ +----+ +----+ | | | | | | | | | |-----| | | |-----| | Tier 2 | | | | | | | | +----+ +----+ +----+ +----+ | | | | | | | | | +-----+ | | +-----+ | +-| |-+ +-| |-+ Tier 3 +-----+ +-----+ | | | | | | <- Servers -> <- Servers -> Figure 1: Typical DC Network Topology 後者のfolded Closトポロジ(fat treeとも呼ばれる)は水平スケールのためのアプローチである。奇数台のステージ(次元とも呼ばれる)を一様な要素で構成する。この構成はLeaf・Spine構成とも呼ばれる。SpineがTier 1、LeafがTier 2に対応する。...

April 13, 2021

OpenStack DevStack をコンテナで起動する

https://github.com/bobuhiro11/containerized-devstack の紹介。 OpenStackには1コマンドで開発向けにall-in-one環境を作る便利なツール 1 があるが、 それはVMや実機を想定環境としている。 もしその環境をコンテナの中で作ることができれば、再セットアップなどが容易になり嬉しい。 もちろん過去に似たような取り組み 2 3 4は何度かなされているが、今となってはメンテナンスされていないので、 自分で作ろうと思い立った。何番煎じだろうが気にしないことにする。 使い方 docker-compose up -d コマンドによって1発でall-in-one環境が出来上がる。 devstackの初期化に30分程度かかるので注意。 TODO 現状では master ブランチの Nova、Glance、Keystone が動く。Neutronも動かしたかったが、Open vSwitchやLinuxBridgeをコンテナの中で動作させることができずスキップとした。Open vSwitch のドキュメント 5 によると、カーネルサポートがなくても動かせるようなので、この辺りはTODOとする。 マルチノードにも対応したい。ノード間のスケジューリングやマイグレーション時の挙動確認のために使いたい。 https://docs.openstack.org/devstack/latest/ ↩︎ https://github.com/janmattfeld/DockStack ↩︎ https://github.com/bodenr/docker-devstack ↩︎ https://github.com/ewindisch/dockenstack ↩︎ https://docs.openvswitch.org/en/latest/intro/install/userspace/ ↩︎

March 31, 2021

サブクラスタ導入によるqcow2高速化

qcow2の構造 サブクラスタの話に入る前に、qcow2のデータ構造について簡単に説明する。 qcow2のデータはクラスタと呼ばれる小さなブロックから構成される。 ゲストから仮想ディスクに対して読み書きすると、 そのバックエンドのqcow2ファイルに対してクラスタ単位でI/Oが実施される。 例えば、クラスタサイズが64KB(QEMUのデフォルト)である環境でゲストから4KB(メジャーなブロックサイズ)単位で読み書きすると、qcow2ファイルへのI/Oは64KB単位になる。 qcow2ファイルはスパースなので、ゲストから見えるサイズと比べて実ディスクのサイズは小さくなる。 この状況では、原理的にゲストからみた仮想ディスク内のオフセットとホスト上のqcow2ファイルからみたそれは異なる。 したがって、仮想ディスクとqcow2ファイルの間でオフセットの変換が必要になる。 qcow2では2段の変換テーブル(L1テーブルおよびL2テーブル)を用いてオフセットの変換を実現している。 それぞれのテーブルを見ていく。 L1テーブルはqcow2ファイルに唯1つだけ存在する。 このテーブルは十分に小さくて、例えば 1TB の qcow2 ファイルであってもわずか 16KB 程度におさまる。 したがって、QEMUではキャッシュの意味で常にこれをRAM上に保持している 1。 このテーブルのエントリは64bitのポインタで、L2テーブルを指している。 L2テーブルはqcow2ファイルへの書き込みが進むにつれて動的に生成され、1つのqcow2ファイルに複数存在する。 エントリのサイズは64bitで、qcow2ファイル内において実データを格納するデータクラスタへのポインタを格納している。 L2テーブルは仮想ディスクサイズによっては巨大なものになり得るので、 そのすべてをRAMに乗せることは難しい。 そのため、古いQEMUでゲストから仮想ディスクへI/Oを発行すると、 L2テーブル参照のために実ディスクへ対して余計なI/Oが発行されてしまう。 そこで、一部のL2テーブルエントリのみをRAMに載せようというアイデアが生まれた。 このアイデアをL2キャッシュとよび、そのサイズをL2キャッシュサイズと呼ぶ。 もしL2キャッシュをL2テーブル全体を覆うようなサイズまで大きくできれば、 L2テーブルは完全にRAM上に乗るので、余計なI/Oを防ぐことができる。 では、そのようなL2キャッシュサイズをどうやって決めれば良いだろうか。 ここで冒頭で触れたクラスタサイズの話に戻る。 クラスタ数は単純に ディスクサイズ / クラスタサイズ で計算できる。 L2テーブルのエントリは64bitなので、L2テーブル全体のサイズは(ディスクサイズ / クラスタサイズ) x 64bit で求められる。 つまり、(L2テーブルを完全にRAMに乗せるための)L2キャッシュサイズもこの式によって計算すれば良い。 クラスタサイズとL2キャッシュサイズの間にどんな関係があるのか詳しくみていく。 クラスタサイズを小さくすると、細かな単位でqcow2ファイルへI/Oが実施されるため余計なI/Oを防ぐことができるが、 L2キャッシュサイズを大きくする必要があるのでRAM使用量が増大してしまう。 また、クラスタ数が増えるとデータサイズに対するメタデータの比率が大きくなるため、 いくらでも小さくすれば良いというわけでは無い。 一方、クラスタサイズを大きくすると、L2キャッシュサイズを小さくできるのでRAM使用量を抑えられるが、 余計なI/Oが発生してしまう。 出典:Subcluster allocation for qcow2 images Libvirtの対応状況 L2キャッシュサイズはqcow2ファイルのI/Oに多大な影響を及ぼすことがわかった。 もちろんこれはQEMUでゲスト起動時にパラメータとして外部から与えることができる2が、 Libvirtではどうだろうか。 実は2021年3月時点では、まだLibvirtからこのパラメータを操作できない。 2016年から Feature Request 3 が存在していて、いくつかパッチ 4 5 も提出されているが、 まだマージへ至っていない。何故なのか。Feature Request のコメントを読んでみる。...

March 18, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録 2

2021/2/24 WSL2 サポート 4f6b785 WSL2(Windows Subsystem for Linux 2)のUbuntu 20.04で gokvm を実行すると、 IOポート 0x64 への出力が無限に繰り返され、Initプロセスの起動まで到達しなかった。 どうやら PS/2 キーボード周りの挙動が原因のようだ。 kvmtool では in (0x61) に対して 0x20 を返している 1 のでそれを踏襲する形で対応した。 IOポート 0x61は NMI (Non-Maskable Interrupt)のステータスとコントロールレジスタとして使われているようだ 2。 このステータスレジスタの内容を調べると、bit 5はmirrors timer 2 output condition を意味するが、 これ以上は解釈できず。 理解できていない部分はあるが、結果として WSL2 での ゲストVMの起動もできるようになった。 0061 r KB controller port B control register (ISA, EISA) system control port for compatibility with 8255 bit 7 parity check occurred bit 6 channel check occurred bit 5 mirrors timer 2 output condition bit 4 toggles with each refresh request bit 3 channel check status bit 2 parity check status bit 1 speaker data status bit 0 timer 2 gate to speaker status 出典:XT, AT and PS/2 I/O port addresses...

March 3, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録

はじめに KVMを利用したナイーブで実験的なVMMを作ってみた。 ioctl で /dev/kvm を叩いて仮想マシンを作成し、その上でLinux Kernelとユーザプロセスを起動できる。 Kernelのデバイスドライバから認識できる程度の非常に簡素なシリアルコンソールのエミュレーションも実装したので、 ログインシェルから操作ができる。 一方で、ネットワーキングやディスクについては現時点ではまだサポートしていない。 最近はKVMを従来のような仮想マシンとしての使い方だけでなく、 マルチテナントなクラウド環境において分離レベルを強化するために、 Google gVisor 1 や Kata Containers 2、 Amazon Firecracker 3 をはじめとした コンテナやマイクロVMでの使い方が登場してきた。 今回作ったgokvmは標準ライブラリのみを使いGo言語で実装したもので、 全体で1,500行程度(ブログ記事作成時点)なので、 自分と同じようにKVMやLinuxのブートプロセスに興味のある方にはとっかかりとして役立つかなと思う。 コミットログを見ながら、何をどう実装したのかについて振り返ってみる。 2021/1/30 プロジェクト始動 632c6e0 最初のコミット。README.md、.gitignore、LICENSEファイルを配置しただけで、 特に特筆することはない。似たようなプロジェクト 4 5 や LWN.net の記事 6 を調べていた。 ミニマムな実装で Linux ユーザランドまでブートさせるようなものは見当たらなかった。 ざっと調べただけなので調査漏れがあるかも。 もともとはkvmtool 4 がその立ち位置だったのかもしれないが、 ちょっとコードが巨大に感じた。kvm-host.c 5 は250行程度のCのコードでkernelのブートができるが、 ユーザランドまでは到達できていないようだ。 2021/2/4 bzImage・initrdのビルドとKVMのラッパー実装 69e3ebb 動作確認用のbzImageとinitrdを make コマンドから生成できるようにした。 bzImageはLinux Kernel本体、initrd はメモリ上の一時的なファイルシステムに対応する。 Linux Kernel バージョンはプロジェクト開始時点で最新の 5.10 を使った。 make tinyconfig を実施したのち、make menuconfig を使って追加で必要なconfigを有効にした。 initrdは、Busyboxをベースとした。 Linux KernelとBusyboxの ....

February 18, 2021

SRv6のLinux Kernel実装

SRv6とは SRv6はIPv6拡張の一つでSource Routingを実現するもの。Source Routingは、データ送信者がその宛先だけでなく、経路についても指定することを意味する。 経由するノードをSID(Segment Identifier)によって識別し、そのリストをパケットヘッダに含めることで、経路を自由に制御できる。SRv6では、IPv6アドレスがSIDに対応する。 SRv6は、EITF(Internet Engineering Task Force)を中心に仕様の策定が進められている 1 。 2020年3月にはRFC8754 2 として公開された。 SRv6で使われるIPv6ヘッダのSRH(Segment Routing Header)について詳しく見ていく。 まず、Routing TypeはSegment Routingではマジックナンバー4になる。 Segment List[0] ~ Segment list[n] のエントリに、最後のセグメントから降順に経由させたいセグメント一覧を列挙していく。 次のセグメントへの番号をSegments Left、最後のSegmentの番号をLast Entryに格納する。 セキュリティ機構であるHMACなど付加情報がある場合には、TLV(Type Length Value)として追加する。 Routing headers are defined in [RFC8200]. The Segment Routing Header (SRH) has a new Routing Type (4). The SRH is defined as follows: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Header | Hdr Ext Len | Routing Type | Segments Left | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Last Entry | Flags | Tag | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Segment List[0] (128-bit IPv6 address) | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | ....

January 17, 2021

mTLS(Mutual TLS)メモ

mTLSとは mutual TLSやTLS相互認証と呼ばれているもの。 きれいにまとまっている記事 1 を読んだので、正確な言い回しができるか自信がないけれど、自分の言葉でメモしておく。 そもそもTLSとは、ネットワーク上で何らかの通信を行う際に用いられる暗号化のためのプロトコルである。 ウェブラウジング、電子メール、Voice over IPなどで利用される。 特にウェブブラウジングにおいては、アドレスバーの左側に鍵マークが表されるので馴染みがある。 TLSは、PKI(Public Key Infrastructure)とX.509証明書から構成される。 X.509は証明書のフォーマットの標準で、httpsの基幹となるTLS/SSLで採用されている。 オンラインだけでなく、オフラインでも電子署名などの用途で使われることもある。 X.509は、公開鍵といくつかのアイデンティティ(ホスト名、組織情報など)から構成され、自分自身あるいは認証局によって署名される。 デフォルトではTLSはクライアントがサーバの身元を検証するだけに使われるため、サーバがクライアントを検証するしくみはアプリケーション側で実装する必要があった。 そこで、コンシューマ向けウェブサービスよりもさらに高いセキュア要件のあるビジネス用途において、サーバ・クライアントが相互に認証できる仕組みとして mTLS が使われることになった。 実行例 単純に動作を知りたいだけなので、curl(クライアント)とNode.js(サーバ)を使って、動作を試してみる。 まず、クライアントとサーバ双方の認証局 CA を作る。 -new -x509が、自身で署名したルートCA用X.509証明書の作成リクエストに対応する。 また、-nodes (No DES)が秘密鍵にパスワードの設定しないことに対応する。 ca.crt の内容を確認すると、SubjectとIssuerが同じ値 example-ca を持っていることがわかる。 つまり、このCAは自身で署名されたということである。 CA:Trueとなっているため、他の証明書に署名することができる。 # 認証局の作成 # 成果物は ca.key と ca.crt # どちらもPEMフォーマット(base64化された秘密鍵とX.509証明書)
 $ openssl req -new -x509 -nodes -days 365 -subj '/CN=example-ca' -keyout ca.key -out ca.crt # 証明書の確認 $ openssl x509 -in ca.crt -text -noout Issuer: CN=example-ca Subject: CN=example-ca CA:TRUE 続いて、サーバの秘密鍵を作成する。 この秘密鍵に対応する証明書はCAから署名される必要があるので、Certificate Signing Request(CSR) を作成する。...

January 12, 2021

RoCE v2 メモ

RDMAをEthernet上で実現する仕組みであるRoCE v2についてマイクロソフト社内での運用 1 について調べてみた。 イントロ RDMAというとInfinibandというイメージだったが、最近はiWARP、RoCEなども候補になる。 RoCEを略さずにいうと、Remote Direct Memory Access over Converged Ethernetとなる。 Remote Direct Memory Access とは、CPUを経由せずにリモートノードの主記憶を読み書きできる仕組みである。Converged Ethernet とはロスレスなEthernetであると理解した。 RoCEは2種類のバージョン v1 と v2 がある。v1はL2ヘッダの後ろにRDMAのペイロードが置かれる構造をしている。原理的にL2サブネット間でのRDMAを想定している。一方、v2はL4ヘッダの後ろにRDMAのペイロードが置かれる。つまり、Ethernet/IP/UDP上のプロトコルなので、IPルーティングを経由したRDMAを実現できる。 そもそもなぜデータセンター内でRDMAが必要になるからというと、TCP/IPスタックで満たせない需要が出てきたからだと思う。例えば、40Gbps・8セッションの通信におけるCPUの利用率をみると、送信側で8%、受信側で12%となる。このオーバヘッドの削減がRDMA導入の狙いとなる。遅延の削減も狙いとなることが多い。エンドノードのネットワークスタックがソフトウェア実装(Linuxカーネルなど)であるため、原理的に遅延が入り込む余地(スケジューリング待ちなど)がある。また、TCP/IPはパケットドロップの発生を前提とした輻輳制御を行うので、ここも遅延の原因となる。 今回はIP CLOS ネットワーク上で RDMA を実現する。ほぼ全てのリンクが40Gで、全てのスイッチがIPルーティングを担当する。 RoCE v2は5-tupleを持つので、ECMP(Equal Cost Multipath Routing)の恩恵を受けられる。 ロスレスネットワークでは、通信経路中のスイッチで、バッファオーバーフローなどによるパケロスが起きてはならない。 そこで、PFC(Priority-based Flow Control)を使って、流量を制御する。 PFCは、リンク間のプロトコルで、スイッチのバッファ利用率がある閾値を超えると、リンク相手のスイッチに対して pause frame を送出する。 pause frame はパケットの送出停止を依頼する意味を持つ。 キューごとに Priority を割り当てられるので、キューの単位でpause frameを送ることができる。 ただ、pause frame を送ってから、実際の送信が停止されるまでには、時間差があるので、余裕を持たせておく。 マイクルソフト社内では、スイッチがShallow Bufferなので、2つのPrirorityのみを使っている。 片方が遅延を重視するリアルタイムトラフィック用途、もう一方が帯域を重視するバルクデータトラフィック用途に使われる。 PFCはリンク間のプロトコルなので、RDMAのエンドノードに到達するまでに何度かスイッチを伝播する。 そこで、エンド間のプロトコルであるDCQDN(Data Center Quantized Congestion Notification)と組み合わせて使っている。 PFCプロトコルには、Priorityをどのヘッダに設定するかによって、2種類の仕様が存在する。もともとはVLANタグの中にPriorityを設定するVLAN-basedな仕様だった。ただ、IPルーティングを前提とするIP CLOSネットワークや、PXEブートとの相性が良くなかった。そこで、IPヘッダのDSCPにPriorityを設定するDSCP-basedな仕様が出てきた。マイクロソフト社内では、単純にPFC PriorityをDSCPの値として割り当てている。現在では主要なスイッチベンダがDSCP-basedをサポートしている。 出典:RDMA over Commodity Ethernet at Scale...

November 25, 2020