🌊

OVN(Open Virtual Network)で手作業仮想ネットワーク

2022/10/12に公開

どうも、インフラエンジニアです
OpenStackにせよk8sにせよインフラを考えるうえでネットワークは欠かせません
今回はOpenStackやOpenShiftで採用されている仮想ネットワーク基盤のOVNを使いつつ、手作業で仮想ネットワークを構築していきたいと思います
この記事では物理ネットワークと仮想ネットワークを特に区別したい場合に仮想ネットワークをオーバレイ、物理ネットワークをアンダーレイと呼びます

OVS(Open vSwitch)

OVNの話をする前にOVSの話をします
VMとVMが通信する場合、一番簡単な方法は物理ネットワークを使ってVMを接続する方法です
物理ネットワーク接続
VMを物理ネットワークに接続する場合
このような方法であれば物理ネットワークを管理するのとほぼ同様の方法でVM同士の通信を管理することができます
当然ですがこの方法にはVMの所属するネットワークが物理ネットワークに限定されるという欠点があります
ハードウェアは仮想化できている一方でネットワークは仮想化できていない状態と言うこともできます
VMに適当なアドレスを与えても通信はできない
VMに適当なアドレスを与えても通信はできない
VMを作成する場合、CPUやメモリを自由に設定できる程度にネットワークインターフェースも自由に設定したいというのは当然の要求です
OVSはこのような要求を実現する手法の一つです
OVSを使用したネットワーク仮想化
OVSを使用したネットワーク仮想化
OVSはovs-vswitchdというデーモンとOVSDBというデータベースから構成されます
VMが別のVMに対してパケットを送信した場合、ovs-vswitchdはデータベースを参照して送信先のVMがどこの物理ホスト上にいるのかを特定します
そしてVMに対して送信したパケットを物理ホストへ送信するパケットにカプセル化してくれます
パケットをカプセル化する方法はいくつかのRFCで定義されていますが、今回はその詳細に関しては触れません
より詳細に触れたい方は「Geneve」や「VXLAN」、「GRE」などで検索してください
要するに、OVSはVM間の通信と物理ホスト間の通信を相互に変換することで仮想ネットワークを実現する技術ということです

OVN(Open Virtual Network)

OVSはネットワークを仮想化する強力な技術です
しかし、ルーターやスイッチ、ファイヤーウォールなどのコンポーネントを全てOVSだけで仮想化しようとすると要求される技術レベルが高くなりすぎてしまいます
(やろうと思えばできるはずです)
そこでOVSをベースに使用してネットワークの仮想化を簡単にする技術がOVNです
OVN
OVN
OVNは各物理ホスト上に存在するovn-controllerと管理サーバ上に存在するovn-northd、Northbound DB、Southbound DBによって構成されています
管理者はNorthbound DB上でオーバーレイネットワークの構成を管理します
例えば、仮想スイッチの作成、仮想ルータの作成などはNorthbound DBで行います
VMがどの物理ホストで稼働しているかといった情報はSouthbound DBで管理されています
Southbound DBは基本的に管理者が変更するものではありません
Southbound DBはNorthbound DBに変更があった場合にovn-northdが勝手に書き換えたり、物理ホスト上で何か変更があった場合にovn-controllerが勝手に書き換えたりして整合性を作ってくれるものです

今回はこのOVNを使って実際にVM上で仮想ネットワークを構築します

完成図(オーバーレイ)

オーバーレイネットワーク
オーバーレイネットワーク
今回構成するオーバーレイネットワークは古典的な構成にします
VMは10.0.0.0/8のネットワークに所属し、仮想スイッチを通じて仮想ルータに接続します
仮想ルーターはオーバーレイネットワーク10.0.0.0/8とアンダーレイネットワーク192.168.1.0/24を接続します
VMからインターネットへの通信は仮想ルーターにネクストホップとして物理ルーター192.168.1.254を設定します

完成図(アンダーレイ)

アンダーレイネットワーク
アンダーレイネットワーク
アンダーレイネットワークはコントローラ1台、ホスト2台、veth2本とします
vethはLinux上に作成できる仮想NICです
今回はvethをnsというネームスペースに所属させて通信を確かめます
ネームスペースというのはほぼコンテナだと思って大丈夫です
図にはbr-intとbr-extという箱がありますが、これはovsが管理するbridgeです
br-intはovsを起動すると勝手に生成されます
br-extの方は物理ネットワークと接続するためのbridgeで、こちらは手動で作る必要があります
br-int <-> br-ext間の接続にはpatch portというのを使用しますが、これは設定中に勝手に作成されます

