本文简单介绍一下,如何基于ECMP,使用Quagga LVS Keepalived构建多活负载均衡方案
1. 背景介绍
负载均衡,主要用于大规模分布式集群下,提供高性能服务。为了给负载均衡器提供高可用,一般利用主备或者主主模式实现。主备模式即只有主负载均衡器提供负载均衡功能,当主负载均衡器出现异常时,自动切换到备负载均衡器,继续提供负载均衡功能。主主模式(也称之为双活负载均衡),两个负载均衡器同时提供负载均衡服务,全部为主模式,流量均摊到两个负载均衡设备。本文基于LVS OSPF Keepalived搭建了一套多活负载均衡。
1.1 LVS LVS(Linux Virtual Server)即linux虚拟服务器。是一个虚拟的服务器集群系统,讲发送给VIP的请求,转发给对应的后端服务器。从而起到负载均衡的作用。后端服务器处理完请求,将结果返还给用户。LVS主要通过工作在linux内核层的ipvs来实现。而对于ipvs,我们可以用ipvsadm来进行配置。ipvsadm工作 在用户空间,ipvs工作在内核空间。ipvsadm和ipvs的关系就好比是iptables和netfilter的关系。综上所述,LVS由两部分组成,ipvs和ipvsadm
ipvsadm: LVS管理工具,通过ipvsadm定义和管理集群规则。 ipvs:LVS核心实现,根据定义好的集群规则进行工作。
注: LVS为四层负载均衡,基于ip和端口进行转发。
1.2 OSPF/ECMP OSPF,开放式最短路径优先协议,一种基于链路状态的内部网关协议。每个OSPF路由器都包含了整个网络的拓扑。并计算通过网络的最短路径。OSPF会通过多播的方式自动对外传播检测到的网络变化。
RIP,即路由信息协议,是一个老的协议。RIP路由器向网络中周期性多播它的整个路由表,而不像ospf只多播网络的变化。RIP通过跳数来测量路由,任何超过15跳的路由,都被视为不可达。
ECMP:等价多路径协议。即当存在多条不同的链路到达同一目的地址时,利用ECMP可以同时使用多条链路,不仅增加了传输带宽,还可以无时延、无丢包的备份失效链路的数据传输。如果利用传统的路由算法,只能利用其中的一条链路进行数据的传输。
1.3 quagga quagga是一个实现ospf的路由软件,用于模拟ospf协议。
2. 双活负载均衡架构
2.1 方案一、利用DNS轮训 配置两套主备模式的负载均衡器,分别配置VIP A和VIP B。在DNS server测针对同一域名,同时注册VIP A和VIP B。然后在请求域名的时候,dns通过轮训返回VIP A或者 VIP B。在服务端,需要通过memcached集群来共享不同节点的内存数据。从而保证session的持久性
2.2 方案二、利用ECMP 在此方案中,不存在主备模式的负载均衡器,所有负载均衡器均为主模式,配置同一VIP。在router测,利用quagga模拟ospf,可以看到到达VIP的链路有两条,当请求到达router的时候,会将流量分配到两台主负载均衡器上。
3. 环境准备
本文利用fusion创建了6台虚拟机。基于centos 7.5,具体信息如下:
代码语言:javascript复制VIP: 10.0.1.100
client: 10.0.1.132
router: 10.0.1.136
lb1: 10.0.1.133
lb2: 10.0.1.135
real_server1: 10.0.1.129
real_server2: 10.0.1.131
注: 所有虚拟机关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
4. 环境部署
4.1 real server端
代码语言:javascript复制安装nginx提供http服务。
yum install nginx -y
systemctl start nginx
systemctl enable nginx
对于real_server1: echo "RealServer1 10.0.1.129" > /usr/share/nginx/html/index.html
对于real_server2: echo "RealServer2 10.0.1.131" > /usr/share/nginx/html/index.html
systemctl restart nginx
配置vip 由于接收数据包的目的IP是VIP,所以需要将vip绑定到lo网卡,因为lo:vip不是用来通信的,所以这里的一定设置/32位子网掩码。对应修改脚本中的”VIP”变量。
由于real_server1, real_sever2, lb1, lb2均绑定vip到lo,所以需要关闭real_server1和real_server2的arp响应。对于linux内核参数arp_ignore和arp_announce的配置,参考文档 https://www.cnblogs.com/lipengxiang2009/p/7451050.html
arp_ignore设置为1:只响应目的IP地址为接收网卡上的本地地址的arp请求。所以对于lo网卡绑定VIP(lo是不接收外部数据包的),所以不会做出响应
arp_announce设置为2:忽略IP数据包的源IP地址,选择该发送网卡上最合适的本地地址作为arp请求的源IP地址。arp_announce的作用是控制系统在对外发送arp请求的数据包时,如何选择发送的源ip地址。因为对于任一主机,当接收到arp请求时,如果该arp的目的ip不是自己的ip,主机会把对端的ip和mac地址的对应关系存储到本地的mac地址表中。所以,如果real_server1发送arp的ip是lo的VIP,那么其他主机就会将VIP和mac的对应关系存储到mac地址表中,当下次像vip发送请求的时候,会直接到达real_server1,而不会经过lb。所以要设置arp_announce为2,这样就避免了通过lo:vip发送arp请求包。
代码语言:javascript复制[root@localhost ~]# cat relserver.sh
#!/bin/bash
# Script to start LVS DR real server.
# description: LVS DR real server
. /etc/rc.d/init.d/functions
VIP=10.0.1.100 #修改为VIP
host=`/bin/hostname`
case "$1" in
start)
/sbin/ifconfig lo down
/sbin/ifconfig lo up
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
/sbin/ifconfig lo:0 $VIP broadcast $VIP netmask 255.255.255.255 up
/sbin/route add -host $VIP dev lo:0
;;
stop)
/sbin/ifconfig lo:0 down
echo 0 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 0 > /proc/sys/net/ipv4/conf/lo/arp_announce
echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 0 > /proc/sys/net/ipv4/conf/all/arp_announce
;;
status)
islothere=`/sbin/ifconfig lo:0 | grep $VIP`
isrothere=`netstat -rn | grep "lo" | grep $VIP`
if [ ! "$islothere" -o ! "isrothere" ];then
# Either the route or the lo:0 device not found.
echo "LVS-DR real server Stopped."
else
echo "LVS-DR real server Running."
fi
;;
*)
# Invalid entry.
echo "$0: Usage: $0 {start|status|stop}"
exit 1
;;
esac
exit 0
[root@localhost ~]# ./realserver.sh start
4.2 lb端
配置lb1和lb2,lb1和lb2为双主的负载均衡器,基于LVS和keepalived配置。 由于我们使用ospf来实现高可用,所以keepalived不需要配置vrrp功能。keepalived只使用期后端服务检测功能。
在keepalived-LVS集群的抢占模式下,master节点的网卡挂在VIP,此时VIP的设备是唯一的。但是在OSPF-LVS集群下,router根据ospf信息,通过修改报文的目的mac地址,转发到对应的LVS来实现负载均衡。并不根据VIP对应的arp信息,所以对应的每台LVS将VIP挂载在lo上。
配置keepalived
代码语言:javascript复制yum install keepalived -y
[root@localhost ~]# cat /etc/keepalived/keepalived.conf
global_defs {
router_id LVS1 #路由器标识
script_user root
enable_script_security
}
virtual_server 10.0.1.100 80 { #此处是VIP
delay_loop 5 #定义健康检查的时间间隔,单位为秒。
lb_algo rr #后端的负载均衡算法,rr为round-robin
lb_kind DR #lvs的调度类型,DR/TUN/NAT
#persistence_timeout 60 #保持客户端的请求在<INT>时间段内全部发到同一个真实服务器。适用于动态网页session共享的环境。默认六分钟。
persistence_granularity 255.255.255.255 #会话保持粒度,配合persistence_timeout使用,通过子网掩码来定义会话保持对一个IP生效还是对一组IP生效。默认值为255.255.255.255,表示单个客户端分配到一个RS上;255.255.255.0表示客户端IP所在的整个网段的请求都会分配给同一台RS
protocol TCP # 使用协议,TCP|UDP|SCTP
inhibit_on_failure on #配置该项,在检测到后端RS节点故障后将weight值改为0,而不是从IPVS中删除
ha_suspend # 在LB节点状态从Master切换到Backup时,不启用对RS节点的健康检查
sorry_server 127.0.0.1 80
real_server 10.0.1.131 80 {
weight 10
HTTP_GET {
url {
path /
status_code 200
}
connect_port 80
connect_timeout 2
retry 1
delay_before_retry 1
}
}
real_server 10.0.1.129 80 {
weight 10
HTTP_GET {
url {
path /
status_code 200
}
connect_port 80
connect_timeout 2
retry 1
delay_before_retry 1
}
}
}
[root@localhost ~]# systemctl reload keepalived
[root@localhost ~]# systemctl enable keepalived
验证结果:
代码语言:javascript复制[root@localhost ~]# ipvsadm -L
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP localhost.localdomain:http rr
-> 10.0.1.129:http Route 10 0 0
-> 10.0.1.131:http Route 10 0 0
[root@localhost ~]#
配置vip
[root@localhost ~]#ifconfig lo:0 10.0.1.100 netmask 255.255.255.255 up
[root@localhost ~]# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.1.133 netmask 255.255.255.0 broadcast 10.0.1.255
inet6 fe80::af35:1bc3:d630:16c1 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:4a:64:33 txqueuelen 1000 (Ethernet)
RX packets 169865 bytes 20546055 (19.5 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 131247 bytes 23749082 (22.6 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 746 bytes 57280 (55.9 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 746 bytes 57280 (55.9 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo:0: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 10.0.1.100 netmask 255.255.255.255
loop txqueuelen 1000 (Local Loopback)
[root@localhost ~]#
配置quagga
代码语言:javascript复制[root@localhost ~]# yum install quagga -y
[root@localhost ~]# cat /etc/quagga/ospfd.conf
hostname 10.0.1.133 #用主机ip代替即可
password test
log file /var/log/quagga/ospfd.log
log stdout
log syslog
service password-encryption
interface ens33 #主机ip所在的网卡名
ip ospf hello-interval 1
ip ospf dead-interval 4
ip ospf priority 0
ip ospf cost 1
router ospf
ospf router-id 10.0.1.133 #用主机ip代替
log-adjacency-changes
network 10.0.1.0/24 area 0.0.0.0
access-list 1 permit 127.0.0.1
line vty
access-class 1
[root@localhost ~]# cat /etc/quagga/zebra.conf
hostname 10.0.1.133 # 主机ip代替
password test
enable password test
log file /var/log/quagga/zebra.log
service password-encryption
interface ens33
access-list 1 permit 127.0.0.1
ip prefix-list ANY seq 5 permit 0.0.0.0/0 le 32
route-map ANY deny 10
match ip address prefix-list ANY
ip protocol ospf route-map ANY
line vty
access-class 1
[root@localhost ~]# systemctl start zebra
[root@localhost ~]# systemctl start ospfd
4.3 router端
quagga配置
代码语言:javascript复制//开启转发
[root@localhost ~]# echo 1 > /proc/sys/net/ipv4/ip_forward
//Router作为网关的接口必须开启arp proxy:
[root@localhost ~]# echo 1 > /proc/sys/net/ipv4/conf/ens33/proxy_arp
[root@localhost ~]# yum install -y quagga
[root@localhost ~]# cat /etc/quagga/ospfd.conf
hostname 10.0.1.136 #主机ip地址
password test
log file /var/log/quagga/ospfd.log
log stdout
log syslog
service password-encryption
interface ens33
ip ospf hello-interval 1
ip ospf dead-interval 4
ip ospf priority 1
ip ospf cost 1
router ospf
ospf router-id 10.0.1.136
log-adjacency-changes
network 10.0.1.0/24 area 0.0.0.0
access-list 1 permit 127.0.0.1
line vty
access-class 1
[root@localhost ~]# cat /etc/quagga/zebra.conf
hostname 10.0.1.136 # HOSTNAME改为IP也可以
password test
enable password test
log file /var/log/quagga/zebra.log
#log syslog
service password-encryption
interface ens33
access-list 1 permit 127.0.0.1
ip prefix-list ANY seq 5 permit 0.0.0.0/0 le 32
route-map ANY permit 10 #将ospf路由映射到kernel
match ip address prefix-list ANY
ip protocol ospf route-map ANY
line vty
access-class 1
[root@localhost ~]#
验证结果:可以看到访router到达vip 10.0.1.100有两条链路,一条是10.0.1.133(lb1),一条是10.0.1.135(lb2)
代码语言:javascript复制[root@localhost ~]# vtysh
Hello, this is Quagga (version 0.99.22.4).
Copyright 1996-2005 Kunihiro Ishiguro, et al.
localhost.localdomain# show ip ospf neighbor
Neighbor ID Pri State Dead Time Address Interface RXmtL RqstL DBsmL
10.0.1.133 0 Full/DROther 3.274s 10.0.1.133 ens33:10.0.1.136 0 0 0
10.0.1.135 0 Full/DROther 3.934s 10.0.1.135 ens33:10.0.1.136 0 0 0
localhost.localdomain# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
O - OSPF, I - IS-IS, B - BGP, A - Babel,
> - selected route, * - FIB route
K>* 0.0.0.0/0 via 10.0.1.2, ens33
O 10.0.1.0/24 [110/1] is directly connected, ens33, 00:03:18
C>* 10.0.1.0/24 is directly connected, ens33
O>* 10.0.1.100/32 [110/11] via 10.0.1.133, ens33, 00:03:09
* via 10.0.1.135, ens33, 00:03:09
C>* 127.0.0.0/8 is directly connected, lo
localhost.localdomain# exit
[root@localhost ~]# ip route
default via 10.0.1.2 dev ens33 proto dhcp metric 100
10.0.1.0/24 dev ens33 proto kernel scope link src 10.0.1.136 metric 100
10.0.1.100 proto zebra metric 11
nexthop via 10.0.1.133 dev ens33 weight 1
nexthop via 10.0.1.135 dev ens33 weight 1
[root@localhost ~]#
4.4 client端
client端,需要配置路由,针对访问VIP的请求均跳转到router,这样router就可以利用ospf的ECMP,将流量分发到后端的lb1和lb2。
5. 结果验证
为了验证router确实最client端数据做了分流。将后端lb做了改动,lb1来对接real_server1, lb2来对接real_server2。通过返回的信息就可以确定lb是否为真的双活。
client1
代码语言:javascript复制[root@localhost ~]# ifconfig|grep 10.0
inet 10.0.1.138 netmask 255.255.255.0 broadcast 10.0.1.255
ether 00:0c:29:c8:e5:37 txqueuelen 1000 (Ethernet)
loop txqueuelen 1000 (Local Loopback)
[root@localhost ~]# ip route
default via 10.0.1.2 dev ens33 proto dhcp metric 100
10.0.1.0/24 dev ens33 proto kernel scope link src 10.0.1.138 metric 100
10.0.1.100 via 10.0.1.136 dev ens33
[root@localhost ~]# traceroute 10.0.1.100
traceroute to 10.0.1.100 (10.0.1.100), 30 hops max, 60 byte packets
1 10.0.1.136 (10.0.1.136) 0.260 ms 0.146 ms 0.146 ms
2 10.0.1.100 (10.0.1.100) 0.293 ms 0.303 ms 0.226 ms
[root@localhost ~]# curl 10.0.1.100
server 10.0.1.129
[root@localhost ~]#
client2
代码语言:javascript复制[root@localhost ~]# ifconfig|grep 10.0
inet 10.0.1.137 netmask 255.255.255.0 broadcast 10.0.1.255
ether 00:0c:29:09:ae:d5 txqueuelen 1000 (Ethernet)
loop txqueuelen 1000 (Local Loopback)
[root@localhost ~]# ip route
default via 10.0.1.2 dev ens33 proto dhcp metric 100
10.0.1.0/24 dev ens33 proto kernel scope link src 10.0.1.137 metric 100
10.0.1.100 via 10.0.1.136 dev ens33
[root@localhost ~]# traceroute 10.0.1.100
traceroute to 10.0.1.100 (10.0.1.100), 30 hops max, 60 byte packets
1 10.0.1.136 (10.0.1.136) 0.278 ms 0.180 ms 0.091 ms
2 10.0.1.100 (10.0.1.100) 0.452 ms 0.370 ms 0.266 ms
[root@localhost ~]# curl 10.0.1.100
server 10.0.1.131
[root@localhost ~]#
参考文献 https://www.cnblogs.com/lipengxiang2009/p/7451050.html https://segmentfault.com/a/1190000020299695