DPVS-FullNAT模式keepalived篇

本文最后更新于:December 3, 2021 pm

本文主要介绍基于CentOS7.9系统部署DPVS的FullNAT模式在使用keepalived进行主备模式配置高可用集群在线上生产环境落地实践时遇到的一些问题和处理的思路。

文中所有IP地址、主机名、MAC地址信息均已进行脱敏或魔改处理,客户端IP使用模拟器生成,但不影响阅读体验。

1、keepalived架构

1.1 单机架构图

为了方便理解我们可以把上面的架构图分为DPVS网络栈、Linux网络栈、RS集群和使用者(SA和Users)这四大部分。在Linux网络栈中的物理网卡使用eth表示,在DPVS网络栈中的物理网卡使用dpdk表示,DPVS网络栈中的网卡虚拟到Linux网络栈中则使用kni后缀表示,在两个网络栈中做了bonding的网卡都使用BOND表示。

默认情况下,对于所有的kni网卡来说,它们的流量都会被DPVS程序劫持。

1.2 网卡用途

keepalived双臂模式架构下,每台DPVS机器最少需要三组网卡,bonding可做可不做,不影响该架构图。上图为做了bonding4的网卡架构,因此网卡名称使用bond0、1、2来表示,只要理解清楚每一组网卡的作用,就能很容易理解图中的架构。

  • bond0:**bond0网卡主要用于运维人员管理机器以及keepalived程序对后端的RS节点进行探活**

    只存在于Linux网络栈中的网卡,因为DPVS网络栈的网卡(包括其虚拟出的kni网卡)都是随着DPVS程序的存在而存在的,因此必须有一个独立于DPVS进程之外的网卡用于管理机器(机器信息监控报警,ssh登录操作等)。

    keepalived程序对后端的RS节点探活的时候只能使用Linux网络栈,因此在上图的架构中,正好也是使用bond0网卡进行探活操作,如果有多个Linux网络栈的内网网卡,则根据Linux系统中的路由来判断(单张网卡的时候也是根据路由判断)。

  • bond1.kni:**bond1.kni在上述架构正常运行的时候是没有任何作用的**

    bond1.kni作为DPVS中的bond1网卡在Linux网络栈中的虚拟网卡,在定位上和bond0是几乎完成重合的,因此最好将其关闭避免对bond0产生干扰。

    之所以不将其彻底删除,是因为当DPVS程序运行异常或者需要对bond1抓包的时候,可以将bond1的流量forward到bond1.kni进行操作。

  • bond2.knibond2.kni主要用于刷新VIP的MAC地址

    kni网卡的mac地址和DPVS中的bond网卡的mac地址是一致的,由于我们常用的ping和arping等命令无法对DPVS中的网卡操作,因此当我们需要发送garp数据包或者是gna数据包来刷新IPv4或者IPv6的VIP地址在交换机中的MAC地址的时候,可以通过DPVS网卡对应的kni网卡来进行操作。

  • bond1:业务流量网卡,主要用于加载LIP、与RS建立连接并转发请求

    local_address_group这个字段配置的LIP一般就是配置在bond1网卡。

  • bond2:业务流量网卡,主要用于加载VIP、与客户端建立连接并转发请求

    dpdk_interface这个字段就是DPVS定制版的keepalived程序特有的字段,能够将VIP配置到dpvs网卡上。

注意:keepalived主备节点之间的通信必须使用Linux网络栈内的网卡,在这个架构中可以是bond0或者是bond2.kni网卡

2、dpdk网卡相关

2.1 原理分析

DPVS中的网卡命名是按照PCIe编号的顺序来命名的,使用dpdk-devbind工具我们可以看到网卡对应的PCIe编号和Linux网络栈中的网卡名称。

如果Linux系统的网卡命名是使用eth*的命名方式并且在/etc/udev/rules.d/70-persistent-net.rules文件中固化了MAC地址和网卡名称的对应关系,那么就需要特别注意PCIe编号DPVS网卡名MAC地址Linux网卡名四者之间的对应关系。

尤其是当机器存在多个网段的网卡且做了bonding的时候,Linux网卡中的eth*和DPVS网卡中的dpdk*并不一定能一一对应,此时最好能修改相关配置并且让机房的同学调整网卡接线(当然直接在dpvs的配置文件中修改对应的网卡顺序也可以)。

