这套组合我已经搭过不下十回了——Web服务集群里,LVS+Keepalived像是那种你平时想不起、一出事就靠得住的老伙计。很多新项目一上来就买云负载均衡,省事是省事,但如果你自己维护过几套业务,就知道自建四层高可用集群有多重要:转发性能高、故障秒级切换、后端节点动态摘除,这三个能力,正好命中Web服务集群最核心的需求。这篇文章我把从选型、概念、配置到排障的完整过程写下来,适合正在做运维、或者准备入门负载均衡高可用的同学直接参考。
先给一个总览:LVS(Linux Virtual Server)负责把流量按调度算法分发给后面的真实服务器,Keepalived负责两件事——用VRRP协议让VIP(虚拟IP)在主备之间漂移,以及定期探测后端节点健康状态。两者配合,就得到了一套既抗单点、又能自动剔除故障节点的Web服务集群。下面我按实际落地的顺序从头讲。
1. 为什么还在用LVS+Keepalived?——选型背后的现实考量
1.1 四层转发和七层代理的本质区别
很多人最开始接触负载均衡是用Nginx,我自己也是。Nginx做七层代理时,要完整解析HTTP头部、维护客户端连接、还要考虑upstream连接复用,逻辑很重。一台Nginx在纯转发场景下,能扛的并发确实不低,但一旦请求体很大、长连接多、或者SSL卸载集中到一台机器上,CPU很容易被打到红线。
LVS不一样,它工作在Linux内核态,本质上是修改数据包的目的MAC或IP,然后按调度算法把包发到后端。它不关心HTTP协议内容,也不维护客户端和服务端之间的业务连接状态(除非你开持久性),所以单机转发能力高出Nginx一个量级。拿我负责过的一个接口网关项目来说,高峰期每秒请求量在两万左右,之前Nginx集群三台还经常CPU告警,换LVS后两台LVS节点CPU长期在15%上下,后端RS的压力反而成了瓶颈。
还有一点容易被忽略:LVS不只是为HTTP服务,理论上你只要定义了端口和协议,TCP/UDP服务都可以做四层分发,Redis、MySQL读写入口、RPC框架,都能挂在这套集群后面。这是七层代理很难替代的通用性。
1.2 单点变高可用:Keepalived补上的关键一环
LVS本身只是内核里的转发规则,它不负责“高可用”。如果你只在一台机器上配ipvsadm,调度器宕机,整个集群立刻瘫痪,这跟没有负载均衡没什么区别。Keepalived在这里补的正是两件事:
- 通过VRRP协议在多个LVS节点间漂移VIP,主节点挂了,备节点自动接管;
- 通过健康检查探测后端真实服务器,发现某个RS异常,自动从转发规则里摘掉,服务恢复后再加回来。
用生活化的类比:LVS是前台总机,负责把来电转给不同工位;Keepalived是前台主管,总机坏了马上有人顶替,哪个工位没人接电话,立刻停止往那边转。
可能你会问:既然云厂商都有负载均衡产品,为什么还要自建?几个现实原因:一是成本,长期运行的固定业务,自建两台通用服务器比按流量付费划算很多;二是可控性,你可以完全掌握调度算法、会话保持时间、健康检查细节;三是学习价值,理解这套原理后,再去看云负载均衡的文档,很多参数一眼就能看懂。当然,如果你只有一两个轻量服务、不想维护服务器,直接用云产品没毛病,工具选择永远看场景。
2. 核心概念:LVS工作模式与集群规划
2.1 三种工作模式(NAT / DR / TUN)
配置LVS之前,必须先搞清楚三种工作模式,否则后面出了问题根本想不明白为什么。
NAT模式:客户端访问VIP,LVS把目标地址改成后端RS的IP,响应包再经过LVS改回源地址返回客户端。优点是后端随便私网IP就行,缺点是进出流量都要过调度器,流量一大调度器就是瓶颈。
DR模式(Direct Routing):客户端访问VIP,LVS只修改数据帧的目标MAC地址,把请求转发给后端的某台RS;后端RS收到包后,发现目标IP是VIP,而这个VIP同时配置在RS的lo接口上,于是RS认为自己就是目标服务器,直接回应客户端。响应包不经过LVS,这就是DR模式性能高的根本原因。代价是LVS和RS必须在同一个二层网络内,并且要在RS上做ARP抑制,避免多台机器同时响应VIP的ARP请求。
TUN模式:通过IP隧道把请求封装后发给后端,可以跨网段部署,但封装/解封装开销大,实际运维中配置复杂,除了个别跨机房场景,我很少见人用。
生产环境我基本都是DR模式。原因很直接:性能最好、配置相对简单、只要注意ARP参数和回环接口就能跑得很稳。
2.2 一套可落地的IP规划与VIP设计
假设我们部署一个典型的Web服务集群,后端是Nginx提供静态和API服务。IP规划如下(模拟生产环境内网地址):
| 角色 | IP地址 | 说明 |
|---|---|---|
| VIP | 192.168.1.100 | 对外提供服务的虚拟IP |
| LVS主节点 | 192.168.1.20 | Keepalived状态为MASTER |
| LVS备节点 | 192.168.1.21 | Keepalived状态为BACKUP |
| RS1 | 192.168.1.11 | Nginx节点,权重2 |
| RS2 | 192.168.1.12 | Nginx节点,权重1 |
| RS3 | 192.168.1.13 | Nginx节点,权重1 |
规划时有几个点要特别留意。VIP不能和任何物理机IP重复,也不建议和DHCP分配池混在一起,最好在网管系统里预留出来。客户端访问的入口如果是内网,VIP用内网地址;如果业务对公网开放,前面一般还有防火墙做DNAT,把公网IP映射到192.168.1.100。
另一件重要的事:RS节点上的VIP必须用32位掩码配置在lo接口(比如192.168.1.100/32 dev lo),而不是和物理网卡放在同一个网段,否则RS往VIP的访问会产生奇怪路由。还要注意,RS的默认网关必须指向物理网段的真实网关,绝对不能指向VIP或LVS节点,否则回包路由会失败。
2.3 DR模式下两个关键ARP参数
这是DR模式的核心难点,我单独拿出来讲。默认情况下,如果VIP配置在RS的lo接口上,当网络上有人问“谁是192.168.1.100”时,LVS主节点和所有RS都会回答“我是”,这样客户端ARP缓存里存的可能是任意一台机器的MAC,流量就乱了。
解决办法是让RS“不响应VIP的ARP请求、也不主动宣告VIP”。需要设置两个内核参数:
arp_ignore = 1:只回答目标IP是本接口IP的ARP请求。RS的物理网卡IP不是VIP,所以不会响应VIP的ARP请求;arp_announce = 2:发送ARP通告时,尽量使用目标IP相同接口的地址作为源地址,避免从lo接口发出VIP的ARP。
很多老教程只设置了net.ipv4.conf.all,没有设置net.ipv4.conf.lo,结果RS还是会泄露VIP的ARP。我在生产上一般把all和lo都设置一遍,保证万无一失。下面这组配置是标准做法,直接放进/etc/sysctl.conf:
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 net.ipv4.conf.eth0.arp_ignore = 1 net.ipv4.conf.eth0.arp_announce = 2设置后执行sysctl -p生效,注意部分内核参数写进all之后,具体网卡接口也需要确认生效值,可以用sysctl -a | grep arp_检查。
3. 手把手搭建LVS+Keepalived高可用集群
3.1 环境准备与系统初始化
我用的是Rocky Linux 9,本质上和CentOS 7/8没有区别,Ubuntu也能用这套方法,只是安装命令换成apt。先把必要的软件装上,两台LVS节点都需要:
yum install -y keepalived ipvsadm systemctl enable keepalived接下来是系统层面几个容易踩坑的点。如果你装了firewalld,需要放行VRRP组播流量,或者干脆在测试环境先停掉:
systemctl stop firewalld systemctl disable firewalldVRRP默认使用的组播地址是224.0.0.18,协议号是112,如果公司网络有防火墙策略,要在交换机或主机防火墙里放行。SELinux如果处于enforcing状态,keepalived绑定VIP会受限,测试环境我建议先setenforce 0,生产环境则要写对应放行策略,不要直接关。
后端RS只需要有Web服务,不需要装keepalived和ipvsadm。RS上要确保Nginx监听80端口,并且有健康检查用的路径(比如/healthz),这是个很重要的细节。
3.2 主节点Keepalived配置详解
主节点/etc/keepalived/keepalived.conf默认没有,需要自己创建。我直接贴一份完整可用的配置,再做逐段说明:
global_defs { router_id LVS_MASTER vrrp_skip_check_adv_addr vrrp_garp_interval 1 vrrp_gna_interval 1 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.1.100/24 dev eth0 } } virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR persistence_timeout 60 protocol TCP real_server 192.168.1.11 80 { weight 2 TCP_CHECK { connect_timeout 3 connect_port 80 } } real_server 192.168.1.12 80 { weight 1 TCP_CHECK { connect_timeout 3 connect_port 80 } } real_server 192.168.1.13 80 { weight 1 TCP_CHECK { connect_timeout 3 connect_port 80 } } }先看global_defs,router_id只是一个标识,主备可以不同,影响不大;但virtual_router_id在主备之间必须完全一致,它是VRRP报文里区分实例的关键值。很多脑裂问题就是两台机器这个ID不一致导致的。
priority决定了谁是主:主节点写100,备节点写90。advert_int 1表示每隔1秒发一次VRRP通告,这是我们评估切换时间的重要依据。Keepalived默认在连续3个通告周期没收到master通告后,备用节点才会升级,所以切换时间大概是3秒左右。如果你希望更快,可以把advert_int改成0.5秒,但这样组播包更频繁,对网络稳定性要求也更高,我实际用下来1秒比较稳。
virtual_server段是LVS转发规则的定义,注意它不属于vrrp_instance,而是keepalived的另一个模块。Keepalived启动后会自动调用ipvsadm,把这些规则加载到内核里。lb_kind DR就是选DR模式,lb_algo wrr表示加权轮询。persistence_timeout 60的意思是,来自同一个客户端IP的连接在60秒内尽量转发到同一台RS,这个可以视业务决定开不开。如果是短请求且后端无状态,可以设为0;如果有登录态且后端没有共享Session,建议按照业务会话长度来设置,比如900。
还需要解释:为什么备节点也要配置完整的virtual_server段?因为备节点平时只是“待命”,但一旦主节点故障,备节点成为MASTER后必须立刻有转发规则可用。如果配置里没有这些real_server,VIP飘过去也只是一个空壳IP,无法分发流量。这是很多人会漏掉的地方。
3.3 后端真实服务器(RS)配置
在每台RS上执行:
ip addr add 192.168.1.100/32 dev lo同时把VIP写进开机自启。最简单的方式是放到/etc/rc.local,记得给执行权限;更规范的做法是写一个systemd service。生产上我更推荐systemd,因为能依赖网络就绪后再执行:
[Unit] Description=VIP on lo After=network-online.target [Service] Type=oneshot ExecStart=/usr/sbin/ip addr add 192.168.1.100/32 dev lo ExecStop=/usr/sbin/ip addr del 192.168.1.100/32 dev lo RemainAfterExit=yes [Install] WantedBy=multi-user.target然后设置前文说的ARP抑制参数,sysctl -p生效。再确认Nginx监听的是80端口。这里有个很容易搞混的地方:RS的Nginx配置里server_name和listen不需要改成VIP,保持原来的配置即可,因为请求到达RS时,目标IP已经是VIP,Nginx拿到的Host头是客户端请求里的域名,不做特殊处理也能正常回包。
RS上如果还有iptables规则,留意不要阻断来自LVS节点的请求。我见过一个案例,RS上默认DROP策略,只放行了某个网段,结果漏了LVS节点所在网段,VIP访问时好时坏。
3.4 启动验证与流量观察
两台LVS节点都启动keepalived:
systemctl start keepalived然后先确认VIP在哪台节点上:
ip addr show dev eth0正常情况下VIP只出现在主节点。接着看LVS规则:
ipvsadm -Ln输出里应该有VIP和三个real_server,状态为Available,权重正确。如果ipvsadm -Ln里没有规则,说明keepalived配置文件有问题,看日志排查。如果real_server后面显示的不是Available,而是类似“no route”或“down”,就要从健康检查入手查。
然后从客户端用VIP访问:
curl -s http://192.168.1.100/healthz多试几次,配合下面的命令观察连接分布:
watch -n1 ipvsadm -Ln --stats--stats会显示每个RS累计转发的包数和字节数,从数字增长能看出流量是否均匀。为了验证高可用,我在主节点上执行systemctl stop keepalived,同时客户端用一个循环脚本持续发请求:
while true; do curl -s -o /dev/null -w "%{http_code} %{remote_addr}\n" http://192.168.1.100/; sleep 1; done大约3秒后,VIP会漂移到备节点,请求不中断或只中断一两个,这就是预期结果。回切也值得测一次:重新启动主节点的keepalived,VIP应该在几十秒内自动回到优先级更高的MASTER节点上。
4. 健康检查、故障切换与脑裂问题
4.1 健康检查机制:从TCP_CHECK到HTTP_GET
前面配置里用的是TCP_CHECK,它只探测TCP端口通不通。优点是轻量、不消耗太多资源,缺点是不够真实——Nginx进程还活着但业务线程卡死,TCP端口照样能连上,这时候Keepalived认为节点正常,流量还会打过去,客户端就踩到502或超时。
更可靠的做法是用HTTP_GET检查一个真实的健康接口,比如/healthz。配置改成这样:
real_server 192.168.1.11 80 { weight 2 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } }Keepalived每delay_loop秒(我设的6秒)会向后端发起一次HTTP请求,只有返回200才认为节点健康。如果后端应用接口返回500,Keepalived会在连续重试3次后把节点摘掉,再每隔2秒继续探测,直到恢复后自动加回。
还有一种更灵活的MISC_CHECK,可以执行自定义脚本来判断节点健康。比如脚本里可以检查磁盘、内存、应用层队列长度,适合复杂场景。自定义脚本要注意执行权限、超时时间、返回码——返回0表示健康,非0表示异常。我建议生产环境优先把健康检查做到应用本身,再配合HTTP_GET,这样最直观也最好排查。
4.2 VRRP主备切换与双主模型
Keepalived的VRRP状态只有三种:MASTER、BACKUP、FAULT。MASTER定期发组播通告,BACKUP收到后刷新计时器;一旦超过阈值时间没收到通告,BACKUP就通过选举升级为MASTER。所以我们看到的3秒切换,本质就是“3个advert_int没等到主节点的声音”。
主备两台机器的virtual_router_id必须相同,否则VRRP报文不匹配,各认各的,脑裂就来了。auth_pass也要一致,它只做简单认证,不是加密,所以不要在跨公网的环境里裸跑VRRP。
硬件资源如果不想浪费一台备机,可以做成双主模型:两个VIP分别落在两台机器上,互相做对方的BACKUP。比如VIP1的MASTER是node1,VIP2的MASTER是node2,node1挂掉后node2同时持有VIP1和VIP2。配置时建两个vrrp_instance,priority互相错开:
vrrp_instance VI_1 { state MASTER virtual_router_id 51 priority 100 } vrrp_instance VI_2 { state BACKUP virtual_router_id 52 priority 90 }注意两个实例要用不同的virtual_router_id,node2上则刚好相反。双主模型适合两台机器规格一致、业务规模较大时使用,能让备机也承担一部分生产流量。我实际维护过一套这样的集群,出现过单机宕机期间另一台流量翻倍,但只要容量预估够了,问题不大。
4.3 脑裂的成因、检测与防范
脑裂是所有高可用方案最怕的问题,表现就是两台LVS节点同时持有VIP,客户端请求有时到主节点、有时到备节点,后端被不同调度器随机分发,整个集群行为完全不可控。
常见的脑裂原因有这么几个:
- 两台机器的
virtual_router_id不一致,VRRP报文无法匹配,双向都不认对方; - 防火墙或交换机阻断了组播224.0.0.18,备用节点收不到主节点通告,以为主节点挂了就升级;
advert_int设置过小,网络发生瞬时拥塞时通告丢失,触发了不必要的切换;- 两台机器priority相同,双方在某些实现下可能出现竞争。
防范措施,首先是保证配置规范:router_id保持一致、auth一致、priority不同。其次是网络层面,不要在LVS节点的防火墙里拦截VRRP,交换机上如果开了组播过滤,要给VRRP放行。还有一招是把VRRP绑定到独立的管理网卡,比如双网卡机器,一张走业务流量,一张专门走VRRP组播,这样即使业务网卡拥堵,也不影响心跳。
检测脑裂最直接的办法是巡检脚本。我们在两台LVS节点上都放一个脚本,检查本机是否持有VIP,然后把结果上报给监控系统。如果监控同时收到两台机器上报“持有VIP”,立即告警。脚本很简单:
#!/usr/bin/env bash VIP="192.168.1.100" if ip addr show | grep -qw "$VIP"; then echo "LVS_HAS_VIP" else echo "LVS_NO_VIP" fi配合zabbix或Prometheus的文本采集,每30秒跑一次,就能实时发现双主。不要等线上出现诡异故障了才去翻IP,脑裂问题越早发现越容易处理。
5. 典型故障排查与调优实录
5.1 常见问题速查
我把这几年遇到的高频问题整理成一张表,方便对照排查:
| 现象 | 可能原因 | 排查命令 / 处理方案 |
|---|---|---|
| 访问VIP完全超时 | VIP不在任何LVS节点上 | ip addr show dev eth0检查主备VIP |
| 访问VIP超时但VIP存在 | LVS转发规则没生效 | ipvsadm -Ln,确认real_server存在 |
| 部分后端请求失败 | 某RS服务异常但健康检查没摘除 | 检查HTTP_GET的检查路径和状态码 |
| 流量全部打向同一台RS | 会话保持时间太长、或权重差异过大 | 调整persistence_timeout,检查weight |
| keepalived频繁切换 | VRRP通告丢失 | 看messages日志,检查组播防火墙策略 |
| RS收不到请求 | LVS与RS二层不通 | 从LVS节点ping RS_IP,检查同网段 |
| RS回包客户端但业务异常 | lo接口VIP未配置或ARP参数失效 | ip addr show lo,`sysctl -a |
排查顺序我一般固定为:先确认VIP在哪(Keepalived状态),再确认内核规则(ipvsadm),再确认后端节点本身健康(curl RS),最后才是抓包。这个顺序能把90%的问题缩小到三个层面里。
5.2 抓包与日志定位一次真实故障
讲一个印象很深的故障案例。某天接口服务快慢不一,一部分请求稳定返回,另一部分大概有20%会在5秒后超时。第一反应是后端某台机器出问题了,但登录每台RS看CPU、内存都正常,Nginx错误日志也没有明显异常。
通过ipvsadm -Ln --stats看到三台RS都有了流量,其中RS1的转发量明显偏高。再手动curl每一台RS的/healthz,RS1偶尔要等4秒才返回,其他节点几十毫秒。问题定位到RS1。但为什么Keepalived没把它摘掉?因为当时用的是TCP_CHECK,端口一直通,应用层的慢根本没有暴露。后来把健康检查改成HTTP_GET /healthz,同时在后端把/healthz接口的响应时间打印出来,Keepalived在连续重试后把RS1摘掉了,集群恢复稳定。
那次之后我养成了两个习惯:所有Web服务必须提供轻量健康接口,且不能依赖数据库;所有 Keepalived 健康检查优先用HTTP_GET,TCP_CHECK只用于非HTTP服务。排查过程中用到的关键命令也放出来:
# 实时查看Keepalived状态 systemctl status keepalived journalctl -u keepalived -f # 查看LVS的连接分布 ipvsadm -Ln --stats # 抓VIP相关流量 tcpdump -i eth0 host 192.168.1.100 and port 80 -nn从抓包里能看到三类重要信息:客户端是否请求VIP、LVS是否向后端转发、RS是否直接回包给客户端。如果LVS有转发但RS没回包,大概率是RS路由或VIP配置问题;如果RS回了包但客户端没收到,就要看防火墙和会话跟踪了。
5.3 性能调优与日常维护建议
集群能跑起来之后,剩下的是细水长流的维护。首先是内核参数,如果LVS节点上的并发连接很大,注意看conntrack是否满了:
cat /proc/sys/net/netfilter/nf_conntrack_max cat /proc/sys/net/netfilter/nf_conntrack_count如果nf_conntrack_count接近最大值,要调高/etc/sysctl.conf里的net.netfilter.nf_conntrack_max,同时把超时时间调短。LVS的DR模式不完全依赖conntrack,但Keepalived在健康检查和部分调度算法下还是会创建conntrack条目,不能忽略。
第二是文件描述符,ulimit -n建议调到65535以上,特别是要转发大量长连接的场景。net.core.somaxconn也建议调大,避免高并发下TCP半连接队列溢出,这个常常藏在连接超时问题的背后。
第三是日常维护节奏。我通常会做这几件事:
- 每天检查一次
ipvsadm -Ln --stats的流量分布,确认没有单台RS流量飙升; - 每周做一次主备切换演练,最简单就是重启keepalived,观察VIP漂移和回切时间;
- 每次变更RS或应用发布,都要在生产环境外先测试健康检查接口;
- 给Keepalived进程、VIP可用性、后端RS健康检查配独立监控告警。
扩容也很简单:新RS配置好VIP和ARP参数后,在Keepalived的virtual_server里加一个real_server,然后systemctl reload keepalived,规则会重新加载。如果当时有存量长连接,reload可能中断它们,所以建议低峰期操作。缩容时,我一般先把权重改成0,让流量自然降为零,再移除real_server,这样对线上影响最小。
6. 写在最后:这套集群让我学到的几件事
如果让我给刚接触这套架构的人一句建议,那就是:先把DR模式的原理彻底想明白,再动手配。很多人配置完发现不通,绕来绕去查防火墙,最后发现是ARP参数没生效。原理通了,配置就是照着填空。
另一个体会是工具越老越稳。LVS和Keepalived都是很多年前就成熟的技术,没有花哨的UI,但确确实实扛住了我经历过的大流量。反而是那些复杂的新架构,在故障时更难定位。高可用不只是靠软件,更要靠监控和演练。VIP漂移这个动作,演练过几十次,真出故障时你才敢笃定地说“等一下,三秒后自动恢复”。
最后再分享一个小技巧:上线前把VIP绑定到拨测系统,每隔一分钟从外部发一次HTTP请求。一旦VIP丢失或双主,第一时间收到告警。这套集群不会替你解决所有问题,但有了它,后端再怎么折腾,用户的入口始终是稳定的。希望这篇从选型到排障的完整记录,能让你少走我踩过的那些坑。