LVS使用思考

一、LVS与nginx、haproxy之类的软件负载均衡器区别

在网络报文传送上的区别

lvs与nginx、haproxy之类的负载局衡器的第一个区别是lvs通过修改网络包、在网络包转发的过程中根据特定的负载均衡算法,实现负载均衡,而nginx、haproxy这种是通过反向代理,根据特定负载均衡算法发起一个新的请求到上游服务器实现负载均衡,lvs工作在内核态,对请求过来的指定端口的网络报文实时的转发,也就是客户端在三次握手建立连接阶段的报文,lvs都会实时转发到上游服务器

而nginx、haproxy(雷同,后面只用nginx说明)通过监听一个端口,先与客户端三次握手建立连接,客户端发送请求到nginx,nginx再与上游服务器三次握手建立连接。nginx再发送相同请求到上游服务器。上游服务器响应nginx,nginx再响应客户端。整个过程中,nginx屏蔽了与上游服务器的交互过程。也就是所谓的反向代理,其中七层、四层的反向代理皆是如此。

搞明白两者在网络报文的请求和响应的区别,可以得知,lvs的负载均衡少发送了很多报文,能实时的将客户端网络报文转发到上游,在网络传输的效率上更高。

在响应处理上的区别

lvs的DR模式、TUN模式可以将响应报文负载在real server上,也就是响应报文直接从real server响应给客户端,而nginx、haproxy这种进出的流量都要经过负载均衡,使得大流量高并发的情况下,负载均衡的压力很大。

二、lvs不同模式配置实现以及抓包分析

名词解释

c_ip:客户端的ip地址
c_port:客户端的端口
v_ip:lvs暴露给客户端的ip地址
v_port: lvs暴露给客户端的端口
d_ip:lvs的内网地址
d_port:lvs内网端口
r_ip:real server的内网地址
r_port:real server的端口
c_mac client的mac地址
d_mac lvs内网网卡mac地址
r_Mac real server 的Mac地址

NAT模式

环境说明

lvs机器上使用docker跑一个centos容器,用来充当客户端,这样就可以使得c_ip与d_ip,r_ip不在同一个网段了。
C_ip: 172.17.0.2
V_ip: 172.17.0.1(实际上就是docker0的地址。)
V_port: 8888
d_ip:192.168.31.100
r_ip: 192.168.31.78
R_port: 9999
D_mac:3c:97:0e:cb:4f:08
R_mac:f0:6e:0b:c1:4f:00

实现细节:

lvs机器上配置:
1、创建转发表
ipvsadm -A -t  172.17.0.1:8888 -s rr

2、添加转发规则:
ipvsadm -a -t 172.17.0.1:8888 -r 192.168.31.78:9999 -m

rip机器上配置:
添加一条默认路由到d_ip,或者改网关也可以:

使用nginx监听9999端口,return一个简单的字符串

客户端请求:


网络报文传送细节分析:

lvs将来自客户端的网络报文中的目标地址和目标端口修改为real server的地址和端口,也就是DNAT,进来的报文tcp四元组是(c_ip,c_port,v_ip,v_port),lvs修改后是(c_ip,c_port,r_ip,r_port),然后发送出去,real server接收到网络报文之后,进行响应,这个时候要配置r_ip上的网关或者加一条默认路由为lvs的d_ip,这个时候从r_ip响应的报文的tcp四元组是(r_ip,r_port,c_ip,c_port),就会将响应报文发送到lvs的d_ip,lvs再进行SNAT,发送给客户端的tcp四元组是(v_ip,v_port,c_ip,c_port),发送出去,整个过程中,客户端发送和接收到的网络报文的tcp四元组是一致的,通信成功。

cip抓包

vip抓包

dip抓包

rip抓包

在请求报文的转发过程中,c_ip和c_port是没有发生改变的。也就是lvs只进行了DNAT,没有进行SNAT。而响应报文是通过r_ip上的默认路由规则发送回d_ip

这里注意一个问题:在测试请求的时候,我们再从另一个与d_ip同网段的客户端192.168.31.113上添加一条路由:到172.17.0.0/16的网路包发往192.168.31.100。这样可以与vip通信了,但是这个时候请求vip,是不能得到正确的响应的,因为r_ip与c_ip网段相同的话,r_ip在构造响应报文的时候,目标Mac地址就是c_ip的Mac地址,响应报文将直接通过交换机发送到客户端上,对于客户端来讲,发起请求的tcp连接四元组是:(c_ip,c_port,v_ip,v_port),而接收到的网路报文的tcp四元组是(r_ip,r_port,c_ip,c_port),两者不一致,客户端是不认的,会发送RST报文进行回复。实际上这个问题在实际使用的时候基本不会出现。因为从客户端请求过来的报文会在公网一跳一跳转发的,进入lvs的机器时候,路由器左右两个接口的网段是不相同的。但是通常我们在做测试的时候会出现这种问题。所以注意下。

在cip上抓包

在rip上抓包

nat模式的使用限制:

r_ip响应的报文是通过路由发送到d_ip上的,需要lvs与real server处在同一网段,这个时候将rip的网关指向dip,报文才能发送过来,总之回来的报文需要经过lvs进行SNAT,(如果是这样的话,想必不是在一个网段里,但是通过设置路由规则,使得rip的响应报文最终到达lvs也是可行,由于本地环境限制,有待进行验证,如果可行的话,由于运维成本太高,也不适用的)。

DR模式

环境说明