インストール

まずはovnをコントローラとホストにインストールします
OSはRocky9、インストールはソースコードからビルドで行います

いつもの

sudo dnf update

ビルドに必要なパッケージをインストールします
(不要なパッケージもかなりインストールしてます)

sudo dnf install git make gcc autoconf automake libtool systemd openssl openssl-devel python3 python3-devel python3-six python3-setuptools python3-pip desktop-file-utils groff-base groff graphviz procps-ng libcap-ng libcap-ng libcap-ng-devel openssl hostname iproute libibverbs
sudo pip3 install sphinx pyOpenSSL

OVNのビルドにはOVSが必要なので先にOVSをインストールします
./configureを実行する際にインストール先をパッケージインストールに合わせています
デフォルトでは少し変な場所にインストールされます

cd /opt
git clone https://github.com/openvswitch/ovs.git
cd ovs
git checkout v3.0.0
./boot.sh
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc
make
sudo make install

OVSがインストールできたらOVNをインストールします

cd /opt
git clone https://github.com/ovn-org/ovn.git
cd ovn
git checkout v22.06.0
./boot.sh
./configure --prefix=usr --localstatedir=/var --sysconfdir=/etc --with-ovs-source=/opt/ovs
make
sudo make install

以上でOVNが使えるようになります

起動(コントローラ)

コントローラを起動します
まずはDBの設定からです
DBの作成に必要なデータはovnのリポジトリに存在します

sudo mkdir /etc/ovn
sudo ovsdb-tool create /etc/ovn/ovnnb_db.db /opt/ovn/ovn-nb.ovsschema
sudo ovsdb-tool create /etc/ovn/ovnsb_db.db /opt/ovn/ovn-sb.ovsschema

Northbound DBを起動します

sudo mkdir -p /var/run/ovn
sudo mkdir -p /var/log/ovn
sudo ovsdb-server /etc/ovn/ovnnb_db.db \
      --remote=punix:/var/run/ovn/ovnnb_db.sock \
      --remote=ptcp:6641 \
      --pidfile=/var/run/ovn/ovnnb_db.pid \
      --detach \
      --log-file=/var/log/ovn/ovnnb_db.log

punixやptcpはそれぞれunixドメインソケット、tcpポートで通信を待ち受けるという意味です
pを外してunix:~やtcp:~とするとアクセスすることができます
--detachはバックグラウンド実行です
次はSouthbound DBです

sudo ovsdb-server /etc/ovn/ovnsb_db.db \
      --remote=punix:/var/run/ovn/ovnsb_db.sock \
      --remote=ptcp:6642 \
      --pidfile=/var/run/ovn/ovnsb_db.pid \
      --detach \
      --log-file=/var/log/ovn/ovnsb_db.log

それぞれ初期化します

sudo ovn-nbctl --db unix:/var/run/ovn/ovnnb_db.sock init
sudo ovn-sbctl --db unix:/var/run/ovn/ovnsb_db.sock init

ovn-nbctl, ovn-sbctlはそれぞれNorthbound DB, Southbound DBを操作するためのコマンドです
northdを起動します

sudo ovn-northd \
      --pidfile=/var/run/ovn/northd.pid \
      --detach \
      --log-file=/var/log/ovn/northd.log \
      --ovnnb-db=unix:/var/run/ovn/ovnnb_db.sock \
      --ovnsb-db=unix:/var/run/ovn/ovnsb_db.sock

最後にデータベースアクセス用のポートを開きます

sudo firewall-cmd --add-port=6641/tcp --zone=public --permanent
sudo firewall-cmd --add-port=6642/tcp --zone=public --permanent

以上でコントローラの起動は完了です

起動(ホスト)

ホストを起動します
カーネルでovsを有効にします

sudo modprobe openvswitch

OVSDBを起動します
起動に必要なデータはovsリポジトリにあります

