2025년 12월 3일 수요일

DPDK 기반의 High Performance Load Balancer 만들기

이번 시간에는 High Performance Network Appliance Series의 마지막 주제인 DPDK 기반의 고성능 L4 Load Balancer 관한 이야기를 해 보고자 한다. 😎 

 

목차
1. DPDK L4 Load Balancer 개요
2. FD.io VPP 기반의 Load Balancer
3. DPVS 프로젝트 소개#1 - Build 하기
4. DPVS 프로젝트 소개#2 - LB 설정하기
5. References


요즘은 Cloud 전성 시대인 만큼 Load Balancer라고 하면 AWS, Azure, Google 등에서 말하는 Cloud LB를 많이들 연상하겠지만, 이번 posting에서는 좀 더 전통적인 방식이라고 할 수 있는 On-premise Load Balancer에 관한 얘기를 꺼내 볼까 한다. 😋


1. DPDK L4 Load Balancer 개요
DPDK는 고성능 network 장비를 만들기 위해 더 이상 없어서는 안될 중요한 존재가 되었다. 필자는 그 동안 여러 차례에 걸쳐서 DPDK 기반 network 기능을 소개한 바 있는데, 이번에 소개할 내용은 DPDK를 활용한 L4 load balancer가 되겠다.

<그 동안 소개했던 DPDK 기반 Network Functions>
  • DPDK L2/L3 switch(Bridging, Routing, NAT, ACL)
  • DPDK VPN(WireGuard, IPsec)
  • DPDK IPS(Suricata, Snort)
  • DPDK Web Server(Nginx)

[그림 1.1] DPDK bypass 기법 [출처 - 참고문헌 9]

(긴말 할 것 없이) L4 Load Balancer는 아래 그림에서 보는 것과 같이 특정 server로 향하는 트래픽을 L4 session 정보(IP, TCP/UDP header 정보)를 기준으로 적절히 분배해주는 장치를 뜻한다.

[그림 1.2] DPDK 기반 Load Balancer 개요(1) - Two Arms mode


[그림 1.3] DPDK 기반 Load Balancer 개요(2) - VRRP 이중화


[그림 1.4] DPDK 기반 Load Balancer 개요(3) - L3 Switch와 ECMP/OSPF를 통한 연계

일반적으로 L4 Load Balancer는 아래 그림과 같이 L7 Proxy(예: HAProxy or Nginx)와 연계하여 사용하는 경우가 많다. 이렇게 하는 이유는 단 한가지 ! 폭주하는 web 트래픽을 효과적으로 처리하기 위해서이다.

[그림 1.5] L4 Load Balancer와 L7 Proxy의 관계 [출처 - 참고문헌 7]

같은 이유에서 DPDK를 Load Balancer에 접목하는 것은 어찌보면 당연한 선택일 수 밖에 없다. 이번 posting에서는 아래와 같이 DPDK를 기반으로하는 2종류의 Load Balancer는 즉, VPP LB와 DPVS LB를 소개하고자 한다.

[그림 1.6] DPDK 기반 Load Balancer - VPP LB와 DPVS LB

과연 어느 것이 더 좋은 선택이 될 것인가 ?

끝으로, load balancing 테스트를 하기 위해서 알아야 할 내용 몇가지를 정리해 보면 다음과 같다.

<lb 환경 설정을 위한 준비 사항>
1) 보통 vip(virtual ip)라고 불리는 가상의 ip를 하나 설정해야 한다. client는 이 가상 ip를 통해 load balancer에 연결한다.
2) load balancer는 client로 부터의 연결 요청을 내부 (복수개의) real server(or application server)에 연결해 준다. 따라서 내부 real server 정보를 설정에 포함시켜야 한다.
3) load balancing의 동작 mode(tunnel, l3dsr, nat) 중 하나를 선택할 수 있어야 한다.
4) load balacning scheduling 알고리즘을 지정할 수 있어야 한다.
5) 기타 등등

이러한 대략적인 사항을 염두해 둔 상태에서, 다음 장으로 넘어가 보기로 하자. 😋


2. FD.io VPP 기반의 Load Balancer
FD.io VPP에는 L4 load balancer 기능(lb plugin과 nat plugin)이 탑재되어 있다. 이 장에서는 vpp load balancing의 개념을 살펴본 후, lb 및 nat44 명령을 사용하여 Load balancer를 만드는 방법을 소개해 보고자 한다.

[그림 2.1] VPP 기반의 Load Balancer

FD.io VPP와 관련한 기본 사항(build 및 구동 방법, 다양한 cli 명령 사용법 등)은 이전 posting을 참조해 주면 좋을 듯 싶다.


1.1 VPP 전용 Load Balancer 개요
VPP에는 아래와 같이 load balancing 전용 명령(lb)이 포함되어 있다.

vpp# lb ?
 lb as                                      lb as <vip-prefix> [protocol (tcp|udp) port <n>] [<address> [<address> [...]]] [del] [flush]
 lb conf                                  lb conf [ip4-src-address <addr>] [ip6-src-address <addr>] [buckets <n>] [timeout <s>]
 lb flush vip                           lb flush vip <prefix> [protocol (tcp|udp) port <n>]
 lb set interface nat4            lb set interface nat4 in <intfc> [del]
 lb set interface nat6            lb set interface nat6 in <intfc> [del]
 lb vip                                    lb vip <prefix> [protocol (tcp|udp) port <n>] [encap (gre6|gre4|l3dsr|nat4|nat6)] [dscp <n>] [type (nodeport|clusterip) target_port <n>] [new_len <n>] [src_ip_sticky] [del]

______________________________________________________

lb plugin은 기본적으로 cloud l4 load balancer를 지향하고 있는데, 이를 위해 아래와 같이 세 가지 유형의 encapsulation 방식을 제공한다.

유형#1) IPv4+GRE 및 IPv6+GRE encap: 특정 VIP(또는 VIP prefix)에 대해 수신된 트래픽은 GRE를 사용하여 서로 다른 AS(Application Server or Real Server)로 터널링된다. 이 방식은 특정 세션이 항상 동일한 AS로 터널링되도록 해준다. 단, 이 방식을 사용하려면 AS에도 별도의 GRE 터널 설정이 필요하다.

유형#2) IPv4+L3DSR encap: L3DSR(Layer 3 Direct Server Return)은 글자그대로 서버가 응답을 Load balancer를 거치지 않고 곧 바로 Client에게 전달하도록 하는 기법니다. 이를 위해 Load balancer는 VIP를 DSCP 비트에 매핑한 상태로 서버로 전송한다. 그러면 서버는 역으로 DSCP-VIP 매핑으로 부터 VIP 값을 알아낸 후, 이를 자신의 ip 대신 사용하여 client에게 응답 패킷을 보내게 된다. 따라서 이 방법 역시 AS에서 이를 처리하는 루틴이 준비되어 있어야만 가능하다.

[그림 2.2] VPP 기반 L3DSR Load Balancing 개념도 [출처: 참고 문헌 5]
📌 DSCP는 우선순위 정보를 담기 위해 사용(QoS)하는 ipv4 header 내의 필드이다. 이 값에 적절한 정보를 실어 보내고 이를 VIP 값과 matching 즉, DSCP 000010 => VIP x.x.x.2 하는 방법을 사용하여 load balancing을 구현한 것이 L3DSR이다. 

