안녕하세요. 오픈소스컨설팅의 엔지니어 조철진 입니다.
저는 오픈소스컨설팅에서 업무를 하다 보니 다양한 Private Cloud 환경을 접할 수 있었는데요. OpenStack 기술 지원을 하면서 발생하는 다양한 문제를 살펴보면 다른 구성 요소보다 네트워크에 대한 병목이 가장 시급하게 개선 되어야 할 문제였던 것 같습니다.
따라서, OVS만을 사용하였을 때 발생할 수 있는 문제들을 해결하기 위해 다양한 컨퍼런스와 서밋에서 소개되어 안정성과 성능이 입증된 OVN에 대한 학습과 적용이 필요했습니다. 이번 글에서는 OVN을 직접 KVM에 적용해보면서 학습해본 경험을 공유 드려보겠습니다.
OVN에 대한 구조와 이론적인 내용을 다루는 글은 다른 블로그나 공식 사이트에도 상당히 많이 소개가 되어있으며, 광범위한 사항들을 다루기 때문에 여기 서는 간단히 개념만 짚어보겠습니다. 제가 정리한 내용은 하단에 접어 둘 테니 나중에 시간이 되면 실습을 하며 다뤘던 부분이 어떤 이론에 해당하는지 살펴보면 좋겠습니다.
CMS의 구성 요소로, OVN의 인터페이스 역할을 합니다. OpenStack에서는 이 플러그인이 Neutron Plugin 으로 구현됩니다. 플러그인의 주요 목적은 CMS 고유 형식의 Logical Network 구성을 OVN이 이해할 수 있는 중간 표현(Intermediate Representation)방식으로 변환하는 것입니다.
OVN/CMS Plugin에서 전달 받은 중간 표현 방식의 Logical Network 구성을 저장합니다. 이 데이터베이스의 스키마는 CMS에서 사용하는 Logical Switch, Router, ACL 등의 개념을 OVN이 바로 해석할 수 있도록 설계되어 있습니다.
OVN Northbound Database와 OVN Southbound Database를 연결합니다.
Northbound Database에서 가져온 Logical Network 구성을 기존 Network 개념으로 변환하여 Southbound Database에 Logical Datapath Flows로 저장합니다.
시스템의 중심 역할을 하는 데이터베이스로 ovn-northd와 각 ovn-controller 가 클라이언트로 연결됩니다.
OVN Southbound Database는 다음과 같은 세 가지 주요 데이터를 포함합니다.
Hipervisor는 PN 및 Binding 테이블을 채우는 반면, ovn-northd는 LN 테이블을 채우는 역할을 합니다. 이는 각 구성 요소가 자신의 역할에 따라 필요한 데이터를 입력하고 관리하는 방식으로 작동합니다.
각 Hypervisor 및 Gateway 에 설치되는 OVN의 에이전트 입니다.
Geneve(Generic Network Virtualization Encapsulation)라는 OVN의 기본 캡슐화 프로토콜을 통해, Hypervisor 간 논리적 네트워크 트래픽을 전달합니다.
다음은 두 개의 Hypervisor가 연결된 OVN DB 예시입니다.
구분 선을 기준으로 좌측 테이블은 논리적인 네트워크를 담당하는 Northbound Database, 우측은
물리적인 네트워크를 담당하는 Southbound Database에 해당합니다.
Logical Switch (ls-int)
ls-int은 논리적 스위치로, LP1(포트 1)과 LP2(포트 2)가 연결되어 있습니다.
Logical Port (LP1, LP2)
LP1의 MAC 주소는 AA
.
LP2의 MAC 주소는 BB
.
Chassis (ovn-controller)
HV1: Geneve 터널링 프로토콜을 사용하며 IP는 10.0.0.10
HV2: Geneve 터널링 프로토콜을 사용하며 IP는 10.0.0.11
Bindings (ovn-controller)
LP1은 HV1에 바인딩되어 있습니다.
LP2는 아직 하이퍼바이저에 바인딩 되기 전 상태입니다.
Pipeline (ovn-northd)
데이터 패킷 흐름이 다음과 같이 정의됩니다:
eth.dst == AA
: LP1으로 라우팅eth.dst == BB
: LP2로 라우팅eth.dst == <broadcast>
: 브로드캐스트Bindings 테이블에 LP2는 HV2 하이퍼바이저로 바인딩 되도록 업데이트된 모습입니다.
이로서, ls-int 스위치에 연결된 두 하이퍼 바이저는 통신이 가능하게 됩니다.
Open vSwitch (OVS)의 가상 네트워크 관리 기능을 확장한 솔루션으로, 복잡한 멀티-테넌트 환경에서 보다 세밀한 네트워크 정책을 제공합니다. OVN은 OVS의 기본적인 L2, L3 스위칭 기능에 로드 밸런싱, ACL, NAT 등의 고급 기능을 제공하여, 가상 네트워크의 관리와 구성을 효율적으로 수행할 수 있도록 돕습니다.
Northbound 는 주로 네트워크 가상화의 관리를 위한 API를 애플리케이션에 제공하는 역할을 합니다. 이 인터페이스를 통해 사용자나 관리 시스템은 OVN 시스템에 논리 네트워크 구성을 명령할 수 있습니다. 예를 들어, 가상 네트워크, 스위치, 라우터의 생성 및 구성, ACL과 같은 요소들을 관리할 수 있습니다
Southbound 는 OVN 컨트롤러에서 명령을 내렸을 때, OVS 스위치에 실제로 설정을 적용하는 역할을 합니다. 또한 네트워크 상태를 실시간으로 모니터링 하여 최단 경로의 Flow로 통신하도록 경로를 최적화하고 장애 발생 시 트래픽을 다른 경로로 우회하는 등 성능 및 신뢰성 관련된 역할도 담당합니다.
서버/사용 네트워크 | Public Network: 10.10.10.0/24 Gateway: 10.10.10.1 | Private Network: 10.10.20.0/24 | VM IP Network: 172.16.0.0/24 Gateway: 172.16.0.1 |
---|---|---|---|
OVN Controller | 10.10.10.233 | X | X |
KVM1(VM1) | 10.10.10.234 | 10.10.20.234 | 172.16.0.201 |
KVM2(VM2) | 10.10.10.235 | 10.10.20.235 | 172.16.0.202 |
OVN 컨트롤러 노드
apt install -y ovn-host ovn-central ovn-common
# Northbound DB (6641), Southbound DB (6642) 포트 열기
ovn-nbctl set-connection ptcp:6641
ovn-sbctl set-connection ptcp:6642
KVM1, KVM2 노드
apt-get -y install ovn-host openvswitch-switch
# kvm1
ovs-vsctl set open . external_ids:ovn-remote="tcp:10.10.10.233:6642" \
external_ids:ovn-nb="tcp:10.10.10.233:6641" \
external_ids:ovn-encap-ip=10.10.10.234 \
external_ids:ovn-encap-type=geneve
# kvm2
ovs-vsctl set open . external_ids:ovn-remote="tcp:10.10.10.233:6642" \
external_ids:ovn-nb="tcp:10.10.10.233:6641" \
external_ids:ovn-encap-ip=10.10.10.235 \
external_ids:ovn-encap-type=geneve
# kvm1, kvm2
cat << EOF > ovs_net.xml
<network>
<name>ovs-net</name>
<forward mode='bridge'/>
<bridge name='br-int'/>
<virtualport type='openvswitch'/>
</network>
EOF
virsh net-define ovs_net.xml
virsh net-autostart ovs-net
virsh net-start ovs-net
root@ovn:~# ovn-sbctl show
Chassis "021bff7f-68cf-4625-8b37-340cae655674"
hostname: kvm1
Encap geneve
ip: "10.10.10.234"
options: {csum="true"}
Chassis "23e299e3-da54-457d-a8e7-850dfeaebfb7"
hostname: kvm2
Encap geneve
ip: "10.10.10.235"
options: {csum="true"}
root@kvm1:~# ovs-vsctl show
0215e66a-1ced-4105-8651-eb2eae66c6cb
Bridge br-int
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
Port ovn-23e299-0
Interface ovn-23e299-0
type: geneve
options: {csum="true", key=flow, remote_ip="10.10.10.235"}
ovs_version: "2.17.9"
root@kvm2:~# ovs-vsctl show
0215e66a-1ced-4105-8651-eb2eae66c6cb
Bridge br-int
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
Port ovn-021bff-0
Interface ovn-021bff-0
type: geneve
options: {csum="true", key=flow, remote_ip="10.10.10.234"}
ovs_version: "2.17.9"
# kvm1
virt-install \
--name vm1 \
--virt-type kvm \
--os-variant=generic \
--vcpus 2 \
--memory 2048 \
--disk "/var/lib/libvirt/images/ubuntest.qcow2",device=disk,bus=virtio,format=qcow2 \
--network bridge=br-int,virtualport_type=openvswitch,model=virtio \
--graphics none --noautoconsole --import
# kvm2
virt-install \
--name vm2 \
--virt-type kvm \
--os-variant=generic \
--vcpus 2 \
--memory 2048 \
--disk "/var/lib/libvirt/images/ubuntest.qcow2",device=disk,bus=virtio,format=qcow2 \
--network bridge=br-int,virtualport_type=openvswitch,model=virtio \
--graphics none --noautoconsole --import
root@kvm1:~# ovs-vsctl show
0215e66a-1ced-4105-8651-eb2eae66c6cb
Bridge br-int
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
Port vnet0
Interface vnet0
Port ovn-23e299-0
Interface ovn-23e299-0
type: geneve
options: {csum="true", key=flow, remote_ip="10.10.10.235"}
ovs_version: "2.17.9"
root@kvm2:~# ovs-vsctl show
0215e66a-1ced-4105-8651-eb2eae66c6cb
Bridge br-int
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
Port ovn-021bff-0
Interface ovn-021bff-0
type: geneve
options: {csum="true", key=flow, remote_ip="10.10.10.234"}
Port vnet0
Interface vnet0
ovs_version: "2.17.9"
root@kvm1:~# ovs-vsctl get interface vnet0 external_ids:iface-id
"c6c32709-bcd2-4585-b321-1606c349767a"
root@kvm1:~# ovs-vsctl get interface vnet0 external_ids:attached-mac
"52:54:00:c4:a1:03"
root@kvm2:~# ovs-vsctl get interface vnet0 external_ids:iface-id
"476e1fee-c08c-4495-9673-80246200de1f"
root@kvm2:~# ovs-vsctl get interface vnet0 external_ids:attached-mac
"52:54:00:5f:d5:6f"
OVN 컨트롤러 노드
ovn-nbctl ls-add ls-int
ovn-nbctl lsp-add ls-int c6c32709-bcd2-4585-b321-1606c349767a
ovn-nbctl lsp-set-addresses c6c32709-bcd2-4585-b321-1606c349767a "52:54:00:c4:a1:03 172.16.0.201/24"
ovn-nbctl lsp-add ls-int 476e1fee-c08c-4495-9673-80246200de1f
ovn-nbctl lsp-set-addresses 476e1fee-c08c-4495-9673-80246200de1f "52:54:00:5f:d5:6f 172.16.0.202/24"
root@ovn:~# ovn-nbctl list Logical_switch_port
_uuid : 606a0825-5dfa-4e5a-b516-b0aac9f15079
addresses : ["52:54:00:c4:a1:03 172.16.0.201/24"]
dhcpv4_options : []
dhcpv6_options : []
dynamic_addresses : []
enabled : []
external_ids : {}
ha_chassis_group : []
name : "c6c32709-bcd2-4585-b321-1606c349767a"
options : {}
parent_name : []
port_security : []
tag : []
tag_request : []
type : ""
up : true
_uuid : 6afc92eb-44a8-4cd8-9094-2be047fc3185
addresses : ["52:54:00:5f:d5:6f 172.16.0.202/24"]
dhcpv4_options : []
dhcpv6_options : []
dynamic_addresses : []
enabled : []
external_ids : {}
ha_chassis_group : []
name : "476e1fee-c08c-4495-9673-80246200de1f"
options : {}
parent_name : []
port_security : []
tag : []
tag_request : []
type : ""
up : true
KVM1, KVM2 노드
cat << /etc/netplan/0-default.yaml > EOF
network:
version: 2
renderer: networkd
ethernets:
ens2:
dhcp4: no
bridges:
br-ext:
openvswitch: {}
addresses:
- 10.10.10.234/24
routes:
- to: 0.0.0.0/0
via: 10.10.10.1
nameservers:
addresses: [8.8.8.8]
interfaces:
- ens2
EOF
netplan apply
ovs-vsctl set open . external-ids:ovn-bridge-mappings=UPLINK:br-ext
root@kvm1:~# ovs-vsctl show
0215e66a-1ced-4105-8651-eb2eae66c6cb
Bridge br-ext
fail_mode: standalone
Port br-ext
Interface br-ext
type: internal
Port ens2
Interface ens2
root@kvm1:~# ovs-vsctl list open .
_uuid : 0215e66a-1ced-4105-8651-eb2eae66c6cb
bridges : [3f241805-581b-46cd-b6cc-0cf50c0e9b51, ed0ffe18-e5e2-4b15-9417-203a707aac0a]
cur_cfg : 51
datapath_types : [netdev, system]
datapaths : {system=dfa80abf-9e7a-4c44-a972-924ec2756944}
db_version : "8.3.0"
dpdk_initialized : false
dpdk_version : none
external_ids : {hostname=kvm1, ovn-bridge-mappings="UPLINK:br-ext", ovn-encap-ip="10.10.10.234", ovn-encap-type=geneve, ovn-nb="tcp:10.10.10.233:6641", ovn-remote="tcp:10.10.10.233:6642", rundir="/var/run/openvswitch", system-id="021bff7f-68cf-4625-8b37-340cae655674"}
iface_types : [bareudp, erspan, geneve, gre, gtpu, internal, ip6erspan, ip6gre, lisp, patch, stt, system, tap, vxlan]
manager_options : []
next_cfg : 51
other_config : {vlan-limit="0"}
ovs_version : "2.17.9"
ssl : []
statistics : {}
system_type : ubuntu
system_version : "22.04"
OVN 컨트롤러 노드
ovn-nbctl ls-add ls-ext
ovn-nbctl lr-add lr-ext
ovn-nbctl lrp-add lr-ext lr-ext-ls-int 00:00:00:00:00:01 172.16.0.1/24
ovn-nbctl lrp-add lr-ext lr-ext-ls-ext 00:00:00:00:00:02 10.10.10.240/24
ovn-nbctl lsp-add ls-int ls-int-lr-ext
ovn-nbctl lsp-set-type ls-int-lr-ext router
ovn-nbctl lsp-set-addresses ls-int-lr-ext router
ovn-nbctl lsp-set-options ls-int-lr-ext router-port=lr-ext-ls-int
ovn-nbctl lsp-add ls-ext ls-ext-lr-ext
ovn-nbctl lsp-set-type ls-ext-lr-ext router
ovn-nbctl lsp-set-addresses ls-ext-lr-ext router
ovn-nbctl lsp-set-options ls-ext-lr-ext router router-port=lr-ext-ls-ext
ovn-nbctl lsp-add ls-ext ls-ext-br-ext
ovn-nbctl lsp-set-type ls-ext-br-ext localnet
ovn-nbctl lsp-set-addresses ls-ext-br-ext unknown
ovn-nbctl lsp-set-options ls-ext-br-ext network_name=UPLINK
ovn-nbctl lr-route-add lr-ext 0.0.0.0/0 10.10.10.1
ovn-sbctl show
로 조회한 Chassis 명을 사용ovn-nbctl lrp-set-gateway-chassis lr-ext-ls-ext 021bff7f-68cf-4625-8b37-340cae655674 100
ovn-nbctl lrp-set-gateway-chassis lr-ext-ls-ext 23e299e3-da54-457d-a8e7-850dfeaebfb7 75
ovn-nbctl lr-nat-add lr-ext snat 10.10.10.240 172.16.0.0/24
최종 토플로지 확인
root@ovn:~# ovn-nbctl show
switch 8bc87a18-7236-474e-802a-072e952c97d8 (ls-ext)
port ls-ext-lr-ext
type: router
router-port: lr-ext-ls-ext
port ls-ext-br-ext
type: localnet
addresses: ["unknown"]
switch 516923e7-805d-473f-9a7a-e29301dd7f74 (ls-int)
port ls-int-lr-ext
type: router
router-port: lr-ext-ls-int
port c6c32709-bcd2-4585-b321-1606c349767a
addresses: ["52:54:00:c4:a1:03 172.16.0.201/24"]
port 476e1fee-c08c-4495-9673-80246200de1f
addresses: ["52:54:00:5f:d5:6f 172.16.0.202/24"]
router 7cc0137d-83c1-4fe4-8a60-5bbf615279ae (lr-ext)
port lr-ext-ls-int
mac: "00:00:00:00:00:01"
networks: ["172.16.0.1/24"]
port lr-ext-ls-ext
mac: "00:00:00:00:00:02"
networks: ["10.10.10.240/24"]
gateway chassis: [23e299e3-da54-457d-a8e7-850dfeaebfb7 021bff7f-68cf-4625-8b37-340cae655674]
nat c29444cf-190e-4e86-b150-7691c025a699
external ip: "10.10.10.240"
logical ip: "172.16.0.0/24"
type: "snat"
여기까지, 진행했다면 내부 통신만 가능했던 VM에서 외부 인터넷으로 통신이 가능합니다.
지금까지 OVN을 구축해서 KVM에 적용해본 사례를 공유 드려보았습니다.
사실 이렇게 OVN을 따로 직접 만들어보면서 적용하는 것은 어떻게 보면 다소 무의미하고, 무모한 일이라고 생각되기도 합니다. 하지만 훨씬 크고 복잡한 클라우드 환경으로 구성을 해보기 앞서 이러한 구성 요소를 직접 다뤄보면서 학습하고 알아가는 과정은 추후 문제해결이나 개선 시에 도움이 되고, SDN/NFV에 대한 전반적 이해에도 도움이 될 것 입니다.
다음에는 OpenStack이나 Kubernetes에 적용한 OVN 사례를 보면서 이야기를 이어나가 볼 수 있도록 하겠습니다.
감사합니다!
[1] https://www.ovn.org/en/architecture/
[2] https://www.ducksource.blog/blog/sdn-with-ovn-and-ovs-lab-build/