sudo mkdir /etc/openvswitch
sudo mkdir /var/run/openvswitch
sudo mkdir /var/log/openvswitch
sudo ovsdb-tool create /etc/openvswitch/vtep.db /opt/ovs/vtep/vtep.ovsschema
sudo ovsdb-tool create /etc/openvswitch/conf.db /opt/ovs/vswitchd/vsitch.ovsschema
sudo ovsdb-server \
        --remote=punix:/var/run/openvswitch/db.sock \
	--detach \
	--pidfile=/var/run/openvswitch/db.pid \
	--log-file=/var/log/openvswitch/db.log \
	/etc/openvswitch/vtep.db \
	/etc/openvswitch/conf.db

初期化します

sudo ovs-vsctl \
        --db=unix:/var/run/openvswitch/db.sock \
	init

ovs-vsctlはovsを操作するコマンドです
ovs-vswitchdを起動します

sudo ovs-vswitchd \
        --pidfile=/var/run/openvswitch/vswitchd.pid \
	--detach \
	--log-file=/var/log/openvswitch/vswitchd.log \
	unix:/var/run/openvswitch/db.sock

DBにOVN接続用データを書き込みます
ここは各ホストごとにUUIDとIPを用意してあげてください
system-idに設定したUUIDがOVNの物理ホスト識別情報になります
接続先はコントローラのSouthbound DBです
ovn-encap-ipは自分自身のIPです

sudo ovs-vsctl \
        --db=unix:/var/run/openvswitch/db.sock \
	set open . \
	external-ids:system-id=7b4d647d-2fd9-0950-1125-fabb06be29b8
sudo ovs-vsctl \
        --db=unix:/var/run/openvswitch/db.sock \
	set open . \
	external-ids:ovn-remote=tcp:192.168.1.110:6642
sudo ovs-vsctl \
        --db=unix:/var/run/openvswitch/db.sock \
	set open . \
	external-ids:ovn-encap-type=geneve
sudo ovs-vsctl \
        --db=unix:/var/run/openvswitch/db.sock \
	set open . \
	external-ids:ovn-encap-ip=192.168.1.111

ovn-controllerを起動します

sudo ovn-controller \
        --pif-file=/var/run/ovn/controller.pid \
	--detach \
	--log-file=/var/log/ovn/controller.log \
	unix:/var/run/openvswitch/db.sock

ovn-encap-typeで指定したプロトコルのポートを開放します

sudo firewall-cmd --add-port=6081/udp --zone=public --permanent

以上でホストの起動は終了です

設定(コントローラ)

では仮想ネットワークを作っていきます
流れとしては

  1. overlayスイッチを作成
  2. underlay接続スイッチを作成
  3. ルーター作成
  4. ルータとスイッチを接続
  5. 物理ネットワークに接続可能なホストの設定

という流れになります
ホスト側にVM相当の物がないのでまだ通信はできません

まずはoverlayスイッチの作成です
スイッチの名前はなんでもいいです

sudo ovn-nbctl ls-add sw

このスイッチはVMなどが接続するためスイッチです
そのため、ホスト側でVMなどが起動したタイミングで各種設定を行います

次はunderlayスイッチの作成です
こちらは少し複雑です

sudo ovn-nbctl ls-add public
sudo ovn-nbctl lsp-add public public-port
sudo ovn-nbctl lsp-set-type public-port localnet
sudo ovn-nbctl lsp-set-addresses public-port unknown
sudo ovn-nbctl lsp-set-options public-port network_name=phynet

lsp-addはポート(接続用の穴)を作成するサブコマンドです
public-portを物理ネットワークに接続するためにはタイプをlocalnet、アドレスをunknownに設定する必要があります
network_nameは適当につけていいですが物理ホストにも同じ名前を設定する必要があります

ルータを作成します

sudo ovn-nbctl lr-add rt
sudo ovn-nbctl lr-route-add rt 0.0.0.0/0 192.168.1.254
sudo ovn-nbctl lr-nat-add snat 192.168.1.253 10.0.0.0/8

デフォルトルートとして物理ルーターを設定します
また、オーバーレイネットワーク(10.0.0.0/8)からの出力をSNATしています

ルーターとスイッチを接続します
まずはoverlayスイッチと接続します
MACアドレスは適当に生成してください

sudo ovn-nbctl lrp-add rt rt-sw 96:2a:9f:22:92:90 10.255.255.254

sudo ovn-nbctl lsp-add sw sw-rt
sudo ovn-nbctl lsp-set-type sw-rt router
sudo ovn-nbctl lsp-set-addresses sw-rt router
sudo ovn-nbctl lsp-set-options sw-rt router-port=rt-sw

