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

QEMU/KVM on WSL2 のログ

Windows10 WSL2上のゲストから/dev/kvmを経由して、その上にさらにネストさせる形で仮想マシンを動かすことができた。 環境 Windows 10 Pro Insider Program (Devチャネル、OSビルド 20246.1) 1 WSL2上のゲスト Ubuntu 20.04.1 LTS (Focal Fossa) Linux 4.19.128-microsoft-standard カーネルパラメータ initrd=\initrd.img panic=-1 nr_cpus=4 swiotlb=force pty.legacy_count=0 QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.7) Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz 手順 WSL2(Windows Subsystem for Linux 2)全体の設定 C:\Users\ユーザ名\.wslconfig に以下の設定を追加することで、 仮想化支援のネストを許可する。 [wsl2] nestedVirtualization=true 参考にしたブログ記事 2 では、WSL2上のゲストカーネルを再ビルドするような手順が紹介されているが、今回は必要なかった。 単純に nestedVirtualization=true とするだけで対応できた。 動作確認 Part.1 QEMU/KVM上でcirrosイメージを動かしてみる。 # non-root ユーザからも KVM の利用を許可 $ sudo chmod a+rw /dev/kvm $ kvm-ok INFO: /dev/kvm exists KVM acceleration can be used # cloud-init を無効化した cirros イメージを取得 $ wget https://github....

November 4, 2020

vDPA(Virtio Data Path Acceleration)メモ

仮想マシンやコンテナ環境で、高パフォーマンス(NICワイヤレート)かつ柔軟なIOを実現する方法。 まだあんまり日本語の情報は見つからない。 触ったわけではないので、勘違いなどあるかも。 vDPAカーネルフレームワーク 2020年3月に、vDPA カーネルフレームワークがLinux 5.7にマージされた。 vDPAカーネルフレームワークが扱うvDPA デバイスとは、 データプレーンがvirtio仕様、コントロールプレーンがベンダ仕様であるデバイスを指す。 ゲスト上のvirtio-netデバイスから見ると、データプレーンがホストをバイパスして物理NICに直接アクセスし、 コントロールプレーンがホストのvDPAフレームワーク(とベンダ依存のドライバ)を経由する、と捉えることができる。 かつてvDPAカーネルフレームワークは、mdevベースで作られた。 mdevはVFIOパススルー時にコントロールプレーンを仲介するもので、 ベンダ依存のコマンドとエミュレートされたPCIデバイス間の変換を担当する。 ただ以下の理由でmdevベースのアプローチを辞め、VFIOから独立した新しいサブシステムとしてvDPAカーネルフレームワークを設計した。 VFIOとmdevは、vDPAと比べるとレイヤの低い部分で抽象化を行っているため、レイヤの高いAPIをVFIOに取り入れるのは自然ではない 世の中のNICがすべてVFIO IOMMUの設計に適しているわけではない vDPAカーネルフレームワークはvhostキャラクタデバイスを提供するので、 QEMUのようなユーザスペースのドライバからvhostデバイスとして扱える。 独立したサブシステムなので、新たなハードウェアの機能に追従できる。 例えば、ライブマイグレーションのために、vhost API経由でデバイスの状態を保存・復元することができる。 また、SVA(Shared Virtual Address)やPASID(Process Address Space ID)もサポートする。 QEMUのようなユーザスペースでホスト・ゲストを仲介するレイヤが必要になるが、 SR-IOVによって単純にパススルーする構成と比べると、攻撃の対象領域を小さく保てる。 Intel Scalable IOVやSub Function(SF)とも相性が良い。 vDPA DPDK フレームワーク vDPAのもう一つのフレームワークとして、ホストのDPDKを使った vDPA DPDK framework も存在する。 QEMUから見ると、単純に vhost-user バックエンドと接続するだけで良い。 ほとんどのメジャーなNICに対して、このvDPA DPDK driverが存在している。 ただvDPA DPDK フレームワークには以下の制約があった。 vhost-userはユーザスペースのAPIなので、ホストのカーネルサブシステムを操作することができない。例えば、eBPFのような機能と協調して動かすことができない。 DPDKはデータプレーンに注力しているため、ハードウェアの操作するためのツールを提供していない。 これらの制約を取り除くために、vDPAカーネルフレームワークが必要だった。 How deep does the vDPA rabbit hole go?, redhat.com にさらに突っ込んだ内容が書かれている。 既存のアプローチとの比較 vDPAと既存のアプローチとの比較をしてみる。 項目 vhost-net vhost-user virtio full HW offload vDPA パフォーマンス 低 中 高 高 NIC側のデータプレーンサポート No No Yes Yes NIC側のコントロールプレーンサポート No No Yes No ライブマイグレーション Yes Yes No Yes 成熟度 高 高 高 中? 出典:Achieving network wirespeed in an open standard manner: introducing vDPA, redhat....