2.2 解决方案

下面的案例是一个仅供参考的比较不容易出问题的组合,eth网卡根据对应的PCIe编号升序进行命名,和dpdk网卡的命名规则一致,同时eth[0-3]为内网网卡,eth[4-5]为外网网卡,与本文的架构图对应,不容易出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$ dpdk-devbind --status-dev net

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

Network devices using kernel driver
===================================
0000:01:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' if=eth0 drv=ixgbe unused=igb_uio
0000:01:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection 10fb' if=eth1 drv=ixgbe unused=igb_uio


$ cat /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="28:6e:45:c4:0e:48", NAME="eth0"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="28:6e:45:c4:0e:4a", NAME="eth1"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="38:e2:ba:1c:dd:74", NAME="eth2"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="38:e2:ba:1c:dd:76", NAME="eth3"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="b4:45:99:18:6c:5c", NAME="eth4"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="b4:45:99:18:6c:5e", NAME="eth5"

$ dpip link -v show | grep -A4 dpdk
1: dpdk0: socket 0 mtu 1500 rx-queue 16 tx-queue 16
UP 10000 Mbps full-duplex auto-nego promisc
addr 38:E2:BA:1C:DD:74 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM
pci_addr driver_name
0000:04:00:0 net_ixgbe
--
2: dpdk1: socket 0 mtu 1500 rx-queue 16 tx-queue 16
UP 10000 Mbps full-duplex auto-nego promisc
addr 38:E2:BA:1C:DD:76 OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM
pci_addr driver_name
0000:04:00:1 net_ixgbe
--
3: dpdk2: socket 0 mtu 1500 rx-queue 16 tx-queue 16
UP 10000 Mbps full-duplex auto-nego promisc
addr B4:45:99:18:6C:5C OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM
pci_addr driver_name
0000:82:00:0 net_ixgbe
--
4: dpdk3: socket 0 mtu 1500 rx-queue 16 tx-queue 16
UP 10000 Mbps full-duplex auto-nego promisc
addr B4:45:99:18:6C:5E OF_RX_IP_CSUM OF_TX_IP_CSUM OF_TX_TCP_CSUM OF_TX_UDP_CSUM
pci_addr driver_name
0000:82:00:1 net_ixgbe

2.3 抓包排障

正常情况下,DPVS网络栈会劫持对应DPVS网卡的全部流量到DPVS网络栈中,因此我们使用tcpdump工具对相应的kni网卡进行抓包是没办法抓到相关的数据包的,比较方便的解决方案是使用dpip相关命令把dpvs网卡的流量forward到对应的kni网卡上,再对kni网卡进行抓包。

1
2
dpip link set <port> forward2kni on      # enable forward2kni on <port>
dpip link set <port> forward2kni off # disable forward2kni on <port>

对于本文架构图中的dpvs节点,命令中的<port>一般为使用dpip命令查看到的bond1网卡bond2网卡

也可以查看下面这个官方的参考链接:

https://github.com/iqiyi/dpvs/blob/master/doc/tutorial.md#packet-capture-and-tcpdump

注意:forward2kni操作非常影响性能,请不要在线上服务节点进行此操作!

3、kni网卡相关

这里主要承接上面介绍kni网卡的作用以及相关的一些问题和解决思路

3.1 kni网卡的作用

一般来说DPVS的网卡都会在Linux网络栈中虚拟一个对应的kni网卡,考虑到默认情况下,对于所有的kni网卡来说,它们的流量都会被DPVS程序劫持。在本文的架构中,kni网卡的主要作用还是辅助定位故障以及做少量补充工作。

  • 当dpvs网卡出现问题时,可以把流量forward到kni网卡进行DEBUG,当VIP出现问题的时候,可以用于刷新VIP的MAC地址
  • kni网卡本身也是一个虚拟网卡,只是所有流量都被DPVS劫持,可以在DPVS中配置路由放行特定的流量到kni网卡实现补充工作,如DPVS节点偶尔需要连接外网的时候可以通过bond2.kni放行该外网IP然后访问外网

3.2 kni网卡路由干扰

3.2.1 案例复现