유형#3) IPv4/IPv6 + NAT4/NAT6 캡슐화: 이 방식은 사용자 공간에서 동작하는 kube-proxy 데이터 플레인을 제공하는데, 이는 iptables 기반 Linux 커널의 kube-proxy를 대체하는 데 사용된다. NAT를 사용하는 방식이지만, 어쩐지 kubernetes 환경에서만 동작하도록 되어 있는 듯 보인다(실제로 그런지 시험을 통해 확인해 보도록 하자).

[그림 2.3] VPP kube-proxy data plane [출처 - 참고문헌 12]


[그림 2.4] VPP kube-proxy external load balancer [출처 - 참고문헌 12]

1.2 LB Plugin Load Balancer 시험하기#1 - Tunnel mode
먼저, 아래 testbed를 참조하여 GRE 터널을 이용한 load balancing 시험을 진행해 보기로 하자.

[그림 2.5] 두개의 포트를 사용하는 GRE tunnel mode LB 시험 환경

<AS#1 gre tunnel>
sudo modprobe ip_gre
sudo ip tunnel add gre1 mode gre remote 192.168.5.254 local 192.168.5.10 ttl 255
sudo ip addr add 10.10.10.1/24 peer 10.10.10.254 dev gre1
sudo ip link set gre1 up

<AS#2 gre tunnel>
sudo modprobe ip_gre
sudo ip tunnel add gre1 mode gre remote 192.168.5.254 local 192.168.5.50 ttl 255
sudo ip addr add 10.10.10.2/24 peer 10.10.10.254 dev gre1
sudo ip link set gre1 up

<Target VPP 장치>
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0

vpp# lb conf ip4-src-address 192.168.5.254
vpp# lb vip 192.168.1.254/32 protocol tcp port 22 encap gre4
vpp# lb as 192.168.1.254/32 protocol tcp port 22 10.10.10.1 10.10.10.2

이 상태에서 테스트를 해 보면, 설정에 문제가 있는지 원하는대로 load balancing이 동작을 안한다. 몇가지 의심가는 설정을 변경하여 테스트해 보아도 제대로 동작하질 않는다. 😓

1.3 LB Plugin Load Balancer 시험하기#2 - NAT mode
이번에는, 아래와 같은 환경에서 2 port를 사용하는 nat 기반의 load balancing이 동작하는지 시험해 보기로 하자. 단, 앞서 설명한 내용으로 봐서는 kubernetes 환경이 아닌 만큼, 제대로 동작될 지 의구심이 들긴 한다. 💢

[그림 2.6] 두개의 포트를 사용하는 NAT mode LB 시험 환경

<Target VPP 장치>
vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24

vpp# lb vip 192.168.1.254/32 protocol tcp port 22 encap nat4 type clusterip target_port 22
vpp# lb as 192.168.1.254/32 protocol tcp port 22 192.168.5.10 192.168.5.50
vpp# lb conf ip4-src-address 192.168.5.254
vpp# lb set interface nat4 in TwoDotFiveGigabitEtherneta/0/0

이 상태에서 client로 부터 ssh 연결을 시도해 보았으나, 역시나 동작하지 않는다. 😓

의심가는 몇가지 설정(파란색 설정 부분)을 수정해 보도록 한다.

vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
vpp# lb vip 192.168.1.253/32 protocol tcp port 22 encap nat4 type clusterip target_port 22 new_len 1024
lb_vip_add ok 1
vpp# lb as 192.168.1.253/32 protocol tcp port 22 192.168.5.10 192.168.5.50
vpp# lb conf ip4-src-address 192.168.5.254 buckets 1024 timeout 30
vpp# lb set interface nat4 in TwoDotFiveGigabitEtherneta/0/0
vpp# 
vpp# show lb vips verbose
 ip4-nat4 [1] 192.168.1.253/32
  new_size:1024
  protocol:6 port:22
  type:clusterip port:5632 target_port:22  counters:
    packet from existing sessions: 0
    first session packet: 0
    untracked packet: 0
    no server configured: 0
  #as:2
    192.168.5.50 512 buckets   0 flows  dpo:14 used
    192.168.5.10 512 buckets   0 flows  dpo:13 used

vip 설정을 변경해 보았으나, 나아질 기미가 보이지 않는다. 정말 cloud 환경에서만 사용 가능한 것일까 ? 😓

1.4 NAT44 plugin 기반 Load Balancer 시험하기
이제부터는 NAT44 static mapping 기능을 사용하여 load balacing 기능을 시험해 보기로 한다.

[그림 2.7] VPP 기반 Load Balancer Testbed - NAT44 기반

vpp# nat44 add load-balancing ?
 nat44 add load-balancing back-end        nat44 add load-balancing back-end protocol tcp|udp external <addr>:<port> local <addr>:<port> [vrf <table-id>] probability <n> [del]

 nat44 add load-balancing static mapping  nat44 add load-balancing static mapping protocol tcp|udp external <addr>:<port> local <addr>:<port> [vrf <table-id>] probability <n> [twice-nat|self-twice-nat] [out2in-only] [affinity <timeout-seconds>] [del]

두가지 방법이 있는데, 아래 명령이 더 많은 option을 담고 있으니, 이를 사용해 보도록 하자.
======================

vpp# set int state TwoDotFiveGigabitEtherneta/0/0 up
vpp# set int ip address TwoDotFiveGigabitEtherneta/0/0 192.168.5.254/24

  => LAN port를 up시키고, ip를 할당한다.

vpp# set interface state TwoDotFiveGigabitEthernet11/0/0 up
vpp# set int ip address TwoDotFiveGigabitEthernet11/0/0 192.168.1.254/24
  => WAN port를 up시키고, ip를 할당한다.

vpp# ip route add 0.0.0.0/0 via 192.168.1.1 TwoDotFiveGigabitEthernet11/0/0
  => default gateway를 추가한다.

vpp# nat44 forwarding enable
   => nat44 forwardng을 enable 시킨다.
vpp# nat44 plugin enable sessions 65535
vpp# set interface nat44 in TwoDotFiveGigabitEtherneta/0/0 out TwoDotFiveGigabitEthernet11/0/0
  => LAN port를 input으로 하고, WAN port를 output으로 하는 NAT44 rule을 추가한다.
vpp# nat44 add interface address TwoDotFiveGigabitEthernet11/0/0
 => NAT44 interface로 WAN port를 등록한다.

이 상태에서 마지막에 아래 명령을 추가해 주도록 한다.

vpp# nat44 add load-balancing static mapping protocol tcp external 192.168.1.254:22 local 192.168.5.10:22 probability 50 local 192.168.5.50:22 probability 50
 => 내부에 server가 여러 개일 경우, local ip:port 형태로 반복해서 열거해 주면 된다. 물론 이때  probability 값도 그에 맞게 조정해 주어야 한다. 2개의 내부 서버를 설정해 주었으므로 probability 값은 50이다. 만일 내부에 4개의 서버가 있다면 probability 값은 25가 되어야 한다.
 => scheduling algorithm: weighted round robin 방식으로 보아야 한다.