October 27, 2020

実践Rust入門 読書メモ

第1章 Rustの特徴 Firefoxの開発元であるMozillaが中心となって開発された。 不正なメモリ領域を指すポインタを許容しない、など安全性を重視している。 また、ガベージコレクションのような複雑なランタイムを持たない。 Stack Overflowのアンケートでは、「最も愛されている言語」で2016年から2018年まで3年連続1位となった。 コンパイラ・バックエンドとしては、LLVMが採用されている。 x86系Linuxを含むいくつかの環境では、外部ライブラリとの静的リンクもサポートしている。 導入事例についても見ていく。 Dropboxの分散ストレージシステム Magic Pocket では、 最初のバージョンが Go で書かれ、そのあと Rust で書き直された。 国内でもドワンゴが分散オブジェクトストレージ Frugalos を開発で採用した。 JavaScriptパッケージレジストリである npm のユーザ認証部分もRustで書かれている。 AWS は、サーバレスコンピューティングのためのセキュアな仮想マシン Firecracker を開発した。 第2章 はじめてのRustプログラム Rustツールチェインは、Rustコンパイラ rustc、パッケージマネージャ cargo、 標準ライブラリ std からなる。 rustup を使うと stable版やnightly版といった複数のツールチェインを管理できる。 また、パッケージのトップディレクトリにrust-toolchainというファイルを作り、 nightlyと書き込んでおくと、そのパッケージではnightlyを利用できる。 nightly版には実験的な機能が含まれていて、毎晩リリースされる。 .rustupディレクトリはrustupによって管理され、.cargoはcargoによって管理される。 前者にはツールチェインの本体、後者にはrustc、cargo、rustupコマンドが配置される。 source ~/.cargo/envして使う。 cargo new --bin helloで新規パッケージを作成する。 println!のように最後に!がついているものはマクロなので、初期段階で評価・展開される。 x.func()は、メソッド呼び出し構文でありfunc(&x)と解釈される。 fn hoge<F>(..) where F: トレイト境界では、Fがwhere句で示されるトレイト境界を満たす型であれば、 どれでも指定できる。 デバッグには、直接GDBを使うよりもrust-lldbやrust-gdbを使うのが便利。 互換性のないエディション(2015エディションと2018エディションの2種類)があり、クレート単位でCargo.tomlで指定できる。 muslターゲットを選択すると、外部ライブラリと静的リンクしたバイナリを作成できる。 第3章 クイックツアー 関数・変数・定数など識別子はスネークケース、型パラメータは英大文字1文字にするのが良い。 自動フォーマッタrustfmtがあるので、それを使うと便利。 エラーメッセージ対してrustc --explain 308で原因・対処法を調べることができる。 implブロックに、メソッドを実装していく。 クロージャは文脈によってFn、FnMut、FnOnceトレイトが自動的に実装される。 PartialEqトレイトやDebugトレイトは#[derive(Debug, PartialEq)]アトリビュートによって自動導出できる。...

October 19, 2020

VMware徹底入門 読書メモ

