使用Keepalived构建LVS高可用集群
LVS的DR模型配置+Keepalive部署
介绍
下图为DR模型的通信过程,图中的IP不要被扑结构中的IP迷惑,图里只是为了说明DR的通信原理,应用到本例中的拓扑上其工作原理不变。
拓扑结构
服务器 | IP地址 | 角色 |
---|---|---|
Srv01 | 172.16.42.100 VIP: 172.16.42.111 |
LVS+Keepalive |
Srv02 | 172.16.42.101 VIP: 192.168.100.1 |
LVS+Keepalive |
Srv03 | 172.16.42.102 VIP: 172.16.42.111 |
Nginx |
Srv04 | 172.16.42.103 VIP: 172.16.42.111 |
Nginx |
另外,我这4台主机都是2个网卡, 其中有ens33是172.16.42.0/24这个网络,ens34是192.168.100.0/24网络,这个网络在本例中没有用,请忽略。
设置后端服务器
ARP的内核参数
由于LVS服务器和后端服务器的网卡上都配置了VIP,那么当客户端联系VIP的时候肯定是和LVS服务器的VIP进行通信,然后由LVS服务器基于规则进行调度,我们知道2层通信是基于MAC地址的,那么首次通信时客户端可能并不知道LVS服务器的MAC地址,那么就需要进行ARP广播来解析出VIP所在的服务器的MAC地址,那么显然对客户端进行ARP应答的只能是LVS服务器不能是后端服务器,所以我们就要在后端上修改内核参数来禁止ARP应答和宣告。那么有2个内核参数表示这两个设置:
arp_ignore:表示接收到ARP广播时的响应级别,默认值为0
-
0,默认值,表示响应所有,只要对方查询的IP配置在我自己这台主机上且无论ARP请求从哪个网卡进来,该主机都会响应
-
1,收到该ARP请求的网卡IP与ARP请求的IP一致,该主机才响应
arp_announce:定义将自己的地址向外通告的级别,默认是0
-
0,表示将本机所有的MAC地址都向外通告
-
1,多网卡主机且都配置了IP地址,那么该主机接入到网络时,无论哪个网卡接入到网络,该网卡都会向外宣告自己所有的MAC地址,所以1表示如果IP不在这个接口上,就避免向外通告,但是不保证一定不会下外通告。
-
2,仅向网卡IP直连的网络进行通告
为什么会有这些级别呢?因为主机可以有多个网卡,每个网卡都对应一个网段,默认情况下这个多网卡主机只要接入网络它就会把自己所在的所有网络地址都向外进行通告。
所以对于后端服务器,也就是本例子中的Nginx,我们应该在lo上配置子接口,且设置arp_ignore为1,arp_announce为2。
设置后端服务器
后端服务器的所有操作都是一样,我这里就演示一台。首先要保证2台后端服务器都安装了Nginx,我用Nginx只是为了后端提供一个Web服务而已,你使用Tomcat也是一样的。
通过这个命令查看当前服务器设置sysctl -a | egrep "arp_ignore|arp_announce"
可以看到这里每个网卡都有这2个参数还有一个All也有,那应该配置在哪里呢?all表示全局,理论上来说在all上配置就可以,但是为了保险我们在ens33上也配置。
记住arp_announce配置为2;apr_ignore配置为1。
sysctl -w net.ipv4.conf.all.arp_announce=2
sysctl -w net.ipv4.conf.ens33.arp_announce=2
我使用sysctl -w来修改只是临时生效,重启就没有了,为了永久有效请修改
/etc/sysctl.conf
文件。
现在在说以为什么arp_announce配置为2,我这个主机有2个网卡,每个网卡所连接的是不同网段。我要使用的是ens33上面这个172.16.42.0/24这个网段,且VIP也是这个网段,当我把ens33的apr_announce配置为2 的时候,这就意味着当这个ens33接入网络时它不会对外宣告ens34的网络设置,也不会对外宣告lo的网络设置(因为我们要在lo上配置一个子接口,该接口的IP就是VIP),下面配置apr_ignore
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.ens33.arp_ignore=1
下面设置lo的子接口,这里为什么要32位呢?因为要把广播地址设置为自己,这样它的广播就不会广播到其他地方。
ifconfig lo:0 172.16.42.111 netmask 255.255.255.255 broadcast 172.16.42.111 up
下面我们在Srv01上ping一下这个地址,发现没有人应答,所以ping不通。
接下来添加一条路由,其目的是由于后端服务器是直接应答客户端请求的,所以就需要确保应答是其源IP一定是VIP(因为客户端请求的就是VIP),但是当LVS修改完数据包发送给后端服务器时,使用的是后端服务器的真实IP地址进行通信的,而网络通信是请求入栈是哪个接口,响应出栈还走哪个接口,这就势必导致后端服务器会使用自己的真实IP而不是VIP对客户端响应,这肯定是不对的,所以我们要通过这一条路由设置让ens33网卡收到数据包后转发给lo:0这个子接口,这样响应出栈的时候就会经过lo:0,这样也就会把响应报文的源IP设置为VIP了。
route add -host 172.16.42.111 dev lo:0
这条命令的含义是如果目标地址是172.16.42.111就要送到lo:0,这样就保证了入栈经过lo:0,那么出栈自然会经过。如果你不理解,那么就要好好看看最上面的图,二层通信是使用mac地址,而此时后端服务器收到这个数据包的时候,IP报文中源IP是客户端的IP,而目标IP则是VIP,只是数据链路层报文mac地址信息被LVS替换了。
之后启动后端服务的Nginx服务,两台后端服务器上都做上面的修改。
安装keepalive
之间通过yum安装即可yum install -y keepalived
。我这里使用的是阿里云的源,它默认就在里面,如下图:
在2个节点都安装。
文件 | 说明 |
---|---|
/usr/sbin/keepalived | 二进制程序 |
/etc/keepalived/keepalived.conf | 配置文件 |
/usr/lib/systemd/system/keepalived.service | 服务文件 |
Keepalive配置文件说明
# 全局配置
global_defs {
# 邮件通知信息
notification_email {
# 定义收件人
acassen@firewall.loc
}
# 定义发件人
notification_email_from Alexandre.Cassen@firewall.loc
# SMTP服务器地址
smtp_server 192.168.200.1
smtp_connect_timeout 30
# 路由器标识,一般不用改,也可以写成每个主机自己的主机名
router_id LVS_DEVEL
# VRRP的ipv4和ipv6的广播地址,配置了VIP的网卡向这个地址广播来宣告自己的配置信息,下面是默认值
vrrp_mcast_group4 224.0.0.18
vrrp_mcast_group6 ff02::12
}
# 定义用于实例执行的脚本内容,比如可以在线降低优先级,用于强制切换
vrrp_script SCRIPT_NAME {
}
# 一个vrrp_instance就是定义一个虚拟路由器的,实例名称
vrrp_instance VI_1 {
# 定义初始状态,可以是MASTER或者BACKUP
state MASTER
# 工作接口,通告选举使用哪个接口进行
interface ens33
# 虚拟路由ID,如果是一组虚拟路由就定义一个ID,如果是多组就要定义多个,而且这个虚拟
# ID还是虚拟MAC最后一段地址的信息,取值范围0-255
virtual_router_id 51
# 使用哪个虚拟MAC地址
use_vmac XX:XX:XX:XX:XX
# 监控本机上的哪个网卡,网卡一旦故障则需要把VIP转移出去
track_interface {
eth0
ens33
}
# 如果你上面定义了MASTER,这里的优先级就需要定义的比其他的高
priority 100
# 通告频率,单位为秒
advert_int 1
# 通信认证机制,这里是明文认证还有一种是加密认证
authentication {
auth_type PASS
auth_pass 1111
}
# 设置虚拟VIP地址,一般就设置一个,在LVS中这个就是为LVS主机设置VIP的,这样你就不用自己手动设置了
virtual_ipaddress {
# IP/掩码 dev 配置在哪个网卡
192.168.200.16/24 dev eth1
# IP/掩码 dev 配置在哪个网卡的哪个别名上
192.168.200.17/24 dev label eth1:1
}
# 虚拟路由,在需要的情况下可以设置lvs主机 数据包在哪个网卡进来从哪个网卡出去
virtual_routes {
192.168.110.0/24 dev eth2
}
# 工作模式,nopreempt表示工作在非抢占模式,默认是抢占模式 preempt
nopreempt|preempt
# 如果是抢占默认则可以设置等多久再抢占,默认5分钟
preempt delay 300
# 追踪脚本,通常用于去执行上面的vrrp_script定义的脚本内容
track_script {
}
# 三个指令,如果主机状态变成Master|Backup|Fault之后会去执行的通知脚本,脚本要自己写
notify_master ""
notify_backup ""
notify_fault ""
}
# 定义LVS集群服务,可以是IP+PORT;也可以是fwmark 数字,也就是防火墙规则
# 所以通过这里就可以看出来keepalive天生就是为ipvs而设计的
virtual_server 10.10.10.2 1358 {
delay_loop 6
# 算法
lb_algo rr|wrr|lc|wlc|lblc|sh|dh
# LVS的模式
lb_kind NAT|DR|TUN
# 子网掩码,这个掩码是VIP的掩码
net_mask 255.255.255.0
# 持久连接超时时间
persistence_timeout 50
# 定义协议
protocol TCP
# 如果后端应用服务器都不可用,就会定向到那个服务器上
sorry_server 192.168.200.200 1358
# 后端应用服务器 IP PORT
real_server 192.168.200.2 1358 {
# 权重
weight 1
# 应用服务器UP或者DOWN,就执行那个脚本
notify_up "这里写的是路径,如果脚本后有参数,整体路径+参数引起来"
notify_down "/PATH/SCRIPTS.sh 参数"
# MSIC_CHECK|SMTP_CHEKC|TCP_CHECK|SSL_GET|HTTP_GET这些都是
# 针对应用服务器做健康检查的方法
MISC_CHECK {}
# 用于检查SMTP服务器的
SMTP_CHEKC {}
# 如果应用服务器不是WEB服务器,就用TCP_CHECK检查
TCP_CHECK {
# 向哪一个端口检查,如果不指定默认使用上面定义的端口
connect_port <PORT>
# 向哪一个IP检测,如果不指定默认使用上面定义的IP地址
bindto <IP>
# 连接超时时间
connect_timeout 3
}
# 如果对方是HTTPS服务器就用SSL_GET方法去检查,里面配置的内容和HTTP_GET一样
SSL_GET {}
# 使用HTTP_GET方法去检查
HTTP_GET {
# 检测URL
url {
# 具体检测哪一个URL
path /testurl/test.jsp
# 检测内容的哈希值,也就是网页的md5值
digest 640205b7b0fc66c1ea91c463fac6334d
# 除了检测哈希值还可以检测状态码,比如HTTP的200 表示正常,两种方法二选一即可
status_code 200
}
url {
path /testurl2/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
url {
path /testurl3/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334d
}
# 向哪一个端口检查,如果不指定默认使用上面定义的端口
connect_port <PORT>
# 向哪一个IP检测,如果不指定默认使用上面定义的IP地址
bindto <IP>
# 连接超时时间
connect_timeout 3
# 尝试次数
nb_get_retry 3
# 每次尝试之间间隔几秒
delay_before_retry 3
}
}
real_server 192.168.200.3 1358 {
weight 1
HTTP_GET {
url {
path /testurl/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334c
}
url {
path /testurl2/test.jsp
digest 640205b7b0fc66c1ea91c463fac6334c
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
配置Srv01和Srv02
配置VRRP部分
Srv01上的keepalived.conf
global_defs {
notification_email {
acassen@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id srv01
}
vrrp_instance VI_1 {
state MASTER
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.16.42.111/24 brd 172.16.42.111 dev ens33 label ens33:0
}
preempt delay 60
}
Srv02上的keepalived.conf,唯一不同的就是state、priority以及router_id。
global_defs {
notification_email {
acassen@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id srv02
}
vrrp_instance VI_1 {
state BACKUP
interface ens33
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.16.42.111/24 brd 172.16.42.111 dev ens33 label ens33:0
}
preempt delay 60
}
启动2个节点,启动后会自动配置ens33:0这个子接口的虚拟IP
在主节点上你通过systemctl status keepalived
看不到它到底是什么角色,不过在BACKUP节点上你可以看到,但是你在主节点日志中cat /var/log/message
里可以看到Srv01进入到MASTER状态,如下图:
查看Srv02的状态
那么你通过停止Srv01上的keepalived服务就看到MASTER会被转移到Srv02上。
使用该命令查看VRRP通告tcpdum -i ens33 -nn host 224.0.0.18
,你在2台主机都会看到相同的信息。
Srv01使用真实物理IP对该地址进行发送通告,那么Srv02也会收到,如果Srv01宕机,那么Srv02就会使用自己的物理IP向该地址发送通告,由于Srv01已经宕机那么此时Srv02的优先级就是最高的,所以Srv02就变成了MASTER。
配置LVS部分
在keepalived.conf文件中增加下面的内容,2台服务器增加的内容一致,所以这里就写一份。
virtual_server 172.16.42.111 80 {
delay_loop 6
lb_algo rr
lb_kind DR
nat_mask 255.255.255.0
persistence_timeout 0
protocol TCP
sorry_server 192.168.200.200 1358
# 后端应用服务器 IP PORT
real_server 172.16.42.102 80 {
weight 1
# 应用服务器UP或者DOWN,就执行那个脚本
notify_up "/usr/local/notify.sh 172.16.42.102 up"
notify_down "/usr/local/notify.sh 172.16.42.102 down"
HTTP_GET {
# 检测URL
url {
path /index.html
# 除了检测哈希值还可以检测状态码,比如HTTP的200 表示正常,两种方法二选一即可
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 172.16.42.103 80 {
weight 1
# 应用服务器UP或者DOWN,就执行那个脚本
notify_up "/usr/local/notify.sh 172.16.42.103 up"
notify_down "/usr/local/notify.sh 172.16.42.103 down"
HTTP_GET {
# 检测URL
url {
path /index.html
# 除了检测哈希值还可以检测状态码,比如HTTP的200 表示正常,两种方法二选一即可
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
这里的notify_up|down脚本我写的很简单就是为了使用一下这个功能,内容如下:
#!/bin/bash
if [ $2 == "up" ]; then
echo "Real server ${1} is UP" > /tmp/notify.txt
elif [ $2 == "down" ]; then
echo "Real server ${1} is DOWN" > /tmp/notify.txt
fi
重启Keepalived服务之后你就可以通过ipvsadm -Ln
查看ipvs规则了,这些规则在2台服务器上都会有,如下图:
测试访问
使用下面的命令快速访问for i in {1..20}; do curl http://172.16.42.111/ | grep "Srv0" --color ; done
可以看到2台服务器交替,因为我们使用的rr调度算法。
故障转移测试
连续访问VIP,然后停止Srv01上面的keepalived服务,这就意味着Srv01也就是失去了VIP,然后观察请求情况以及是否触发之前设定的脚本。
查看Srv02上面的日志
总结
为了提供一个Nginx或者某种后端服务器的负载均衡功能,那么我们需要一个LVS来做调度,但是一台LVS存在单点故障,为了解决LVS单点故障我们使用Keepalived来组建一个LVS的高可用集群。