혹은

vpp# nat44 add load-balancing static mapping protocol tcp external 192.168.1.254:22 local 192.168.5.10:22 probability 50 local 192.168.5.50:22 probability 50 self-twice-nat
 => self-twice-nat은 source와 destination address를 모두 변경하는 full-nat 개념임.
   
혹은

vpp# nat44 add load-balancing static mapping protocol tcp external 192.168.1.254:22 local 192.168.5.10:22 probability 50 local 192.168.5.50:22 probability 50 self-twice-nat affinity 10
  => affinity <timeout> option을 사용하면, health check 기능을 흉내낼 수가 있다. 즉, 위와 같이 설정할 경우, real server(backend server)로 부터 10초간 반응이 없을 경우, 자동으로 다음 server로 연결되게 된다.

vpp# show nat44 static mappings
NAT44 static mappings:
TCP external 192.168.1.254:22   
 local 192.168.5.10:22 vrf 0 probability 50
 local 192.168.5.50:22 vrf 0 probability 50

이 상태에서 Client로 부터 ssh 연결을 시도해 보도록 한다.

<Windows PC>
ssh chyi@192.168.1.254
  => 테스트 결과, 1:1 비율로 ssh service가 분배되는 것을 알 수 있다. 😀
__________________________________________________________

지금까지 vpp에서 제공하는 load balancing 기능을 간략히 살펴 보았다. lb plugin은 기본적으로 cloud load balancer를 지향하고 있어서 그런지, on-premise 구성에서는 정상 동작하지 않는 것 같다(필자가 아직 뭔가를 제대로 이해하지 못한 것 같기도 하다. 😂). 하지만, nat plugin에서 제공하는 load balancing 기능을 사용한다면, 간단하면서도 사용하기 쉬운 load balancer를 구축할 수 있을 것으로 보인다.


3. DPVS 프로젝트 소개#1 - Build 하기
이번 장에서는 DPDK 기반의 또 다른 load balancer solution인 DPVS project에 대해 study해 보기로 하자.

3.1 DPVS(DPDK Virtual Server) 개요
DPVS(DPDK LVS)는 DPDK에 기반한 고성능 Layer 4 load balancer로 (아주 오래된 프로젝트인)Linux LVS projectalibaba/LVS project에서 파생되었다.

[그림 3.1] DPVS overview1 [출처 - 참고문헌 1]

DPVS는 고성능을 내기 위해 아래와 같은 다양한 기법을 사용한다(원문이 의미 전달에 유리할 듯하여 그대로 사용함).

  • Kernel by-pass (user space implementation).
  • Share-nothing, per-CPU for key data (lockless).
  • RX Steering and CPU affinity (avoid context switch).
  • Batching TX/RX.
  • Zero Copy (avoid packet copy and syscalls).
  • Polling instead of interrupt.
  • Lockless message for high performance IPC.
  • Other techs enhanced by DPDK.

[그림 3.2] DPVS overview2 [출처 - 참고문헌 1]

DPVS load balancer의 주요 특징을 정리해 보면 다음과 같다(역시 원문을 그대로 유지해 둠).
  • L4 Load Balancer, supports FNAT, DR, Tunnel and DNAT reverse proxy modes.
  • NAT64 mode for IPv6 quick adaption without changing backend server.
  • SNAT mode for Internet access from internal network.
  • Adequate schedule algorithms like RR, WLC, WRR, MH(Maglev Hash), Conhash(Consistent Hash), etc.
  • User-space lite network stack: IPv4, IPv6, Routing, ARP, Neighbor, ICMP, LLDP, IPset, etc.
  • Support KNIVLANBondingIP Tunnel for different IDC environment.
  • Security aspects support TCP SYN-proxyAllow/Deny ACL.
  • QoS features such as Traffic ControlConcurrent Connection Limit.
  • Versatile tools, services can be configured with dpip ipvsadm command line tools, or from config files of keepalived, or via restful API provided by dpvs-agent.

[그림 3.3] DPVS LB Architecture [출처 - 참고문헌 1]

3.2 DPVS project build 하기(1차 시도)
먼저, 아래 site의 내용을 참조하여 DPVS source code를 build해 보기로 하자. 여기에서는 Ubuntu 22.04 or 24.04 LTS를 대상으로 설명한다.


<Ubuntu 22.04 LTS - build PC>
a) Build dependency#1
$ sudo apt install automake libnl-3-dev libnl-genl-3-dev openssl libpopt-dev pkg-config numactl
📌 참고로, pkg-config >= 0.29.2이 설치되어야 한다.

b) Build dependency#2 - libxdp, libbpf 설치
$ git clone https://github.com/xdp-project/xdp-tools
$ cd xdp-tools/
$ ./configure
$ make
$ sudo make install
chyi@earth:/usr/local/lib$ ls -l libxdp*
-rw-rw-r-- 1 chyi chyi 500030 11월 20 20:45 libxdp.a
lrwxrwxrwx 1 chyi chyi     11 11월 20 20:45 libxdp.so -> libxdp.so.1
lrwxrwxrwx 1 chyi chyi     15 11월 20 20:45 libxdp.so.1 -> libxdp.so.1.5.0
-rwxrwxr-x 1 chyi chyi 267000 11월 20 20:45 libxdp.so.1.5.0

$ git clone https://github.com/libbpf/libbpf
$ cd libbpf/
$ cd src
$ make
$ sudo make install
$ ls -l /usr/lib/x86_64-linux-gnu/libbpf.so*
lrwxrwxrwx 1 root root     22  4월 23  2025 /usr/lib/x86_64-linux-gnu/libbpf.so -> /usr/lib64/libbpf.so.1
lrwxrwxrwx 1 root root     15  3월  8  2024 /usr/lib/x86_64-linux-gnu/libbpf.so.0 -> libbpf.so.0.5.0
-rw-r--r-- 1 root root 318056 12월  1  2022 /usr/lib/x86_64-linux-gnu/libbpf.so.0.5.0
📌 이후 build 과정에서 확인해 보니, 얘 대신 libbpf.so.1이 사용되었다.

c) Build dependency#3 - openssl 1.1.1w version 설치
(나중에 안 사실이지만) dpvs는 openssl 1.x대의 코드를 일부 사용하고 있다. 따라서 openssl 3.x version을 사용하는 경우라면, 아래와 같이 1.x version을 내려 받아 build해 줄 필요가 있다.

$ wget https://openssl-library.org/source/old/1.1.1/openssl-1.1.1w.tar.gz
$ cd openssl-1.1.1w/
$ mkdir output
$ ./Configure shared no-sse2 no-zlib --prefix=/mnt/hdd/workspace/VPP/DPVS/openssl-1.1.1w/output --openssldir=/mnt/hdd/workspace/VPP/DPVS/openssl-1.1.1w/output/openssl
$ make
$ make install
$ cd output
$ ls -la
합계 28
drwxrwxr-x  7 chyi chyi 4096 11월 21 15:21 .
drwxrwxr-x 20 chyi chyi 4096 11월 21 15:21 ..
drwxrwxr-x  2 chyi chyi 4096 11월 21 15:21 bin
drwxrwxr-x  3 chyi chyi 4096 11월 21 15:21 include
drwxrwxr-x  4 chyi chyi 4096 11월 21 15:21 lib
drwxrwxr-x  5 chyi chyi 4096 11월 21 15:21 openssl
drwxrwxr-x  4 chyi chyi 4096 11월 21 15:21 share

