OVSでSRv6を使ってみる
OVS(Open vSwitch) 3.2でSRv6がサポートされた 1 ので使ってみる。 その使い方を端的にいうと以下のように type=srv6 としたポートを作成すれば良い。 VXLANやGeneveなど既存のトンネリングと同様のフレームワークを使って実装されているので、 options:remote_ipやoptions:local_ip でトンネルの両端(SRv6では双方のSIDに相当)を指定する。 それらに加えてSRv6では中継するルータをSegment Listとして設定できるよう特別なオプション options:srv6_segs が存在する。 ちなみにInner PacketとしてはIPv4、IPv6両方をサポートしている。 ovs-vsctl add-br br0 ovs-vsctl add-port br0 srv6_0 -- \ set int srv6_0 type=srv6 \ options:remote_ip=fc00:100::1 \ options:srv6_segs="fc00:100::1,fc00:200::1,fc00:300::1" OVSでは主にkernelspaceとuserspaceの2種類のデータパスが存在しているが、 SRv6はuserspaceのみ対応している 2。 つまりDPDKやAFXDP 3 のような仕組みを使ってデプロイする必要がある。 Feature Linux upstream Linux OVS tree Userspace GRE 3.11 1.0 2.4 VXLAN 3.12 1.10 2.4 Geneve 3.18 2.4 2.4 … … … … SRv6 NO NO 3.2 さてどうやって動かすのか。 mininetのスクリプト 4 をベースとしてそれをシンプルなコマンド列に落とし込んだ。 以下のような構成で動かしてみる。...
Ansible実践ガイド [基礎編] 第4版 読書メモ
どうやら2016年にもAnsibleを勉強していたようだが 1 、 すっかり忘れてしまったので改めて Ansible実践ガイド 第4版[基礎編] を読んだ。 気になったところをメモしていく。 Playbookの階層 1つのPlaybookはいくつかのPlayから構成される。 PlayはTargets、Vars、Tasks、Handlersの4つのセクションから構成される。 TargetsとTasksは基礎的なセクションで、 それぞれ対象のホストと実行するタスクの一覧を指定するために使われる。 VarsとHandlersは補助的なセクションで、それぞれ変数と実行制御 (例えばsystemdサービスのリスタート)を指定するために使われる。 Playbook Play: Targets Vars Tasks Handlers Play: Targets Vars Tasks Handlers テスト 単純なテストはansible.builtin.assertモジュールでカバーできる。 複雑なテストのためには Ansible Molecule 2 を使う。 テスト環境構築、文法チェック、冪等性のチェックなどの仕組みを持っている。 あるいは Ansible Spec 3 を使うと、RubyのRSpecを利用した Serverspecによってテストできる。 Ansible Galaxy ansible-galaxy install geerlingguy.mysql のようにしてロールをインストールできる。 ~/.ansible/roles あるいは /usr、/etc 配下に配置される。 --roles-path で指定することもできる。 Collectionは ~/.ansible/collections に配置される。 requirements.txt でその一覧を管理することができる。 チューニング ansible.cfgまたはANSIBLE_CACHE_PLUGINでファクトキャッシュを有効化しておくと良い。 forksを増やしておくと、 ローカルノードのリソースやネットワークの負荷とトレードオフになるが、 並列数を増やせる。 Ansibleは並列実行するとき全てのホストで足並みを揃えてタスクを1つずつ実行している。 これはストラテジプラグインで制御できる。 例えば ansible.builtin.free とするとホストごとに独立してタスクを進められる。 もちろんホスト間の依存には注意する。 SSHの多重接続(ControlMaster、ControlPath、ControlPersist)を有効化しておくと、 タスクごとにSSHを確立することによるオーバーヘッドを削減できる。 ちなみにOpenSSH6....
つくって学ぶkubebuilder 読書メモ
つくって学ぶkubebuilder を読んだ。 簡潔によく纏まっている資料で勉強になった。 読んだ感想としてCustom Resource(CR)は自由度が高いので作りたくなるけど、 迂闊にその数を増やすと管理しきれなくなると思う。 おそらく本質的にCRでないと実現できない課題に対してのみ採用して、 かつミニマムに作ったり使ったりするのが良いんだろうと感じた。 例えばここで題材としたMarkdownViewに関しては、 現実にはCRじゃなく標準リソースの組み合わせで実現した方が良いんだろう。 もちろん題材としてはとっつきやすく良かった。 性能面はどうだろうか。 Reconcileループは1ループを軽量にしないと収束時間の予測が難しく運用しづらそう。 リトライはその状態を次のループにわたすなど、おそらく実装上のテクニックがいくつかあるんだろう。 標準のコントローラ、etcd、kube-apiserverへ与える性能影響も気になる。 Kubebuilerに相当するフレームワークは他にあるんだろうか? Kubebuilderが標準でメジャーなのかな。 ざっくり調べたところKubernetes Way(client-goとcode-generator)とOperator SDKがありそうだ。 ここからメモ。私は Kubebuilder を今回初めて触るので誤解も含まれているはず。 序章 KubebuilderはCustom ControllerやOperatorを開発するためのフレームワークで、 controller-toolsとcontroller-runtimeが含まれる。 Custom Resource(CR)のコントローラをCustom Controllerと呼ぶ。 CRの仕様はCRD(Custom Resource Definition)で定義され、これはGoの構造体から自動生成される。 Kubernetesのリソースは宣言的、冪等、レベルドリブントリガーといった特徴があるので、 それに則った形で実装する。 MarkdownViewというCustom Resource/Controllerを題材にして手を動かしながら学べる。 ここ にコードが公開されているのであわせて読むと良い。 MarkdownView Custom Resourceを使うと以下を実現できる。 ConfigMapにMarkdownを1ファイルずつ保存する MarkdownのレンダラーとしてmdBookイメージを使ったDeploymentを作成する コンテナイメージとレプリカ数をCustom Resourceで指定する mdBookのDeploymentに外部からHTTPでアクセスできるようSerivceを作成する kubebuilder init/edit サブコマンド まずは空のディレクトリ上でinitサブコマンドを発行してプロジェクトの雛形を作る。 途中で変更する場合にはeditサブコマンドを使う。 重要なオプションは以下の2つ。 --domainでCRDのグループ名を指定する --repoにGoモジュール名を指定 makeコマンドを頻繁に使うのでmake helpでmakeターゲット一覧を確認しておくと良い。 ファイル中の//+kubebuilderは重要なマーカーなので削除しないよう注意する。 cmd/main.goがCustom Controllerのエントリポイントとなる。 config/配下にマニフェストが集約されている。 これらマニフェストは kustomization.yaml でまとめて管理されている。 create サブコマンド createサブコマンドで新たなAPIやWebhookを追加することができる。 例えばAPIの場合には、 create apiサブコマンドでCustom ResourceやCustom Controllerの雛形を生成できる。...
MininetでSRv6 L3VPNを動かす
Mininet 1 の中でSRv6 L3VPNを動かす実験をやってみた。 スクリプトはこちら。 ひとまず図のような小さな構成で動かすことができたのでここで紹介したい。 2台のルータ(r1とr2)がSRv6によるEncap/Decapを担当しており、 r1-r2間でL3VPNに関する情報をeBGPで交換する。 r1とr2はそれぞれ2つのVRF(vrf10とvrf20)を持っており、 VRFごとにテナントが分けられている(Tenant10とTenant20)。 もちろんテナントごとにL3の疎通性はなくIPレンジの重複は許されているので、 ここでは同じPrefixを割り当てている。 r1とr2ではBGPデーモンとしてFRRを使っている。設定の一部を抜粋するとこんな感じ。 FRRの設定ファイル(frr.conf)全体についてはmininetlab 2 に記載している。 FRR本体のテストコード 3 が非常に簡潔で綺麗にまとまっているので、それを参考にした。 FRRではSRv6 L3VPNの開発は活発に行われているので、できれば新しいバージョンをおすすめする。 ここではFRR 8.5を使った。 mininet> r1 vtysh -c "show running-config" # 一部抜粋 router bgp 65001 bgp router-id 203.0.113.1 bgp default ipv4-vpn bgp default ipv6-unicast bgp bestpath as-path multipath-relax no bgp network import-check neighbor r1-eth0 interface remote-as external ! segment-routing srv6 locator default exit exit ! router bgp 65001 vrf vrf10 bgp router-id 203....
TRexのラッパーを作ってみた
まずはTRexの紹介から。 TRex 1 はソフトウェア実装のトラフィックジェネレータで、 Stateful/Stateless の2モードをサポートしている。 Statelessは状態を持たない対象DUT(Device Under Test)宛に パケット列を生成するためのモードで、 スイッチングやルーティングの性能計測ツールとして利用できる。 TRexは多機能ではあるが、個人的なニーズとしては単純なTCP/IPのパケット列を そのサイズを変えながら生成したいことが多いのでTRexのラッパーとしてautotrex 2 を作ってみた。 autotrexは自動的にベンチマークの実行・集計をするためのもので、 例えば成果物として下記のような図を簡単に生成できる。 ここでは簡単に使い方を記録しておく。 autotrex では trex_cfg.yaml (TRexの世界でよく使われる設定ファイル)に記載した2つのポートのうち、 1つ目のポートが送信、2つ目のポートが受信を担当する。 送信・受信それぞれのパケット数をカウントし、その誤差がある閾値 (例えば0.01%)以下であるような最大のパケットレート (Packet Per Second、PPS)を二分探索で求める。 もちろん生成したいパケット列は自分で下記のように自由に設定できる。 私がよく使うものはautotrexリポジトリ同梱されていて 3、 簡単に使えるようになっている。 # tcp_1pkt.py から抜粋 pkt=Ether()/IP(src="16.0.0.1", dst="48.0.0.1") / TCP(dport=12, sport=1025)/(payload_size*'x') ./run.sh tcp_1pkt.py のようにパケット列を記載したPythonファイル tcp_1pkt.py を 引数に与えて run.sh コマンドを発行すると、自動的にサイズを 変えながらパケット生成が行われる。 一連のベンチマーク実行が完了すると、成果物としてパケットレート、L1・L2のスループットを表すcsvファイルとpngファイルが出力される。 どのようなパケットが生成されるかについては./simulate.sh tcp_1pkt.pyコマンドで 事前に知ることができる。 独自にパケットを生成したい場合には便利かもしれない。 性能が欲しい環境では下記のように trex_cfg.yaml でCPU情報を設定する。 経験的にmaster_thread_id、latency_thread_id、threads にはユニークなCPUを 割り当てた方が高い性能を得られる。詳しくはドキュメント 4 に説明がある。 platform: master_thread_id: 0 latency_thread_id: 5 dual_if: - socket: 0 threads: [1, 2, 3, 4] 以上、個人的に使っているツールの紹介。おしまい。...
プログラマーのためのCPU入門 読書メモ
非常に分かりやすく要点が整理されていて良い本だった。 特にアセンブリで書かれた実験用サンプルコード 1 があったのが嬉しかった。 概念として知ってはいても動かしてみると腑に落ちる感覚がある。 ここでは自分に宛てて雑多なメモを残しておく。 アセンブリ記法 AT&T記法よりもIntel記法の方がわかりやすい。これは自分も感じる。 Intel記法はdestination が左側にくる記法。 gdb向けに~/.gdbinit内でset disassembly-flavor intel とやっとくと良いかもね。 命令実行にかかるサイクル数の計測 add、mul、mov命令にかかるサイクル数を統計的に計測できる 2 。 大量に同一の命令を実行することで統計的に値を出していく。 さらにそれらの命令間で真のデータ依存を持たせることで、 スーパースカラやスーパーパイプラインの影響を排除し1命令ずつ実行していく。 分岐予測 分岐命令のメモリアドレス → ジャンプ先のメモリアドレスをテーブルにキャッシュしておく。 そして再度同じ分岐命令が発行された時にジャンプ先を予測するのが基礎的なアイデア。 関数からの復帰の場合にはreturn address専用のテーブルにキャッシュしておく。 条件を伴う分岐の場合には”100010”のようなビットマップの構造で過去数回分の結果を保持しておいてそれとマッチさせることで予測する。 あるいは対象の命令とメモリアドレスの観点で近くにある他の条件分岐の結果も考慮に入れる場合もある。 精度は95%程度、普通のCPUキャッシュヒット率は97〜%くらいなのでちょっと低め。 投機実行 条件分岐予測によってアウトオブオーダー実行することを投機実行と呼ぶ。 まあ本質的にはこれがアウトオブオーダー実行の一番の目的になる。 つまり、基本ブロック(条件分岐などで区切られた命令列)の領域を超えた命令のアウトオブオーダー実行できるため。 キャッシュコヒーレンシ SMPにおけるキャッシュコヒーレンシはMSIプロトコルとその派生によって実現されている。 アイデアとしてはCPU0がメモリに書き込んだとき、同一アドレスを持つキャッシュライン(64バイトの集まり)が CPU0以外に登場すればそこにinvalidateフラグを立てるというもの。 つまりCPU0以外のCPUがそのメモリアドレスにアクセスする場合には必ずキャッシュミスが起こり、 それに伴い最新の値を主記憶から取得できるようにしている。 Memory consistency 特にマルチコアでメモリアクセスがある時に、メモリアクセスの順番が入れ替わることで意図しない結果を引き起こすので、それの対策としてMemory Consistencyを考えないといけない。 Memory consistencyとは、要するにアウトオブオーダー実行に制約を加えること。 とはいえインオーダー実装だとしてもメモリの仕組み(複数バンクとか?)によっては順序の入れ替えが発生しうるので注意。 x86だとTSOモデル?を元にしている。 x86ではstoreやload命令のメモリアクセス順は普通入れ替わらないが、異なるメモリ属性領域にある場合には入れ替えが起こる可能性がある。 x86 SSEではlfenceやsfence命令が入った。 Linuxのbarrier()マクロ、GCCのmemory clobber、C言語のvolatile修飾子はここでいうメモリ順序を保証しないことに注意する。 これは複雑な領域なので、プログラミング言語の提供するチャネルのような抽象化されたものを使った方が良い。 ordering_unexpected.S 3 でメモリの前段にあるストアバッファに起因するメモリアクセス順の入れ替わりを再現できる。 アトミック操作 キャッシュコヒーレンシプロトコルのMESIにおけるE(排他状態)を使うと実現できる。 LL/SC命令で囲った領域もアトミックな操作ができる。 実際にはSC命令の時にLL命令で読み出したメモリアドレスに書き込みがあれば失敗・なければ成功となる。 なので、LL/SC命令のブロックを成功するまで繰り返せば良い。 もちろん性能を確実に引き出すのは難しい。 単一のプロセッサであっても時分割されるとアトミック操作は必要。 現代的なCPU 現代的なCPUとしてRISC-VアーキテクチャのBOOM(The Berkeley Out-of-Order RISC-V Processor) 4 がある。...
Intel NUC 12 Pro で仮想環境をつくる
開発機として Intel NUC 12 Pro 1 を買って Proxmox VE 2 を入れてみた。 これまでは古い Thinkpad を開発機としていたので、快適になったかなと思う。 スペックはこんな感じ。 メモリとディスクは重視したいところだったので多めにした。 一方でコア数はそこまで拘らないのでi3のモデルとした。 vPro 搭載モデルも検討したが、流通量が少なそうなこと、仮想化すればリモートでの管理操作の頻度は減るだろうということ、から見送った。 NUC:Intel NUC12WSHi3(Core i3-1220P) 電源コード:サンワサプライ KB-DM3S-1 メモリ:Team SO-DIMM DDR4 3200MHz PC4-25600 32GBx2 ストレージ:Crucial CT2000P3PSSSD8JP 2TB M.2 PCIe4.0 元々はESXi 8を使おうかと思っていたが、 “Shutting down firmware services” のエラーに苦しみ結局解決できず諦めることにした。 ちなみにPコア・Eコアが混在することによる問題もいくつか報告されているが、 それに対する知見はそれなりに広まっていて 3 4 私の環境でも同じように解決できた。 ESXi 7についてはいくつか 12 世代 Intel NUC での動作事例が見つかったが、コミュニティドライバ?が必要なので面倒に感じてしまい諦めた。 残りの候補として cockpit-machines と Proxmox VE を考えていた。 この中でもできるだけ手軽に使いたかったので Proxmox VE を選択した。 Proxmox VEのインストール手順については特筆するものはなく、 ダイアログにしたがってやっていけば自然にインストールできた。 まだまだ触れていないが、第一印象はなかなか快適で良さそうだ。 Intel NUC12WSHi3 ↩︎...
LUA4-U3-AGTE-NBK ドライバのインストール
特権コンテナで遊んでいたら /lib/modules 配下を壊してしまったようだ。 よくわからないがこのマシンから外部に出ていくときに、疎通したりしなかったりする。 イーサネットをUSBタイプAとして受けるアダプタ BUFFALO LUA4-U3-AGTE-NBK 1 を使っていて、 それに対応するドライバが怪しい。 ログはこんな感じ。 ubuntu2004thinkpad:~$ dmesg IPv6: ADDRCONF(NETDEV_CHANGE): enx9096f349a025: link becomes ready usb 3-1: USB disconnect, device number 45 ax88179_178a 3-1:1.0 enx9096f349a025: unregister 'ax88179_178a' usb-0000:00:14.0-1, ASIX AX88179 USB 3.0 Gigabit Ethernet ax88179_178a 3-1:1.0 enx9096f349a025: Failed to read reg index 0x0002: -19 ax88179_178a 3-1:1.0 enx9096f349a025: Failed to write reg index 0x0002: -19 ax88179_178a 3-1:1.0 enx9096f349a025 (unregistered): Failed to write reg index 0x0002: -19 ax88179_178a 3-1:1....
vhost-userのネゴシエーション
はじめに gokvmをvhost-userに対応させるにあたり、初期化部分について調査したのでメモとして残しておく。 QEMUのドキュメントVhost-user Protocol 1 に詳しくまとまっているが、 実際に動かしてみないことには分からない部分(例外処理だったり、リクエストの順番だったり、ログの落ち方だったり) もあると思うので動かしてみた。 色々試行錯誤してみたものの、結局 QEMU と DPDK の2つだけで手軽に試すことができた。 ここでは QEMU をサーバモード、DPDK をクライアントモードで動かした。 サーバモードが vhost-user 用の Unix Domain Socket の生成、クライアントがその Socket への接続を担当する。 dpdk-skeleton のビルド DPDK にはいくつかサンプルプログラムが用意されているが、ここでは dpdk-skeleton を利用することにした。 私の使っているディストリビューションではそれは DPDK パッケージに同梱されていなかったので、以下でビルドした。 $ git clone git@github.com:DPDK/dpdk.git $ cd dpdk $ meson setup -Dexamples=skeleton build $ cd build $ ninja $ file ./examples/dpdk-skeleton ./examples/dpdk-skeleton: ELF 64-bit LSB shared object, x86-64, ... 仮想マシンの起動 軽量なVMイメージであるCirrosをQEMUからブートさせた。 ここでpath=$HOME/vhost-net0 がUnix Domain Socket に対応する。 logfile=$HOME/vhost-net0....
自己分析ツール
一年前に受けたストレングスファインダーの結果を見つけた。いつか役に立つかもしれないのでここに残しておく。 さらに似たような 16personalities.com なるものを今日見つけたのでやってみた。 ストレングスファインダー 2021/1/18 に実施。2,000円程度の書籍を買う必要があり、テストには1時間弱かかる。 16personalities 2023/1/31 に実施。無料で15分程度でテストを受けることができる。 profile から結果に飛べる。 どうやら冒険家(Turbulent Adventurer, ISFP-T)に該当するようだ。
自作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 対応 ↩︎
自作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以外の数値を書き込む。 その他 Virt Queue の初期化方法や Avail Ring、Used Ring の使い方は virtio-net と全く同じ。...
自作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 と表現している。...
自作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 空間のあるオフセットにあるデータを読みたいときは次のような手続きになる。...
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 性能はどうだろう。25G NICで64バイトショートパケットを流した時のスループットとCPU使用率についてのデータがあった。 仮想マシンを経由するPVPのシナリオでは、vhostuser の採用によってCPU使用率は差し置いたままで、 スループットが向上する。 ただ、DPDKには届かない。コンテナを経由するPCPのシナリオでは、ユーザ・カーネル間のデータコピーを省略できるため、AF_XDPがベストチョイスかと思う。 向き不向きがあるので、シナリオによって選び方が変わるだろう。...
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....
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ヘッダ の中身を見ることができる。...
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のビルド環境を使ってビルドすることにした。 FROM ghcr.io/buddying-inc/centos68:latest RUN 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.repo RUN yum install -y gcc perl glibc-static kernel kernel-devel \ autoconf zlib-devel zlib-static openssl-static openssl-devel 上のDockerfileをもとに buildenv-v2....
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など巨大なコミュニティを持つツールを使う。
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を使うことは多くの場合難しい。 Broadcom Trident2 でVXLANをサポート。Trident2+とTrident3でVXLANのルーティングをサポート Mellanox SpectrumチップはVXLANのブリッジングとルーティングをサポート 他にもCariumやBarefoot NetworksのチップがVXLANのブリッジングとルーティングをサポート ソフトウェア側について見てみると、LinuxのVXLANサポートは随分前からあり、VRFのサポートは2015年にCumulus Networksによって追加された。 カーネル4....