LVS集群
一.LVS简介
LVS 全称为 Linux Virtual Server ,它是一个负载均衡/高可用性集群,主要针对大业务量的网络应用(如新闻服务、网上银行 电子商务等)。 LVS 建立在一个主控服务器(通常为双机)及若干真实服务器( realserver )所组成的集群之上 。real-server 负责实际提供服务,主控服务器根据指定的调度算法对real-server 进行控制。而集群的结构对于用户来说是透明的,客户端只与单个的 IP (集群系统的虚拟IP)进行通信,也就是说,从客户端的视角来看,这里只存在单个服务器。Real-server 可以提供众多服务,ftp、http、dns、telnet、smtp ,以及现在比较流行将其用于 MySQL 集群 。主控服务器负责对 Real-Server 进行控制。客户端在向 LVS 发出服务请求时, Director 会通过特定的调度算法指定由某个 Real-Server 来应答请求,而客户端只与Load Balancer的IP (即虚拟的 VIP )进行通信。
二、LVS集群的体系结构
访问流程:CIP <–> VIP == DIP <–> RIP
三、LVS集群的类型
vs-nat: 修改请求报文的目标IP,多目标IP的DNAT
lvs-dr: 操纵封装新的MAC地址
lvs-tun: 在原请求IP报文之外新加一个IP首部
lvs-fullnat: 修改请求报文的源和目标IP
(1).nat模式
1.客户端发送访问请求,请求数据包中含有请求来源(cip),访问目标地址(VIP)访问目标端口
(9000port)
2.VS服务器接收到访问请求做DNAT把请求数据包中的目的地由VIP换成RS的RIP和相应端口
3.RS1相应请求,发送响应数据包,包中的相应保温为数据来源(RIP1)响应目标(CIP)相应端口
(9000port)
4.VS服务器接收到响应数据包,改变包中的数据来源(RIP1–>VIP),响应目标端口(9000–>80)
5.VS服务器把修改过报文的响应数据包回传给客户端
6.lvs的NAT模式接收和返回客户端数据包时都要经过lvs的调度机,所以lvs的调度机容易阻塞
客户请求到达vip后进入PREROUTING,在没有ipvs的时候因该进入本机INPUT,当IPVS存在后访问请求在通过PREROUTING后被ipvs结果并作nat转发
因为ipvs的作用点是在PREROUTING和INPUT链之间,所以如果在prerouting中设定规则会干扰ipvs的工作。所以在做lvs时要把iptables的火墙策略全清理掉。
(2).dr模式
DR:Direct Routing,直接路由,LVS默认模式,应用最广泛,通过为请求报文重新封装一个MAC首部进行转发,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源IP/PORT,以及目标IP/PORT均保持不变
1.客户端发送数据帧给vs调度主机帧中内容为客户端IP+客户端的MAC+VIP+VIP的MAC
2.VS调度主机接收到数据帧后把帧中的VIP的MAC该为RS1的MAC,此时帧中的数据为客户端IP+客户端的MAC+VIP+RS1的MAC
3.RS1得到2中的数据包做出响应回传数据包,数据包中的内容为VIP+RS1的MAC+客户端IP+客户端IP的MAC
(3).tun模式
1.客户端发送请求数据包,包内有源IP+vip+dport
2.到达vs调度器后对客户端发送过来的数据包重新封装添加IP报文头,新添加的IP报文头中包含
TUNSRCIP(DIP)+TUNDESTIP(RSIP1)并发送到RS1
3.RS收到VS调度器发送过来的数据包做出响应,生成的响应报文中包含SRCIP(VIP)+DSTIP(CIP)
+port,响应数据包通过网络直接回传给client
(4).fullnet模式(此类型kernel默认不支持)
fullnat:通过同时修改请求报文的源IP地址和目标IP地址进行转发
CIP --> DIP
VIP --> RIP
1.VIP是公网地址,RIP和DIP是私网地址,且通常不在同一IP网络;因此,RIP的网关一般不会指向DIP
2.RS收到的请求报文源地址是DIP,因此,只需响应给DIP;但Director还要将其发往Client
3.请求和响应报文都经由Director
4.支持端口映射
(5).几种工作模式总结
对比维度 | NAT 模式(Network Address Translation) | DR 模式(Direct Routing) | TUN 模式(IP Tunneling) | FULLNAT 模式(补充模式) |
---|---|---|---|---|
核心原理 | LVS 负载均衡器同时作为网关,对请求和响应的 IP / 端口进行转换: - 客户端→LVS(目标 IP 为 VIP) - LVS→后端(目标 IP 转为 RIP,源 IP 为 DIP) - 后端响应→LVS(源 IP 转为 VIP) - LVS→客户端 | LVS 仅处理请求转发,响应直接从后端返回客户端: - 客户端→LVS(目标 IP 为 VIP) - LVS 通过 MAC 地址转发至后端(目标 IP 仍为 VIP,后端需绑定 VIP) - 后端直接响应客户端(源 IP 为 VIP) | LVS 通过 IP 隧道(如 IPIP)转发请求,响应直接返回客户端: - 客户端→LVS(目标 IP 为 VIP) - LVS 封装请求为隧道包(目标 IP 为 RIP,原 IP 为 VIP) - 后端解封装后处理,直接响应客户端 | 同时转换源 IP 和目标 IP,解决跨网段问题: - 客户端→LVS(源 IP 转为 LVS 的 DIP,目标 IP 转为 RIP) - 后端响应→LVS(源 IP 转为 VIP,目标 IP 转为客户端 IP) - LVS→客户端 |
数据路径 | 请求和响应均经过 LVS(双向转发) | 请求经过 LVS,响应直接从后端到客户端(单向转发) | 请求经过 LVS,响应直接从后端到客户端(单向转发) | 请求和响应均经过 LVS(双向转发) |
后端服务器要求 | 无需绑定 VIP,默认网关需指向 LVS 的 DIP(确保响应返回 LVS) | 需在 lo 接口绑定 VIP(避免 ARP 冲突),默认网关无需指向 LVS | 需支持 IP 隧道(如加载 ipip 模块),并在 lo 接口绑定 VIP | 无需绑定 VIP,默认网关可指向任意(只需能与 LVS 通信) |
网络限制 | 后端服务器需与 LVS 在同一网段(DIP 和 RIP 需互通) | 后端服务器需与 LVS 在同一二层网络(依赖 MAC 地址转发,跨网段需特殊配置) | 后端服务器可与 LVS 跨网段(通过隧道通信,支持异地部署) | 后端服务器可与 LVS 跨网段(无网络范围限制) |
性能特点 | 性能瓶颈在 LVS(双向流量均经过 LVS,带宽和并发受限于 LVS) | 性能最优(LVS 仅处理请求,无响应流量压力) | 性能较好(隧道封装有轻微开销,但远低于 NAT) | 性能低于 DR/TUN,但高于 NAT(转换逻辑更复杂) |
优点 | 配置简单,后端无需特殊设置;支持任意操作系统 | 性能极强,无流量瓶颈(响应不经过 LVS);适用大流量场景 | 支持后端跨网段部署(如异地机房);性能接近 DR | 解决跨网段部署问题,后端网络配置灵活;支持任意组网 |
缺点 | LVS 易成为瓶颈(双向流量);后端规模受限(同一网段) | 需同一二层网络(跨网段需 VLAN 等配置);后端需绑定 VIP(配置稍复杂) | 后端需支持隧道(部分老旧系统不兼容);隧道封装有轻微性能损耗 | 性能不如 DR/TUN;LVS 仍需处理双向流量,可能成为瓶颈 |
典型适用场景 | 小规模服务(如内部 API、低流量网站);后端服务器类型多样(无需特殊配置) | 高并发、大流量场景(如电商秒杀、视频直播);后端与 LVS 在同一机房 | 后端跨地域部署(如多机房负载均衡);需突破网段限制的场景 | 跨网段部署且无法使用 DR/TUN(如公有云 VPC 内不同子网);对后端网络灵活性要求高的场景 |
四、调度算法
LVS的调度算法决定了如何将客户端请求分配到后端真实服务器(Real Server)
调度类型 | 算法名称 | 核心原理 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|
静态算法 | 轮询(Round Robin, RR) | 按顺序轮流将请求分配给后端服务器(如 A→B→C→A→B…) | 实现简单,无状态,对服务器要求低(无需感知负载) | 不考虑服务器性能差异和实时负载(可能将请求分配给高负载服务器) | 后端服务器性能均匀、无明显负载差异的场景(如静态资源服务) |
加权轮询(Weighted RR, WRR) | 按权重比例分配请求(权重高的服务器接收更多请求,如权重 A:B=2:1,则 A→A→B→A→A→B…) | 可根据服务器性能设置权重(性能强的服务器权重高),灵活调整分配比例 | 权重固定,无法动态响应服务器实时负载变化 | 后端服务器性能有差异(如配置不同的服务器集群),且负载相对稳定的场景 | |
源地址哈希(Source Hash, SH) | 根据客户端 IP 计算哈希值,固定将同一 IP 的请求分配给同一后端服务器 | 天然支持会话保持(同一客户端请求始终到同一服务器) | 哈希结果固定,若某台服务器下线,该哈希对应的客户端请求会失败(需手动处理) | 需要会话保持的场景(如未使用分布式会话的登录状态、购物车等) | |
目标地址哈希(Destination Hash, DH) | 根据请求目标 IP 计算哈希值,固定将同一目标 IP 的请求分配给同一后端服务器 | 适合缓存场景(同一目标 IP 的请求到同一服务器,提升缓存命中率) | 目标 IP 分布不均时,可能导致服务器负载失衡 | 反向代理缓存服务(如缓存特定域名 / IP 的内容)、DNS 解析服务 | |
动态算法 | 最少连接(Least Connections, LC) | 优先将请求分配给当前连接数最少的后端服务器(实时统计连接数) | 动态响应服务器负载变化,避免高负载服务器接收更多请求 | 未考虑服务器性能差异(可能将请求分配给性能弱但当前连接少的服务器) | 后端服务器性能均匀,且请求处理时间差异较大的场景(如 API 服务、数据库访问) |
加权最少连接(Weighted LC, WLC) | 结合权重和连接数,计算 “加权连接数”(连接数 / 权重),优先分配给值最小的服务器 | 兼顾服务器性能(权重)和实时负载(连接数),动态调整更合理 | 权重需预先配置,无法自动感知服务器性能变化 | 后端服务器性能有差异,且负载动态变化的场景(如混合配置的应用服务器集群) | |
最短预期延迟(Shortest Expected Delay Scheduling, SED) | 基于 “(连接数 + 1)/ 权重” 计算优先级,优先分配给值最小的服务器(优化 WLC 的初始分配) | 初始状态下,权重高的服务器会优先接收请求(避免 WLC 初始时低权重服务器先分配) | 仍依赖预设权重,无法完全动态适配 | 需快速将请求分配给高权重服务器的场景(如刚启动的集群,需优先利用高性能服务器) | |
永不队列(Never Queue, NQ) | 当某服务器连接数为 0 时,优先分配请求给它;否则按 SED 算法分配 | 避免空闲服务器等待,提升资源利用率 | 仅优化初始分配,高负载时仍依赖 SED 逻辑 | 服务器频繁启停或存在临时空闲节点的场景(如弹性伸缩集群) | |
负载均衡器(Locality-Based Least Connections, LBLCR) | 结合目标地址哈希和最少连接:同一目标 IP 优先分配到历史服务器,若负载过高则分配到新服务器 | 兼顾缓存命中率和负载均衡,适合静态资源 + 动态请求混合场景 | 实现复杂,对哈希和负载统计的精度要求高 | 既有静态缓存(如图片)又有动态请求(如 API)的混合服务 |
- 静态算法:不考虑后端服务器实时负载,仅按预设规则(轮询、权重、哈希)分配,适合负载稳定、需求简单的场景。
- 需会话保持选 源地址哈希;需缓存优化选 目标地址哈希;性能均匀选 轮询;性能有差异选 加权轮询。
- 动态算法:根据后端服务器实时连接数(结合权重)分配,适合负载动态变化、服务器性能有差异的场景。
- 常规动态场景首选 加权最少连接(WLC);需优化初始分配选 SED;弹性伸缩场景选 NQ。
- 选择原则:
- 优先根据 “是否需要会话保持”“服务器性能是否均匀”“负载是否动态变化” 三大因素判断;
- 无特殊需求时,加权轮询(静态) 和 加权最少连接(动态) 是最常用的选择。
五、LVS实战案例
(1)部署NAT模式集群
实验环境准备
主机名 | IP地址 | 备注 |
---|---|---|
client | 192.168.1.100 | Client(充当客户端访问) |
lvs | 192.168.1.150;192.168.0.150(仅主机) | Director Server(调度器) |
rs1 | 192.168.0.10(仅主机) | Real Server1 |
rs2 | 192.168.0.20(仅主机) | Real Server2 |
1.首先在rs1和rs2上部署web服务器,如下:
#安装http服务
[root@rs1 ~]# yum install httpd -y
#关闭防火墙,并设置httpd服务开机自启
[root@rs1 ~]# systemctl disable --now firewalld.service
[root@rs1 ~]# systemctl enable --now httpd
#在默认配置文件内添加测试内容,重启服务生效后进行本地测试
[root@rs1 ~]# echo "hello `hostname -I`" > /var/www/html/index.html
[root@rs1 ~]# systemctl restart httpd
[root@rs1 ~]# curl localhost
2.将RS网关设置为规划的内网地址192.168.0.150
[root@RS1 ~]# vim /etc/NetworkManager/system-connections/eth0.nmconnection
[root@RS1 ~]# nmcli connection reload
[root@RS1 ~]# nmcli connection up eth0
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)
[root@RS1 ~]# route -n
3.在LVS上开启内核路由转发
[root@LVS ~]# vim /etc/sysctl.conf
[root@LVS ~]# sysctl -p
net.ipv4.ip_forward = 1
4.在LVS上进行ipvsadm的安装和配置
[root@LVS ~]# ipvsadm -A -t 192.168.1.150:80 -s rr
[root@LVS ~]# ipvsadm -a -t 192.168.1.150:80 -r 192.168.0.10:80 -m
[root@LVS ~]# ipvsadm -a -t 192.168.1.150:80 -r 192.168.0.20:80 -m
[root@LVS ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.1.150:80 rr
-> 192.168.0.10:80 Masq 1 0 0
-> 192.168.0.20:80 Masq 1 0 0
5.测试
(2)部署DR模式集群
实验环境准备
主机名 | IP地址 | 备注 |
---|---|---|
client | 192.168.1.100 | Client(充当客户端访问) |
lvs | 192.168.0.150 | Director Server(调度器) |
rs1 | 192.168.0.10 | Real Server1 |
rs2 | 192.168.0.20 | Real Server2 |
同时在lvs及rs1和rs2上规划VIP:192.168.0.220
1.首先在rs1和rs2上部署web服务器,如下:
#安装http服务
[root@rs1 ~]# yum install httpd -y
#关闭防火墙,并设置httpd服务开机自启
[root@rs1 ~]# systemctl disable --now firewalld.service
[root@rs1 ~]# systemctl enable --now httpd
#在默认配置文件内添加测试内容,重启服务生效后进行本地测试
[root@rs1 ~]# echo "hello `hostname -I`" > /var/www/html/index.html
[root@rs1 ~]# systemctl restart httpd
[root@rs1 ~]# curl localhost
#在rs上做arp抑制
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
自此,对rs1和rs2配置完成
2.对lvs调度器进行配置
#关闭防火墙
[root@lvs ~]# systemctl disable --now firewalld.service
#安装ipvsadm,并配置
[root@lvs ~]# yum install ipvsadm.x86_64 -y
[root@lvs ~]# ipvsadm -A -t 192.168.0.220:80 -s rr
[root@lvs ~]# ipvsadm -a -t 192.168.0.220:80 -r 192.168.0.10:80 -g
[root@lvs ~]# ipvsadm -a -t 192.168.0.220:80 -r 192.168.0.20:80 -g
[root@lvs ~]# ipvsadm -Ln
3.在client上进行测试
[root@client ~]# for i in {1..10} ; do curl 192.168.0.220 ; done
六、使用脚本进行LVS的自动化部署
#在LVS调度器上运行此脚本进行LVS的自动化部署
#!/bin/bash
# LVS部署脚本 - 支持DR和NAT模式
# VIP=192.168.95.10
# RIP1=192.168.95.11
# RIP2=192.168.95.12
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 配置参数
NAT_DEV=eth1
HOST_DEV=eth0
NAT_VIP=172.25.254.100
NAT_DIP=192.168.0.200
DR_VIP=192.168.0.220
DR_DIP=192.168.0.200
RS1_IP=192.168.0.10
RS2_IP=192.168.0.20
# 检查是否为root用户
check_root() {
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误:此脚本需要root权限运行${NC}"
exit 1
fi
}
# 安装ipvsadm
install_ipvsadm() {
echo -e "${YELLOW}正在检查ipvsadm...${NC}"
if ! rpm -qa ipvsadm &> /dev/null; then
echo -e "${YELLOW}安装ipvsadm...${NC}"
dnf install -y ipvsadm
if [ $? -ne 0 ]; then
echo -e "${RED}安装ipvsadm失败${NC}"
exit 1
fi
fi
echo -e "${GREEN}ipvsadm已安装${NC}"
}
# 检查SSH连接
check_ssh() {
echo -e "${YELLOW}检查RS节点SSH连接...${NC}"
for ip in $RS1_IP $RS2_IP; do
if ! ssh -o BatchMode=yes -o ConnectTimeout=2 "root@$ip" 'exit' &> /dev/null; then
echo -e "${RED}错误:无法通过SSH连接到$ip${NC}"
exit 2
fi
done
echo -e "${GREEN}SSH连接检查通过${NC}"
}
# 配置LVS DR模式
configure_dr() {
case $1 in
start)
echo -e "${YELLOW}启动LVS DR模式...${NC}"
# 配置本地VIP
cat > /etc/NetworkManager/system-connections/lo.nmconnection <<EOF
[connection]
id=lo
type=loopback
interface-name=lo
[ipv4]
method=manual
address1=127.0.0.1/8
address2=$DR_VIP/32
EOF
chmod 600 /etc/NetworkManager/system-connections/lo.nmconnection
ip a add $DR_VIP/32 dev lo &> /dev/null
# 配置ARP参数
cat >/tmp/sysctl.conf <<EOF
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
EOF
sysctl -p /tmp/sysctl.conf &> /dev/null
# 配置IPVS规则
ipvsadm -C
ipvsadm -A -t $DR_VIP:80 -s rr
ipvsadm -a -t $DR_VIP:80 -r $RS1_IP:80 -g
ipvsadm -a -t $DR_VIP:80 -r $RS2_IP:80 -g
# 配置RS节点
for IP in $RS1_IP $RS2_IP
do
scp /etc/NetworkManager/system-connections/lo.nmconnection root@$IP:/etc/NetworkManager/system-connections/ &> /dev/null
ssh -l root $IP "ip addr add $DR_VIP/32 dev lo" &> /dev/null
scp /tmp/sysctl.conf root@$IP:/etc/sysctl.d/lvs.conf &> /dev/null
ssh -l root $IP 'sysctl -p /etc/sysctl.d/lvs.conf' &> /dev/null
done
echo -e "${GREEN}LVS DR模式已启动${NC}"
;;
stop)
echo -e "${YELLOW}停止LVS DR模式...${NC}"
# 移除本地VIP和规则
rm -f /etc/NetworkManager/system-connections/lo.nmconnection
ip addr del $DR_VIP/32 dev lo &> /dev/null
ipvsadm -C
# 清理RS节点配置
for IP in $RS1_IP $RS2_IP
do
cat > /tmp/sysctl.conf-stop <<EOF
net.ipv4.conf.all.arp_ignore = 0
net.ipv4.conf.all.arp_announce = 0
net.ipv4.conf.lo.arp_ignore = 0
net.ipv4.conf.lo.arp_announce = 0
EOF
scp /tmp/sysctl.conf-stop root@$IP:/etc/sysctl.d/lvs.conf &> /dev/null
ssh -l root $IP "ip addr del $DR_VIP/32 dev lo; rm -f /etc/NetworkManager/system-connections/lo.nmconnection; sysctl -p /etc/sysctl.d/lvs.conf" &> /dev/null
done
echo -e "${GREEN}LVS DR模式已停止${NC}"
;;
*)
echo -e "${RED}未知操作: $1${NC}"
exit 1
;;
esac
}
# 配置LVS NAT模式
configure_nat() {
case $1 in
start)
echo -e "${YELLOW}启动LVS NAT模式...${NC}"
# 配置本地VIP
ip addr add $NAT_VIP/32 dev $NAT_DEV &> /dev/null
# 启用IP转发
echo 1 > /proc/sys/net/ipv4/ip_forward
sysctl -w net.ipv4.ip_forward=1 &> /dev/null
# 配置SNAT规则
iptables -t nat -F
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o $HOST_DEV -j MASQUERADE
# 配置IPVS规则
ipvsadm -C
ipvsadm -A -t $NAT_VIP:80 -s rr
ipvsadm -a -t $NAT_VIP:80 -r $RS1_IP:80 -m
ipvsadm -a -t $NAT_VIP:80 -r $RS2_IP:80 -m
# 配置RS节点网关
for IP in $RS1_IP $RS2_IP
do
ssh -l root $IP "ip route add default via $NAT_DIP" &> /dev/null
# 确保RS节点已启动HTTP服务
ssh -l root $IP "systemctl is-active --quiet httpd || systemctl start httpd" &> /dev/null
done
echo -e "${GREEN}LVS NAT模式已启动${NC}"
;;
stop)
echo -e "${YELLOW}停止LVS NAT模式...${NC}"
# 移除本地VIP和规则
ip addr del $NAT_VIP/32 dev $NAT_DEV &> /dev/null
ipvsadm -C
# 禁用IP转发
echo 0 > /proc/sys/net/ipv4/ip_forward
sysctl -w net.ipv4.ip_forward=0 &> /dev/null
# 清除SNAT规则
iptables -t nat -F
# 清理RS节点网关
for IP in $RS1_IP $RS2_IP
do
ssh -l root $IP "ip route del default via $NAT_DIP" &> /dev/null
done
echo -e "${GREEN}LVS NAT模式已停止${NC}"
;;
*)
echo -e "${RED}未知操作: $1${NC}"
exit 1
;;
esac
}
# 主函数
main() {
check_root
install_ipvsadm
check_ssh
case $1 in
start)
case $2 in
dr|DR|Dr)
configure_dr start
;;
nat|NAT|Nat)
configure_nat start
;;
*)
echo -e "${RED}Usage: \n\t$0 <start | stop> < nat | dr >${NC}"
exit 1
;;
esac
;;
stop)
case $2 in
dr|DR|Dr)
configure_dr stop
;;
nat|NAT|Nat)
configure_nat stop
;;
*)
echo -e "${RED}Usage: \n\t$0 <start | stop> < nat | dr >${NC}"
exit 1
;;
esac
;;
*)
echo -e "${RED}Usage: \n\t$0 <start | stop> < nat | dr >${NC}"
exit 1
;;
esac
}
main