Metonymical Deflection

ゆるく日々のコト・たまにITインフラ

CentOS7 ovs(Open vSwitch)+ovnのネットワーク設定方法

ovs+ovnのoverlayネットワークを構築し、KVM仮想マシンとDockerコンテナのトラフィックをBridgeで外部ネットワークに流して使用する場合の設定方法を記載したいと思います。
これにより、仮想マシンとコンテナと外部ネットワーク機器が同一VLANで通信可能になります。
ただ、実用性の観点で冗長化できないため、あまり使えないかもしれませんが、ovnの仕組みを理解する上では活用できるかと思います。

1.環境

1-1.VMWare
筐体                             : 自作PC(Win10pro)
CPU                           : Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
VMWare              : VMware(R) Workstation 15 Pro 15.1.0 build-13591040  
OS                               : CentOS7.6(1810)
Kernel                           : 3.10.0-957.el7.x86_64
Installed Environment Groups     : Server with GUI
Add-Ons for Selected Environment : Virtualization Client, Virtualization Hypervisor, Virtualization Tools 
ovs&ovn                          : 2.11.1

ベアメタル環境でも同様に設定可能です。

1-2.全体構成その1:Underlay

f:id:metonymical:20190721175822p:plain

1-3.全体構成その2:Overlay

f:id:metonymical:20190721175834p:plain
補足
「'」が付いている箇所は、主に「'」が付いていない箇所を設定すると自動設定されます。*1

1-4 .全体の流れ ~概要~
  1. ovs+ovnのビルドとインストール
  2. ovs+ovnの設定
  3. docker+consulのクラスタ設定
  4. (1)~(7)の設定
1-5 .全体の流れ ~コマンドのみ~

以下のコマンドを投入していきます。
やりたいことが既に決まっている方は、構成図とコマンドの内容を見るだけでもよいと思います。
しかし、ovs+ovnやdocker+consulの設定もそこそこボリュームがあるため、(1)~(7)の辿り着くまでが少々しんどいかもしれません。。

(1)
docker network create -d openvswitch --subnet=192.168.31.0/24 ovs

(2)
vi /etc/sysconfig/network-scripts/ifcfg-br-ext

DEVICE=br-ext
DEVICETYPE=ovs
TYPE=OVSBridge
BOOTPROTO=static
NM_CONTROLLED=no
ONBOOT=yes
HOTPLUG=no

vi /etc/sysconfig/network-scripts/ifcfg-ens36

DEVICE=ens36
ONBOOT=yes
DEVICETYPE=ovs
TYPE=OVSPort
OVS_BRIDGE=br-ext
BOOTPROTO=none
NM_CONTROLLED=no
OVS_OPTIONS="vlan_mode=trunk trunks=300-301"
HOTPLUG=no

(3)
ovn-sbctl --db=tcp:192.168.30.101:6642 show

ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet301:br-ext
export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME l2gwport "" 301
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses l2gwport unknown
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-type l2gwport l2gateway
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-options l2gwport network_name=physnet301 l2gateway-chassis=28928b40-900b-4f48-b4ed-8d9d2f1082fd