在本图的架构中,bond1.knibond0网卡在定位上都是属于内网网卡,如果两个网卡都是同一个网段的话,就需要尤其注意内网流量进出网卡的情况。这里我们使用一台虚拟机进行示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ip a
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:66:3b:08 brd ff:ff:ff:ff:ff:ff
inet 10.31.100.2/16 brd 10.31.255.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:47:37:3e brd ff:ff:ff:ff:ff:ff
inet 10.31.100.22/16 brd 10.31.255.255 scope global noprefixroute eth1
valid_lft forever preferred_lft forever
...
$ ip r
...
10.31.0.0/16 dev eth0 proto kernel scope link src 10.31.100.2 metric 100
10.31.0.0/16 dev eth1 proto kernel scope link src 10.31.100.22 metric 101
...

上面的这台虚拟机有两个处于10.31.0.0/16网段的网卡,分别是eth0(10.31.100.2)eth1(10.31.100.22),查看路由表的时候可以看到对10.31.0.0/16这个网段有两条路由分别指向eth0eth1的IP,不同的是两者的metric。接下来我们做个测试:

首先我们在10.31.100.1这台机器上面ping这台虚拟机的eth1(10.31.100.22),然后直接使用tcpdump进行抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 对eth1(10.31.100.22)网卡进行抓包的时候抓不到对应的icmp包
$ tcpdump -A -n -vv -i eth1 icmp
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

# 接着我们对eth0(10.31.100.2)网卡进行抓包的时候发现能够抓到外部机器(10.31.100.1)对eth1(10.31.100.22)网卡的icmp数据包
$ tcpdump -A -n -vv -i eth0 icmp
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:29:44.789831 IP (tos 0x0, ttl 64, id 1197, offset 0, flags [DF], proto ICMP (1), length 84)
10.31.100.1 > 10.31.100.22: ICMP echo request, id 16846, seq 54, length 64
E..T..@.@.Y.
.d.
.d...VSA..6y2.a....yA...................... !"#$%&'()*+,-./01234567
16:29:44.789898 IP (tos 0x0, ttl 64, id 16187, offset 0, flags [none], proto ICMP (1), length 84)
10.31.100.22 > 10.31.100.1: ICMP echo reply, id 16846, seq 54, length 64
E..T?;..@._.
.d.
.d...^SA..6y2.a....yA...................... !"#$%&'()*+,-./01234567
16:29:45.813740 IP (tos 0x0, ttl 64, id 1891, offset 0, flags [DF], proto ICMP (1), length 84)
10.31.100.1 > 10.31.100.22: ICMP echo request, id 16846, seq 55, length 64
E..T.c@.@.V.
.d.

3.2.2 原理分析

到这里我们就可以发现:尽管10.31.100.22是在eth1上面,但是实际上流量是经过eth0,也就是说eth1上面实际并没有流量。这也就很好地对应了路由表中10.31.100.2 metric 100要小于10.31.100.22 metric 101符合metric越小优先级越高的原则。

将上面的情况套用到bond0bond1.kni网卡上,也会存在相似的问题,如果开启了IPv6网络,还需要考虑是否会有bond1.kni网卡在IPv6网络路由通告下发默认网关路由。这样一来就容易存在路由流量可能走bond0也可能走bond1.kni的问题,抛开两者物理网卡和虚拟网卡的性能差距先不谈,更重要的是:

  • 默认情况下bond1.kni网卡的流量都会被DPVS程序劫持,所以走bond1.kni网卡的请求都会不正常;
  • 而恰好RS节点的探活是通过Linux网络栈实现的,如果这时候到RS节点的路由是走bond1.kni网卡,就会让keepalived误认为该后端RS节点处于不可用状态,从而将其weight降为0;
  • 如果整个集群的RS都是如此,就会导致这个集群的VIP后无可用RS(weight均为0),最终的结果就是请求无法正常转发到RS导致服务彻底不可用。

3.2.3 解决思路

因此在这里最方便的一种解决方案就是直接关闭bond1.kni,直接禁用,仅当需要DEBUG的时候再启用,就可以有效地避免这类问题。

3.3 kni网卡IP不通

3.3.1 原理分析

