本文主要介绍在CentOS7.9
系统上部署DPVS的FullNAT模式和在RealServer上安装toa模块获取客户端的真实IP。
此前的文章已经介绍过DPVS简介与部署以及DPDK在DPVS中的应用及原理分析,有需要的同学可以先补一下相关的内容。由于之前的文章中的部署步骤只介绍到了DPVS的部署,并没有涉及相关的各种负载均衡模式的配置,以及时间过去大半年之后,DPVS的版本和对应的DPDK版本都有所更新,因此这里再重新详细写一篇新的部署教程。
本文中安装的DPVS版本为
1.8-10
,dpdk版本为18.11.2
,和前文不同,安装步骤和操作也有差异。
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
这里编译安装的过程中不会出现之前使用
dpdk17.11.2
版本出现的ndo_change_mtu
问题
2.1.3 配置hugepage
和其他的一般程序不同,dpvs使用的dpdk并不是从操作系统中索要内存,而是直接使用大页内存(hugepage),极大地提高了内存分配的效率。hugepage的配置比较简单,官方的配置过程中使用的是2MB的大页内存,这里的28672
指的是分配了28672
个2MB的大页内存,也就是一个node对应56GB的内存,一共分配了112GB的内存,这里的内存可以根据机器的大小来自行调整。但是如果小于1GB可能会导致启动报错。
单个CPU的系统可以参考dpdk的官方文档
# 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
需要注意的是
carrier
参数是从DPDK v18.11版本开始新增的,默认值为off
。我们需要在加载rte_kni.ko
模块的时候带上carrier=on
参数才能够使KNI设备工作正常。
在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地址信息我们可以得到下面的简单架构图。
该模式下不需要使用系统自带的ip、ifconfig等工具对DPVS虚拟出的kni网卡进行配置
这里我们使用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
中剥离出来
TOA source code is included into DPVS project(in directory kmod/toa) since v1.7 to support IPv6 and NAT64. It is derived from the Alibaba TOA. For IPv6 applications which need client's real IP address, we suggest to use this TOA version.
由于我们这里的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模式的配置。