本文主要介绍在CentOS7.9
系统上部署DPVS的FullNAT模式和在RealServer上安装toa模块获取客户端的真实IP。
此前的文章已经介绍过DPVS简介与部署以及DPDK在DPVS中的应用及原理分析,有需要的同学可以先补一下相关的内容。由于之前的文章中的部署步骤只介绍到了DPVS的部署,并没有涉及相关的各种负载均衡模式的配置,以及时间过去大半年之后,DPVS的版本和对应的DPDK版本都有所更新,因此这里再重新详细写一篇新的部署教程。
1、准备工作
在正式开始安装之后我们需要先对机器的硬件参数进行一些调整,DPVS官方对硬件有一定的要求(主要是因为底层使用的DPDK),dpdk官方给出了一份支持列表,虽然支持性列表上面的平台支持得很广泛,但是实际上兼容性和表现最好的似乎还是要Intel的硬件平台。
1.1 硬件部分
1.1.1 硬件参数
- 机器型号:
PowerEdge R630
- CPU:两颗
Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz
- 内存:
16G*8 DDR4-2400 MT/s(Configured in 2133 MT/s)
,每个CPU64G,共计128G - 网卡1:
Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
- 网卡2:
Intel Corporation Ethernet 10G 2P X520 Adapter (rev 01)
- 系统:
CentOS Linux release 7.9.2009 (Core)
- 内核:
3.10.0-1160.36.2.el7.x86_64
1.1.2 BIOS设置
开始之前,先进入BIOS中关闭超线程和启用NUMA策略。其中DPVS是非常典型的CPU繁忙型应用(进程所在的CPU使用率一直都是100%),为了保证性能,建议关闭CPU的超线程设置。同时因为DPVS使用的是我们手动分配的大页内存,为了保证CPU亲和性,最好在BIOS中直接打开NUMA策略。
1.1.3 网卡PCI ID
使用dpvs
的PMD驱动
接管网卡之后,如果网卡的数量较多,容易搞混,最好提前记录下对应的网卡名
、MAC地址
和PCI ID
,避免后面操作的时候搞混。
使用lspci
命令可以查看对应网卡的PCI ID
,接着我们可以查看/sys/class/net/
这个目录下对应网卡名目录下的device
文件,就能够得知网卡对应的PCI ID
。最后就可以把网卡名-MAC地址-PCI ID
三个参数串起来。
$ lspci | grep -i net
01:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
01:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
$ file /sys/class/net/eth0/device
/sys/class/net/eth0/device: symbolic link to `../../../0000:01:00.0'
1.2 软件部分
1.2.1 系统软件
# 编译安装dpvs需要使用的工具以及查看CPU NUMA信息的工具
$ yum group install "Development Tools"
$ yum install patch libnuma* numactl numactl-devel kernel-devel openssl* popt* libpcap-devel -y
# 如果需要ipvsadm支持ipv6需要安装libnl3-devel
$ yum install libnl libnl-devel libnl3 libnl3-devel -y
# 注意kernel以及相应的kernel组件的版本需要和现在使用的kernel版本相对应
$ uname -r
3.10.0-1160.36.2.el7.x86_64
$ rpm -qa | grep kernel | grep "3.10.0-1160.36.2"
kernel-3.10.0-1160.36.2.el7.x86_64
kernel-devel-3.10.0-1160.36.2.el7.x86_64
kernel-tools-libs-3.10.0-1160.36.2.el7.x86_64
kernel-debug-devel-3.10.0-1160.36.2.el7.x86_64
kernel-tools-3.10.0-1160.36.2.el7.x86_64
kernel-headers-3.10.0-1160.36.2.el7.x86_64
1.2.2 dpvs和dpdk
# dpvs我们直接使用git从github拉取最新的版本
$ git clone https://github.com/iqiyi/dpvs.git
# dpdk我们从官网下载18.11.2版本,放到dpvs目录下方便操作
$ cd dpvs/
$ wget https://fast.dpdk.org/rel/dpdk-18.11.2.tar.xz
$ tar -Jxvf dpdk-18.11.2.tar.xz
完成上述步骤之后就可以开始下面的安装了。
2、安装步骤
2.1 DPDK安装
2.1.1 安装dpdk-patch
在dpvs文件夹的patch目录下面有对应支持的dpdk版本的patch补丁,如果不清楚自己到底需要哪个补丁,官方的建议是全部安装
$ ll dpvs/patch/dpdk-stable-18.11.2
total 44
-rw-r--r-- 1 root root 4185 Jul 22 12:47 0001-kni-use-netlink-event-for-multicast-driver-part.patch
-rw-r--r-- 1 root root 1771 Jul 22 12:47 0002-net-support-variable-IP-header-len-for-checksum-API.patch
-rw-r--r-- 1 root root 1130 Jul 22 12:47 0003-driver-kni-enable-flow_item-type-comparsion-in-flow_.patch
-rw-r--r-- 1 root root 1706 Jul 22 12:47 0004-rm-rte_experimental-attribute-of-rte_memseg_walk.patch
-rw-r--r-- 1 root root 16538 Jul 22 12:47 0005-enable-pdump-and-change-dpdk-pdump-tool-for-dpvs.patch
-rw-r--r-- 1 root root 2189 Jul 22 12:47 0006-enable-dpdk-eal-memory-debug.patch
安装patch的操作也非常的简单
# 我们首先把所有的patch复制到dpdk的根目录下面
$ cp dpvs/patch/dpdk-stable-18.11.2/*patch dpvs/dpdk-stable-18.11.2/
$ cd dpvs/dpdk-stable-18.11.2/
# 然后我们按照patch的文件名顺序依次进行安装
$ patch -p 1 < 0001-kni-use-netlink-event-for-multicast-driver-part.patch
patching file kernel/linux/kni/kni_net.c
$ patch -p 1 < 0002-net-support-variable-IP-header-len-for-checksum-API.patch
patching file lib/librte_net/rte_ip.h
$ patch -p 1 < 0003-driver-kni-enable-flow_item-type-comparsion-in-flow_.patch
patching file drivers/net/mlx5/mlx5_flow.c
$ patch -p 1 < 0004-rm-rte_experimental-attribute-of-rte_memseg_walk.patch
patching file lib/librte_eal/common/eal_common_memory.c
Hunk #1 succeeded at 606 (offset 5 lines).
patching file lib/librte_eal/common/include/rte_memory.h
$ patch -p 1 < 0005-enable-pdump-and-change-dpdk-pdump-tool-for-dpvs.patch
patching file app/pdump/main.c
patching file config/common_base
patching file lib/librte_pdump/rte_pdump.c
patching file lib/librte_pdump/rte_pdump.h
$ patch -p 1 < 0006-enable-dpdk-eal-memory-debug.patch
patching file config/common_base
patching file lib/librte_eal/common/include/rte_malloc.h
patching file lib/librte_eal/common/rte_malloc.c
2.1.2 dpdk编译安装
$ cd dpvs/dpdk-stable-18.11.2
$ make config T=x86_64-native-linuxapp-gcc
$ make
# 出现Build complete [x86_64-native-linuxapp-gcc]的字样就说明make成功
$ export RTE_SDK=$PWD
$ export RTE_TARGET=build
2.1.3 配置hugepage
和其他的一般程序不同,dpvs使用的dpdk并不是从操作系统中索要内存,而是直接使用大页内存(hugepage),极大地提高了内存分配的效率。hugepage的配置比较简单,官方的配置过程中使用的是2MB的大页内存,这里的28672
指的是分配了28672
个2MB的大页内存,也就是一个node对应56GB的内存,一共分配了112GB的内存,这里的内存可以根据机器的大小来自行调整。但是如果小于1GB可能会导致启动报错。
# for NUMA machine
$ echo 28672 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
$ echo 28672 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
$ mkdir /mnt/huge
$ mount -t hugetlbfs nodev /mnt/huge
# 需要开机自动挂载的话可以在
$ echo "nodev /mnt/huge hugetlbfs defaults 0 0" >> /etc/fstab
# 配置完成后我们可以看到内存的使用率立马上升了
$ free -g # 配置前
total used free shared buff/cache available
Mem: 125 1 122 0 1 123
$ free -g # 配置后
total used free shared buff/cache available
Mem: 125 113 10 0 1 11
# 使用numactl查看内存状态也可以看到确实是两边的CPU内存各分配了56G
$ numactl -H
available: 2 nodes (0-1)
node 0 cpus: 0 2 4 6 8 10 12 14 16 18
node 0 size: 64184 MB
node 0 free: 4687 MB
node 1 cpus: 1 3 5 7 9 11 13 15 17 19
node 1 size: 64494 MB
node 1 free: 5759 MB
node distances:
node 0 1
0: 10 21
1: 21 10
2.1.4 配置ulimit
默认情况下系统的ulimit
限制打开的文件描述符数量如果太小会影响dpvs正常运行,因此我们将其调大一些:
$ ulimit -n 655350
$ echo "ulimit -n 655350" >> /etc/rc.local
$ chmod a+x /etc/rc.local
2.2 挂载驱动模块
首先我们需要让系统挂载我们已经编译好的dpdk驱动(PMD驱动),然后再将网卡使用的默认驱动换为我们这里编译好的PMD驱动
$ modprobe uio
$ insmod /path/to/dpdk-stable-18.11.2/build/kmod/igb_uio.ko
$ insmod /path/to/dpdk-stable-18.11.2/build/kmod/rte_kni.ko carrier=on
在dpdk-stable-18.11.2/usertools
目录下有一些辅助我们安装使用dpdk的脚本,我们可以用它们来降低配置的复杂度,这里我们可以使用dpdk-devbind.py
脚本来变更网卡的驱动
# 首先我们关闭我们需要加载PMD驱动的网卡
$ ifdown eth{2,3,4,5}
# 查看网卡状态,注意要特别关注网卡对应的PCI ID,下面只截取部分有用的输出结果
$ ./usertools/dpdk-devbind.py --status
Network devices using kernel driver
===================================
0000:04:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' if=eth2 drv=ixgbe unused=igb_uio
0000:04:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' if=eth3 drv=ixgbe unused=igb_uio
0000:82:00.0 'Ethernet 10G 2P X520 Adapter 154d' if=eth4 drv=ixgbe unused=igb_uio
0000:82:00.1 'Ethernet 10G 2P X520 Adapter 154d' if=eth5 drv=ixgbe unused=igb_uio
从上面的输出结果我们可以看到目前的网卡使用的是ixgbe
驱动,而我们的目标是让其使用igb_uio
驱动。注意如果这个时候系统的网卡太多,前面我们记录下来的网卡名-MAC地址-PCI ID
三个参数就可以派上用场了。
# 对需要使用dpvs的网卡加载特定的驱动
$ ./usertools/dpdk-devbind.py -b igb_uio 0000:04:00.0
$ ./usertools/dpdk-devbind.py -b igb_uio 0000:04:00.1
$ ./usertools/dpdk-devbind.py -b igb_uio 0000:82:00.0
$ ./usertools/dpdk-devbind.py -b igb_uio 0000:82:00.1
# 再次检查是否加载成功,下面只截取部分有用的输出结果
$ ./usertools/dpdk-devbind.py --status
Network devices using DPDK-compatible driver
============================================
0000:04:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' drv=igb_uio unused=ixgbe
0000:04:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' drv=igb_uio unused=ixgbe
0000:82:00.0 'Ethernet 10G 2P X520 Adapter 154d' drv=igb_uio unused=ixgbe
0000:82:00.1 'Ethernet 10G 2P X520 Adapter 154d' drv=igb_uio unused=ixgbe
2.3 DPVS安装
$ cd /path/to/dpdk-stable-18.11.2/
$ export RTE_SDK=$PWD
$ cd /path/to/dpvs
$ make
$ make install
# 查看bin目录下的二进制文件
$ ls /path/to/dpvs/bin/
dpip dpvs ipvsadm keepalived
# 注意查看make过程中的提示信息,尤其是keepalived部分,如果出现下面的部分则表示IPVS支持IPv6
Keepalived configuration
------------------------
Keepalived version : 2.0.19
Compiler : gcc
Preprocessor flags : -D_GNU_SOURCE -I/usr/include/libnl3
Compiler flags : -g -g -O2 -fPIE -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -O2
Linker flags : -pie -Wl,-z,relro -Wl,-z,now
Extra Lib : -lm -lcrypto -lssl -lnl-genl-3 -lnl-3
Use IPVS Framework : Yes
IPVS use libnl : Yes
IPVS syncd attributes : No
IPVS 64 bit stats : No
# 为了方便管理可以将相关的操作命令软链接到/sbin下方便全局执行
$ ln -s /path/to/dpvs/bin/dpvs /sbin/dpvs
$ ln -s /path/to/dpvs/bin/dpip /sbin/dpip
$ ln -s /path/to/dpvs/bin/ipvsadm /sbin/ipvsadm
$ ln -s /path/to/dpvs/bin/keepalived /sbin/keepalived
# 检查dpvs相关命令能否正常工作,注意其他命令要在dpvs进程启动后才能正常使用
$ dpvs -v
dpvs version: 1.8-10, build on 2021.07.26.15:34:26
2.4 配置dpvs.conf
在dpvs/conf
目录下面有着各种配置方式的dpvs配置文件范例,同时在dpvs.conf.items
文件中记录了所有的参数,建议同学们全部阅读一遍了解了基本语法之后再进行配置。默认的dpvs启动的配置文件的是/etc/dpvs.conf
。
这里简单摘几个部分出来说一下(!
为注释符号):
-
日志的格式可以手动调成DEBUG并且修改日志输出的位置方便定位问题
global_defs { log_level DEBUG log_file /path/to/dpvs/logs/dpvs.log }
-
如果需要定义多个网卡,可以参考这个配置
netif_defs { <init> pktpool_size 1048575 <init> pktpool_cache 256 <init> device dpdk0 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk0.kni } <init> device dpdk1 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk1.kni } <init> device dpdk2 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk2.kni } <init> device dpdk3 { rx { queue_number 16 descriptor_number 1024 rss all } tx { queue_number 16 descriptor_number 1024 } fdir { mode perfect pballoc 64k status matched } kni_name dpdk3.kni } }
-
多个网卡的同一个收发队列共用同一个CPU
<init> worker cpu1 { type slave cpu_id 1 port dpdk0 { rx_queue_ids 0 tx_queue_ids 0 } port dpdk1 { rx_queue_ids 0 tx_queue_ids 0 } port dpdk2 { rx_queue_ids 0 tx_queue_ids 0 } port dpdk3 { rx_queue_ids 0 tx_queue_ids 0 } }
-
如果需要单独指定某个CPU来处理
ICMP
数据包,可以在该worker的参数中添加icmp_redirect_core
<init> worker cpu16 { type slave cpu_id 16 icmp_redirect_core port dpdk0 { rx_queue_ids 15 tx_queue_ids 15 } }
DPVS进程启动后可以直接在Linux系统的网络配置文件中对相应的网卡进行配置,使用起来和其他的eth0之类的网卡是完全一样的。
运行成功之后,使用dpip
命令和正常的ip
、ifconfig
命令都能够看到对应的dpdk
网卡,IPv4和IPv6网络都能够正常使用。下图只截取部分信息,IP和MAC信息已脱敏,IPv6信息已摘除。
$ dpip link show
1: dpdk0: socket 0 mtu 1500 rx-queue 16 tx-queue 16
UP 10000 Mbps full-duplex auto-nego
addr AA:BB:CC:23:33:33 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM
$ ip a
67: dpdk0.kni: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether AA:BB:CC:23:33:33 brd ff:ff:ff:ff:ff:ff
inet 1.1.1.1/24 brd 1.1.1.255 scope global dpdk0.kni
valid_lft forever preferred_lft forever
$ ifconfig dpdk0.kni
dpdk0.kni: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 1.1.1.1 netmask 255.255.254.0 broadcast 1.1.1.255
ether AA:BB:CC:23:33:33 txqueuelen 1000 (Ethernet)
RX packets 1790 bytes 136602 (133.4 KiB)
RX errors 0 dropped 52 overruns 0 frame 0
TX packets 115 bytes 24290 (23.7 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
3、配置FullNat
为了校验我们的DPVS能够正常工作,这里我们参考官方的配置文档,先配置一个最简单的双臂模式的FNAT。参考官方的架构图并修改其中的IP地址信息我们可以得到下面的简单架构图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWrJXOM5-1645782758324)(https://resource.tinychen.com/20210728123202.svg)]
这里我们使用dpdk2网卡作为wan口,dpdk0网卡作为lan口
# 首先我们把VIP 10.0.96.204 加到dpdk2网卡(wan)上
$ dpip addr add 10.0.96.204/32 dev dpdk2
# 接着我们需要添加两条路由,分为是wan口网段的路由和到RS机器网段的路由
$ dpip route add 10.0.96.0/24 dev dpdk2
$ dpip route add 192.168.229.0/24 dev dpdk0
# 最好再加一条到网关的默认路由保证ICMP数据包的回包能跑通
$ dpip route add default via 10.0.96.254 dev dpdk2
# 使用RR算法建立转发规则
# add service <VIP:vport> to forwarding, scheduling mode is RR.
# use ipvsadm --help for more info.
$ ipvsadm -A -t 10.0.96.204:80 -s rr
# 这里为了方便测试我们只添加一台RS
# add two RS for service, forwarding mode is FNAT (-b)
$ ipvsadm -a -t 10.0.96.204:80 -r 192.168.229.1 -b
# 添加LocalIP到网络中,FNAT模式这里需要
# add at least one Local-IP (LIP) for FNAT on LAN interface
$ ipvsadm --add-laddr -z 192.168.229.204 -t 10.0.96.204:80 -F dpdk0
# 然后我们查看一下效果
$ dpip route show
inet 192.168.229.204/32 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope host metric 0 proto auto
inet 10.0.96.204/32 via 0.0.0.0 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope host metric 0 proto auto
inet 10.0.96.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope link metric 0 proto auto
inet 192.168.229.0/24 via 0.0.0.0 src 0.0.0.0 dev dpdk0 mtu 1500 tos 0 scope link metric 0 proto auto
inet 0.0.0.0/0 via 10.0.96.254 src 0.0.0.0 dev dpdk2 mtu 1500 tos 0 scope global metric 0 proto auto
$ dpip addr show
inet 10.0.96.204/32 scope global dpdk2
valid_lft forever preferred_lft forever
inet 192.168.229.204/32 scope global dpdk0
valid_lft forever preferred_lft forever
$ ipvsadm -ln
IP Virtual Server version 0.0.0 (size=0)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.0.96.204:80 rr
-> 192.168.229.1:80 FullNat 1 0 0
$ ipvsadm -G
VIP:VPORT TOTAL SNAT_IP CONFLICTS CONNS
10.0.96.204:80 1
192.168.229.204 0 0
然后我们在RS上面启动一个nginx,设置返回IP和端口号,看看效果:
server {
listen 80 default;
location / {
default_type text/plain;
return 200 "Your IP and port is $remote_addr:$remote_port\n";
}
}
直接对VIP使用ping和curl命令进行测试:
$ ping -c4 10.0.96.204
PING 10.0.96.204 (10.0.96.204) 56(84) bytes of data.
64 bytes from 10.0.96.204: icmp_seq=1 ttl=54 time=47.2 ms
64 bytes from 10.0.96.204: icmp_seq=2 ttl=54 time=48.10 ms
64 bytes from 10.0.96.204: icmp_seq=3 ttl=54 time=48.5 ms
64 bytes from 10.0.96.204: icmp_seq=4 ttl=54 time=48.5 ms
--- 10.0.96.204 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 8ms
rtt min/avg/max/mdev = 47.235/48.311/48.969/0.684 ms
$ curl 10.0.96.204
Your IP and port is 192.168.229.204:1033
可以发现不管在什么机器上面都只会返回LIP的IP和端口号,如果需要获取用户的真实IP,那么就需要安装TOA模块
4、RS安装TOA模块
目前开源社区提供toa模块的版本比较多,这里我们为了保证兼容性,直接使用dpvs官方提供的toa
和uoa
模块,根据他们的官方描述,他们的toa
模块是从Alibaba TOA
中剥离出来
由于我们这里的RS机器和DPVS机器都是使用版本的CentOS7系统,因此我们可以直接在DPVS机器上面编译toa模块,再复制到各个RS机器上使用
$ cd /path/to/dpvs/kmod/toa/
$ make
顺利编译完成之后会在当前目录下生成一个toa.ko
模块文件,这就是我们需要的文件,直接使用insmod
命令加载模块然后检查
$ insmod toa.ko
$ lsmod | grep toa
toa 279641 0
确保开机加载模块,可以在rc.local
文件中加入下面的指令
/usr/sbin/insmod /path/to/toa.ko
# for example:
# /usr/sbin/insmod /home/dpvs/kmod/toa/toa.ko
除了toa模块之外,还有针对UDP协议的uoa模块,和上面的toa模块编译安装过程完全一致,这里不再赘述。
在RS机器上面加载了toa模块后我们再次使用curl测试效果:
$ curl 10.0.96.204
Your IP and port is 172.16.0.1:62844
至此,整个DPVS的FullNat模式就算是部署完成并且能够正常工作了。由于DPVS支持非常多的配置组合,后面会再专门写一篇关于IPv6、nat64、keepalived、bonding、Master/Backup模式的配置。