实现LVS-DR和Session绑定

2018-05-04|Categories: Cluster, Linux, Network|Tags: , |

LVS的三种工作模式以及各自的工作原理已经在《LVS简介》介绍过,这篇文章只介绍LVS-DR的实现过程。

实现LVS-DR

网络拓扑

这个实验的网络拓扑如下图,总共需要用到5台Linux虚拟机(不包括交换机):

lvs-dr network topology

注意:

  • 在生产环境中,VIP是一个公网IP,供用户访问。
  • 对于后端Real Server(RS),VIP绑定在它们的环回(loopback)网卡上。

详细的网络参数 2018-05-09

LVS-DR实验的关键是网络拓扑,第一次实验是对照视频教程做的,虽然结果正确,但理解并不透彻,导致第二次实验一直出错。经过反复的思考,配合Tcpdump的抓包结果,终于把之前模糊的地方搞清楚了。因此,我认为很有必要把详细的网络参数记录下来。

客户端的IP和路由

网卡 网卡模式 IP地址 地址获取方式 默认网关
eth2 host only 172.16.125.61/24 static
eth3 bridge 192.168.2.226/23 dhcp
eth3:1 10.0.0.61/24 manual 192.168.11.0/24 via 10.0.0.74

因为是用虚拟机完成实验,所以网卡模式分为「Host-only(仅主机)」和「Bridge(桥接)」两种,前者只能与局域网内的成员通信,后者还可以访问互联网。

我的所有虚拟机默认都有两块网卡:

  • 一块设为桥接模式,动态获取192.168.2.0/23网段内的地址。
  • 一块是仅主机模式,默认配置172.16.125.0/24网段内的地址。

我会根据实验需要选择性禁用某块网卡,或添加网卡别名,但只要前面两个IP地址存在,我通常不会修改。换句话说,上表中地址获取方式为「dhcp」和「static」的网卡开机就拿到了对应的IP地址,在本次实验中未做修改。

几种地址获取方式区别如下:

  • dhcp:在网卡配置文件设置了BOOTPROTO=dhcp
  • static:在网卡配置文件设置了BOOTPROTO=static或是BOOTPROTO=none,以及IPADDR
  • manual:通过命令手工指定,例如ifconfig eth3:1 10.0.0.61/24
    • 为了与默认的网卡设置有明显区别,手工指定的地址全部绑定到网卡别名,例如eth3:1ens34:1
  • script:通过脚本来设置。同样是绑定到网卡别名。脚本内容在后面。

路由器的IP

网卡 网卡模式 IP地址 地址获取方式
ens33 bridge 192.168.3.58/23 dhcp
ens33:1 10.0.0.74/24 manual
ens34 host only 172.16.125.74/24 static
ens34:1 192.168.11.74/24 manual

同一个网段的IP地址,不能同时绑定到不同的网卡上,否则无法通信。以192.168.2.0/23网段为例,我曾经粗心大意把它和标准的C类网段192.168.2.0/24混淆,然后,

  • 桥接网卡ens33从DHCP服务器获取了192.168.3.58/23
  • 我在仅主机网卡ens34:1手工指定了192.168.2.100/23

结果在客户端ping 192.168.2.100总是不成功,因为同一个网段的IP地址绑定到不同网卡会导致路由冲突

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
192.168.2.0     0.0.0.0         255.255.254.0   U     0      0        0 ens34
192.168.2.0     0.0.0.0         255.255.254.0   U     100    0        0 ens33
...

所以在被ping的时候无法返回响应包:

$ tcpdump -i any -nn -c 2 icmp and host 192.168.2.226
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
18:41:06.411374 IP 192.168.2.226 > 192.168.2.100: ICMP echo request, id 4753, seq 338, length 64
18:41:07.413183 IP 192.168.2.226 > 192.168.2.100: ICMP echo request, id 4753, seq 339, length 64
2 packets captured
2 packets received by filter
0 packets dropped by kernel

VS的IP和路由

网卡 网卡模式 IP地址 地址获取方式 默认网关
ens34 host only 172.16.125.71/24 static default via 172.16.125.74
ens34:1 192.168.11.71/24 script

RS1的IP和路由

网卡 网卡模式 IP地址 地址获取方式 默认网关
lo:1 loopback 192.168.11.71/24 script
ens34 host only 172.16.125.72/24 static default via 172.16.125.74

RS2的IP和路由

网卡 网卡模式 IP地址 地址获取方式 默认网关
lo:1 loopback 192.168.11.71/24 script
ens34 host only 172.16.125.73/24 static default via 172.16.125.74

配置路由器

绑定IP地址

ip a add 10.0.0.74/24 dev ens33

ip a add 192.168.11.74/24 dev ens34
ip a add 172.16.125.74/24 dev ens34

