Keepalived一个基于VRRP 协议来实现的 LVS 服务高可用方案,可以利用其来解决单点故障。一个LVS服务会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟IP
,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候, 备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性。
如上述所说,Keepalived 提供了很好的高可用性保障服务
,它可以检查服务器的状态,如果有服务器出现问题,Keepalived 会将其从系统中移除,并且同时使用备份服务器代替该服务器的工作,当这台服务器可以正常工作后,Keepalived 再将其放入服务器群中,这个过程是 Keepalived 自动完成的,不需要人工干涉,我们只需要修复出现问题的服务器即可。
Keepalived 是以 VRRP
协议为实现基础的,VRRP全称Virtual Router Redundancy Protocol
,即虚拟路由冗余协议
。
虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个master 和多个 backup,master 上面有一个对外提供服务的 VIP(Virtual IP Address)
(该路由器所在局域网内其他机器的默认路由为该 vip),master 会发组播,当 backup 收不到 vrrp 包时就认为 master 宕掉了,这时就需要根据 VRRP 的优先级来选举
一个 backup 当 master。这样的话就可以保证路由器的高可用了。
keepalived 主要有三个模块,分别是core、check 和 vrrp。core 模块为keepalived的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。check 负责健康检查,包括常见的各种检查方式。vrrp 模块是来实现 VRRP 协议的。
首先,每个节点有一个初始优先级,由配置文件中的priority
配置项指定,MASTER 节点的 priority 应比 BAKCUP 高。运行过程中 keepalived 根据 vrrp_script 的 weight
设定,增加或减小节点优先级。规则如下:
VRRP通告
的 IP 作为比较对象,IP较大者为MASTER。Keepalived 官网下载地址: https://link.zhihu.com/?target=https%3A//www.keepalived.org/download.html
将keepalived-2.2.2.tar.gz安装包上传到服务器上的某个目录下,此处为/usr/local/src目录下
进入安装目录下并解压安装包:
cd /usr/local/src/
tar -zxvf keepalived-2.2.2.tar.gz
将解压的目录移动到/usr/local/keepalived下并进入移动后的目录:
mv keepalived-2.2.2 ../keepalived
cd ../keepalived/
生成makefile文件:
./configure
安装执行:
make && make install
完成后会在以下路径生成:
/usr/local/etc/keepalived/keepalived.conf
/usr/local/etc/sysconfig/keepalived
/usr/local/sbin/keepalived
将配置文件放到默认路径下:
mkdir /etc/keepalived
cp /usr/local/keepalived/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
将keepalived启动脚本(源码目录下),放到/etc/init.d/目录下:
cp /usr/local/keepalived/keepalived/etc/init.d/keepalived /etc/rc.d/init.d/
将keepalived启动脚本变量引用文件放到/etc/sysconfig/目录下:
cp /usr/local/keepalived/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
将keepalived主程序加入到环境变量/usr/sbin/目录下:
cp /usr/local/sbin/keepalived /usr/sbin/
启动keepalived:
centos8的启动
service keepalived start #如果启动不了,可以尝试重启centos8
centos7的启动
sudo systemctl start keepalived //启动
sudo systemctl restart keepalived
附注:
service keepalived stop //停止服务
service keepalived status //查看服务状态
! Configuration File for keepalived
# 全局定义块
global_defs {
# 邮件通知配置,用于服务有故障时发送邮件报警,可选项
notification_email {
541223550@qq.com
}
# 通知邮件从哪里发出
notification_email_from root@localhost
# 通知邮件的smtp地址
smtp_server 127.0.0.1
# 连接smtp服务器的超时时间
smtp_connect_timeout 30
# 标识本节点的字条串,通常为hostname,但不一定非得是hostname。故障发生时,邮件通知会用到
router_id LVS_DEVEL
}
# 做健康检查的脚本配置,当时检查失败时会将vrrp_instance的priority减少相应的值
vrrp_script chk_haproxy {
# 待执行脚本
script "/etc/keepalived/chk_nginx.sh"
# 执行间隔
interval 2
# 控制priority增减
weight 2
}
# VRRP实例定义块
vrrp_instance VI_1 {
# 标识当前节点的状态,可以是MASTER或BACKUP,当其他节点keepalived启动时会将priority比较大的节点选举为MASTER
state MASTER
# 节点固有IP(非VIP)的网卡,用来发VRRP包
interface ens192
# 取值在0-255之间,用来区分多个instance的VRRP组播。同一网段中virtual_router_id的值不能重复,否则会出错
virtual_router_id 100
# 用来选举master的,要成为master,那么这个选项的值最好高于其他机器50个点,该项取值范围是[1-254](在此范围之外会被识别成默认值100)
priority 200
# 发VRRP包的时间间隔,即多久进行一次master选举(可以认为是健康查检时间间隔)
advert_int 1
# 认证区域,认证类型有PASS和HA(IPSEC),推荐使用PASS(密码只识别前8位)
authentication {
auth_type PASS
auth_pass 1111
}
# 调用chk_http_port中定义的脚本,当使用track_script时可以不用加nopreempt,只需要加上preempt_delay 5,这里的间隔时间要大于vrrp_script中定义的时长
track_script {
chk_haproxy
}
# 允许一个priority比较低的节点作为master,即使有priority更高的节点启动。nopreemt必须在state为BACKUP的节点上才生效(因为是BACKUP节点决定是否来成为MASTER的)
nopreempt
# 启动多久之后进行接管资源(VIP/Route信息等),前提是没有nopreempt选项
preempt_delay 300
# 虚拟ip地址
virtual_ipaddress {
192.168.26.34
}
notify "/etc/keepalived/main_script.sh"
}
# 虚拟服务定义块
virtual_server 192.168.26.34 9999{ # 虚拟服务器的IP地址和端口
# 延迟轮询时间(单位秒)
delay_loop 6
# 后端调试算法
lb_algo wrr
# LVS调度类型NAT/DR/TUN
lb_kind DR
# nat掩码
nat_mask 255.255.255.0
# 持久化超时时间,保持客户端的请求在这个时间段内全部发到同一个真实服务器,解决客户连接的相关性问题
persistence_timeout 1
# 传输协议
protocol TCP
# 真实提供服务的服务器
real_server 192.168.26.36 9999 {
# 权重
weight 1
# 健康检查方式 HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK
TCP_CHECK {
# 连接超时时间
connect_timeout 10
# 检测失败后的重试次数,若达到重试次数还是失败则将其从服务器池中移除
nb_get_retry 3
# 下次重试的时间延迟
delay_before_retry 3
# 连接端口
connect_port 9999
}
}
real_server 192.168.26.54 9999 {
weight 1
TCP_CHECK {
connect_timeout 10
nb_get_retry 3
delay_before_retry 3
connect_port 9999
}
}
}
virtual_server 192.168.26.34 3306{
delay_loop 6
lb_algo wrr
lb_kind DR
nat_mask 255.255.255.0
persistence_timeout 1
protocol TCP
real_server 192.168.26.36 3306 {
weight 1
TCP_CHECK {
connect_timeout 10
nb_get_retry 3
delay_before_retry 3
connect_port 3306
}
}
real_server 192.168.26.54 3306 {
weight 1
TCP_CHECK {
connect_timeout 10
nb_get_retry 3
delay_before_retry 3
connect_port 3306
}
}
}
在Nginx配置目录下(/etc/nginx/conf.d/)增加health.conf的配置文件,该配置文件用于配置Nginx health的接口。
server {
listen 80 default_server;
server_name localhost;
default_type text/html;
return 200 'Health';
}
Nginx健康检测脚本:/etc/keepalived/scripts/check_nginx.sh
#!/bin/sh
set -x
timeout=30 #指定默认30秒没返回200则为非健康,该值可根据实际调整
if [ -n ${timeout} ];then
httpcode=`curl -sL -w %{http_code} -m ${timeout} http://localhost -o /dev/null`
else
httpcode=`curl -sL -w %{http_code} http://localhost -o /dev/null`
fi
if [ ${httpcode} -ne 200 ];then
echo `date`': nginx is not healthy, return http_code is '${httpcode} >> /etc/keeperalived/keepalived.log
killall keepalived
exit 1
else
exit 0
fi
#!/bin/sh
if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
echo "$(date) nginx pid not found">>/etc/keepalived/keepalived.log
killall keepalived
fi
#!/bin/bash
set -x
warn_receiver=$1
ip=$(ifconfig bond0|grep inet |awk '{print $2}')
warningInfo="${ip}_keepalived_changed_status_to_$1"
warn-report --user admin --key=xxxx --target=${warn_receiver} ${warningInfo}
echo $(date) $1 >> /etc/keepalived/status
说明:
LVS是Linux Virtual Server的简称,也就是Linux虚拟服务器,是一个由章文嵩博士发起的自由软件项目,官方站点是:The Linux Virtual Server Project。现在LVS已经是Linux标准内核的一部分,在Linux2.4内核以前,使用LVS时必须重新编译内核以支持LVS功能模块,但是从Linux2.4内核心之后,已经完全内置了LVS的各个功能模块,无需给内核打任何补丁,可以直接使用LVS提供的各种功能。
使用LVS技术要达到的目标是:通过LVS提供的负载均衡技术和Linux操作系统实现一个高性能,高可用的服务器群集,它具有良好的可靠性、可扩展性和可操作性。从而以低廉的成本实现最优的服务性能。
使用LVS架设的服务器集群系统有三个部分组成:最前端的负载均衡层(Loader Balancer),中间的服务器群组层,用Server Array表示,最底层的数据共享存储层,用Shared Storage表示。在用户看来所有的应用都是透明的,用户只是在使用一个虚拟服务器提供的高性能服务。
LVS的各个层次的详细介绍:
Load Balancer层:位于整个集群系统的最前端,有一台或者多台负载调度器(Director Server)组成,LVS模块就安装在Director Server上,而Director的主要作用类似于一个路由器,它含有完成LVS功能所设定的路由表,通过这些路由表把用户的请求分发给Server Array层的应用服务器(Real Server)上。同时,在Director Server上还要安装对Real Server服务的监控模块Ldirectord,此模块用于监测各个Real Server服务的健康状况。在Real Server不可用时把它从LVS路由表中剔除,恢复时重新加入。
Server Array层:由一组实际运行应用服务的机器组成,Real Server可以是WEB服务器、MAIL服务器、FTP服务器、DNS服务器、视频服务器中的一个或者多个,每个Real Server之间通过高速的LAN或分布在各地的WAN相连接。在实际的应用中,Director Server也可以同时兼任Real Server的角色。
Shared Storage层:是为所有Real Server提供共享存储空间和内容一致性的存储区域,在物理上,一般有磁盘阵列设备组成,为了提供内容的一致性,一般可以通过NFS网络文件系统共享数 据,但是NFS在繁忙的业务系统中,性能并不是很好,此时可以采用集群文件系统,例如Red hat的GFS文件系统,oracle提供的OCFS2文件系统等。
从整个LVS结构可以看出,Director Server是整个LVS的核心,目前,用于Director Server的操作系统只能是Linux和FreeBSD,linux2.6内核不用任何设置就可以支持LVS功能,而FreeBSD作为 Director Server的应用还不是很多,性能也不是很好。对于Real Server,几乎可以是所有的系统平台,Linux、windows、Solaris、AIX、BSD系列都能很好的支持。
1、当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间
2、 PREROUTING链首先会接收到用户请求,判断目标IP确定是本机IP,将数据包发往INPUT链
3、 IPVS是工作在INPUT链上的,当用户请求到达INPUT时,IPVS会将用户请求和自己已定义好的集群服务进行比对,如果用户请求的就是定义的集群服务,那么此时IPVS会强行修改数据包里的目标IP地址及端口,并将新的数据包发往POSTROUTING链
4、 POSTROUTING链接收数据包后发现目标IP地址刚好是自己的后端服务器,那么此时通过选路,将数据包最终发送给后端的服务器
1、 DS:Director Server。指的是前端负载均衡器节点。
2、 RS:Real Server。后端真实的工作服务器。
3、 VIP:向外部直接面向用户请求,作为用户请求的目标的IP地址。
4、 DIP:Director Server IP,主要用于和内部主机通讯的IP地址。
5、 RIP:Real Server IP,后端服务器的IP地址。
6、 CIP:Client IP,访问客户端的IP地址
通过将请求报文中的目标地址与目标端口修改,来实现报文的传送
1、当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP
2、PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链
3、IPVS比对数据包请求的服务是否为集群服务,若是,修改数据包的目标IP地址为后端服务器IP,后将数据包发至POSTROUTING链。 此时报文的源IP为CIP,目标IP为RIP
4、POSTROUTING链通过选路,将数据包发送给Real Server
5、Real Server比对发现目标为自己的IP,开始构建响应报文发回给Director Server。 此时报文的源IP为RIP,目标IP为CIP
6、Director Server在响应客户端前,此时会将源IP地址修改为自己的VIP地址,然后响应给客户端。 此时报文的源IP为VIP,目标IP为CIP
(1) RS应该和DIP应该使用私网地址,且RS的网关要指向DIP;
(2) 请求和响应报文都要经由director转发;极高负载的场景中,director可能会成为系统瓶颈;
(3) 支持端口映射;
(4) RS可以使用任意OS;
(5) RS的RIP和Director的DIP必须在同一IP网络;
缺陷:对Director Server压力会比较大,请求和响应都需经过director server,director往往会成为系统的性能瓶颈
通过为请求报文重新封装一个MAC首部进行转发,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源IP/PORT,以及目标IP/PORT均保持不变
1、当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP
2、PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链
3、 IPVS比对数据包请求的服务是否为集群服务,若是,将请求报文中的源MAC地址修改为DIP的MAC地址,将目标MAC地址修改RIP的MAC地址,然后将数据包发至POSTROUTING链。 此时的源IP和目的IP均未修改,仅修改了源MAC地址为DIP的MAC地址,目标MAC地址为RIP的MAC地址
4、 由于DS和RS在同一个网络中,所以是通过二层来传输。POSTROUTING链检查目标MAC地址为RIP的MAC地址,那么此时数据包将会发至Real Server。
5、 RS发现请求报文的MAC地址是自己的MAC地址,就接收此报文。处理完成之后,将响应报文通过lo接口传送给eth0网卡然后向外发出。 此时的源IP地址为VIP,目标IP为CIP
6、 响应报文最终送达至客户端
(1) 确保前端路由器将目标IP为VIP的请求报文发往Director:
(a) 在前端网关做静态绑定;
(b) 在RS上使用arptables;
© 在RS上修改内核参数以限制arp通告及应答级别;
修改RS上内核参数(arp_ignore和arp_announce)将RS上的VIP配置在lo接口的别名上,并限制其不能响应对VIP地址解析请求。
(2) RS的RIP可以使用私网地址,也可以是公网地址;RIP与DIP在同一IP网络;RIP的网关不能指向DIP,以确保响应报文不会经由Director;
(3) RS跟Director要在同一个物理网络;
(4) 请求报文要经由Director,但响应不能经由Director,而是由RS直接发往Client;
(5) 不支持端口映射;
缺陷:RS和DS必须在同一机房中,因为它是由二层进行转发的根据MAC地址来进行匹配
在原有的IP报文外再次封装多一层IP首部,内部IP首部(源地址为CIP,目标IIP为VIP),外层IP首部(源地址为DIP,目标IP为RIP)
1、当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP 。
2、 PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链
3、IPVS比对数据包请求的服务是否为集群服务,若是,在请求报文的首部再次封装一层IP报文,封装源IP为为DIP,目标IP为RIP。然后发至POSTROUTING链。 此时源IP为DIP,目标IP为RIP
4、 POSTROUTING链根据最新封装的IP报文,将数据包发至RS(因为在外层封装多了一层IP首部,所以可以理解为此时通过隧道传输)。 此时源IP为DIP,目标IP为RIP
5、 RS接收到报文后发现是自己的IP地址,就将报文接收下来,拆除掉最外层的IP后,会发现里面还有一层IP首部,而且目标是自己的lo接口VIP,那么此时RS开始处理此请求,处理完成之后,通过lo接口送给eth0网卡,然后向外传递。 此时的源IP地址为VIP,目标IP为CIP
6、响应报文最终送达至客户端
(1) DIP, VIP, RIP都应该是公网地址;
(2) RS的网关不能,也不可能指向DIP;
(3) 请求报文要经由Director,但响应不能经由Director;
(4) 不支持端口映射;
(5) RS的OS得支持隧道功能;
①RR(Round Robin):轮询调度
轮询调度算法的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从1开始,直到N(内部服务器个数),然后重新开始循环。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。【提示:这里是不考虑每台服务器的处理能力】
②WRR:weight,加权轮询(以权重之间的比例实现在各主机之间进行调度)
由于每台服务器的配置、安装的业务应用等不同,其处理能力会不一样。所以,我们根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。
③SH:source hashing,源地址散列。主要实现会话绑定,能够将此前建立的session信息保留了
源地址散列调度算法正好与目标地址散列调度算法相反,它根据请求的源IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的并且没有超负荷,将请求发送到该服务器,否则返回空。它采用的散列函数与目标地址散列调度算法的相同。它的算法流程与目标地址散列调度算法的基本相似,除了将请求的目标IP地址换成请求的源IP地址。
④DH:Destination hashing:目标地址散列。把同一个IP地址的请求,发送给同一个server。
目标地址散列调度算法也是针对目标IP地址的负载均衡,它是一种静态映射算法,通过一个散列(Hash)函数将一个目标IP地址映射到一台服务器。目标地址散列调度算法先根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。
①LC(Least-Connection):最少连接
最少连接调度算法是把新的连接请求分配到当前连接数最小的服务器,最小连接调度是一种动态调度短算法,它通过服务器当前所活跃的连接数来估计服务器的负载均衡,调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某台服务器,其连接数加1,当连接中止或超时,其连接数减一,在系统实现时,我们也引入当服务器的权值为0时,表示该服务器不可用而不被调度。
简单算法:active256+inactive(谁的小,挑谁)
②WLC(Weighted Least-Connection Scheduling):加权最少连接。
加权最小连接调度算法是最小连接调度的超集,各个服务器用相应的权值表示其处理性能。服务器的缺省权值为1,系统管理员可以动态地设置服务器的权限,加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。
简单算法:(active256+inactive)/weight【(活动的连接数+1)/除以权重】(谁的小,挑谁)
③SED(Shortest Expected Delay):最短期望延迟
基于wlc算法
简单算法:(active+1)*256/weight 【(活动的连接数+1)*256/除以权重】
④NQ(never queue):永不排队(改进的sed)
无需队列,如果有台realserver的连接数=0就直接分配过去,不需要在进行sed运算。
⑤LBLC(Locality-Based Least Connection):基于局部性的最少连接
基于局部性的最少连接算法是针对请求报文的目标IP地址的负载均衡调度,不签主要用于Cache集群系统,因为Cache集群中客户请求报文的布标IP地址是变化的,这里假设任何后端服务器都可以处理任何请求,算法的设计目标在服务器的负载基本平衡的情况下,将相同的目标IP地址的请求调度到同一个台服务器,来提高个太服务器的访问局部性和主存Cache命中率,从而调整整个集群系统的处理能力。
基于局部性的最少连接调度算法根据请求的目标IP地址找出该目标IP地址最近使用的RealServer,若该Real Server是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于一半的工作负载,则用“最少链接”的原则选出一个可用的服务器,将请求发送到该服务器。
⑥LBLCR(Locality-Based Least Connections withReplication):带复制的基于局部性最少链接
带复制的基于局部性最少链接调度算法也是针对目标IP地址的负载均衡,该算法根据请求的目标IP地址找出该目标IP地址对应的服务器组,按“最小连接”原则从服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载,则按“最小连接”原则从这个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。
当您需要进行更详细的 LVS(Linux Virtual Server)操作时,下面是一些示例命令和它们的解释:
ipvsadm -L -n
该命令会显示当前的 LVS 规则和状态,包括虚拟 IP、端口、后端服务器以及权重等信息。选项 -n
可以显示数字化的 IP 地址和端口。
添加一个 LVS 规则:
ipvsadm -A -t <虚拟 IP:端口> -s <调度算法> -t是tcp -u是udp
该命令会添加一个 LVS 规则,其中 <虚拟 IP:端口>
是您希望负载均衡的虚拟 IP 和端口,<调度算法>
是负载均衡使用的调度算法,例如 rr
(Round Robin)表示轮询算法,wrr
(Weighted Round Robin)表示加权轮询算法。
添加一个后端真实服务器:
ipvsadm -a -t <虚拟 IP:端口> -r <真实服务器 IP:端口> -g -w <权重>
该命令会添加一个后端真实服务器到指定的 LVS 规则中,其中 <虚拟 IP:端口>
是您设置的虚拟 IP 和端口,<真实服务器 IP:端口>
是后端真实服务器的 IP 和端口,-g
表示将请求直接转发给后端服务器,-w <权重>
可以指定该服务器的权重。
修改 LVS 规则中的后端真实服务器权重:
ipvsadm -e -t <虚拟 IP:端口> -r <真实服务器 IP:端口> -w <新权重>
该命令会修改指定 LVS 规则中的后端真实服务器的权重,其中 <虚拟 IP:端口>
是您设置的虚拟 IP 和端口,<真实服务器 IP:端口>
是要修改的后端真实服务器的 IP 和端口,<新权重>
是您希望设置的新权重。
删除一个后端真实服务器:
ipvsadm -d -t <虚拟 IP:端口> -r <真实服务器 IP:端口>
该命令会从指定的 LVS 规则中删除一个后端真实服务器,其中 <虚拟 IP:端口>
是您设置的虚拟 IP 和端口,<真实服务器 IP:端口>
是要删除的后端真实服务器的 IP 和端口。
删除一个 LVS 规则:
ipvsadm -D -t <虚拟 IP:端口>
该命令会删除指定的 LVS 规则,其中 <虚拟 IP:端口>
是要删除的虚拟 IP 和端口。
这些命令提供了一些常见的 LVS 操作示例,请确保在负载均衡器和真实服务器上设置正确的网络配置,并确保网络连通性和正确的路由设置。另外,确保已经启用了 IP 转发功能(通过 /proc/sys/net/ipv4/ip_forward
进行设置)以允许数据包在负载均衡器和真实服务器之间进行转发。
LVS语法格式 : ipvsadm [参数] [域名或IP地址]
LVS常用参数 :
-6 基于IPv6网络协议
-a 向指定虚拟服务添加真实服务器
-A 添加新的虚拟服务
-c 显示ipvs中目前存在的连接
-C 清空所有的虚拟服务规则
-d 删除指定真实服务器
-D 删除虚拟服务
-e 编辑指定真实服务器
-E 编辑虚拟服务
-g 设置LVS为直接路由工作模式
-h 显示帮助信息
-i 设置LVS为隧道工作模式
-L 显示内核中的虚拟服务规则
-m 设置LVS为NAT工作模式
-M 设置客户地址的子网掩码
-p 设置持久稳固的服务
-r 设置真实服务器
-R 恢复虚拟服务规则
-s 设置调度算法
-S 保存虚拟服务规则
-t 设置TCP协议的虚拟服务
-u 设置UDP协议的虚拟服务
-w 设置真实服务器的权值
-Z 清零转发消息的统计数据
本实验基于Centos7.9操作系统, 总共5台设备,两台做后端服务器,两台做lvs + keepalived , 一台客户机,实验以LVS(DR) + keepalived 来做双机热备,实验环境拓扑图如下图所示.
从用户的角度来看 ,会直接访问192.168.246.132,用户不关心内部如何协调
整个流程 : 用户会直接访问vip,这时Director Server的ens33网卡上是配置了vip的,lvs会根据路由配置,调度算法等将用户的请求合理的分发给两个Real server服务器,当Director Server宕机之后,vip就会浮动到Backup Server上面,重复以上的过程,服务器收到请求会给用户响应。
服务器名 | 主机名 | IP地址 | 虚拟设备名 | 虚拟IP |
---|---|---|---|---|
Director Server | lvs1 | 192.168.246.128 | ens33:0 | 192.168.246.132 |
Backup Server | lvs2 | 192.168.246.129 | ens33:0 | 192.168.246.132 |
Real Server1 | server1 | 192.168.246.130 | lo:0 | 192.168.246.132 |
Real Server2 | server2 | 192.168.246.131 | lo:0 | 192.168.246.132 |
client | client | 192.168.246.134 | ens33:0 | 192.168.246.132 |
请确保机器关闭防火墙
systemctl stop firewalld
setenforce 0
登录 Real Server1 与 Real Server2
登录服务端
vim udp_server.c
代码如下:
#include
#include
#include
#include
#include
#include
#include
#define PORT 80
#define BUFFER_SIZE 1024
#define REPLY_MESSAGE "Server reply"
int main() {
int sockfd;
struct sockaddr_in server_addr, client_addr;
char buffer[BUFFER_SIZE];
// 创建UDP套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("192.168.246.132"); // 替换为 ens33 的 IP 地址
server_addr.sin_port = htons(PORT);
// 将套接字绑定到地址和端口
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
while (1) {
// 接收消息
memset(buffer, 0, sizeof(buffer));
socklen_t client_addr_len = sizeof(client_addr);
ssize_t bytes_received = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
if (bytes_received < 0) {
perror("recvfrom failed");
exit(EXIT_FAILURE);
}
printf("Received message from %s:%d: %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);
// 发送回复消息
ssize_t bytes_sent = sendto(sockfd, REPLY_MESSAGE, strlen(REPLY_MESSAGE), 0, (struct sockaddr *)&client_addr, client_addr_len);
if (bytes_sent < 0) {
perror("sendto failed");
exit(EXIT_FAILURE);
}
printf("Reply message sent\n");
}
return 0;
}
编译
gcc -o udp_server udp_server.c
运行
./udp_server & #后台运行
服务端抓包
tcpdump -i any dst 192.168.246.132
2台服务器为lo:0绑定Vip地址,抑制ARP广播
登录主机Real Server1,Real Server2,编写以下脚本文件realserver.sh
vim /etc/init.d/realserver.sh
内容如下:
#!/bin/bash
#description: Config realserver
VIP=192.168.246.132
#/etc/rc.d/init.d/functions
case "$1" in
start)
/sbin/ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP
/sbin/route add -host $VIP dev lo:0
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
sysctl -p >/dev/null 2>&1
echo "RealServer Start OK"
;;
stop)
/sbin/ifconfig lo:0 down
/sbin/route del $VIP >/dev/null 2>&1
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
echo "RealServer Stoped"
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
exit 0
请根据实际情况,修改VIP地址。
**登录主机Real Server1,Real Server2,**分别执行脚本
bash /etc/init.d/realserver.sh
执行完了之后,验证一下 使用ifconfig 你会发现 回环地址的网卡会多出一个lo:0的 网卡;
注意 : 机器重启后脚本失效 实现成开机自启 (打开文件 (vim /etc/rc.local))
内容如下:
bash /etc/init.d/realserver.sh
登录主机 lvs1 和 lvs2
安装 keppalived (1.4有详细教程)
安装 lvs管理工具 yum install ipvsadm
加载内核模块
modprobe ip_vs
备份配置文件
mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf_bak
添加Ip转发
vim /etc/sysctl.conf
添加 net.ipv4.ip_forward = 1
登录主机lvs1
vim /etc/keepalived/keepalived.conf
内容如下
global_defs {
router_id LVS_TEST #服务器名字
}
vrrp_instance VI_1 {
state MASTER #配置主备,备用机此配置项为BACKUP
interface ens33 #指定接口
virtual_router_id 51 #指定路由ID,主备必须一样
priority 101 #设置优先级,主略高于备份
advert_int 1 #设置检查时间
authentication {
auth_type PASS #设置验证加密方式
auth_type 1234 #设置验证密码
}
virtual_ipaddress {
192.168.246.132
}
}
请根据实际情况修改以下部分
说明:
interface ens33 表示本地网卡为ens33
192.168.246.132 表示虚拟ip
重启keepalived服务
systemctl restart keepalived
验证虚拟IP是否生效
登录主机lvs1
vim /etc/keepalived/keepalived.conf
内容如下
global_defs {
router_id LVS_TEST #服务器名字
}
vrrp_instance VI_1 {
state BACKUP #配置主备,备用机此配置项为BACKUP
interface ens33 #指定接口
virtual_router_id 51 #指定路由ID,主备必须一样
priority 90 #设置优先级,备份略低于主
advert_int 1 #设置检查时间
authentication {
auth_type PASS #设置验证加密方式
auth_type 1234 #设置验证密码
}
virtual_ipaddress {
192.168.246.132
}
}
重启keepalived服务
systemctl restart keepalived
查看虚拟ip
注意:此时是没有的。因为master和bakcup只能同时存在一个。只有当master异常时,bakcup才会出现虚拟ip。
登录主机lvs1,关掉主服务器的keepalived服务
systemctl stop keepalived
登录主机lvs2,验证备份的keepalived是否生效
登录 lv1 和 lvs2
cd /etc/keepalived/
vim ipvsadm.sh
内容如下:
ipvsadm -C #清空规则
sudo ipvsadm -A -u 192.168.246.132:80 -s sh
sudo ipvsadm -a -u 192.168.246.132:80 -r 192.168.246.130:80 -g -w 1
sudo ipvsadm -a -u 192.168.246.132:80 -r 192.168.246.131:80 -g -w 1
根据实际情况更改VIP与 服务器ip ,此处采用的调度算法是源哈希 采用的协议是udp, 工作方式是DR
同样的要将脚本设置为开机自启 :
vim /etc/rc/local
内容如下:
bash /etc/keepalived/ipvsadm.sh
登录客户端
vim udp_client.c
代码如下:
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "192.168.246.132"
#define PORT 80
#define BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s \n" , argv[0]);
exit(EXIT_FAILURE);
}
int sockfd;
struct sockaddr_in server_addr;
char message[] = "123";
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &(server_addr.sin_addr)) <= 0) {
perror("inet_pton failed");
exit(EXIT_FAILURE);
}
// 绑定套接字到指定 IP 地址
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[1], &(client_addr.sin_addr));
if (bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 发送消息
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bytes_sent < 0) {
perror("sendto failed");
exit(EXIT_FAILURE);
}
printf("Message sent: %s\n", message);
// 接收服务端回复
ssize_t bytes_received = recv(sockfd, buffer, BUFFER_SIZE, 0);
if (bytes_received < 0) {
perror("recv failed");
exit(EXIT_FAILURE);
}
buffer[bytes_received] = '\0';
printf("Message received: %s\n", buffer);
close(sockfd);
return 0;
}
编译
gcc -o udp_client udp_client.c
运行
./udp_client 192.168.246.134
客户端可以指定ip发送
在本机上 ip addr add 192.168.246.135/24 dev ens33 ip可以添加多个
t_addr;
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[1], &(client_addr.sin_addr));
if (bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
perror(“bind failed”);
exit(EXIT_FAILURE);
}
// 发送消息
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bytes_sent < 0) {
perror(“sendto failed”);
exit(EXIT_FAILURE);
}
printf(“Message sent: %s\n”, message);
// 接收服务端回复
ssize_t bytes_received = recv(sockfd, buffer, BUFFER_SIZE, 0);
if (bytes_received < 0) {
perror(“recv failed”);
exit(EXIT_FAILURE);
}
buffer[bytes_received] = ‘\0’;
printf(“Message received: %s\n”, buffer);
close(sockfd);
return 0;
}
编译
```shell
gcc -o udp_client udp_client.c
运行
./udp_client 192.168.246.134
客户端可以指定ip发送
在本机上 ip addr add 192.168.246.135/24 dev ens33 ip可以添加多个