因为Linux网络栈中的kni网卡和DPVS网络栈中的网卡实际上对应的是一个物理网卡(或一组物理网卡),流经这个网卡的网络流量只能由一个网络栈处理。默认情况下,对于所有的kni网卡来说,它们的流量都会被DPVS程序劫持。这也就意味着bond2.kni网卡上的IP不仅是无法ping通,也无法进行其他的正常访问操作。但是DPVS程序支持针对特定的IP放行相关的流量到Linux网络栈中(通过kni_host路由实现),就可以实现该IP的正常访问。

举个例子:一组x520网卡组bonding,在Linux网络栈中显示为bond2.kni,在DPVS网络中显示为bond2,而另外的bond0网卡则只是一个在linux网络栈中的bonding网卡,与DPVS无关。我们使用一些简单的命令来进行对比:

使用ethtool工具查看,bond0网卡能正常获取网卡速率等信息,而kni网卡则完全无法获取任何有效信息,同时在DPVS网络栈中使用dpip命令则能够看到该bond2网卡非常详细的物理硬件信息。

使用lspci -nnv命令查看两组网卡的详细信息,我们还可以看到bond0网卡使用的是Linux的网卡驱动ixgbe,而bond2网卡使用的是DPVS的PMDigb_uio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
$ ethtool bond0
Settings for bond0:
Supported ports: [ ]
Supported link modes: Not reported
Supported pause frame use: No
Supports auto-negotiation: No
Supported FEC modes: Not reported
Advertised link modes: Not reported
Advertised pause frame use: No
Advertised auto-negotiation: No
Advertised FEC modes: Not reported
Speed: 20000Mb/s
Duplex: Full
Port: Other
PHYAD: 0
Transceiver: internal
Auto-negotiation: off
Link detected: yes
$ ethtool bond2.kni
Settings for bond2.kni:
No data available

$ dpip -s -v link show bond2
3: bond2: socket 0 mtu 1500 rx-queue 16 tx-queue 16
UP 20000 Mbps full-duplex auto-nego
addr 00:1C:34:EE:46:E4
ipackets opackets ibytes obytes
15451492 31306 6110603685 4922260
ierrors oerrors imissed rx_nombuf
0 0 0 0
mbuf-avail mbuf-inuse
1012315 36260
pci_addr driver_name
net_bonding
if_index min_rx_bufsize max_rx_pktlen max_mac_addrs
0 0 15872 16
max_rx_queues max_tx_queues max_hash_addrs max_vfs
127 63 0 0
max_vmdq_pools rx_ol_capa tx_ol_capa reta_size
0 0x1AE9F 0x2A03F 128
hash_key_size flowtype_rss_ol vmdq_que_base vmdq_que_num
0 0x38D34 0 0
rx_desc_max rx_desc_min rx_desc_align vmdq_pool_base
4096 0 1 0
tx_desc_max tx_desc_min tx_desc_align speed_capa
4096 0 1 0
Queue Configuration:
rx0-tx0 cpu1-cpu1
rx1-tx1 cpu2-cpu2
rx2-tx2 cpu3-cpu3
rx3-tx3 cpu4-cpu4
rx4-tx4 cpu5-cpu5
rx5-tx5 cpu6-cpu6
rx6-tx6 cpu7-cpu7
rx7-tx7 cpu8-cpu8
rx8-tx8 cpu9-cpu9
rx9-tx9 cpu10-cpu10
rx10-tx10 cpu11-cpu11
rx11-tx11 cpu12-cpu12
rx12-tx12 cpu13-cpu13
rx13-tx13 cpu14-cpu14
rx14-tx14 cpu15-cpu15
rx15-tx15 cpu16-cpu16
HW mcast list:
link 33:33:00:00:00:01
link 33:33:00:00:00:02
link 01:80:c2:00:00:0e
link 01:80:c2:00:00:03
link 01:80:c2:00:00:00
link 01:00:5e:00:00:01
link 33:33:ff:bf:43:e4



$ lspci -nnv
...

01:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
...
Kernel driver in use: ixgbe
Kernel modules: ixgbe

...

81:00.0 Ethernet controller [0200]: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection [8086:10fb] (rev 01)
...
Kernel driver in use: igb_uio
Kernel modules: ixgbe

3.3.2 解决思路

如果想要bond2.kni的网卡上面的IP相关操作正常,可以针对该IP添加kni_host路由,具体操作如下:

1
2
3
4
5
6
# bond2.kni_ip可以替换为任意的一个IP
dpip route add <bond2.kni_ip>/32 scope kni_host dev bond2
dpip route del <bond2.kni_ip>/32 scope kni_host dev bond2
# ipv6网络操作也是一样
dpip route -6 add <bond2.kni_ip>/128 scope kni_host dev bond2
dpip route -6 del <bond2.kni_ip>/128 scope kni_host dev bond2

注意:放行的时候一定要一个IP一个IP放行,掩码一定要是32或者是128,一次批量放行多个IP会非常影响性能!

3.4 VIP能ping通但http请求异常

对于DPVS来说,ping请求和http请求的处理逻辑是完全不一样的。对于ping请求的icmp和icmpv6数据包,都是由DPVS网络栈本身来进行处理,并不会涉及到后面的RS节点。

能ping通则说明DPVS程序工作正常,http请求异常则说明后端的RS节点状态异常,也有可能是LIP和RS之间的通信出现了问题导致数据包无法顺利到达。

当然还有一种可能就是:LIP和RS之间的通信正常,但是用来RS探活的网卡和RS之间的通信异常导致keepalived进程误认为RS节点出现了问题从而将weight降为0。

这种情况的一个常见案例就是IPv6网络下的DPVS节点和RS节点跨网段通信,而DPVS节点上面没有添加ipv6的跨网段路由。

4、keepalived相关

keepalived出现的问题主要可以分为两个方面:脑裂和主备切换。

4.1 脑裂

一般来说,keepalived脑裂的最根本原因就是两台机器都以为自己是老大(master),造成这种情况的原因主要有两个:网络不通或者配置文件错误。这两种故障原因和排查思路在网上很多帖子都十分常见,此处不做赘述。比较少见的是一种由于交换机存在BUG,使得同一个vlan内出现两组不同的vrrp_instance使用相同的virtual_router_id引发的脑裂。

4.1.1 交换机BUG导致脑裂

当使用组播通信的时候,对于部分有BUG的交换机,不同的vrrp_instance之间如果virtual_router_id一致也有可能会出现脑裂。注意这里说的virtual_router_id一致指的是仅仅virtual_router_id这一个参数一样,就算authentication配置不同的密码,也是会收到对方的组播包,但是只是会报错提示received an invalid passwd,并不会出现脑裂(因为这里是不同的vrrp_instance而不是同一个vrrp_instance内的不同节点)。

一般来说virtual_router_id的范围是1-255,很明显这个变量设计之初就是假定一个vlan内的IP数量不要超过一个C,这样virtual_router_id就可以直接使用IP的最后一截。实际上如果vlan划分合理或者规划得当的话不太容易遇到这种问题,但是如果机房网络的vlan划分过大,又或者是机房网络质量差、交换机老旧的时候,就需要额外注意virtual_router_id冲突的问题。

下面摘录一段keepalived官网的相关描述:

arbitrary unique number from 1 to 255 used to differentiate multiple instances of vrrpd running on the same network interface and address family (and hence same socket).

Note: using the same virtual_router_id with the same address family on different interfaces has been known to cause problems with some network switches; if you are experiencing problems with using the same virtual_router_id on different interfaces, but the problems are resolved by not duplicating virtual_router_ids, the your network switches are probably not functioning correctly.

4.1.2 解决思路

组播

keepalived主备节点之间的通信默认情况下是通过组播来进行的,组播的原理这里不赘述,默认情况下不论是IPv4还是IPv6都会使用一个组播地址,对于一些常见的如BGP、VRRP协议的数据包,RFC是有提前定义划分好相对应的组播地址供其使用,keepalived官方使用的组播地址遵循定义规范,具体如下:

Multicast Group to use for IPv4 VRRP adverts Defaults to the RFC5798 IANA assigned VRRP multicast address 224.0.0.18 which You typically do not want to change.
vrrp_mcast_group4 224.0.0.18

Multicast Group to use for IPv6 VRRP adverts (default: ff02::12)
vrrp_mcast_group6 ff02::12

如果我们没办法确认节点所处网络中是否有使用了该virtual_router_idvrrp_instance,可以尝试修改组播地址来避免冲突。

单播