雑多なメモ vSpehre環境のインターフェイス GUI:vSphere Client、vSphere Web Client CLI:vSphere CLI、VMware vSphere Power CLI DCUI(Direct Console User Interface):ESXiのコンソール 仮想ディスクは .vmdk という単一のファイルとして作成され、データストアに格納される。 VLAN VST 構成の場合、ポートグループ = VLAN の構成が一般的で推奨されている。 vSphere DRSは、ESXi間の負荷を平準化する目的で、vMotionを自動的に実行すること。 vMotionはいわゆるライブマイグレーションのこと。 Cross vSwitch vMotion:仮想スイッチが異なっているホスト間 Cross vCenter vMotion:vCenterが異なっているホスト間 Long Distance vMotion:最大150msのネットワーク遅延を許容 テンプレートやクローンによって仮想マシンを複製する際、ホスト名・IPアドレス・ライセンス情報などユニークな設定項目については「カスタマイズ仕様」として扱える vSphere 5.5移行ではESXiホスト上でパケットキャプチャやトレースを行うためのCLI pktcap-uw が追加された。物理NIC、仮想ポート、仮想スイッチ、仮想分散スイッチのパケットをキャプチャできる 仮想マシンを構成するファイル VMXファイル:詳細な構成情報・ハードウェア情報 VSWPファイル:仮想マシンのスワップアウト先 VMDKファイル(-flat.VMDKファイル):仮想ディスクの内容が保存される VMSDファイル:スナップショットとメタデータ vCenter配下のESXiホストをロックダウンモードに設定すると、直接ログインできなくなる ESXiホストはデータセンター内に配置される VMDKの格納 VMFS:複数のESXiホストからアクセス可能なクラスタファイルシステムでFC、iSCSI上に作成される Lazy Zeroed(シックプロビジョニング):VMDK作成時に領域を確保。ゼロ初期化なし。 Eager Zeroed(シックプロビジョニング):VMDK作成時に領域を確保。ゼロ初期化あり。 シンプロビジョニング:必要時に領域を拡張する。 NFS シンプロビジョニング:NFSサーバ側で管理するため。 VLANの扱い EST(External Switch Tagging) ESXiホストの物理NIC をポートVLAN(アクセスポート)として扱う 標準仮想スイッチ(VLAN IDの指定):0または空白 分散仮想スイッチ(VLANタイプの指定):なし VST(Virtual Switch Tagging) 物理スイッチでタグVLANを設定して、仮想スイッチのポートグループにVLANを割り振る 標準仮想スイッチ:1~4094 分散仮想スイッチ:VLANを選択してIDを入力 VGT(Virtual Guest Tagging) 物理スイッチでタグVLANを設定する、VLANタグがついたまま仮想マシンにフレームを転送する 標準仮想スイッチ:4095 または ALL 分散仮想スイッチ:VLANトランクを選択して、範囲を入力 参考 VMware徹底入門 第4版

October 15, 2020

XDPメモ(アーキテクチャ、性能、ユースケース)

はじめに The eXpress data path: fast programmable packet processing in the operating system kernel 1 を読んだ。 この文章はほとんどこの論文をもとに書いたが、一部ニュース記事を引用している。 eBPF/XDPが流行っているということは、BCC、bpftrace、Facebook Katran、Cloudflare Gatebot などeBPF/XDPを使うプロジェクトのGithub Star数から感じ取れる。 eBPF/XDPには、特殊なハードウェア・ソフトウェアに依存せず、 カーネルの仕掛けとして高速パケット処理を実現できるという強力なメリットがある。 一方であまり弱点を主張するような記事は見当たらないので、実際のところどうなのか感触を知りたい。 XDPを使うとNICデバイスドライバのコンテキストで、eBPF Verifilerの制約はありつつも、 比較的自由にパケット処理を実現できる。 また、成熟したLinuxのネットワークスタックと共存しつつ、1コアで24Mppsという高速なパケット処理を実現できる。 XDPプログラムは、eBPFの制約のもとC言語で記述することができ、clangでELFバイナリにコンパイルする。 XDPの競合としてカーネルバイパスなDPDKがある。両者の特徴は以下のとおり。 DPDK: カーネルバイパスによってコンテキストスイッチを避け高速化を図る コアを専有する(特定のコアでCPU 100%に張り付かせてポーリング) スループットとレイテンシどちらの観点で見てもDPDKのほうが優れている ネットワークスタックを再実装する必要がある XDP: 専用コアが不要(電力面などで有利) 容易にロード・アンロードできる 名前空間などカーネルの機能に強く依存するコンテナ環境の普及で、XDPの重要度が増しているように感じる やはりカーネルネットワークスタックと共存できるというメリットが強い アーキテクチャ パケット着信のたびに、デバイスドライバのフックポイントでXDPプログラムが実行される。 このフックポイントは、デバイスドライバの処理の中でも初期に位置する(sk_buff の割り当て前)。 XDPプログラムは、ヘッダのパース、eBPFマップの読み書き、ヘルパ関数(FIBのルックアップなど)の呼び出しを経て、 最終的にパケットをどこに送り出すか決定する。 送り先については、XDPプログラムの終了コードで制御することができ、 ドロップさせる、同インターフェイスに送り返す、他インターフェイスに転送する、AF_XDPとしてユーザプログラムに送る、 通常のネットワークスタックに送る、から選択する。 2つ以上のXDPプログラムを同インターフェイスに紐付けたい場合には tail call として処理を引き渡すことができる。 外部のシステムとデータのやり取りをするために、eBPF Mapが用意されている。 eBPF Verifilerが厳しくチェックしているので、必ずeBPF Mapを使うことになりそうだ。 出典:The eXpress data path: fast programmable packet processing in the operating system kernel...