也可以修改网卡配置文件,以便网卡重启后仍然保留地址:

$ vim /etc/sysconfig/network-scripts/ifcfg-ens34

DEVICE=ens34
ONBOOT=yes
BOOTPROTO=none
# The 1st IP
IPADDR1=192.168.11.74
PREFIX1=24
# The 2nd IP
IPADDR2=172.16.125.74
PREFIX2=24

CentOS 6不支持IPADDR1这种写法,每个网卡配置文件只能配置一个IP地址,同一个网卡要绑定其它IP必须创建网卡别名:

$ vim /etc/sysconfig/network-scripts/ifcfg-eth1:1

DEVICE=eth1:1
ONBOOT=yes
BOOTPROTO=none
IPADDR=172.16.125.74
PREFIX=24

开启IPv4转发

echo 1 > /proc/sys/net/ipv4/ip_forward

配置RS

绑定RIP、添加默认网关

ip a add 172.16.125.72/24 dev ens33

# 只添加网络路由
ip r add 10.0.0.0/24 via 172.16.125.74

# 或者添加默认网关
ip r add default via 172.16.125.74

也可以直接在网卡配置文件添加默认网关:

$ vim /etc/sysconfig/network-scripts/ifcfg-ens33
GATEWAY=172.16.125.74

# 重启网卡
$ ifdown ens33
$ ifup ens33

但最简洁的方法是,通过nmcli命令修改网卡配置文件的默认网关字段:

nmcli c m ens33 +ipv4.gateway 172.16.125.74
nmcli c up ens33

对于RS2,只需要把上面所有步骤中的172.16.125.72改为172.16.125.73即可。

绑定VIP

在LVS-DR中,必须给VS和RS配置同样的VIP。为了避免地址冲突,需要把RS配置成「Non-ARP Device」,也就是不响应ARP广播,以下脚本可以简化配置过程:

$ vim ~/lvs_rs_vip.sh

#!/bin/bash
vip='192.168.11.71'
mask='255.255.255.0'
dev='lo:1'

case $1 in
    start)
        echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
        echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore #可注释掉
        echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
        echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce #可注释掉
        ifconfig $dev $vip netmask $mask #broadcast $vip up
        #route add -host $vip dev $dev
        ;;
    stop)
        ifconfig $dev down
        echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
        echo 0 > /proc/sys/net/ipv4/conf/lo/arp_ignore
        echo 0 > /proc/sys/net/ipv4/conf/all/arp_announce
        echo 0 > /proc/sys/net/ipv4/conf/lo/arp_announce
        ;;
    *)
        echo "Usage: $(basename $0) start|stop"
        exit 1
        ;;
esac

在每台RS执行上面的脚本:

bash ~/lvs_rs_vip.sh start

若要清除VIP、恢复相关内核参数的原始值,则执行:

bash ~/lvs_rs_vip.sh stop

安装、配置、启动httpd

yum install httpd -y
echo 'Real server at 172.16.125.72' > /var/www/html/index.html
systemctl start httpd

单机测试

# 默认网关
ping 172.16.125.74

# 本机httpd服务状态
curl 172.16.125.72

对于RS2,只需要把上面所有步骤中的172.16.125.72改为172.16.125.73即可。

配置VS

绑定DIP、添加默认网关

ip a add 172.16.125.71/24 dev ens33
ip r add default via 172.16.125.74

开启IPv4转发

echo 1 > /proc/sys/net/ipv4/ip_forward

安装ipvsadm

yum install ipvsadm -y

绑定VIP、添加ipvs规则

创建一个脚本:

$ vim ~/lvs_vs_vip_rules.sh

#!/bin/bash
vip='192.168.11.71'
iface='ens34:1'
mask='255.255.255.0'
port='80'
rs1='172.16.125.72'
rs2='172.16.125.73'
scheduler='wrr'
type='-g'

case $1 in
    start)
        ifconfig $iface $vip netmask $mask #broadcast $vip up
        iptables -F
        ipvsadm -A -t ${vip}:${port} -s $scheduler
        ipvsadm -a -t ${vip}:${port} -r ${rs1} $type -w 1
        ipvsadm -a -t ${vip}:${port} -r ${rs2} $type -w 2
        ;;
    stop)
        ipvsadm -C
        ifconfig $iface down
        ;;
    *)
        echo "Usage $(basename $0) start|stop"
        exit 1
        ;;
esac

脚本里使用的调度算法是wrr(Weighted Round Robin),工作模式为LVS-DR(-g,g表示gateway)。

绑定VIP、添加ipvs规则两件事通过一个脚本完成:

bash ~/lvs_vs_vip_rules.sh start

删除VIP和ipvs规则就换成stop参数:

bash ~/lvs_vs_vip_rules.sh stop

查看ipvs规则:

$ ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.11.71:80 wrr
  -> 172.16.125.72:80             Route   1      0          0
  -> 172.16.125.73:80             Route   2      0          0

查看本机所有IP和对应的网卡:

$ ip -4 a | grep -E 'inet\b' | awk '{ printf("%-10s %s\n", $NF, $2) }'
lo         127.0.0.1/8
ens34      172.16.125.71/24
ens34:1    192.168.11.71/24

单机测试

# 默认网关
ping 172.16.125.74

# 与后端httpd服务的连接状态
curl 172.16.125.72
curl 172.16.125.73

注意,不要在VS上curl $VIP,这个操作不会被调度到后端的RS,因为请求只能发送到本机,无法到达后端RS:

$ curl 192.168.11.71
curl: (28) Connection timed out after 60003 milliseconds
$ tcpdump -i any -nn -c 2 tcp and dst port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
01:52:57.641198 IP 192.168.11.71.58920 > 192.168.11.71.80: Flags [S], seq 1449635044, win 43690, options [mss 65495,sackOK,TS val 58995272 ecr 0,nop,wscale 7], length 0
01:52:58.643798 IP 192.168.11.71.58920 > 192.168.11.71.80: Flags [S], seq 1449635044, win 43690, options [mss 65495,sackOK,TS val 58996274 ecr 0,nop,wscale 7], length 0
2 packets captured
2 packets received by filter
0 packets dropped by kernel

配置客户端

绑定IP地址、添加默认网关

ip a add 10.0.0.61/24 dev eth3

# 添加默认网关
ip r a 192.168.11.0/24 via 10.0.0.74

单机测试

# 默认网关
ping 10.0.0.74

# VIP是否可以到达
ping 192.168.11.71

整体测试

$ for i in {1..90}; do curl -s 192.168.11.71; done | sort | uniq -c
     30 Real server at 172.16.125.72
     60 Real server at 172.16.125.73

可以看到,RS2(172.16.125.73)被调度的次数是RS1的2倍,因为RS2的权重(-w 2)是RS1的2倍。

至此,LVS-DR已实现。

基于防火墙标记实现LVS-DR

现在越来越多的网站启用了HTTPS,网站同时存在HTTPS和HTTP两种访问方式,如果让LVS同时处理这两种数据包,一种方法是配置两组VS:

# 1st VS
ipvsadm -A -t 192.168.11.71:80
# add RS ...

# 2nd VS
ipvsadm -A -t 192.168.11.71:443
# add RS ...

但还有更好的办法:使用防火墙标记(FireWall Mark,FWM),把访问80和443端口的数据包打上同样的标记,然后针对这个标记配置一组VS。实现步骤如下:

先添加iptables规则:

iptables -t mangle -A PREROUTING -p tcp -m multiport --dports 80,443 \
            -j MARK --set-mark 80443

# 80443只是我为了方便记忆而选取的,可以是其它值

再添加ipvs规则:

# 清空原有规则
ipvsadm -C

ipvsadm -A -f 80443 -s wrr
ipvsadm -a -f 80443 -r 172.16.125.72 -g
ipvsadm -a -f 80443 -r 172.16.125.73 -g -w 2

查看ipvs规则,注意FWM 80443 wrr字样,表示VS基于FWM调度:

$ ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
FWM  80443 wrr
  -> 172.16.125.72:0              Route   1      0          0
  -> 172.16.125.73:0              Route   2      0          0

配置RS支持HTTPS:

yum install mod_ssl -y
systemctl restart httpd

测试:

$ for i in {1..12}; do curl -sk https://192.168.11.71; curl -s 192.168.11.71; done | sort | uniq -c
      8 Real server at 172.16.125.72
     16 Real server at 172.16.125.73

至此,基于防火墙标记的LVS-DR已实现。

LVS持久化(Session绑定)

LVS持久化有什么用

LVS持久化就是Session绑定,需要用到-p, --persistent [timeout]选项,指定这个选项之后,无论使用哪一种调度算法,在一段时间内(默认360秒),能够把来自同一个地址的请求始终发往同一个RS

相关ManPage解释如下:

Specify  that  a virtual service is persistent. If this option is speci‐
fied, multiple requests from a client are redirected to  the  same  real
server  selected for the first request.  Optionally, the timeout of per‐
sistent sessions may  be  specified  given  in  seconds,  otherwise  the
default of 300 seconds will be used. This option may be used in conjunc‐
tion with protocols such as SSL  or  FTP  where  it  is  important  that
clients consistently connect with the same real server.

