LVS是工作在四层的高性能负载均衡服务器,由于工作在TCP/IP层并不涉及到用户态,摆脱了套接字65535数量的限制,所以性能十分强悍,当然优秀的背后少不了我们国人章文嵩的付出,感谢开源如此优秀的作品。
LVS 是工作在内核netfilter的INPUT链路上的一组ipvs框架,他的使用有点类似于配置netfilter实现防火墙过滤需要借助iptables这组工具,当然LVS也是如此,只提供了接口,如果想要配置ipvs需要首先安装ipvsadm这组工具,ipvsadm在base源中就有提供
在开始之前首先判断的系统是否支持ipvs,最单粗暴的办法
[root@lvs ~]# grep -i "ipvs" /boot/config-3.10.0-327.el7.x86_64
CONFIG_NETFILTER_XT_MATCH_IPVS=m
# IPVS transport protocol load balancing support
# IPVS scheduler
# IPVS SH scheduler
# IPVS application helper
[root@lvs ~]#
在开始设计负载均衡之前我们首先考虑以下四点?
是否需要会话保持
如果设计的集群牵扯到会话保持那么在lvs的调度算法上可能需要有所选择
方式一:调度算法控制
采用:SH,LBLC,LBLCR进行控制
方式二:集群参数控制*
采用:ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]]
需要注意的是任何形式的会话保持策略都有损负载均衡集群的公平性
-
是否需要共享存储
这个是任何的集群服务几乎都要考虑的问题,文件一致性,不过这个问题似乎也不难解决,基础的NFS可以解决这种问题,或者任何NAS服务都可以作为解决方案,需要注意的是业务场景以及业务需求进行决定,一般分为两种形式
1、类主从(rsync+inotify)
2、分布式文件系统
-
服务是否需要借助防火墙标记实现
如果你的服务比如nginx牵扯到80以及443的rewrite跳转,那么当80rewirte之后意味着将重新进行请求,我们希望他跳转443时仍然是同一台服务器,这个时候就需要借助iptables在prerouting链路上将2种服务封装为1种标记,我们根据标记进行处理
打标记方法(在Director主机):iptables -t mangle -A PREROUTING -d $vip -p $proto --dport $port -j MARK --set-mark NUMBER
健康状态检查如何实现
由于LVS本身四层的特性决定了不具备高等协议http,https等7层协议的健康探测能力,所以如果一旦RS种一台服务出现了宕机,ipvs规则并不能实现自动移除,所以需要一种工作在七层或者说用户态上的守护进程台实时进行探测服务可用性,如果出现异常则尽快将其从ipvs列表中移除,当恢复时则自动添加,当全部宕机时提供sorryserver的功能
方案一:keepalived、ldirectord、corosync,推荐本方法
方案二:自行shell脚本实现,不推荐此方法,对资源消耗太大
调度算法的选择
ipvs scheduler:根据其调度时是否考虑各RS当前的负载状态,可分为静态方法和动态方法两种:
-
静态方法:仅根据算法本身进行调度;
RR:roundrobin,轮询;
WRR:Weighted RR,加权轮询;
SH:Source Hashing,实现session sticky,源IP地址hash;将来自于同一个IP地址的请求始终发往第一次挑中的RS,从而实现会话绑定;
DH:Destination Hashing;目标地址哈希,将发往同一个目标地址的请求始终转发至第一次挑中的RS,典型使用场景是正向代理缓存场景中的负载均衡; -
动态方法:主要根据每RS当前的负载状态及调度算法进行调度;
LC:least connections 最少连接数, Overhead=activeconns256+inactiveconns 活动连接数256+非活动连接数 非活动连接数对系统消耗非常低,几万个也就是几兆内存,一般非活动连接数可以忽略不计
WLC:Weighted LC 加权最少连接数,Overhead=(activeconns256+inactiveconns)/weight 活动连接数256+非活动连接数/权重
SED:Shortest Expection Delay 最短的预期延迟,Overhead=(activeconns+1)*256/weight 同等条件下当前服务器连接数为0时,算法预制1个链接数,0+1除以权重值(这里为了方便不乘256),所以权重值越大,性能越高,得到的结果越小,越优先提供服务
NQ:Never Queue 从不排队,当服务器之间权重值相差较大时,A:1 B:10意味着SED下优先给10个请求到B服务器,A则在10个请求之前一直闲置,NQ的出现是指每台服务器按照权重先负载起来,不至于一直工作到B不能工作为止LBLC:Locality-Based LC,动态的DH算法,也就是最少连接的目标地址hash算法; LBLCR:LBLC with Replication,带复制功能的LBLC,通过动态的DH算法访问到B,用户的请求在A,B则向A请求,假如A服务器承载了90%的活跃,B服务器承载了10%活跃,A将一部分活跃通过缓存项的复制功能复制到B,同时在进行请求;
-
NAT拓朴设计:
Useragent | Director | RS1 | RS2 |
---|---|---|---|
CIP:172.16. 3. 93 | VIP:172.16.3.181 DIP:192.168.1.254 | RIP:192.168.1.11 | RIP:192.168.1.12 |
配置信息:
Useragent:
[root@userclient ~]# ifconfig eno16777736 172.16.3.93 netmask 255.255.255.0 up ##配置eno16777736
Director配置:
[root@lvs ~]# ifconfig eno16777736 172.16.3.181 netmask 255.255.255.0 up ##配置eno16777736为172.16.3.181 ,临时生效重启失效,建议修改配置文件
[root@lvs ~]# ifconfig eno33554992 192.168.1.254 netmask 255.255.255.0 up
[root@lvs ~]# iptables -F ##清空规则防止iptables的input链对ipvs产生影响
[root@lvs ~]# ipvsadm -A -t 172.16.3.181:80 -s wrr ##添加集群服务,-t 指定tcp协议,-s 指定调度策略,这里为加权轮询
[root@lvs ~]# ipvsadm -a -t 172.16.3.181:80 -r 192.168.1.11:80 -m -w 1 ## -m 指的是nat模型,-w 配置权重值
[root@lvs ~]# ipvsadm -a -t 172.16.3.181:80 -r 192.168.1.12:80 -m -w 2
[root@lvs ~]# ipvsadm-save -n > /etc/sysconfig/ipvsadm ##备份ipvs配置
[root@lvs ~]# echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf ##开启ipv4核心转发
[root@lvs ~]# sysctl -p
RS1配置:
[root@node1 ~]# ifconfig eno16777736 192.168.1.11 netmask 255.255.255.0 up
[root@node1 ~]# route add default gw 192.168.1.254
[root@node1 ~]# yum install nginx -y;syatemctl start nginx;systemctl enable nginx
[root@node1 ~]# echo "R1,192.168.1.11
" > /usr/share/nginx/html/index.html
RS2配置:
[root@node1 ~]# ifconfig eno16777736 192.168.1.12 netmask 255.255.255.0 up
[root@node1 ~]# route add default gw 192.168.1.254
[root@node1 ~]# yum install nginx -y;syatemctl start nginx;systemctl enable nginx
[root@node1 ~]# echo "R1,192.168.1.12
" > /usr/share/nginx/html/index.html
测试:
由于没有配置会话保持的情况下,根据wlc的特性以及我们分配的权重值得到以下结果:
[root@userclient ~]# for k in {1..10};do curl 172.16.3.181:80;done
R2,192.168.1.12
R1,192.168.1.11
R2,192.168.1.12
R2,192.168.1.12
R1,192.168.1.11
R2,192.168.1.12
R2,192.168.1.12
R1,192.168.1.11
R2,192.168.1.12
R2,192.168.1.12
DR模型处理流程:
1、当用户通过公网访问请求到达服务器网络时,报文结构是【CIP+port|VIP+port】,由于路由器末梢区域接口和VIP工作在同一网络,第一次路由器不知道VIP的IP地址
2、路由器发出ARP广播请求问,谁是VIP,这个过程是ARP广播通告(arpannounce),而DR收到请求之后将会进行ARP响应(arpignore),并提供自己的MAC。此时其他配置了VIP的RS服务器由于限制了arpannounce和arpignore则无法响应,所以只有DR能响应自己的MAC
3、当路由器收到来自DR的MAC后,在原来的报文基础上添加【源MAC(路由器MAC)|目标MAC(VIP的Mac)】+【CIP+port|VIP+port】被交换机根据MAC交换送往DR
4、当DR收到后拆分MAC报文后报文得到【CIP+port|VIP+port】,到达防火墙input链上的ipvs匹配到port是集群服务端口,则将报文修改为【源MAC(VIP的Mac)|目标MAC(RS1的Mac)】【CIP+port|VIP+port】进行交换机交换到RS1
5、当RS1收到交换机的数据包拆分得到【CIP+port|VIP+port】,发现目标IP是本机的lo:0的VIP,自己无法处理,必须通过lo:0的vip来处理(配置route add -h $vip dev lo:0的形式指定),当lo:0收到后,得到port,交给进程进行处理,然后【VIP+port|CIP+port】交给外层eno网卡进行返回
6、eno外层网卡收到后封装【源MAC(RS的Mac)|目标MAC(路由的Mac)】【VIP+port|CIP+port】通过路由器一直送达公司外层路由器...然后通过公网返回给用户
DR拓朴设计:
Useragent | Director | RS1 | RS2 |
---|---|---|---|
CIP:172.16. 3. 93 | DIP:172.16.3.181 VIP:172.16.3.99 | RIP:172.16.3.87 VIP:172.16.3.99 | RIP:172.16.3.89 VIP:172.16.3.99 |
配置信息:
Useragent:
[root@userclient ~]# ifconfig eno16777736
eno16777736: flags=4163 mtu 1500
inet 172.16.3.93 netmask 255.255.255.0 broadcast 172.16.3.255
Director配置:
[root@lvs ~]# yum install ipvsadm -y
[root@lvs ~]# ifconfig eno16777736:0 172.16.3.99 netmask 255.255.255.255 broadcast 172.16.3.99 up
[root@lvs ~]# iptables -F ##清空规则防止iptables的input链对ipvs产生影响
[root@lvs ~]# ipvsadm -A -t 172.16.3.99:80 -s wrr ##添加集群服务,-t 指定tcp协议,-s 指定调度策略,这里为加权轮询
[root@lvs ~]# ipvsadm -a -t 172.16.3.99:80 -r 172.16.3.87:80 -g -w 1 ##为172.16.3.99:80集群服务添加RS,-g 指定DR模型,-w指定权重
[root@lvs ~]# ipvsadm -a -t 172.16.3.99:80 -r 172.16.3.89:80 -g -w 2
[root@lvs ~]# ipvsadm-save -n > /etc/sysconfig/ipvsadm ##保存策略到文件,便于下次恢复
[root@lvs ~]# ifconfig
eno16777736: flags=4163 mtu 1500
inet 172.16.3.181 netmask 255.255.255.0 broadcast 172.16.3.255
eno16777736:0: flags=4163 mtu 1500
inet 172.16.3.99 netmask 255.255.255.255 broadcast 172.16.3.99
RS1配置:
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore ##仅在请求的目标IP配置在本地主机的接收到请求报文接口上时,才给予响应;
[root@node1 ~]# echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
[root@node1 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce ##必须避免向非本网络通告;
[root@node1 ~]# echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
[root@node1 ~]# ifconfig lo:0 172.16.3.99 netmask 255.255.255.0 broadcast 172.16.3.99 up
[root@node1 ~]# route add -host 172.16.3.99 dev lo:0 ##当收到目标ip为172.16.3.99的请求丢给lo:0进行处理
[root@node1 ~]# yum install nginx -y;syatemctl start nginx;systemctl enable nginx
[root@node1 ~]# echo "R1,172.16.3.87
" > /usr/share/nginx/html/index.html
RS2配置:
[root@node2 ~]# echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
[root@node2 ~]# echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
[root@node2 ~]# echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
[root@node2 ~]# echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
[root@node2 ~]# ifconfig lo:0 172.16.3.99 netmask 255.255.255.0 broadcast 172.16.3.99 up
[root@node2 ~]# route add -host 172.16.3.99 dev lo:0
[root@node2 ~]# yum install nginx -y;syatemctl start nginx;systemctl enable nginx
[root@node2 ~]# echo "R2,172.16.3.89
" > /usr/share/nginx/html/index.html
测试结果:
由于没有配置会话保持的情况下,根据wlc的特性以及我们分配的权重值得到以下结果:
[root@userclient ~]# for k in {1..10};do curl 172.16.3.99:80;done
R2,172.16.3.89
R2,172.16.3.89
R1,172.16.3.87
R2,172.16.3.89
R2,172.16.3.89
R1,172.16.3.87
R2,172.16.3.89
R2,172.16.3.89
R1,172.16.3.87
R2,172.16.3.89
[root@userclient ~]#
对于DR模式的配置我们可以借助脚本很方面的实现:
RS的脚本配置:
#!/bin/bash
#
vip='172.16.3.99'
mask='255.255.255.255'
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 lo:0 $vip netmask $mask broadcast $vip up
route add -host $vip dev lo:0
;;
stop)
ifconfig lo:0 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
Director的脚本配置:
#!/bin/bash
#
vip='172.16.3.99'
iface='eno16777736:0'
mask='255.255.255.255'
port='80'
rs1='172.16.3.87'
rs2='172.16.3.89'
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 1
;;
stop)
ipvsadm -C
ifconfig $iface down
;;
*)
echo "Usage $(basename $0) start|stop"
exit 1
;;
esac