September 17, 2020

Educational Codeforces 95 B - Negative Prefixes

提出コード 2つの要素$a_l$と$a_r$をSwapしたとき、$a_r > a_l$であれば、 ある範囲ではPrefixSumが増加し、それ以外では変化しないということになる。 つまり、単純に固定されていない要素を降順に並べ替えればよい。 $0 \sim l-1$の範囲ではPrefixSumの変化なし $l \sim r-1$の範囲ではPrefixSumが$+(a_r-a_l)$増加する $r \sim n-1$の範囲ではPrefixSumの変化なし

September 16, 2020

ABC 177 F - I hate Shortest Path Problem

提出コード 縦方向、横方向にそれぞれどれだけ移動する必要があるか。 縦方向 単純に下に行くだけなので$k$回の移動となる。 横方向 $a_x$を、マス$x$まで移動したときに元々どこからきたのかという情報とする。 つまり、1段目の$a_x$からスタートして、$k$段目の$x$まで移動したということになる。 各$k$ごとに、移動できない領域のいずれかのマスからスタートして、$b_i+1$に移動すると考えればよい。 答えは、$\underset{x}{min} (x-a_x) + k$となる。 以下は、入力例1における$a_x$の遷移を図示したもの。

September 1, 2020

ABC 175 F - Making Palindrome

提出コード 解説放送を見て道筋は理解できたが、そこからが長かった。 比較的実装が重い問題だと思う。 答えの回文LRを文字列Lと文字列Rに分けて考える。どちらも空文字列で初期化しておく。 $s_i$を追加するときは、Lの右側に追加するか、あるいはRの左側に追加する。 何度か$s_i$を追加して、L="abcde"、R="cba" となったとする。 このとき部分文字列 abc はすでに回文の条件を満たすのでL="abc"、R=""と見なしてもよいということになる。 なので、LとRの組 (L, R) を頂点として、ダイクストラ法を適用していけばよい。 LやRとして現れるのは$s_i$のPrefixとSuffixのみなので、今回の制約では十分間に合う。 頂点 (L, R) からの遷移については、すべての$s_i$について、$s_i$をLあるいはRに追加したときに部分的に回文の条件を満たすかどうかをチェックする。条件を満たす場合には、次の頂点に遷移できる。

August 28, 2020

ABC 176 F - Brave CHAIN

提出コード 赤diffだった。問題文を読むと手を付けれそうな気がしたけど、そんなに簡単じゃなかった。 まず、$O(N^3)$のDPを考えて、それをもとに計算量を削減していくというアプローチを取る。 $i = 2, 5, 8, 11, \dots$ のように3文字おきに見ていく。 dp[i][x][y]を$i$番目まで見たとき、直前の2つの文字が$x$、$y$であるときの最大スコアとする。 a = v[i], b = v[i+1], c = v[i+2] として、パターン分けしていく。 スコア +1となる場合。どの3つでスコアを増やすか考える。 A) $a = b = c$ の場合、すべての$(x, y)$の組について dp[i+3][x][y] <= dp[x][y] となるので、代わりに答えに+1しておく。$O(1)$ B) $a = b = x$の場合、すべての$y$について dp[i+3][c][y] <= max(dp[i][a][*]) + 1 とする。$O(N)$ C) $a = x = y$の場合、dp[i+3][b][c] <= dp[i][a][a] + 1とする。$O(N)$ スコア +1とならない場合。どの2つを残すか考える。 D) $x$、$y$ を残す場合、dp[i+3][x][y] <= dp[i][x][y]なので何もしない。$O(1)$ E) $x$、$a$ を残す場合、すべての$x$について dp[i+3][x][a] <= dp[i][x][*]とする。$O(N)$...