Note: If a virtual service is to handle FTP connections then persistence
must be set for the virtual service if Direct Routing or  Tunnelling  is
used as the forwarding mechanism. If Masquerading is used in conjunction
with an FTP service than persistence is not necessary, but the ip_vs_ftp
kernel  module  must be used.  This module may be manually inserted into
the kernel using insmod(8).

使用SSL和FTP协议时,都必须启用持久化,用于SSL是保证连接的安全性,用于FTP则是保证功能正常运行。对于FTP协议,LVS-DR或LVS-TUN模式必须启用持久化;LVS-NAT不是必须启用,但必须使用ip_vs_ftp内核模块。

LVS持久化的工作原理

官方文档Persistence Handling in LVS解释了持久化的工作原理:

In the persistent port, when a client first accesses the service, LinuxDirector will create a connection template between the given client and the selected server, then create an entry for the connection in the hash table. The template expires in a configurable time, and the template won't expire until all its connections expire.

Although the persistent port may lead to slight load imbalance among servers, it is a good solution to connection affinity.

当客户端访问启用了持久化的端口之后,VS会在客户端和被选中的RS之间建立一个连接模板,并在哈希表里给这个连接创建一条记录。连接模板会在指定的时间段之后过期,但模板在过期前会保证它管理的所有连接已经完成。持久化端口会导致轻微的负载不平衡,但这是解决连接关系的好办法。

我把「connection affinity」直译为连接关系,又是一个新的术语,以下是我从HAProxy公司官网上找到的定义:

What is the difference between Persistence and Affinity?

Affinity: this is when we use an information from a layer below the application layer to maintain a client request to a single server
Persistence: this is when we use Application layer information to stick a client to a single server
sticky session: a sticky session is a session maintained by persistence

The main advantage of the persistence over affinity is that it’s much more accurate, but sometimes, Persistence is not doable, so we must rely on affinity.

Using persistence, we mean that we’re 100% sure that a user will get redirected to a single server.
Using affinity, we mean that the user may be redirected to the same server…

这里说「关系」是使用应用层下面的信息(IP:port)来连接某个客户端到指定服务器,而「持久化」则是使用应用层信息(HTTP cookie)来把特定客户端连接到特定服务器。持久化比关系准确得多,但有时候做不了持久化,只能依赖关系。

很明显HAProxy网站这篇文章的持久化和LVS的持久化不是一回事,因为LVS的持久化并不使用应用层信息。

启用LVS持久化

启用LVS持久化很简单,只需要修改前面的ipvs规则,在末尾加上-p选项即可:

ipvsadm -E -f 80443 -s wrr -p

查看ipvs可以看到persistent 360

$ ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
FWM  80443 wrr persistent 360
  -> 172.16.125.72:0              Route   1      0          0
  -> 172.16.125.73:0              Route   2      0          0

ManPage里说--persistent的默认值是300秒,而实际运行结果是360秒,应该是ManPage未更新到最新版本。

此时再测试,所有的请求都被调度到权重高的那台RS:

$ for i in {1..12}; do curl -sk https://192.168.11.71; curl -s 192.168.11.71; done | sort | uniq -c
     24 Real server at 172.16.125.73

LVS持久化粒度

前面启用持久化是把-p选项加在-f 80443后面,这是基于每个防火墙标记的「持久化粒度(Persistent Granularity)」,持久化粒度是LVS官方文档使用的术语,某些资料中出现的「PFWMC、PPC、PCC」的说法在官方文档中并未发现。

LVS的持久化粒度默认是基于单个客户端的,可以通过-M, --netmask netmask选项设置网络掩码把多个客户端分为一组,以便访问相同的RS:

We add the persistent netmask into persistent services. The client source address is masked with this netmask for the purpose of creating and accessing the templates, then all clients within the peristent netmask will access the same server.

The default persistent mask is 255.255.255.255, which means that the persistent granularity is per client.

例如:

ipvsadm -A -t virtualdomain:www -p -M 255.255.255.0
ipvsadm -a -t virtualdomain:www -r 192.168.1.2
ipvsadm -a -t virtualdomain:www -r 192.168.1.3

如果把持久化端口设为0,客户端所有类型的请求都会被调度到RS,例如:

ipvsadm -C

ipvsadm -A -t 192.168.11.71:0 -p
ipvsadm -a -t 192.168.11.71:0 -r 172.16.125.72
ipvsadm -a -t 192.168.11.71:0 -r 172.16.125.73
$ ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.11.71:0 wlc persistent 360
  -> 172.16.125.72:0              Route   1      0          0
  -> 172.16.125.73:0              Route   1      0          0

完成以上配置后,连SSH请求都会被调度到RS,如下图所示:

lvs Catch-all persistence

很显然这是非常不安全的,必须谨慎使用这个配置,同时务必设置防火墙规则,只放行特定的数据包。

至此,LVS持久化已实现。

Leave A Comment