还有一种解决方案就是不使用组播,改为使用单播。单播不仅在网络通信质量上往往比组播更好,而且也很难出现virtual_router_id冲突的问题。同样的,如果keepalived集群之间出现因为主备节点之间组播通信质量差导致频繁出现主备切换,除了改善节点之间的通信网络质量之外,也可以尝试修改通信方式为单播。

1
2
3
4
5
6
7
8
9
10
11
# ipv4网络配置单播
unicast_src_ip 192.168.229.1
unicast_peer {
192.168.229.2
}

# ipv6网络配置单播
unicast_src_ip 2000::1
unicast_peer {
2000::2
}

上图中的unicast_src_ip是本机的IP,而unicast_peer则是对端的IP,注意这里的unicast_peer是可以有多个IP的(对应一主一备和一主多备或者多备抢占等情况)。

单播虽然在稳定性上的表现更加优秀,但是相应的配置量也大大增加,需要运维同学在每一组vrrp_instance都增加对应的单播配置,并且主备节点之间的配置内容也不同(一般都是unicast_src_ipunicast_peer对调)。并且单播相关配置一旦改错几乎就会发生脑裂,这就对配置管理检查和分发提出了更高的要求。

4.2 主备切换

keepalived主备切换的时候容易出现的问题主要是当IP已经切换到了另一台机器,但是对应交换机上面的MAC地址表记录的VIP对应的MAC地址还没有更新。这种情况常见的解决方案就是使用arping(IPv4)操作或者是ping6(IPv6)来快速手动刷新MAC记录或者是配置keepalived来自动持续刷新MAC记录。

4.2.1 手动刷新MAC

对于DPVS程序,刷新IPv4的VIP的MAC地址时,如果VIP和对应的kni网卡上的IP是同一个网段,则可以直接对kni网卡使用arping命令来刷新MAC地址(kni网卡和DPVS网卡的MAC地址一致);但是IPv6网络并没有arp这个东西,刷新MAC记录需要使用ping6命令,而这在DPVS的kni网卡中是行不通的。个人建议使用python或go之类的能够网络编程的语言编写一个简单的程序实现发送gna数据包,然后在keepalived中配置脚本当进入master状态的时候就执行脚本刷新MAC地址,即可解决IPv6下VIP切换的MAC地址更新问题。

4.2.2 keepalived刷新MAC

还有一种方案就是配置keepalived,让keepalived自己发送garp和gna数据包,keepalived配置中有比较多的vrrp_garp*相关的配置可以调整发送garp和gna数据包的参数

1
2
3
4
5
6
7
8
# 发送garp/gna数据包的间隔。这里是每10秒发送一轮
vrrp_garp_master_refresh 10
# 每次发送三个garp/gna数据包
vrrp_garp_master_refresh_repeat 3
# 每个garp数据包的发送间隔为0.001秒
vrrp_garp_interval 0.001
# 每个gna数据包的发送间隔为0.001秒
vrrp_gna_interval 0.001

不过使用keepalived配置还有一个问题:

  • keepalived发送garp/gna数据包的网卡是interface参数指定的网卡,也就是主备节点用来通信的网卡
  • keepalived发送的garp/gna数据包想要生效必须要是VIP所在的DPVS中的网卡或者是对应的kni网卡

因此如果想让keepalived来刷新VIP的MAC地址,需要将这个网卡修改为本文架构图中的bond2.kni网卡,也就是对应双臂网络架构模式下的外网网卡,同时如果使用单播通信的话还需要加上对应节点的kni_host路由以确保单播能正常通信。

4.2.3 小结

以上两种方案各有优劣,需要结合内外网网络质量、使用单播还是多播、网络路由配置管理、keepalived文件管理等多个因素考虑。

5、集群最大连接数

这部分主要是分析对比传统的LVS-DR模式和DPVS-FNAT模式下两者的最大TCP连接数性能限制瓶颈。

5.1 LVS-DR模式

首先我们看一下传统的LVS-DR模式下的连接表