August 26, 2020

Codeforces #663 Div.2 D.505

提出コード もしも $n \ge 4$ かつ $m \ge 4$ の場合には、どうやっても条件A(縦横の長さがともに偶数の正方形内に含まれる1の数が奇数)を満たすことができない。 例えば、$2 \times 2$の正方形が条件Aを満たすとき、その正方形を4つ連結すると、 その和は偶数になってしまう。つまり、$4 \times 4$の正方形では条件Aを満たすことができない。 それ以外の場合には、何度か操作することで必ず条件Aを満たすことができる。 最小の操作回数はDPを使って解くことができる。 $dp_{i,bit}$を、$i$番目の行まで見たときに、$i$番目のビット列が$bit$であるときの 最小の操作回数とする。 更新式は以下の通り。ただし、$bit$と$nbit$と間で条件Aを満たすことを確認する。 $$ \begin{aligned} dp_{i+1,nbit} \Leftarrow dp_{i, bit} + \sum_{j=0}^{m-1} ((nbit » j) \& 1) \oplus a_{i+1,j} \end{aligned} $$

August 22, 2020

virtio-net の概要とアーキテクチャ

十分に枯れていてあまりトラブルになることは無いけれど、今後も使い続けられる技術なので、 その挙動を知っておくことに意味はあると思う。 NICの完全仮想化・準仮想化 おさらい。完全仮想化は、ゲスト自身が仮想環境で動作していることを認識できないような仮想化のこと。 ホストは、ゲストが利用するデバイスの挙動をすべて模倣する必要がある。 デバイスの模倣のために、大量のトラップが発生し、CPUがホスト側の処理に使われてしまう。 また、デバイスのエミュレーションをホスト側ユーザプロセスで行う場合には、 スケジューリング待ちによる遅延も発生してしまう。 一方、準仮想化では、ゲスト自身が仮想環境で動作していることを認識した上で、 ホストと協力してパフォーマンスを向上の図る。 virtio は準仮想化専用のデバイスドライバによって、仮想的なデバイスを操作する。 アーキテクチャ フロントエンドドライバとバックエンドドライバを、vringが繋ぐ構成になっている。 フロントエンドドライバ ゲスト側で動作する。ゲストOSが発行したIOをvringを経由して、バックエンドドライバへ送る。 バックエンドドライバ ホスト側で動作する。vring経由で受け取ったIOを、物理デバイスへ送る。 https://www.cs.cmu.edu/~412/lectures/Virtio_2015-10-14.pdf Virtio 仕様 SAMLやebXMLなどを標準化している団体 OASIS が Virtual I/O Device (VIRTIO) Version 1.1 として仕様をまとめている。 コールバックや各種データ構造の仕様を決めている。 Virtqueue 実際のIOを担当する部分で、ゲスト物理メモリの一部をホスト側と共有することで、 双方向での読み書きを実現できる。 また、双方向に通知を送るための仕組みも持っており、ホスト側へはMMIO経由で、 ゲスト側へは割り込みを使って通知を送る。 disable_cdやenable_cbといった関数で、割り込み抑制をもう一方へ伝えることができる。 例えば、ゲスト側ドライバで一定期間の間は割り込みが不要な場合には、 その旨を disable_cd でホスト側へ伝える。 通知を送る際には kick を呼ぶ。 struct virtqueue_ops { int (*add_buf)(struct virtqueue *vq, struct scatterlist sg[], unsigned int out_num, unsigned int in_num, void *data); void (*kick)(struct virtqueue *vq); void *(*get_buf)(struct virtqueue *vq, unsigned int *len); void (*disable_cb)(struct virtqueue *vq); bool (*enable_cb)(struct virtqueue *vq); }; https://elixir....

August 21, 2020