これでルーターとoverlayスイッチが接続できます
underlay接続スイッチも同じことをします

sudo ovn-nbctl lrp-add rt rt-public 6a:10:b5:75:18:3e 192.168.1.253

sudo ovn-nbctl lsp-add public public-rt
sudo ovn-nbctl lsp-set-type public-rt router
sudo ovn-nbctl lsp-set-addresses public-rt router
sudo ovn-nbctl lsp-set-options public-rt router-port=rt-public

最後に物理ネットワークと通信可能な物理ホストを登録します
細かい注意事項はありますが今回は全ての物理ホストを登録します
登録するのは先ほど設定したUUIDです

sudo ovn-nbctl lrp-set-gateway-chassis rt-public 7b4d647d-2fd9-0950-1125-fabb06be29b8
sudo ovn-nbctl lrp-set-gateway-chassis rt-public 6c782f7d-c768-6480-4548-57fd477a746a

コントローラ側の設定は以上です

設定(ホスト)

ホストの設定をします
ホストでやることは以下の三つです

  1. デフォルトのネットワーク設定の削除
  2. 物理ネットワーク接続用OVSブリッジの作成
  3. 物理接続ネットワークのOVNへの登録

まずはデフォルトネットワーク設定を削除します
ネットワークが落ちるのでsshができなくなる点に注意してください

sudo nmcli con del enp1s0

物理ネットワーク用OVSブリッジを作成します

sudo ovs-vsctl add-br br-ext
sudo ovs-vsctl add-port br-ext enp1s0
sudo ip link set br-ext up
sudo ip address add 192.168.1.111/24 dev br-ext

最後にOVNへ物理ネットワーク用の接続を登録します

sudo ovs-vsctl set open . external-ids:ovn-bridge-mappings=phynet:br-ext

動作確認用vethの作成

ここまでの操作でOVNの設定は完了です
それでは動作確認の為にvethを作成します
まずは10.0.0.1のアドレスを持つvethを作成します

sudo ip netns add ns
sudo ip link add veth type veth peer name vtap
sudo ip link set veth netns ns
sudo ip link set dev vtap up
sudo ip netns exec ns ip link set veth up
sudo ip netns exec ns ip link set lo up
sudo ip netns exec ns ip addr add 10.0.0.1/8 dev veth
sudo ip netns exec ns ip route add default via 10.255.255.254

作成したvethをスイッチに接続します

sudo ip netns exec ns cat /sys/class/net/veth/address
# 42:8e:2e:77:f0:5b
sudo ovn-nbctl --db=tcp:192.168.1.110:6641 \
        lsp-add sw sw-port1
sudo ovn-nbctl --db=tcp:192.168.1.110:6641 \
        lsp-set-addresses sw-port1 42:8e:2e:77:f0:5b
sudo ovs-vsctl --db=unix:/var/run/openvswitch/db.sock \
        add-port br-int vtap -- set interface vtap external_ids:iface-id=sw-port1

10.0.0.2に関しても同様です
vethに付与するIPと接続するスイッチのポートが異なるだけです

sudo ip netns add ns
sudo ip link add veth type veth peer name vtap
sudo ip link set veth netns ns
sudo ip link set dev vtap up
sudo ip netns exec ns ip link set veth up
sudo ip netns exec ns ip link set lo up
sudo ip netns exec ns ip addr add 10.0.0.2/ dev veth
sudo ip netns exec ns ip route add default via 10.255.255.254
sudo ip netns exec ns cat /sys/class/net/veth/address
# e6:9f:bc:16:d7:e9
sudo ovn-nbctl --db=tcp:192.168.1.110:6641 \
        lsp-add sw sw-port2
sudo ovn-nbctl --db=tcp:192.168.1.110:6641 \
        lsp-set-addresses sw-port2e6:9f:bc:16:d7:e9
sudo ovs-vsctl --db=unix:/var/run/openvswitch/db.sock \
        add-port br-int vtap -- set interface vtap external_ids:iface-id=sw-port2

疎通確認

VM-VM

sudo ip netns exec ns ping 10.0.0.2
# PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
# 64 bytes from 10.0.0.1: icmp_seq=1 ttl=113 time=10.4 ms

VM-インターネット

sudo ip netns exec ns ping 8.8.8.8
# PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
# 64 bytes from 8.8.8.8: icmp_seq=2 ttl=113 time=14.9 ms

Discussion