(4)
export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
export IFACE_ID=$(ovs-vsctl get interface vnet1 external_ids:iface-id | sed s/\"//g)
export MAC_ADDR=$(ovs-vsctl get interface vnet1 external_ids:attached-mac | sed s/\"//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME $IFACE_ID
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses $IFACE_ID $MAC_ADDR
ovn-nbctl --db=tcp:192.168.30.101:6641 show

(5)
export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
export IFACE_ID=$(ovs-vsctl get interface vnet1 external_ids:iface-id | sed s/\"//g)
export MAC_ADDR=$(ovs-vsctl get interface vnet1 external_ids:attached-mac | sed s/\"//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME $IFACE_ID
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses $IFACE_ID $MAC_ADDR
ovn-nbctl --db=tcp:192.168.30.101:6641 show

(6)
docker run -itd \
--privileged \
--network ovs \
--name c2 \
c76 \
/sbin/init

(7)
docker run -itd \
--privileged \
--network ovs \
--name c3 \
c76 \
/sbin/init

2.ovs+ovnのビルドとインストール

ControllerNode(c76ovn01)にてビルドとインストールを実施します。またDockerもインストールしてしまいます。

2-1.ControllerNode
yum -y install @'Development Tools' rpm-build yum-utils wget libpcap-devel numactl-devel

mkdir -p /root/rpmbuild/SOURCES
cd /root/rpmbuild/SOURCES
wget https://www.openvswitch.org/releases/openvswitch-2.11.1.tar.gz
tar zxvf openvswitch-2.11.1.tar.gz
sed -e 's/@VERSION@/0.0.1/' openvswitch-2.11.1/rhel/openvswitch-fedora.spec.in > ovs.spec

yum-builddep -y ovs.spec && \
cd openvswitch-2.11.1 && \
./boot.sh && \
./configure && \
make rpm-fedora RPMBUILD_OPT="--without check" && \
make rpm-fedora-ovn RPMBUILD_OPT="--without check"

cd /root/rpmbuild/SOURCES/openvswitch-2.11.1/rpm/rpmbuild/RPMS/noarch
yum -y localinstall python-openvswitch-2.11.1-1.el7.noarch.rpm

cd /root/rpmbuild/SOURCES/openvswitch-2.11.1/rpm/rpmbuild/RPMS/x86_64
yum -y localinstall \
openvswitch-2.11.1-1.el7.x86_64.rpm \
ovn-2.11.1-1.el7.x86_64.rpm \
ovn-common-2.11.1-1.el7.x86_64.rpm \
ovn-central-2.11.1-1.el7.x86_64.rpm \
ovn-host-2.11.1-1.el7.x86_64.rpm \
ovn-docker-2.11.1-1.el7.x86_64.rpm

curl -sSL https://get.docker.com/ | sh

systemctl start openvswitch
systemctl enable openvswitch
systemctl start ovn-northd
systemctl enable ovn-northd
systemctl start docker
systemctl enable docker
2-2.ComputeNode

ComputeNode(c76ovn02&c76ovn03)にてビルドとインストールを実施します。違いは、ovn-central-2.11.1-1.el7.x86_64.rpmが無いだけです。

yum -y install @'Development Tools' rpm-build yum-utils wget libpcap-devel numactl-devel

mkdir -p /root/rpmbuild/SOURCES
cd /root/rpmbuild/SOURCES
wget https://www.openvswitch.org/releases/openvswitch-2.11.1.tar.gz
tar zxvf openvswitch-2.11.1.tar.gz
sed -e 's/@VERSION@/0.0.1/' openvswitch-2.11.1/rhel/openvswitch-fedora.spec.in > ovs.spec

yum-builddep -y ovs.spec && \
cd openvswitch-2.11.1 && \
./boot.sh && \
./configure && \
make rpm-fedora RPMBUILD_OPT="--without check" && \
make rpm-fedora-ovn RPMBUILD_OPT="--without check"

cd /root/rpmbuild/SOURCES/openvswitch-2.11.1/rpm/rpmbuild/RPMS/noarch
yum -y localinstall python-openvswitch-2.11.1-1.el7.noarch.rpm

cd /root/rpmbuild/SOURCES/openvswitch-2.11.1/rpm/rpmbuild/RPMS/x86_64
yum -y localinstall \
openvswitch-2.11.1-1.el7.x86_64.rpm \
ovn-2.11.1-1.el7.x86_64.rpm \
ovn-common-2.11.1-1.el7.x86_64.rpm \
ovn-host-2.11.1-1.el7.x86_64.rpm \
ovn-docker-2.11.1-1.el7.x86_64.rpm

curl -sSL https://get.docker.com/ | sh

systemctl start openvswitch
systemctl enable openvswitch
systemctl start ovn-controller
systemctl enable ovn-controller
systemctl start docker
systemctl enable docker

3.ovs+ovnの設定

3-1.Overlay用のInteface設定
c76ovn01
nmcli con add type ethernet autoconnect yes con-name ens37 ifname ens37
nmcli con mod ens37 ipv4.method manual ipv4.addresses 192.168.30.101/24
nmcli con up ens37

c76ovn02
nmcli con add type ethernet autoconnect yes con-name ens37 ifname ens37
nmcli con mod ens37 ipv4.method manual ipv4.addresses 192.168.30.102/24
nmcli con up ens37

c76ovn03
nmcli con add type ethernet autoconnect yes con-name ens37 ifname ens37
nmcli con mod ens37 ipv4.method manual ipv4.addresses 192.168.30.103/24
nmcli con up ens37
3-2.ovnの設定
c76ovn01
ovn-nbctl set-connection ptcp:6641
ovn-sbctl set-connection ptcp:6642
ovs-appctl -t ovsdb-server ovsdb-server/add-remote ptcp:6640
3-3.ovsの設定
c76ovn02
export CENTRAL_IP=192.168.30.101
export ENCAP_TYPE=geneve
export LOCAL_IP=192.168.30.102

ovs-vsctl set Open_vSwitch . external_ids:ovn-remote="tcp:${CENTRAL_IP}:6642"
ovs-vsctl set Open_vSwitch . external_ids:ovn-nb="tcp:${CENTRAL_IP}:6641"
ovs-vsctl set Open_vSwitch . external_ids:ovn-encap-ip=${LOCAL_IP}
ovs-vsctl set Open_vSwitch . external_ids:ovn-encap-type="${ENCAP_TYPE}"

systemctl restart ovn-controller

c76ovn03
export CENTRAL_IP=192.168.30.101
export ENCAP_TYPE=geneve
export LOCAL_IP=192.168.30.103

ovs-vsctl set Open_vSwitch . external_ids:ovn-remote="tcp:${CENTRAL_IP}:6642"
ovs-vsctl set Open_vSwitch . external_ids:ovn-nb="tcp:${CENTRAL_IP}:6641"
ovs-vsctl set Open_vSwitch . external_ids:ovn-encap-ip=${LOCAL_IP}
ovs-vsctl set Open_vSwitch . external_ids:ovn-encap-type="${ENCAP_TYPE}"

systemctl restart ovn-controller
3-4.設定確認

102と103が101とESTABとなっていればOKです。

c76ovn01
ss -ant |grep 6642

出力例
[root@c76ovn01 ~]# ss -ant |grep 6642
LISTEN     0      10           *:6642                     *:*
ESTAB      0      0      192.168.30.101:6642               192.168.30.102:44102
ESTAB      0      0      192.168.30.101:6642               192.168.30.103:34252

上記がOKな場合、br-intが自動生成され、GeneveによるOverlayのトンネル設定も自動で実施されます。また、ovs上では以下のようなステータスとなっていればOKです。

c76ovn02&03
ovs-vsctl show

出力例
[root@c76ovn02 ~]# ovs-vsctl show
e2d97138-977a-4579-bc6a-dfb43d8769cb
    Bridge br-int
        fail_mode: secure
        Port br-int
            Interface br-int
                type: internal
        Port "ovn-925250-0"
            Interface "ovn-925250-0"
                type: geneve
                options: {csum="true", key=flow, remote_ip="192.168.30.103"}
    ovs_version: "2.11.0"
3-5.virshによるovsの設定

c76ovn02&03にてovsのPortGroup設定を実施しておきます。*2

c76ovn02&03
vi /tmp/ovsnw.xml

<network>
<name>ovsnw</name>
<forward mode='bridge'/>
<bridge name='br-int'/>
<virtualport type='openvswitch'/>
<portgroup name='untag' default='yes'>
</portgroup>
<portgroup name='vlan300'>
  <vlan>
    <tag id='300'/>
  </vlan>
</portgroup>
<portgroup name='vlan301'>
  <vlan>
    <tag id='301'/>
  </vlan>
</portgroup>
</network>

virsh net-list
virsh net-define /tmp/ovsnw.xml
virsh net-start ovsnw
virsh net-autostart ovsnw
virsh net-list

4.docker+consulのクラスタ設定

4-1.Dockerの設定その1

Option設定を読込みできるようにします。

c76ovn01&02&03

vi /usr/lib/systemd/system/docker.service

EnvironmentFile=-/etc/sysconfig/docker
EnvironmentFile=-/etc/sysconfig/docker-storage
EnvironmentFile=-/etc/sysconfig/docker-network
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock \
$OPTIONS \
$DOCKER_STORAGE_OPTIONS \
$DOCKER_NETWORK_OPTIONS \
$ADD_REGISTRY \
$BLOCK_REGISTRY \
$INSECURE_REGISTRY

出力例
青文字はコメントアウトしてください。
[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
EnvironmentFile=-/etc/sysconfig/docker
EnvironmentFile=-/etc/sysconfig/docker-storage
EnvironmentFile=-/etc/sysconfig/docker-network
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock \
$OPTIONS \
$DOCKER_STORAGE_OPTIONS \
$DOCKER_NETWORK_OPTIONS \
$ADD_REGISTRY \
$BLOCK_REGISTRY \
$INSECURE_REGISTRY

ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
4-2.Dockerの設定その2
c76ovn01
vi /etc/sysconfig/docker
OPTIONS="--cluster-store=consul://127.0.0.1:8500 --cluster-advertise=192.168.30.101:0"

systemctl daemon-reload
systemctl restart docker

c76ovn02
vi /etc/sysconfig/docker
OPTIONS="--cluster-store=consul://127.0.0.1:8500 --cluster-advertise=192.168.30.102:0"

systemctl daemon-reload
systemctl restart docker

c76ovn03
vi /etc/sysconfig/docker
OPTIONS="--cluster-store=consul://127.0.0.1:8500 --cluster-advertise=192.168.30.103:0"

systemctl daemon-reload
systemctl restart docker
4-3.consulのインストール

consulでクラスタを構成します。
一度設定をミスると色々やっかいだった*3ので、VMWareなどで各Nodeを動作させている方は、この手順を進める前にスナップショットなりクローンを作成しておいた方が良いと思います。

c76ovn01&ovn02&ovn03
yum -y install epel-release && \
yum -y install python-pip && \
pip install pip --upgrade && \
pip install flask

wget https://releases.hashicorp.com/consul/1.5.2/consul_1.5.2_linux_amd64.zip
unzip consul_1.5.2_linux_amd64.zip
mv consul /usr/local/bin
4-4.consulの設定

最初に暗号キーを生成しておきます。生成した暗号キーは全Nodeで共通にしますので、任意の1台で生成すればOKです。ここでは例としてc76ovn01にて生成します。

c76ovn01
consul keygen

出力例
[root@c76ovn01 ~]# consul keygen
az3EW5ykRod6hBS1FiSFAA==

各Nodeごとに設定ファイルを作成します。赤文字の箇所のみ異なっています。

c76ovn01
sudo mkdir -p /etc/consul.d/scripts
sudo mkdir /var/consul

vi /etc/consul.d/config.json
{
    "bootstrap_expect": 3,
    "client_addr": "0.0.0.0",
    "datacenter": "dc1",
    "data_dir": "/var/consul",
    "domain": "consul",
    "enable_script_checks": true,
    "dns_config": {
        "enable_truncate": true,
        "only_passing": true
    },
    "enable_syslog": true,
    "encrypt": "az3EW5ykRod6hBS1FiSFAA==",
    "leave_on_terminate": true,
    "log_level": "INFO",
    "rejoin_after_leave": true,
    "bind_addr": "192.168.30.101",
    "server": true,
    "start_join": [
        "192.168.30.101",
        "192.168.30.102",
        "192.168.30.103"
    ],
    "ui": false
}

c76ovn02
sudo mkdir -p /etc/consul.d/scripts
sudo mkdir /var/consul

vi /etc/consul.d/config.json
{
    "bootstrap_expect": 3,
    "client_addr": "0.0.0.0",
    "datacenter": "dc1",
    "data_dir": "/var/consul",
    "domain": "consul",
    "enable_script_checks": true,
    "dns_config": {
        "enable_truncate": true,
        "only_passing": true
    },
    "enable_syslog": true,
    "encrypt": "az3EW5ykRod6hBS1FiSFAA==",
    "leave_on_terminate": true,
    "log_level": "INFO",
    "rejoin_after_leave": true,
    "bind_addr": "192.168.30.102",
    "server": true,
    "start_join": [
        "192.168.30.101",
        "192.168.30.102",
        "192.168.30.103"
    ],
    "ui": false
}

c76ovn03
sudo mkdir -p /etc/consul.d/scripts
sudo mkdir /var/consul

vi /etc/consul.d/config.json
{
    "bootstrap_expect": 3,
    "client_addr": "0.0.0.0",
    "datacenter": "dc1",
    "data_dir": "/var/consul",
    "domain": "consul",
    "enable_script_checks": true,
    "dns_config": {
        "enable_truncate": true,
        "only_passing": true
    },
    "enable_syslog": true,
    "encrypt": "az3EW5ykRod6hBS1FiSFAA==",
    "leave_on_terminate": true,
    "log_level": "INFO",
    "rejoin_after_leave": true,
    "bind_addr": "192.168.30.103",
    "server": true,
    "start_join": [
        "192.168.30.101",
        "192.168.30.102",
        "192.168.30.103"
    ],
    "ui": false
}
4-5.consulのサービス化設定

consulをサービス化し、自動起動するように設定します。

c76ovn01&02&03
vi /etc/systemd/system/consul.service

[Unit]
Description=Consul Startup process
After=network.target

[Service]
Type=simple
ExecStart=/bin/bash -c '/usr/local/bin/consul agent -config-dir /etc/consul.d/'
TimeoutStartSec=0

[Install]
WantedBy=default.target

systemctl daemon-reload
systemctl start consul
systemctl enable consul

consul members

出力例
全Nodeで同様の出力となります。
[root@c76ovn03 ~]# consul members
Node            Address              Status  Type    Build  Protocol  DC   Segment
c76ovn01.md.jp  192.168.30.101:8301  alive   server  1.5.2  2         dc1  
c76ovn02.md.jp  192.168.30.102:8301  alive   server  1.5.2  2         dc1  
c76ovn03.md.jp  192.168.30.103:8301  alive   server  1.5.2  2         dc1  

[root@c76ovn03 ~]# systemctl status consul
● consul.service - Consul Startup process
   Loaded: loaded (/etc/systemd/system/consul.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2019-07-21 16:27:32 JST; 1s ago
 Main PID: 28182 (consul)
    Tasks: 14
   Memory: 18.2M
   CGroup: /system.slice/consul.service
           mq28182 /usr/local/bin/consul agent -config-dir /etc/consul.d/

Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: consul: Adding LAN server c76ovn01.md.jp (Addr: tcp/192.168.30.101:8300) (DC: dc1)
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: consul: Adding LAN server c76ovn02.md.jp (Addr: tcp/192.168.30.102:8300) (DC: dc1)
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: serf: EventMemberJoin: c76ovn02.md.jp.dc1 192.168.30.102
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: serf: Re-joined to previously known node: c76ovn01.md.jp.dc1: 192.168.30.101:8302
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: agent: Started HTTP server on [::]:8500 (tcp)
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: consul: Handled member-join event for server "c76ovn01.md.jp.dc1" in area "wan"
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: consul: Handled member-join event for server "c76ovn02.md.jp.dc1" in area "wan"
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: agent: (LAN) joining: [192.168.30.101 192.168.30.102 192.168.30.103]
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: agent: (LAN) joined: 3
Jul 21 16:27:32 c76ovn03.md.jp consul[28182]: agent: started state syncer

ここまで完了したら、一度全Nodeを再起動しておいてください。
再起動完了後、以下のコマンドでDockerとconsulが正常に起動していることを確認してください。

systemctl status docker
systemctl status consul

5.(1)~(7)の設定

5-1.事前準備

ovnのdocker-overlayドライバを各ComputeNodeで起動しておきます。

c76ovn02&03
ovn-docker-overlay-driver --detach
5-2.(1)の設定:DockerOverlaySwitchの作成

(1’)は自動生成されていますが、Overlay上では認識されていません。
以下のコマンドにより、ovnのNorthboundDBにDockerOverlaySwitchが生成されます。それと同時にDocker上のNWとしても認識されます。
c76ovn02もしくはc76ovn03のどちらか一方で設定すればOKです。

c76ovn02 or 03
docker network create -d openvswitch --subnet=192.168.31.0/24 ovs

出力例
[root@c76ovn02 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
7a78f73ce06d        bridge              bridge              local
368be43f05a1        host                host                local
efee695f0f0c        none                null                local
b5fb702e9489        ovs                 openvswitch         global

[root@c76ovn02 ~]# ovn-nbctl --db=tcp:192.168.30.101:6641 show
switch b70b9243-80b3-4ad6-8940-55bff06251fc (b5fb702e948902881b1b933191b9ee83f8dad4eb434a9d27d408d7a2d5fe2aa2)
5-3.(2)の設定:br-extの作成

br-intに無理やりインターフェースをアタッチしても外部とのBridge接続による疎通はできません。この辺りはNeutronのアーキテクチャに近い感じです。br-intとbr-extをPatch接続させて、外部ネットワークとブリッジ接続します。

c76ovn02
3-5に合わせて300-301をTrunkしていますが、今回の構成であれば301のみでOKです。

vi /etc/sysconfig/network-scripts/ifcfg-br-ext

DEVICE=br-ext
DEVICETYPE=ovs
TYPE=OVSBridge
BOOTPROTO=static
NM_CONTROLLED=no
ONBOOT=yes
HOTPLUG=no

vi /etc/sysconfig/network-scripts/ifcfg-ens36

DEVICE=ens36
ONBOOT=yes
DEVICETYPE=ovs
TYPE=OVSPort
OVS_BRIDGE=br-ext
BOOTPROTO=none
NM_CONTROLLED=no
OVS_OPTIONS="vlan_mode=trunk trunks=300-301"
HOTPLUG=no

systemctl restart network
5-4.(3)の設定:br-intとbr-extのPatch接続

先にovn-sbctlコマンドでシャーシIDを取得した後に設定を入れます。

c76ovn02
(3)
ovn-sbctl --db=tcp:192.168.30.101:6642 show

ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet301:br-ext
export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME l2gwport "" 301
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses l2gwport unknown
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-type l2gwport l2gateway
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-options l2gwport network_name=physnet301 l2gateway-chassis=28928b40-900b-4f48-b4ed-8d9d2f1082fd
補足その1
ovn-sbctl --db=tcp:192.168.30.101:6642 showの出力例を以下に記載します。
赤文字の箇所をl2gateway-chassis=の後に加えます。
出力例
[root@c76ovn02 ~]# ovn-sbctl --db=tcp:192.168.30.101:6642 show
Chassis "1fdd4703-9c73-4afd-adf3-e0e90b7afb63"
    hostname: "c76ovn03.md.jp"
    Encap geneve
        ip: "192.168.30.103"
        options: {csum="true"}
    Port_Binding "35d89c2d-0505-41aa-b0c6-6aff6f483899"
    Port_Binding "b7131d7ecd548dcc4b6eb38bef0179b1bc75645e5683b69e6131da69d0aed1be"
Chassis "28928b40-900b-4f48-b4ed-8d9d2f1082fd"
    hostname: "c76ovn02.md.jp"
    Encap geneve
        ip: "192.168.30.102"
        options: {csum="true"}
    Port_Binding "16b258df72cb52ddb2527be69578c1f953c9275e154d4107743b0ee3ac13f095"
    Port_Binding "2c5a0afd-b12a-419e-b6f5-718c5d0e6446"
    Port_Binding "l2gwport"
[root@c76ovn02 ~]#
補足その2
設定後、以下のようになっていればOKです。
出力例
[root@c76ovn02 ~]# ovn-nbctl --db=tcp:192.168.30.101:6641 show
switch b70b9243-80b3-4ad6-8940-55bff06251fc (b5fb702e948902881b1b933191b9ee83f8dad4eb434a9d27d408d7a2d5fe2aa2)
    port l2gwport
        type: l2gateway
        parent:
        tag: 301
        addresses: ["unknown"]
[root@c76ovn02 ~]#

[root@c76ovn02 ~]# ovs-vsctl show
090be24a-f730-4d28-a4a0-dcf6d97fe618
    Bridge br-int
        fail_mode: secure
        Port "patch-br-int-to-l2gwport"
            Interface "patch-br-int-to-l2gwport"
                type: patch
                options: {peer="patch-l2gwport-to-br-int"}
        Port "ovn-1fdd47-0"
            Interface "ovn-1fdd47-0"
                type: geneve
                options: {csum="true", key=flow, remote_ip="192.168.30.103"}
        Port br-int
            Interface br-int
                type: internal
    Bridge br-ext
        Port "patch-l2gwport-to-br-int"
            Interface "patch-l2gwport-to-br-int"
                type: patch
                options: {peer="patch-br-int-to-l2gwport"}
        Port br-ext
            Interface br-ext
                type: internal
        Port "ens36"
            trunks: [300, 301]
            Interface "ens36"
    ovs_version: "2.11.1"
[root@c76ovn02 ~]#

ここまででアップリンクに相当する部分が構築できました。
あとは、仮想マシンやコンテナをアタッチしていきます。

コンテナは以下のコマンドで作成したDockerネットワークのovsにアタッチさせるだけで自動設定してくれます。

docker network create -d openvswitch --subnet=192.168.31.0/24 ovs

一方、KVMの仮想マシンについては、もう一工夫必要になります。

5-5.(4)と(5)の設定:KVM仮想マシンのアタッチ

先にvirt-installコマンドなどで仮想マシンを起動しておいてください。
その際、仮想マシンに使用するインターフェースは3-5で設定したPortGroupのうちvlan301を使用するようにしてください。

virt-installはこんな感じです。ポイントは赤文字の箇所です。

virt-install --connect=qemu:///system \
 --name=c7623 \
 --disk /var/lib/libvirt/images/c7623.qcow2,format=qcow2,bus=virtio \
 --network bridge=virbr0,model=virtio \
 --network network=ovsnw,portgroup=vlan301,model=virtio \
 --initrd-inject=./c76min.ks \
 --extra-args='ks=file:/c76min.ks biosdevname=0 net.ifnames=0 console=tty0 console=ttyS0,115200n8' \
 --vcpus=1 \
 --ram=1024 \
 --accelerate \
 --hvm \
 --location='/var/lib/libvirt/images/CentOS-7-x86_64-DVD-1810.iso' \
 --nographics \
 --os-type=linux \
 --os-variant=centos7.0 \
 --arch=x86_64

これにより、以下のような設定になります。ポイントはvnet1とovsnw, vlan301がBridgeしている点です。

vnet0 virbr0 eth0 libvirtでDefault定義
vnet1 ovsnw, vlan301 eth1 3-5で定義

仮想マシンが起動したら以下のコマンドにて、ovn上に登録していきます。
SW_NAME、IFACE_ID、MAC_ADDRを取得し、それをNorthboundDBに登録する形となります。(((4)と(5)のコマンドは同じですが、それぞれのComputeNodeにて実行するため取得するIDが異なります))

c76ovn02
(4)
export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
export IFACE_ID=$(ovs-vsctl get interface vnet1 external_ids:iface-id | sed s/\"//g)
export MAC_ADDR=$(ovs-vsctl get interface vnet1 external_ids:attached-mac | sed s/\"//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME $IFACE_ID
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses $IFACE_ID $MAC_ADDR
ovn-nbctl --db=tcp:192.168.30.101:6641 show

c76ovn03
(5)
export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
export IFACE_ID=$(ovs-vsctl get interface vnet1 external_ids:iface-id | sed s/\"//g)
export MAC_ADDR=$(ovs-vsctl get interface vnet1 external_ids:attached-mac | sed s/\"//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME $IFACE_ID
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses $IFACE_ID $MAC_ADDR
ovn-nbctl --db=tcp:192.168.30.101:6641 show

出力例
[root@c76ovn02 ~]# ovn-nbctl --db=tcp:192.168.30.101:6641 show
switch b70b9243-80b3-4ad6-8940-55bff06251fc (b5fb702e948902881b1b933191b9ee83f8dad4eb434a9d27d408d7a2d5fe2aa2)
    port l2gwport
        type: l2gateway
        parent:
        tag: 301
        addresses: ["unknown"]
    port 35d89c2d-0505-41aa-b0c6-6aff6f483899
        addresses: ["52:54:00:f6:45:c2"]
    port 2c5a0afd-b12a-419e-b6f5-718c5d0e6446
        addresses: ["52:54:00:d7:a6:96"]
[root@c76ovn02 ~]#
5-6.(6)と(7)の設定:Dockerコンテナのアタッチ

DockerコンテナのアタッチはPlug-Inで自動的にovs&ovnに登録されるため、「--network ovs」だけ忘れずに設定すれば問題ありません。

c76ovn02
(6)
docker run -itd \
--privileged \
--network ovs \
--name c2 \
centos \
/sbin/init

c76ovn03
(7)
docker run -itd \
--privileged \
--network ovs \
--name c3 \
centos \
/sbin/init

構成を確認してみます。

確認コマンド
docker network ls
ovn-nbctl --db=tcp:192.168.30.101:6641 show
ovn-sbctl --db=tcp:192.168.30.101:6642 show
ovs-vsctl show

出力例
ここでは例としてc76ovn03にて実施します。
ovs-vsctl showだけは02&03の両方で出力させます。
[root@c76ovn03 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
116424d7f10a        bridge              bridge              local
db62b02926e4        docker_gwbridge     bridge              local
dcdbfcc3e390        host                host                local
da1e0883a8ed        none                null                local
b5fb702e9489        ovs                 openvswitch         global

[root@c76ovn03 ~]# ovn-nbctl --db=tcp:192.168.30.101:6641 show
switch b70b9243-80b3-4ad6-8940-55bff06251fc (b5fb702e948902881b1b933191b9ee83f8dad4eb434a9d27d408d7a2d5fe2aa2)
    port b7131d7ecd548dcc4b6eb38bef0179b1bc75645e5683b69e6131da69d0aed1be
        addresses: ["02:dc:06:e4:de:3c 192.168.31.2"]
    port 16b258df72cb52ddb2527be69578c1f953c9275e154d4107743b0ee3ac13f095
        addresses: ["02:26:b3:e1:ab:5c 192.168.31.3"]
    port l2gwport
        type: l2gateway
        parent:
        tag: 301
        addresses: ["unknown"]
    port 35d89c2d-0505-41aa-b0c6-6aff6f483899
        addresses: ["52:54:00:f6:45:c2"]
    port 2c5a0afd-b12a-419e-b6f5-718c5d0e6446
        addresses: ["52:54:00:d7:a6:96"]

[root@c76ovn03 ~]# ovn-sbctl --db=tcp:192.168.30.101:6642 show
Chassis "1fdd4703-9c73-4afd-adf3-e0e90b7afb63"
    hostname: "c76ovn03.md.jp"
    Encap geneve
        ip: "192.168.30.103"
        options: {csum="true"}
    Port_Binding "35d89c2d-0505-41aa-b0c6-6aff6f483899"
    Port_Binding "b7131d7ecd548dcc4b6eb38bef0179b1bc75645e5683b69e6131da69d0aed1be"
Chassis "28928b40-900b-4f48-b4ed-8d9d2f1082fd"
    hostname: "c76ovn02.md.jp"
    Encap geneve
        ip: "192.168.30.102"
        options: {csum="true"}
    Port_Binding "16b258df72cb52ddb2527be69578c1f953c9275e154d4107743b0ee3ac13f095"
    Port_Binding "2c5a0afd-b12a-419e-b6f5-718c5d0e6446"
    Port_Binding "l2gwport"

[root@c76ovn02 ~]# ovs-vsctl show
090be24a-f730-4d28-a4a0-dcf6d97fe618
    Bridge br-int
        fail_mode: secure
        Port "16b258df72cb52d"
            Interface "16b258df72cb52d"
        Port "vnet1"
            tag: 301
            Interface "vnet1"
        Port "patch-br-int-to-l2gwport"
            Interface "patch-br-int-to-l2gwport"
                type: patch
                options: {peer="patch-l2gwport-to-br-int"}
        Port "ovn-1fdd47-0"
            Interface "ovn-1fdd47-0"
                type: geneve
                options: {csum="true", key=flow, remote_ip="192.168.30.103"}
        Port br-int
            Interface br-int
                type: internal
    Bridge br-ext
        Port "patch-l2gwport-to-br-int"
            Interface "patch-l2gwport-to-br-int"
                type: patch
                options: {peer="patch-br-int-to-l2gwport"}
        Port br-ext
            Interface br-ext
                type: internal
        Port "ens36"
            trunks: [30, 300, 301]
            Interface "ens36"
    ovs_version: "2.11.1"

[root@c76ovn03 ~]# ovs-vsctl show
2cbade66-e4e8-48e1-83e7-2ff460c1d665
    Bridge br-ext
        Port "ens36"
            trunks: [300, 301]
            Interface "ens36"
        Port br-ext
            Interface br-ext
                type: internal
    Bridge br-int
        fail_mode: secure
        Port "ovn-28928b-0"
            Interface "ovn-28928b-0"
                type: geneve
                options: {csum="true", key=flow, remote_ip="192.168.30.102"}
        Port br-int
            Interface br-int
                type: internal
        Port "b7131d7ecd548dc"
            Interface "b7131d7ecd548dc"
        Port "vnet1"
            tag: 301
            Interface "vnet1"
    ovs_version: "2.11.1"

[root@c76ovn03 ~]# docker container exec c3 ping 192.168.31.2
PING 192.168.31.2 (192.168.31.2) 56(84) bytes of data.
64 bytes from 192.168.31.2: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 192.168.31.2: icmp_seq=2 ttl=64 time=0.070 ms
^C
[root@c76ovn03 ~]# docker container exec c3 ping 192.168.31.203
PING 192.168.31.203 (192.168.31.203) 56(84) bytes of data.
64 bytes from 192.168.31.203: icmp_seq=1 ttl=64 time=0.946 ms
64 bytes from 192.168.31.203: icmp_seq=2 ttl=64 time=0.697 ms
^C
[root@c76ovn03 ~]# docker container exec c3 ping 192.168.31.2
PING 192.168.31.2 (192.168.31.2) 56(84) bytes of data.
64 bytes from 192.168.31.2: icmp_seq=1 ttl=64 time=0.028 ms
64 bytes from 192.168.31.2: icmp_seq=2 ttl=64 time=0.073 ms
64 bytes from 192.168.31.2: icmp_seq=3 ttl=64 time=0.027 ms
^C
[root@c76ovn03 ~]# docker container exec c3 ping 192.168.31.202
PING 192.168.31.202 (192.168.31.202) 56(84) bytes of data.
64 bytes from 192.168.31.202: icmp_seq=1 ttl=64 time=2.34 ms
64 bytes from 192.168.31.202: icmp_seq=2 ttl=64 time=2.28 ms
64 bytes from 192.168.31.202: icmp_seq=3 ttl=64 time=2.98 ms
^C
[root@c76ovn03 ~]# docker container exec c3 ping 192.168.31.203
PING 192.168.31.203 (192.168.31.203) 56(84) bytes of data.
64 bytes from 192.168.31.203: icmp_seq=1 ttl=64 time=0.256 ms
64 bytes from 192.168.31.203: icmp_seq=2 ttl=64 time=0.984 ms
64 bytes from 192.168.31.203: icmp_seq=3 ttl=64 time=0.262 ms
^C
[root@c76ovn03 ~]# docker container exec c3 ping 192.168.31.254
PING 192.168.31.254 (192.168.31.254) 56(84) bytes of data.
64 bytes from 192.168.31.254: icmp_seq=1 ttl=64 time=9.48 ms
64 bytes from 192.168.31.254: icmp_seq=2 ttl=64 time=3.94 ms
64 bytes from 192.168.31.254: icmp_seq=3 ttl=64 time=3.91 ms

NorthboundDBとSouthboundDBの状態を確認する以下のコマンドですが

ovn-nbctl --db=tcp:192.168.30.101:6641 show
ovn-sbctl --db=tcp:192.168.30.101:6642 show

図に起こすとこんな感じになります。
f:id:metonymical:20190721180630p:plain
最初に載せた2つの図と合わせて観ると、より明確になってくるかと思います。

6.補足

ComputeNodeを再起動やShutdownしたいときは、以下のようなコマンドで必ずコンテナを終了させた後に、再起動やShutdownしてください。

docker container stop c2

というのも、Dockerコンテナのアタッチ時はほぼほぼ自動で設定してくれていましたが、デタッチ時は上記コマンドのように正常にコンテナを終了させないと、NorthboundDB上にゴミが残ります。場合によっては消せなくなるので、何度も繰り返し試したい方は必ずコンテナを終了させるようにしましょう。

以上です。

7.最後に

以下のサイトを参考にさせて頂きました。
OVN 設定サンプル | OVN config example 2015/12/27
Using OVN with KVM and Libvirt - Scott's Weblog - The weblog of an IT pro focusing on cloud computing, Kubernetes, Linux, containers, and networking
OVN - L2 Breakout Options | Blog @ Weiti.ORG
Dustin Spinhirne: The OVN Gateway Router

ovnは正直取っつきにくいかもしれません。
しかし、openstack stein以降では、ovnが実装されるようなので、今のうちから、しっかり理解しておくことは大切かなと思います。

また、今回はKVMとDockerでしたが、LXC/LXDでも試してみました。
しかし、FakeBridgeを使用する点が障壁となり疎通できませんでした。
さらに、アップリンク側にDPDKも動作させてみたのですが、設定方法は解ったものの、これまた疎通できずで断念しました。
Bridgeではなく、PAT+Routingだったら上手くいくような気がしていますが、取り急ぎ、一旦まとめてみようと思い今回の記事を書いた次第です。

なお、今回はl2gatewayという方法でBridgeさせましたが、localnetという方法もあります。こちらは、全ComputeNodeにてbr-extを作成した後、以下のコマンドでNorthboundDBに登録すれば設定可能です。

export SW_NAME=$(ovn-nbctl --db=tcp:192.168.30.101:6641 ls-list | awk '{print $2}' | sed s/\(//g | sed s/\)//g)
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-add $SW_NAME lcntport "" 301
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-addresses lcntport unknown
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-type lcntport localnet
ovn-nbctl --db=tcp:192.168.30.101:6641 lsp-set-options lcntport network_name=physnet301

localnetの場合、冗長化できそうな雰囲気はありますが、L2ループの懸念があるため、あまり推奨されていないようです。

ただ、いずれにせよ、ovn単体で使うのはあまり現実的ではないと感じたので、やはりopenstackやovirtなどと一緒に使用するのが良いと思います。

*1:例外はあります。例えば、(1')は自動で設定されますが、その後(1)を手動設定した際に初めて利用可能となる、といったケースがあります。また、(2)を手動設定した後、(3)を設定しないと、(2’)や(3’)がNorthboundDB上で認識されない、など。

*2:ここではUntagやvlan300も作成されていますが、vlan301のみでOKです。他は削除して構いません。

*3:私の環境では、正しい設定に直してもエラーが解消されなかったりしました。本編とあまり関係の無いところで時間を取られるのは、しんどいのでスナップショットやクローン化をおススメします。