$ export PKG_CONFIG_PATH=/mnt/hdd/workspace/VPP/DPVS/openssl-1.1.1w/output/lib/pkgconfig

d) dpdk 24.11 version build
dpvs v1.10 버젼에 대해서는 dpdk 24.11 version을 사용할 것을 권고하고 있다. 따라서, 여기에서는 해당 version을 내려 받아 build하기로 한다.

$ wget https://fast.dpdk.org/rel/dpdk-24.11.tar.xz
$ tar xvJf ./dpdk-24.11.tar.xz
$ cd dpdk-24.11/
$ cp ../dpvs/patch/dpdk-24.11/*.patch .
  -> 미리 받아둔 dvps source code로 부터 몇가지 patch file을 복사하여 patch를 하도록 한다.
$ patch -p1 < 0001-pdump-add-cmdline-packet-filters-for-dpdk-pdump-tool.patch 
patching file app/pdump/main.c
patching file lib/pdump/rte_pdump.c
patching file lib/pdump/rte_pdump.h
$ patch -p1 < 0002-debug-enable-dpdk-eal-memory-debug.patch 
patching file lib/eal/common/rte_malloc.c
patching file lib/eal/include/rte_malloc.h
$ patch -p1 < 0003-ixgbe_flow-patch-ixgbe-fdir-rte_flow-for-dpvs.patch 
patching file drivers/net/ixgbe/ixgbe_flow.c
$ patch -p1 < 0004-bonding-allow-slaves-from-different-numa-nodes.patch 
patching file drivers/net/bonding/rte_eth_bond_pmd.c
$ patch -p1 < 0005-bonding-fix-problem-in-mode-4-dropping-multicast-pac.patch 
patching file drivers/net/bonding/rte_eth_bond_pmd.c
$ patch -p1 < 0006-bonding-device-supports-sending-packets-from-user-sp.patch 
patching file drivers/net/bonding/rte_eth_bond_pmd.c
patching file lib/mbuf/rte_mbuf.h

$ mkdir dpdklib
$ mkdir dpdkbuild
$ meson setup -Denable_kmods=true -Dprefix=/mnt/hdd/workspace/VPP/DPVS/dpdk-24.11/dpdklib dpdkbuild

$ ninja -C dpdkbuild
$ cd dpdkbuild; ninja install
$ cd ../dpdklib
$ ls -la
합계 40
drwxrwxr-x  6 chyi chyi  4096 11월 21 15:35 .
drwxrwxr-x 18 chyi chyi  4096 11월 21 15:34 ..
drwxr-xr-x  2 chyi chyi  4096 11월 21 15:35 bin
drwxr-xr-x  3 chyi chyi 20480 11월 21 15:35 include
drwxr-xr-x  3 chyi chyi  4096 11월 21 15:35 lib
drwxr-xr-x  3 chyi chyi  4096 11월 21 15:35 share

$ export PKG_CONFIG_PATH=/mnt/hdd/workspace/VPP/DPVS/openssl-1.1.1w/output/lib/pkgconfig:/mnt/hdd/workspace/VPP/DPVS/dpdk-24.11/dpdklib/lib/x86_64-linux-gnu/pkgconfig

e) dvps build 하기
$ git clone https://github.com/iqiyi/dpvs
$ cd dpvs
$ git checkout v1.10.2
$ vi config.mk
  -> Golang으로 구현한 dpvs_agent와 healthcheck daemon을 build하기 위해 설정을 변경해 준다.
#export CONFIG_DPVS_AGENT=n
export CONFIG_DPVS_AGENT=y
...
$ make
...
에러 발생

$ vi tools/dpip/lldp.c
아래 위치에서 어이 없는 에러가 발생한다. 코드가 문제가 있으니, 일단 막기로 한다.
...
#if 0 
            printf(message->message);
#endif

$ make
$ make install
$ cd bin
$ ls -la
합계 49276
drwxrwxr-x  2 chyi chyi     4096 11월 21 16:08 .
drwxrwxr-x 15 chyi chyi     4096 11월 21 16:08 ..
-rwxr--r--  1 chyi chyi   321680 11월 21 16:08 dpip
-rwxr--r--  1 chyi chyi 27721128 11월 21 16:08 dpvs
-rwxr--r--  1 chyi chyi 13533368 11월 21 16:08 dpvs-agent
-rwxr--r--  1 chyi chyi  8102072 11월 21 16:08 healthcheck
-rwxr--r--  1 chyi chyi   200336 11월 21 16:08 ipvsadm
-rwxr--r--  1 chyi chyi   557512 11월 21 16:08 keepalived

  • dpvs => main load balancer routine(program)
  • dpip => IP address, route, vlan, neigh 등 관리 tool
  • ipvsadm and keepalived => LVS project에서 가져옴(수정함). ipvsadm은 load balancing 기능 담당, keepalived는 vrrp 이중화 기능 담당
  • dpvs-agent and healthcheck => keepalived를 대체하기 위해 Golang으로 구현하였으며, HTTP API를 제공함.

$ sudo ldconfig
$ export LD_LIBRARY_PATH=/mnt/hdd/workspace/VPP/DPVS/openssl-1.1.1w/output/lib:/mnt/hdd/workspace/VPP/DPVS/dpdk-24.11/dpdklib/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH

$ ./dpvs -h
./dpvs: error while loading shared libraries: libbpf.so.1: cannot open shared object file: No such file or directory

libbpf.so.1 파일을 찾아 보니, (이미) /usr/lib64 디렉토리에 있다.

export LD_LIBRARY_PATH=/mnt/hdd/workspace/VPP/DPVS/openssl-1.1.1w/output/lib:/usr/lib64:/mnt/hdd/workspace/VPP/DPVS/dpdk-24.11/dpdklib/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
$ ./dpvs -h

Usage: ./dpvs DPVS application options:
   -v, --version           display DPVS version info
   -c, --conf FILE         specify config file for DPVS
   -p, --pid-file FILE     specify pid file of DPVS process
   -x, --ipc-file FILE     specify unix socket file for ipc communication between DPVS and Tools
   -h, --help              display DPVS help info

3.3 DPVS project build 하기(2차 시도)
여기까지 build한 결과물을 target 장치로 올려 실제로 돌려 보니, 허걱 ~ 아래와 같은 문제가 발생한다. 😓 

<Ubuntu 24.04 LTS - Target device>
$ cat dpvs_run.sh 
#!/bin/sh
export LD_LIBRARY_PATH=/home/chyi/dpvs/dpvs_out/bpf_xdp:/home/chyi/dpvs/dpvs_out/dpdk/lib/x86_64-linux-gnu:/home/chyi/dpvs/dpvs_out/openssl/lib:$LD_LIBRARY_PATH
./dpvs -- -a 0000:0a:00.0 -l 1-9

$ sudo ./dpvs_run.sh 
current thread affinity is set to FFFFFFFFF
ERROR: This system does not support "VPCLMULQDQ".
Please check that RTE_MACHINE is set correctly.
EAL: unsupported cpu type.
EAL: Error - exiting with code: 1
Invalid EAL parameters

이 문제를 해결하기 위해, 아래와 같이 cpu model 정보 및 vpclmulqdq에 대한 no option을 주고 다시 시도해 보았으나, 결과가 동일하다. 도대체 무엇이 문제일까 ?

<Ubuntu 22.04 LTS - build PC>
$ meson -Dplatform=native -Dcpu_instruction_set=broadwell -Denable_kmods=true -Dprefix=/home/chyi/dpvs/source/dpdk-24.11/dpdklib -Dc_args=-mno-vpclmulqdq -Dcpp_args=-mno-vpclmulqdq dpdkbuild

Build 작업을 수행한 PC는 Ubuntu 22.04 LTS이지만, Target 장치는 Ubuntu 24.04 LTS(Server)라는 차이가 있다. 따라서, Target 장치에서 위의 build 과정을 반복해 보기로 한다. 헐, 그런데, 이번에는 dvps를 build하는 과정에서 또 다른 문제가 발생한다(산너머 산이다). 😅

$ cd dpvs
$ make
...
 /mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.c -o /mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.o
In file included from /usr/lib/gcc/x86_64-linux-gnu/13/include/immintrin.h:43,
                 from /usr/lib/gcc/x86_64-linux-gnu/13/include/x86intrin.h:32,
                 from /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/include/rte_vect.h:26,
                 from /mnt/hdd/workspace/DPVS/dpvs/src//../include/dpdk.h:22,
                 from /mnt/hdd/workspace/DPVS/dpvs/src//../include/ipvs/proxy_proto.h:23,
                 from /mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.c:18:
In function ‘_mm256_loadu_si256’,
    inlined from ‘rte_mov32’ at /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/include/rte_memcpy.h:127:9,
    inlined from ‘rte_mov64’ at /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/include/rte_memcpy.h:149:2,
    inlined from ‘rte_mov128’ at /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/include/rte_memcpy.h:161:2,
    inlined from ‘rte_memcpy_generic’ at /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/include/rte_memcpy.h:422:4,
    inlined from ‘rte_memcpy’ at /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/include/rte_memcpy.h:757:10,
    inlined from ‘proxy_proto_insert’ at /mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.c:572:9:
/usr/lib/gcc/x86_64-linux-gnu/13/include/avxintrin.h:929:10: error: array subscript ‘__m256i_u[3]’ is partly outside array bounds of ‘char[108]’ [-Werror=array-bounds=]
  929 |   return *__P;
      |          ^~~~
/mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.c: In function ‘proxy_proto_insert’:
/mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.c:426:10: note: at offset 96 into object ‘ppv1buf’ of size 108
  426 |     char ppv1buf[108], tbuf1[INET6_ADDRSTRLEN], tbuf2[INET6_ADDRSTRLEN];
      |          ^~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:81: /mnt/hdd/workspace/DPVS/dpvs/src/ipvs/ip_vs_proxy_proto.o] 오류 1
make[1]: 디렉터리 '/mnt/hdd/workspace/DPVS/dpvs/src' 나감
make: *** [Makefile:36: all] 오류 1

얘는 또 뭐냐 ? 확인해 보니, Ubuntu 24.04에 기본으로 탑재된 gcc 13.x이 문제를 일으키는 것으로 보인다. 따라서 gcc-11 version으로 다시 시도해 본다.

$ make HOSTCC=gcc-11 CC=gcc-11
📌 Makefile을 직접 편집하여 gcc => gcc-11로 수정해도 좋다.

$ vi src/dpdk.mk
아래와 같이 dpdk pkgconfig path도 함께 수정해 주자.
# LIBDPDKPC_PATH := /path/to/dpdk/build/lib/pkgconfig
LIBDPDKPC_PATH := /mnt/hdd/workspace/DPVS/dpdk-24.11/dpdklib/lib/x86_64-linux-gnu/pkgconfig
...

OK, 몇 차례의 시행 착오를 거쳐, 결국 build에 성공하였다. 😎 아래 내용은, 지금까지 build하면서 발생한 주의 사항을 요약해 본 것이다.

<Build 시 주의 사항>
[1] dpdk와 dpvs code에서 openssl 1.1.x version을 사용하고 있음. Ubuntu 22.04 이상의 경우 openssl 3.x version이 사용되고 있으니, 별도로 build해서 사용해야 함.
  ==> 적절한 PKG_CONFIG_PATH 설정이 필요하다.

[2] dpvs는 gcc 11.x version으로 build해야 함. Ubuntu 24.04에 있는 gcc 13.x로 build시 아래 에러 발생함.
/usr/lib/gcc/x86_64-linux-gnu/13/include/avxintrin.h:929:10: error: array subscript ‘__m256i_u[3]’ is partly outside array bounds of ‘char[108]’ [-Werror=array-bounds=]
  ==> 이 경우, gcc 대신 gcc-11을 사용하도록 Makefile을 수정하여 해결함.

[3] dpvs 실행 시, 아래 에러 발생
ERROR: This system does not support "VPCLMULQDQ".
Please check that RTE_MACHINE is set correctly.
  ==> 이 경우, target 장치(Ubuntu 24.04 LTS)에서 직접 build하여 문제를 해결함.
________________________________________________________________

OK, 이상으로 dpdk와 dpvs code를 정상적으로 build하기까지의 과정을 상세히 알아 보았다. 이어지는 장에서는 앞서 build한 내용을 target 장치에서 돌려 보며, Load Balancing 기능이 정상 동작하는지를 확인해 보도록 하자.


4. DPVS 프로젝트 소개#2 - LB 설정하기
이번 장에서는 앞 장에서 build한 DPVS Load Balacer가 정상 동작하는지를 확인해 보고자 한다. 다행히도 다양한 load balancing 환경 구성과 관련하여 아래 tutorial에 자세히 설명되어 있다. 😍



[그림 4.1] DPVS Load Balancer


4.1 Target 장치에서 dpvs 돌려 보기
먼저, (늘 그렇듯이) DPDK 환경 설정을 먼저 해야 한다.

<Ubuntu 24.04 LTS - Target device>
$ sudo su
# sh -c 'echo 8192 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages'
📌 1024로 설정했더니, (dpvs 실행 시) memory가 부족하다는 error를 출력한다.
# sudo sh -c 'echo 8192 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages'

# mkdir /mnt/huge
# mount -t hugetlbfs nodev /mnt/huge

# modprobe vfio-pci
# ifconfig enp10s0 down
# ifconfig enp17s0 down
# dpdk-devbind.py -b vfio-pci 0000:0a:00.0
# dpdk-devbind.py -b vfio-pci 0000:11:00.0
# dpdk-devbind.py -s

Network devices using DPDK-compatible driver
============================================
0000:0a:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic
0000:11:00.0 'Ethernet Controller I226-V 125c' drv=vfio-pci unused=igc,uio_pci_generic

Network devices using kernel driver
===================================
0000:0b:00.0 'Ethernet Controller I226-V 125c' if=enp11s0 drv=igc unused=vfio-pci,uio_pci_generic 
0000:0c:00.0 'Ethernet Controller I226-V 125c' if=enp12s0 drv=igc unused=vfio-pci,uio_pci_generic 
0000:0d:00.0 'Ethernet Controller I226-V 125c' if=enp13s0 drv=igc unused=vfio-pci,uio_pci_generic *Active*
0000:0e:00.0 'Ethernet Controller I226-V 125c' if=enp14s0 drv=igc unused=vfio-pci,uio_pci_generic 
0000:0f:00.0 'Ethernet Controller I226-V 125c' if=enp15s0 drv=igc unused=vfio-pci,uio_pci_generic 
0000:10:00.0 'Ethernet Controller I226-V 125c' if=enp16s0 drv=igc unused=vfio-pci,uio_pci_generic 
...

다음으로 dpvs config file을 준비하도록 하자.

# cp dpvs/dpvs.conf.single-nic.sample  /etc/dpvs.conf
# vi /etc/dpvs.conf

[그림 4.2] /etc/dpvs.conf 파일 수정

이번에는 main program인 dpvs를 구동시켜 볼 차례이다. 앞 장에서 build해 둔, library path를 아래와 같이 지정하도록 하자.

export LD_LIBRARY_PATH=/home/chyi/dpvs/dpvs_out/bpf_xdp:/home/chyi/dpvs/dpvs_out/dpdk/lib/x86_64-linux-gnu:/home/chyi/dpvs/dpvs_out/openssl/lib:$LD_LIBRARY_PATH
📌 물론, build 후 설치 시에 적절한 system directory를 선택하여 진행했다면, 이 단계가 굳이 필요 없을 수도 있다.

# ./dpvs -- -a 0000:0a:00.0 -l 0-4
📌 -l 0-4는 lcore 0-4를 의미하며, 이는 /etc/dpvs.conf 내용과 일치해야 한다.
📌 위의 LD_LIBRARY_PATH 설정 내용은 .bashrc나 .profile에 유지해 두면 편리하다.

current thread affinity is set to FFFFFFFFF
EAL: Detected CPU lcores: 36
EAL: Detected NUMA nodes: 1
EAL: Detected static linkage of DPDK
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'VA'
EAL: VFIO support initialized
EAL: Using IOMMU type 1 (Type 1)
DPVS: dpvs version: 1.10-2, build on 2025.11.25.13:27:23
DPVS: dpvs-conf-file: /etc/dpvs.conf
DPVS: dpvs-pid-file: /var/run/dpvs.pid
DPVS: dpvs-ipc-file: /var/run/dpvs.ipc
CFG_FILE: Opening configuration file '/etc/dpvs.conf'.
CFG_FILE: log_level = WARNING
NETIF: dpdk0:rx_queue_number = 4
NETIF: worker cpu1:dpdk0 rx_queue_id += 0
NETIF: worker cpu1:dpdk0 tx_queue_id += 0
NETIF: worker cpu2:dpdk0 rx_queue_id += 1
NETIF: worker cpu2:dpdk0 tx_queue_id += 1
NETIF: worker cpu3:dpdk0 rx_queue_id += 2
NETIF: worker cpu3:dpdk0 tx_queue_id += 2
NETIF: worker cpu4:dpdk0 rx_queue_id += 3
NETIF: worker cpu4:dpdk0 tx_queue_id += 3
SAPOOL: sapool_filter_enable = on
NETIF: Ethdev port_id=0 invalid rss_hf: 0x3afbc, valid value: 0x38d34
NETIF: Ethdev port_id=0 invalid tx_offload: 0x1000e, valid value: 0x20807f


OK, 죽지 않고 돌아간다. 이 상태에서 dpip(dpvs용으로 개조된 ip 명령) 명령을 실행해 보니, dpdk0 interface가 보인다.

# ./dpip link show
1: dpdk0: socket 0 mtu 1500 rx-queue 4 tx-queue 4
   UP 1000 Mbps full-duplex auto-nego lldp  
   addr 4C:4B:F9:45:1F:F8 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM OF_TX_FAST_FREE


한편, 이 상태에서 top 명령을 쳐보면 dpvs의 CPU 사용량이 500%(master 1 + slave worker 4개가 사용)에 육박하는 것을 알 수 있다.

[그림 4.3] dpvs cpu 사용량 확인

4.2 한개의 포트를 사용하는 Full-NAT 모드 Load Balancer 실험하기
지금까지는 dpvs 관련 기본 동작을 확인해 보았으니, 이번에는 (일반적인 load balancer 구성은 아니지만) 한개의 port만을 사용하여 load balancing이 가능한지 확인해 보기로 하자.

[그림 4.4] 한개의 포트를 사용하는 Full-NAT mode LB 시험 환경

# cp dpvs/dpvs.conf.single-nic.sample  /etc/dpvs.conf
# vi /etc/dpvs.conf
  -> 아래와 같이 cpu0(master), cpu1(slave) 2개의 worker만을 남기고 나머지 worker 설정은 모두 지운다.

! global config       
global_defs {
   !log_level   WARNING
   log_level   DEBUG
   log_file    /var/log/dpvs.log
   ! <init> log_async_mode    on
   <init> kni               on
   lldp                on

! netif config
netif_defs {
   <init> pktpool_size     524287
   <init> pktpool_cache    256

   <init> device dpdk0 {
       rx {
           queue_number        4
           descriptor_number   1024
           rss                 all
       }
       tx {
           queue_number        4
           descriptor_number   1024
       }
       mtu                   1500
       !promisc_mode
       ! allmulticast
       kni_name                dpdk0.kni
   }
}

! worker config (lcores)
worker_defs {
   <init> worker cpu0 {
       type    master
       cpu_id  0
   }

   <init> worker cpu1 {
       type    slave
       cpu_id  1
       port    dpdk0 {
           rx_queue_ids     0
           tx_queue_ids     0
           ! isol_rx_cpu_ids  9
           ! isol_rxq_ring_sz 1048576
       }
   }
}
...
! sa_pool config
sa_pool {
   <init> pool_hash_size  16
   <init> flow_enable     off
}

____________________________________________________________________________________
📌 [주의] worker cpu를 2개 이상 사용하도록 설정할 경우, load balancing이 제대로 동작하지 않는 문제가 있다. 확인해 보니, DPVS FNAT, SNAT 등은 DPDK의 rte_flow라는 feature를 필요로 하는데, NIC driver 마다 이 기능을 지원함에 있어 차이가 있다고 한다. 참고로, 필자가 현재 사용중인 NIC driver는 igc인데, 얘는 rte_flow feature를 지원하지 않는단다. 따라서 이런 경우에는 어쩔 수 없이 2개의 cpu(각각 master, slave)만을 사용하도록 설정해야만 한다. 😂



아래와 같은 option을 주어 dpvs를 시작한다.

# ./dpvs -- -a 0000:0a:00.0 -l 0-1 --main-lcore 0 &
  -> OK, dpvs가 정상적으로 올라온다.

다음으로, ip & load balancing 설정을 연이어 진행해 주도록 한다.

# ./dpip addr add 192.168.5.254/24 dev dpdk0

# ./ipvsadm -A -t 192.168.5.254:80 -s rr
📌 -s 다음에는 
rr|wrr|wlc|conhash|mh|fo 등의 scheduling algorithm을 지정할 수 있다.

# ./ipvsadm -a -t 192.168.5.254:80 -r $192.168.5.50:80 -b
📌 -b는 fullnat mode를 의미한다.

# ./ipvsadm --add-laddr -z 192.168.5.199 -t 192.168.5.254:80 -F dpdk0
___________________
OK, 설정이 마무리 되었으니, 이 상태에서 Client PC로 부터 VIP(192.168.5.254)로 http 접속을 시도해 본다. 결과는 Real Server(192.168.5.50)로 정상 연결된다. 😍

4.3 한개의 포트를 사용하는 DR(Direct Routing) 모드 Load Balancer 실험하기
이번에는 한개의 port를 사용하는 DR mode 기능을 시험해 보기로 하자.

[그림 4.5] 개의 포트를 사용하는 DR mode LB 시험 환경
📌 VPP에서 말하는 L3DSR과는 다른 기능으로 보아야 한다.

이전과 동일한 방식으로 dpvs를 구동시킨다(/etc/dpvs.conf 파일도 그대로 사용하자).

./dpvs -- -a 0000:0a:00.0 -l 0-1 --main-lcore 0 &
  -> OK, dpvs가 정상적으로 올라온다.

다음으로, 아래와 같이 ip & load balancing 설정을 진행한다.

# ./dpip addr add 192.168.5.1/24 dev dpdk0
./dpip addr add 192.168.5.254/32 dev dpdk0

./dpip route add 192.168.5.0/24 dev dpdk0

./ipvsadm -A -t 192.168.5.254:22 -s rr

./ipvsadm -a -t 192.168.5.254:22 -r 192.168.5.50 -g
# ./ipvsadm -a -t 192.168.5.254:22 -r 192.168.5.60 -g
📌 -g option은 gatewaying (direct routing) mode를 선택할 때 사용한다.

# ./ipvsadm -L
IP Virtual Server version 1.10.2 (size=0)
Prot LocalAddress:Port Scheduler Flags
 -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.5.254:ssh rr
 -> 192.168.5.50:ssh             Route   1      0          0          
 -> 192.168.5.60:ssh             Route   1      0          0


DR mode의 경우는 RS(Real Server) 쪽에서도 설정해 주어야 하는 내용이 있다.

<Real Server 설정 변경>
sudo ip addr add 192.168.5.254/32 dev lo
sudo sysctl -w net.ipv4.conf.lo.arp_ignore=1 

[그림 4.6] RS lo(loopback) interface 설정 변경 모습

OK, 이 상태에서 Client PC(192.168.5.10)에서 VIP(192.168.5.254)로 ssh 접속을 시도하니, Real Server(192.168.5.50, 192.168.5.60)로 정상 연결된다. 😍

4.4 두개의 포트를 사용하는 Full-NAT 모드 Load Balancer 실험하기
이번에는 일반적인 load balanacer 구성 즉, 2개의 port를 사용하고, 외부망에서 접속 요청일 들어올 경우, 이를 내부 서버(Real Server)에 연결 시켜 주는 동작(2 arm, full-nat mode)을 시험해 보도록 하자.

[그림 4.7] 두개의 포트를 사용하는 Full-NAT mode LB 시험 환경
📌 VPP의 nat44 load balacing 기능과 유사한 내용이다.

먼저, dpvs/conf/dpvs.conf.sample 파일을 /etc/dpvs.conf로 복사 후, 아래와 같이 편집하도록 한다.

! global config
global_defs {
   log_level   DEBUG
   log_file    /var/log/dpvs.log
   ! <init> log_async_mode    on
   <init> kni               on
   ! <init> pdump             off
   lldp                on
}

! netif config
netif_defs {
   <init> pktpool_size     1048575
   <init> pktpool_cache    256
   <init> fdir_mode        perfect

   <init> device dpdk0 {
       rx {
           queue_number        4
           descriptor_number   1024
           rss                 all
       }
       tx {
           queue_number        4
           descriptor_number   1024
           mbuf_fast_free      on
       }
       ! mtu                   1500
       ! promisc_mode
       ! allmulticast
       kni_name                dpdk0.kni
   }
 <init> device dpdk1 {
       rx {
           queue_number        4
           descriptor_number   1024
           rss                 all
       }
       tx {
           queue_number        4
           descriptor_number   1024
           mbuf_fast_free      on
       }
       ! mtu                   1500
       ! promisc_mode
       ! allmulticast
       kni_name                dpdk1.kni
   }
}

!worker 설정에서 cpu0, cpu1만 남기고 나머지는 모두 제거하도록 하자.
worker_defs {
   <init> worker cpu0 {
       type    master
       cpu_id  0
   }

   <init> worker cpu1 {
       type    slave
       cpu_id  1
       port    dpdk0 {
           rx_queue_ids     0
           tx_queue_ids     0
           ! isol_rx_cpu_ids  9
           ! isol_rxq_ring_sz 1048576
       }
       port    dpdk1 {
           rx_queue_ids     0
           tx_queue_ids     0
           ! isol_rx_cpu_ids  9
           ! isol_rxq_ring_sz 1048576
       }
   }
}

...

! sa_pool config
sa_pool {
   <init> pool_hash_size  16
   <init> flow_enable     off
}

________________________________________________________________

다음으로 dpvs를 아래와 같은 option을 주어 실행해 보도록 하자.

# ./dpvs -- -a 0000:0a:00.0 -a 0000:11:00.0 -l 0-1 --main-lcore 0 &
  -> 2개의 physical port를 사용해야 하므로, -a option을 두번 사용하는 것에 주목하자.
  -> OK, dpvs가 정상적으로 구동된다.

[그림 4.8] 2개의 port를 사용하는 dpvs cpu 사용량 확인

이 상태에서 ip & routing table 설정을 하도록 한다.

# ./dpip addr add 192.168.1.254/32 dev dpdk1
# ./dpip route add 192.168.1.0/24 dev dpdk1
# ./dpip route add 192.168.5.0/24 dev dpdk0


이어서, ipvsadm tool을 이용하여 load balancing 설정을 해 주도록 한다.

# ./ipvsadm -A -t 192.168.1.254:80 -s rr
# ./ipvsadm -a -t 192.168.1.254:80 -r 192.168.5.10 -b
# ./ipvsadm -a -t 192.168.1.254:80 -r 192.168.5.50 -b
# ./ipvsadm --add-laddr -z 192.168.5.254 -t 192.168.1.254:80 -F dpdk0

마지막으로 앞서 설정한 내용이 정상적으로 출력되는지 확인해 보도록 한다.

# ./dpip link show
1: dpdk0: socket 0 mtu 1500 rx-queue 4 tx-queue 4
   UP 1000 Mbps full-duplex auto-nego lldp  
   addr 4C:4B:F9:45:1F:F8 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM OF_TX_FAST_FREE  
2: dpdk1: socket 0 mtu 1500 rx-queue 4 tx-queue 4
   UP 1000 Mbps full-duplex auto-nego lldp  
   addr 4C:4B:F9:45:1F:FF OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM OF_TX_FAST_FREE  

# ./dpip addr show
inet6 fe80::4e4b:f9ff:fe45:1ff8/64 scope link dpdk0
    valid_lft forever preferred_lft forever
inet6 fe80::4e4b:f9ff:fe45:1fff/64 scope link dpdk1
    valid_lft forever preferred_lft forever
inet 192.168.1.254/32 scope global dpdk1
    valid_lft forever preferred_lft forever
inet 192.168.5.254/32 scope global dpdk0
    valid_lft forever preferred_lft forever

# ./dpip route show

inet 192.168.5.254/32 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope host metric 0 proto auto  
inet 192.168.1.254/32 via 0.0.0.0 src 0.0.0.0 dev dpdk1 mtu 1500 tos 0 scope host metric 0 proto auto  
inet 192.168.1.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk1 mtu 1500 tos 0 scope link metric 0 proto auto  
inet 192.168.5.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope link metric 0 proto auto

# ./ipvsadm -ln
IP Virtual Server version 1.10.2 (size=0)
Prot LocalAddress:Port Scheduler Flags
 -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.254:80 rr
 -> 192.168.5.10:0               FullNat 1      0          0          
 -> 192.168.5.50:0               FullNat 1      0          0          

# ./ipvsadm -G
VIP:VPORT            TOTAL    SNAT_IP              CONFLICTS  CONNS      
192.168.1.254:80     1         
                             192.168.5.254        0          0

_____________________________________________________________________

OK, 이 상태에서 외부 PC로 부터 http 접속을 반복적으로 시도해 보니, OK 모두 정상 동작한다. 😍
📌 web browser의 특성 상, cache를 지워주어야 새로운 연결이 가능하다.

4.5 KNI interface 동작 확인
DPVS에서 말하는 KNI는 아래 그림과 같이, linux host 상에 가상의 network interface(예: dpdk0.kni, dpdk1.kni)를 만들어, 패킷이 DPDK를 거쳐 linux host 상의 application과 통신하도록 해 주는 것(kernel network interface)을 말한다. 

[그림 4.9] DPVS KNI 개념도(1) [출처 - 참고문헌 2]
📌 VPP의 경우는 KNI 설정에 해당하는 다양한 기능(linux control plugin, tap interface 등)을 제공하고 있어 매우 편리하다.

[그림 4.10] DPVS KNI 개념도(2)

DPVS KNI 가 정상적으로 동작하기 위해서는 아래와 같이 몇가지 설정이 필요하다.

[1] /etc/dvps.conf 파일 내용 수정 후, dvps 구동
_________________________________________
global_defs {
    ...
    <init> kni                  on
    ...
}

<init> device dpdk0 {
               kni_name                dpdk0.kni
}
<init> device dpdk1 {
               kni_name                dpdk1.kni
}
_________________________________________

[2] dpvs 구동 후, KNI interface 확인
$ ifconfig -a
dpdk0.kni: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 4c:4b:f9:45:1f:f8  txqueuelen 1000  (Ethernet)
        RX packets 5256  bytes 417896 (417.8 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

dpdk1.kni: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 4c:4b:f9:45:1f:ff  txqueuelen 1000  (Ethernet)
        RX packets 103  bytes 7762 (7.7 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[3] dpip 명령으로 forward2kni 설정 
# dpip link set dpdk0 forward2kni on
# dpip link set dpdk1 forward2kni on

[4] ip 명령으로 ip/routing table 설정 진행
# ip link set dpdk0.kni up
# ip link set dpdk1.kni up
# ip addr add 192.168.5.199/24 dev dpdk0.kni
# ip addr add 192.168.1.199/24 dev dpdk1.kni
# ip route add default via 192.168.1.1 dev dpdk1.kni

[그림 4.11] DPVS KNI interface를 설정한 모습

4.6 그 밖의 재밌는 features
지면 관계상 모든 기능을 다 설명할 수는 없지만, 아래 tutorial page를 보면, DPVS에서 지원하는 다양한 기능을 확인할 수가 있다.


1) 다양한 forwarding mode: Full-NAT, DR(Direct Routing), Tunnel(IP tunnel), NAT, SNAT mode 지원
2) One Arm(Standalone 구성), Two Arm(Gateway 구성) 모드 지원
3) Bonding, VLAN, Tunnel device 지원
4) KNI interface 지원: Linux host stack에서 다양한 application 구동 가능
5) HA 기능: OSPF/BGP 기반 ECMP 및 VRRP(keepalived) 지원
6) TC(Traffic Control) 기능 : Linux kernel에서 제공하는 traffic shaping 기능 지원
7) IPV4, IPv6 지원
8) ...

[그림 4.12] OSPF/ECMP를 지원하는 Full-NAT mode 개요도 [출처 - 참고문헌 2]


[그림 4.13] VRRP를 지원하는 Full-NAT mode(1 Port 사용) 개요도 [출처 - 참고문헌 2]

__________________________________________________
지금까지 3개의 장에 걸쳐서 VPP 기반의 load balancer와 DPVS load balancer를 간략히 살펴 보았다. 

1) DPVS는 load balancer 자체를 겨냥한 project이기 때문에, 그렇지 못한 VPP에 비해 load balancing 기능 측면에서 볼 때 보다 우수한 느낌이다.
2) VPP는 build, 설치, 설정(CLI), 다양한 기능 제공(L2/L3 switch, NAT, ACL 등 무수히 많음) 등 모든 면에서 완성도가 높은 것이 사실이다. 다만, load balacing plugin의 경우는 조금은 아쉬운 느낌이다.

늘 그렇지만, 처음 계획보다 미흡한 내용이 많이 남아 있다. 아쉬운 부분은 나중을 기약하며, 이번 posting을 마치고자 한다. 끝까지 읽어주셔서 감사드린다. 😎

To be continued...


5. References
<DPVS LB>
[1] https://github.com/iqiyi/dpvs
[2] https://github.com/iqiyi/dpvs/blob/master/doc/tutorial.md
[3] https://github.com/iqiyi/dpvs/blob/master/doc/faq.md#nic
[4] https://github.com/intel/high-density-scalable-load-balancer/blob/main/doc/Quick_start.md

<LB>
[5] https://events19.linuxfoundation.org/wp-content/uploads/2018/07/WhiteBox-Load-Balancer-ONS-2019.pdf
[6] https://docs.ngkore.org/ebpf/building-ebpf-based-l4-load-balancer/
[7] https://www.haproxy.com/assets/content-library/other/art-2006-making_applications_scalable_with_lb.pdf

<VPP LB & Miscellaneous>
[8] https://vpp.flirble.org/master/developer/plugins/lb.html
[9] https://proceedings.jacow.org/icalepcs2019/papers/mopha044.pdf
[10] https://static.sched.com/hosted_files/onsna19/73/ONS.NA.2019.VPP_LB_public.pdf
[11] https://storage.googleapis.com/gweb-research2023-media/pubtools/2904.pdf
[12] https://wiki.fd.io/images/b/b6/VPP_K8S_GTPU_OSSNA.pdf

[13] And, Google~

Slowboot


댓글 없음:

댓글 쓰기