在另一台测试机上使用docker跑一个centos容器(不能在lvs机器上跑),用来充当客户端,这样就可以使得c_ip与d_ip,r_ip不在同一个网段了。
c_ip: 172.17.0.2
容器所在宿主机的ip:192.168.31.113
v_ip:192.168.31.111
v_port: 8888
d_ip:192.168.31.100
r_ip: 192.168.31.78
r_port: 9999
d_mac:3c:97:0e:cb:4f:08
r_mac:f0:6e:0b:c1:4f:00

配置细节

将之前配置清空
lvs机器上配置:
创建转发表:
ipvsadm -A -t 192.168.31.111:8888 -s rr
添加转发规则:
ipvsadm -a -t 192.168.31.111:8888 -r 192.168.31.78:8888 -g

Real server机器上配置:
1、因为要添加相同的vip到lo:01接口上,所以为了防止进行arp应答,添加以下内核配置。
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
2、配置相同的vip到lo:01接口上:
ifconfig lo:01 192.168.31.111 netmask 255.255.255.255 broadcast 192.168.31.111 up
配置子网掩码为255.255.255.255,使得real server上的vip成为一个单点局域网,防止进行arp应答。广播域也是单点。
3、添加一条路由规则,如果请求的目标ip是vip,使得出去的源ip也是vip:
route add -host 192.168.31.111 dev lo:01

请求并抓包:

cip上抓包:

vip上抓包:
可以看到,在lvs机器上抓到的包都只有流入,没有流出,也就是real server直接响应了客户端。

rip上抓包:

网络报文传送细节分析:

DR模式的原理就是将流入lvs的报文的目的mac地址修改为realserver的mac地址。发送出去,在二层网络中通过交换机发送到了realserver。由于只修改了目的mac地址,DR模式工作在数据链路层,不支持端口映射,也就是v_port与r_port必须一致,如果在添加转发规则的时候选择的是DR模式,就算配置的r_port与v_port不一致,使用ipvsadm -Ln查看发现配置上的规则被lvs修改为一致了。DR模式相比于NAT模式由于只修改了mac地址,所以性能更高一些。但是也是因为只能修改mac地址,所以不能支持端口映射也是硬伤,这个通常是我们不能接受的。

TUN(IP 隧道)模式

环境说明:

在另一台测试机上使用容器机器上使用docker跑一个centos容器(不能在lvs机器上跑),用来充当客户端,这样就可以使得c_ip与d_ip,r_ip不在同一个网段了。
c_ip: 172.17.0.2
容器所在宿主机的ip:192.168.31.113
v_ip:192.168.31.111
v_port: 8888
d_ip:192.168.31.100
r_ip: 192.168.31.78
r_port: 9999
d_mac:3c:97:0e:cb:4f:08

配置细节

将之前的配置清空:
lvs机器配置:
1、创建转发表
ipvsadm -A -t 192.168.31.111:8888 -s rr
2、添加转发规则,-i表示使用ip隧道模式
ipvsadm -a -t 192.168.31.111:8888 -r 192.168.31.78:8888 -i
3、打开ipip模块
modprobe ipip
这个时候会出现一块隧道模式的网卡:
Ip add show,此时的状态是关闭的而且是没有地址的

4、将vip绑到隧道模式的网卡上,并且启动
ifconfig tunl0 192.168.31.111 netmask 255.255.255.0 broadcast 192.168.31.255 up

rip配置

同样的方法在rip上开启ipip模块,绑定vip并且启动,但是这个时候和DR模式一样,限制的子网掩码和广播域都是单点局域网

进行请求测试

cip抓包:


vip抓包:

rip抓包


网络报文传送细节分析:

TUN模式同DR模式一样,响应报文也是直接发送到客户端不用经过lvs,与DR不同的是,DR模式通过修改mac地址,请求报文只能在同一个二层网络中流转,而TUN模式通过在原始IP报文外面封装一层ip头部,这样请求报文就可以搭载着这个ip发送到其他网段而不受同一网段限制。TUN模式同样不支持端口映射,因为没有修改传输层的东西,这个通常也不能接受。而且多一层封装性能也不如DR模式来得好,配置也比较复杂。

FULL-NAT

FULL-NAT模式对请求报文进行DNAT的同时进行SNAT,例如请求报文的四元组是(c_ip,c_port,v_ip,v_port),经过lvs修改之后变成(d_ip,d_port,r_ip,r_port),类似于反向代理发起一个新的请求,但是却有本质上的区别。Real server也不用添加默认路由或者改网关地址了。直接响应DIP即可。FULL-NAT解决了lvs与real server跨网段的问题。但是相比于NAT模式同时进行了SNAT,性能有所消耗。

总结

想要实现更加丰富的功能,就要修改更多的东西,同时就会引起新能下降,DR模式修改最少,性能最好。七层反向代理实现负载均衡功能更加丰富,但是需要解包到应用层,性能会更差一些。这些本无法避免。所以在根据业务选型的时候做好心理准备,不必一味纠结性能问题。

业务量不大的情况下,像nginx、haproxy这种七层负载均衡器完全可以应对,其本身也能支持较大并发连接处理。如果还要再大一些,通常我们都是四层、七层混合负载均衡架构,提高可用性的同时,也支持应用层的防火墙、缓存等功能。

上面NAT、DR、TUN模式的抓包文件我放在了github上。有需要的朋友可以下载下来用wireshark查看,地址:
https://github.com/pllsxyc/lvs_tcpdump.git

留下评论