1
2
3
4
5
6
7
8
9
10
11
$ ipvsadm -lnc | head | column -t
IPVS connection entries
pro expire state source virtual destination
TCP 00:16 FIN_WAIT 44.73.152.152:54300 10.0.96.104:80 192.168.229.111:80
TCP 00:34 FIN_WAIT 225.155.149.221:55182 10.0.96.104:80 192.168.229.117:80
TCP 00:22 ESTABLISHED 99.251.37.22:53601 10.0.96.104:80 192.168.229.116:80
TCP 01:05 FIN_WAIT 107.111.180.141:15997 10.0.96.104:80 192.168.229.117:80
TCP 00:46 FIN_WAIT 44.108.145.205:57801 10.0.96.104:80 192.168.229.116:80
TCP 12:01 ESTABLISHED 236.231.219.215:36811 10.0.96.104:80 192.168.229.111:80
TCP 01:36 FIN_WAIT 91.90.162.249:52287 10.0.96.104:80 192.168.229.116:80
TCP 01:41 FIN_WAIT 85.35.41.0:44148 10.0.96.104:80 192.168.229.112:80

从上面我们可以看出DPVS的连接表和LVS的连接表基本上大同小异,DPVS多了一列CPU核心数和LIP信息,但是从原理上有着极大的区别。

以下分析假定其他性能限制条件无瓶颈

首先对于LVS-DR模式而言,我们知道Client是直接和RS建立连接的,LVS在此过程中是只做数据包转发的工作,不涉及建立连接这个步骤,那么影响连接数量的就是ProtocolCIP:PortRIP:Port这五个变量,也就是常说的五元组。

1
Protocol CIP:Port RIP:Port

考虑到Protocol不是TCP就是UDP,可以将其视为常量,也就是针对LVS-DR而言:真正影响TCP连接数的是CIP:Port(RIP:Port往往是固定的),但是由于CIP:Port理论上是可以足够多的,所以这个时候TCP连接数的最大限制往往是在RS上面,也就是RS的数量和RS的性能决定了整个LVS-DR集群的最大TCP连接数。

5.2 DPVS-FNAT模式

接着我们使用ipvsadm -lnc命令来查看一下FNAT模式下的Client<-->DPVS<-->RS之间的连接情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ipvsadm -lnc | head
[1]tcp 90s TCP_EST 197.194.123.33:56058 10.0.96.216:443 192.168.228.1:41136 192.168.229.80:443
[1]tcp 7s TIME_WAIT 26.251.198.234:21164 10.0.96.216:80 192.168.228.1:44896 192.168.229.89:80
[1]tcp 7s TIME_WAIT 181.112.211.168:46863 10.0.96.216:80 192.168.228.1:62976 192.168.229.50:80
[1]tcp 90s TCP_EST 242.73.154.166:9611 10.0.96.216:443 192.168.228.1:29552 192.168.229.87:443
[1]tcp 3s TCP_CLOSE 173.137.182.178:53264 10.0.96.216:443 192.168.228.1:8512 192.168.229.87:443
[1]tcp 90s TCP_EST 14.53.6.35:23820 10.0.96.216:443 192.168.228.1:44000 192.168.229.50:443
[1]tcp 3s TCP_CLOSE 35.13.251.48:15348 10.0.96.216:443 192.168.228.1:16672 192.168.229.79:443
[1]tcp 90s TCP_EST 249.109.242.104:5566 10.0.96.216:443 192.168.228.1:10112 192.168.229.77:443
[1]tcp 3s TCP_CLOSE 20.145.41.157:6179 10.0.96.216:443 192.168.228.1:15136 192.168.229.86:443
[1]tcp 90s TCP_EST 123.34.92.153:15118 10.0.96.216:443 192.168.228.1:9232 192.168.229.87:443
$ ipvsadm -lnc | tail
[16]tcp 90s TCP_EST 89.99.59.41:65197 10.0.96.216:443 192.168.228.1:7023 192.168.229.50:443
[16]tcp 3s TCP_CLOSE 185.97.221.45:18862 10.0.96.216:443 192.168.228.1:48159 192.168.229.50:443
[16]tcp 90s TCP_EST 108.240.236.85:64013 10.0.96.216:443 192.168.228.1:49199 192.168.229.50:443
[16]tcp 90s TCP_EST 85.173.18.255:53586 10.0.96.216:443 192.168.228.1:63007 192.168.229.87:443
[16]tcp 90s TCP_EST 182.123.32.10:5912 10.0.96.216:443 192.168.228.1:19263 192.168.229.77:443
[16]tcp 90s TCP_EST 135.35.212.181:51666 10.0.96.216:443 192.168.228.1:22223 192.168.229.88:443
[16]tcp 90s TCP_EST 134.210.227.47:29393 10.0.96.216:443 192.168.228.1:26975 192.168.229.90:443
[16]tcp 7s TIME_WAIT 110.140.221.121:54046 10.0.96.216:443 192.168.228.1:5967 192.168.229.84:443
[16]tcp 3s TCP_CLOSE 123.129.23.120:18550 10.0.96.216:443 192.168.228.1:7567 192.168.229.83:443
[16]tcp 90s TCP_EST 72.250.60.207:33043 10.0.96.216:443 192.168.228.1:53279 192.168.229.86:443

然后我们逐个分析这些字段的含义:

  • [1]:这个数字表示的是CPU核心数,对应我们在dpvs.conf中配置的worker cpucpu_id,从这个字段可以看到每个DPVS进程的worker线程工作的负载情况

  • tcp:tcp或者udp,对应这一条连接的类型,这个无需解释

  • 90s30s7s3s:对应这一条连接的时间

  • CLOSE_WAITFIN_WAITSYN_RECVTCP_CLOSETCP_ESTTIME_WAIT:对应这一条tcp连接的状态

  • 最后这一组四个IP+Port的组合就是Client<-->DPVS<-->RS的对应关系:

    1
    CIP:Port VIP:Port LIP:Port RIP:Port

那么对于DPVS-FNAT模式来说,加入了LIP之后变成了四组IP+Port的组合,再加上前面的cpu_idProtocol就是影响连接数的十元组。

1
cpu_id Protocol CIP:Port VIP:Port LIP:Port RIP:Port
  • 开始分析之前我们需要知道上面的这四组IP+Port的组合实际上是分为两个四元组,即CIP:Port VIP:Port为一个四元组,LIP:Port RIP:Port为一个四元组,两个四元组之间为一一对应关系
  • 首先我们还是排除掉ProtocolVIP:PortRIP:Port,因为这三组五个变量基本也是固定的,可以视为常量
  • 接着是CIP:Port理论上是可以足够多的,不会对我们的集群最大TCP连接数产生影响
  • 然后是cpu_id,虽然一台机器最多可以有16个work cpu,但是并不意味着十元组的最大连接数=除cpu_id外的九元组的最大连接数*16,DPVS程序会把cpu_id根据LIP的端口号进行分配,从而尽可能地把负载均分到所有的CPU上面。所以这里的cpu_id和LIP的端口号也是一一对应的关系
  • 最后是LIP:Port,我们知道一个IP可以使用的端口数量最多不超过65536个,由于RIP:Port是固定的,因此这个四元组的最大TCP连接数<=LIP数量*65536*RIP数量

又因为两个四元组之间为一一对应关系,cpu_id和LIP的端口号也是一一对应的关系,所以对于DPVS-FNAT模式来说,LIP的数量往往才是限制整个集群最大连接数的关键,如果集群有多连接数的需求,建议在规划之初就要预留足够数量的IP给LIP使用。

这里顺便提一下,结合官方文档和实测,x520/82599、x710网卡在使用igb_uio这个PMD的时候,在ipv6网络下fdir不支持perfect模式,建议使用signature模式,但是注意这个模式下仅可使用一个LIP,会对集群的最大连接数有限制。

官方文档链接可以点击这里查看。

We found there exists some NICs do not (fully) support Flow Control of IPv6 required by IPv6. For example, the rte_flow of 82599 10GE Controller (ixgbe PMD) relies on an old fashion flow type flow director (fdir), which doesn’t support IPv6 in its perfect mode, and support only one local IPv4 or IPv6 in its signature mode. DPVS supports the fdir mode config for compatibility.

6、写在最后

DPVS确实在性能和功能方面都有着非常优秀的表现,也确实在落地初期会踩很多坑,建议多看文档,多查资料,多看源码,等到真正用起来之后也确实会给我们带来很多的惊喜和收获。最后顺便提一句,如果只想搭建一个小规模集群尝尝鲜,普通的IPv4网络和常见的x520网卡就足够了,当然有条件的同学可以尝试使用ECMP架构和一些比较好的网卡(如Mellanox)。