
做了好几年负载均衡相关的工作我最大的感受就是方案做得再花哨也得先保证入口不死。很多团队把后端池子调度做得风生水起一画架构图Nginx就一台机器孤零零摆在最前面——后端再健康入口一挂全员停工。Keepalived这个工具我第一次接触它是为了给LVS做双机热备后来发现它给Nginx、HAProxy做入口高可用也是一把好手靠一个虚拟IP的漂移让负载均衡节点从“单点故障”变成“双活/主备”。这篇文章我不打算只贴一堆配置而是把为什么要给负载均衡做高可用、Keepalived切换的原理是什么、健康检查脚本怎么设计、以及线上那些坑怎么排查一次讲清楚。适合谁看如果你想给现有的Nginx/HAProxy/LVS入口加一个不复杂但可靠的冗余机制或者你正在被“VIP不漂移”“脑裂”“切换后业务不通”这些问题折磨这篇应该能帮你省不少时间。1. 负载均衡节点的高可用设计从单点走向主备1.1 一个入口节点就是一条独木桥先还原一个真实场景。早期我维护过一个公司官网的集群架构不复杂两台机器跑后端业务一台Nginx当统一入口。当时觉得后端都做了双机稳定性已经很好了。结果某天下午网站突然打不开登录服务器一看Nginx进程正常、后端正常、磁盘正常最后发现是当入口那台机器的网卡物理松动链路层直接断了。这件事让我印象很深负载均衡器本身不产生业务数据但它是一个汇聚点所有流量都从它身上过。它挂了后端再健康也没有意义。更麻烦的是很多人会下意识把负载均衡和高可用混在一起觉得“我有两台Nginx不也是高可用吗”——两台Nginx如果没有一套联动机制入口IP还是写死在其中一台上的那另一台就只是个摆设。所以做高可用要的不是“有备用机器”而是“备用机器能自动接管流量入口身份”。这个身份在IP层体现为一个虚拟IPVIP始终在某一台节点上当主节点异常VIP自动迁移到备节点。谁占用VIP谁就是当前的负载均衡入口。1.2 做入口冗余的几种思路为什么选Keepalived给入口做冗余常见的有几条路DNS轮询或DNS切换把同一个域名解析到多个IP客户端随机访问。问题在于DNS缓存不可控切换生效要等TTL很多客户端还会重试旧IP故障感知很慢。硬件负载均衡设备做主备F5等设备自带HA能力稳定但贵不是每个团队都能接受。云厂商SLB/ALB托管负载均衡在云上很省心但如果是自建机房、混合云或者业务有私有化交付需求用不上。软件层VIP漂移Keepalived就是这类方案的典型代表。Keepalived之所以在服务器软件方案里口碑不错是因为它把“VIP漂移”和“健康检查”整合在了一套配置里。你不需要额外写脚本去调网卡IP它自己通过VRRP协议和对端通信发现主节点失联后自动把VIP绑定到备节点上同时发送免费ARP告诉交换机“这个IP对应的MAC换了”。这一切对TCP连接层面的业务来说基本是秒级的。网络层还有个“等开销负载均衡”的概念也就是ECMPEqual-Cost Multipath让路由器把流量按等价路径同时分担到多个入口上。这个思路也很优秀但它依赖三层设备支持适合网络团队直接管理路由器的场景。对大多数业务团队来说在Linux服务器上把Keepalived和现有负载均衡软件结合起来是投入产出比最高的一条路不改变现有数据转发路径只增加一个VIP和一个冗余节点就能把入口单点问题解决掉。2. Keepalived切换原理为什么能在几秒内完成VIP漂移2.1 VRRP多个节点共用一个虚拟门牌号Keepalived最核心的部分是VRRP协议全称是Virtual Router Redundancy Protocol虚拟路由冗余协议。它最初是给路由器做冗余用的思路很简单一组路由器共享一个虚拟IP作为网关地址组内选出一个Master处理流量其余是BackupMaster定期向Backup发送通告报文。如果Backup一段时间收不到Master的通告就会认为Master挂了于是选举新的Master。这就像写字楼门口有一个“值班岗亭”的岗位编号甲和乙两名保安轮值。甲在岗时每小时用对讲机喊一次“我在”乙听到就继续待命一旦乙超过一定时间没听到甲的声音就直接顶上去站在岗亭里对外来说门牌号和服务窗口完全没变。在Keepalived里报文的协议号是112默认目的地址是组播224.0.0.18。同一个VRRP组由virtual_router_id标识同一网段里只有相同ID的节点才会互相竞争同一个VIP。每台参与节点有自己独立的优先级Keepalived通过比较优先级决定谁是Master。理解这一点对排查问题特别重要很多人以为配置里写了state MASTER那台机器就永远是Master。实际不是state只是初始状态提示真正决定身份的是priority。几台节点启动后互相收到通告自动选出优先级最高的那个当Master。所以哪怕你把备用节点也写成state MASTER只要它priority低它也不会抢主。2.2 优先级、通告间隔与抢占行为VRRP的两个关键参数是priority和advert_int。priority范围是1到254值越大越容易被选为Master。主备设计里主节点priority通常设150备节点设100中间留出足够差值避免因为一个脚本权重调整导致角色频繁翻转。advert_int是Master向外发送通告的间隔单位是秒。默认1秒Backup一般要连续3个通告周期没收到Master报文才会触发切换。这就是为什么很多人测切换时间发现并不是立刻发生而是大致在3秒左右。想加快切换可以把advert_int调整为0.3秒甚至0.2秒但代价是VRRP报文发送更频繁对CPU和网络有一定开销而且网络抖动时更容易误判。我的习惯是先保持默认1秒等业务确定“多少秒断流可接受”后再调整。还有一个重要概念叫抢占。默认情况下如果一台较高优先级的Backup上线或者Master恢复后回归它会抢占VIP。这个行为对很多有长连接的业务不太友好——负载均衡节点重启或网络抖动恢复后VIP忽然从备切回主连接会断一片。于是Keepalived提供了nopreempt模式字面意思是不抢占Master故障后Backup升为Master等原Master恢复了它也只是作为Backup待着VIP继续留在接管节点上。我在生产环境里对Nginx这种入口服务倾向使用nopreempt。哪怕两台机器性能有细微差异只要备机能扛住流量就不必切回去减少一次无谓的连接抖动。注意nopreempt必须在参与互备的节点两侧都配置如果只配了一边效果可能会不符合预期这是新手容易踩的细节。2.3 健康检查决定的是“切换质量”单看VRRPKeepalived能做到“对方主机挂了就切换”但这是不够的。更常见的情况是负载均衡进程还在但后端接口响应超时了或者Nginx进程变成僵尸状态端口已经不监听了。这种时候对面的Keepalived收不到任何异常信号因为VRRP报文是正常的VIP自然也不会漂移。所以Keepalived提供了vrrp_script机制让你自定义健康检查脚本周期性去探测本机负载均衡服务的真实可用性。脚本返回0表示正常返回非0表示异常。异常后Keepalived会根据你配置的weight值调整本节点优先级让当前节点不再适合持有VIP从而触发切换。我习惯把健康检查设计成“两层探测”第一层检查本机负载均衡进程是否存活第二层检查它背后的真实服务是否可用。比如Nginx入口后面有两个后端端口脚本里就curl一下这两个端口的健康地址。进程在、端口通、后端返回2xx/3xx才算节点健康。只有进程在但后端已废流量进来也是打不出去的不如痛痛快快把VIP让给备机。3. 直接抄作业Nginx双机与Keepalived完整配置流程3.1 架构规划与节点角色下面这份配置以最常见的Nginx负载均衡入口为例。两台机器部署NginxNginx反代到后端Web集群外部统一通过VIP访问Nginx入口。VIP在主节点上时由主节点提供服务主节点故障则VIP漂移到备节点。节点初始角色物理IPkeepalived优先级说明node1MASTER192.168.1.10150正常状态下持有VIPnode2BACKUP192.168.1.11100node1异常时接管VIPVIP虚拟IP192.168.1.100-对客户端暴露的入口地址这个规划里优先级差值50同时后面配置的健康检查脚本weight设为-60这样当node1健康检查失败时node1的优先级会从150降到90低于node2的100于是VIP漂移到node2。这里的关键账是weight绝对值必须大于两个节点之间的优先级差值否则主节点脚本失败后依然比备机优先级高VIP根本不会动。3.2 安装与环境准备操作系统以CentOS/RHEL系为例安装Keepalived很简单yum install -y keepalived nginx systemctl enable --now keepalivedUbuntu/Debian系改成apt install keepalived nginx。安装完成后先别急着配看一眼SELinux状态getenforce如果结果是Enforcing要么把SELinux设为Permissive要么给keepalived放行相关域。很多人在测试环境一切正常上了生产环境却发现问题查了一圈是SELinux拦截了VRRP报文或者脚本执行。我一般建议在测试阶段先临时setenforce 0确认方案有效后再单独放行规则。VRRP依赖112协议所以要检查节点防火墙。如果使用firewalldfirewall-cmd --permanent --add-protocolvrrp firewall-cmd --reload如果用的是单播模式那么防火墙只需放行对端指定IP的VRRP协议即可比放行组播路径更可控后面我会展开说。3.3 主节点keepalived.conf逐段拆解Keepalived的主配置文件在/etc/keepalived/keepalived.conf。直接给一份能用的主节点配置global_defs { router_id LVS_NGINX_01 enable_script_security } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 3 weight -60 fall 2 rise 1 } vrrp_instance VI_WEB { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 nopreempt unicast_src_ip 192.168.1.10 unicast_peer { 192.168.1.11 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } track_script { check_nginx } notify_master /etc/keepalived/notify.sh master notify_backup /etc/keepalived/notify.sh backup notify_fault /etc/keepalived/notify.sh fault }逐个解释关键参数的含义。global_defs里的router_id只是标识本机身份在邮件告警和日志里能看到不强求格式。enable_script_security是让脚本在受控环境下执行如果脚本有外部输入需要注意安全性。vrrp_script定义健康检查脚本相关参数script指向检查脚本interval表示每2秒执行一次timeout表示单次执行最多等3秒weight-60表示脚本失败时把当前节点优先级减60fall2表示连续失败2次才认为故障避免偶发抖动误切换rise1表示脚本成功1次就恢复健康状态。vrrp_instance是VRRP实例的关键配置段。interface必须填承载VIP的网卡名虚拟Router号51只在同一二层网络内有效不同业务组不要冲突。priority前面已经解释advert_int设为1秒。nopreempt保证发生故障切换后原主恢复时不抢回VIP。这里我推荐使用unicast_src_ip和unicast_peer这种单播模式而不是默认组播。原因很简单很多云主机和虚拟化网络默认屏蔽组播或者对组播报文处理非常慢导致两个Keepalived节点收不到对方通告出现“双主脑裂”。单播模式下节点直接把VRRP报文发给对端IP只要IP层通通告就能送达。这个改动非常小但能省掉很多线上的玄学问题。virtual_ipaddress里写上VIP地址并且指定绑定的网卡和别名。label eth0:1的意思是VIP以eth0的子接口形式存在便于用ip命令观察。track_script把上面定义的检查脚本挂到VRRP实例上。这样脚本异常时本节点优先级才会被扣减。notify_master/notify_backup/notify_fault三个钩子非常有用分别在节点角色切换时触发。你可以在脚本里记录切换时间、清理iptables状态、通知监控系统、甚至联动外部设备。这是让“切换动作”更加可观测的关键。3.4 备节点配置差异备节点的配置和主节点基本相同只有几处不一样global_defs { router_id LVS_NGINX_02 enable_script_security } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 3 weight -60 fall 2 rise 1 } vrrp_instance VI_WEB { state BACKUP interface eth0 virtual_router_id 51 priority 100 advert_int 1 nopreempt unicast_src_ip 192.168.1.11 unicast_peer { 192.168.1.10 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } track_script { check_nginx } notify_master /etc/keepalived/notify.sh master notify_backup /etc/keepalived/notify.sh backup notify_fault /etc/keepalived/notify.sh fault }state换成BACKUPpriority换成100unicast_src_ip和unicast_peer互相对调。其余参数保持一致。这里注意一个细节备节点的healthy script weight虽然也是-60但因为它本来就是BACKUP状态脚本失败只会让它的优先级更低并不会影响Master的持有关系而如果Master也故障它会接管VIP。所以两侧配置对称是合理的不要随意省略。3.5 健康检查脚本整套方案里最容易被低估的部分创建/etc/keepalived/check_nginx.sh#!/bin/bash # 检查Nginx主进程是否存活 if ! pgrep -x nginx /dev/null 21; then exit 1 fi # 再检查后端真实服务这里假设Nginx upstream指向本机8080和8081端口 for port in 8080 8081; do if ! curl -s -o /dev/null -m 3 http://127.0.0.1:${port}/health; then exit 1 fi done exit 0给脚本加执行权限chmod x /etc/keepalived/check_nginx.sh脚本里第一个检查用pgrep -x nginx。实际环境中Nginx master进程名确实就是nginx不会有额外参数所以这个写法没问题。更稳妥的办法是用pidof nginx因为pidof对进程名匹配更宽松些。要根据自己系统的命令情况来选。第二个检查是curl后端健康地址。这里有三个容易犯的约定一是要用-m指定超时时间如果后端接口普遍在3s内响应这个值就设4到5秒别设成1秒否则偶发慢请求会把节点打成不健康二是健康地址不要返回502/504否则Nginx层面已经失败说明入口后端整体异常这种情况下让VIP漂移反而是正确的三是这段curl逻辑只是示例你要根据自己后端业务接口去调整不一定非要本机端口也可以是Nginx反代的下游具体IP但从方案安全角度我建议优先检查Nginx能访问到后端的路径。如果Nginx是systemd管理的也可以换成if ! systemctl is-active --quiet nginx; then exit 1 fi但注意systemctl命令返回语义在不同发行版略有差异脚本里要测试过再上生产。3.6 启动、抓包与故障模拟验证两部分节点都配好之后先做语法检查keepalived -t -f /etc/keepalived/keepalived.conf输出中没有报错就可以启动服务systemctl restart keepalived验证VIP是否落在主节点上ip -br addr show eth0主节点上应该能看到192.168.1.100这个地址。再确认VRRP报文在跑tcpdump -ni eth0 proto 112主节点上应该能周期性看到VRRP通告备节点上也能收到。接着做故障模拟在主节点停掉Nginx或者直接停掉Keepalivedsystemctl stop keepalived然后观察备节点watch -n 1 ip -br addr show eth0正常情况下备节点会在几秒内绑定192.168.1.100同时日志/var/log/messages或者journalctl -u keepalived里会看到切换到MASTER的记录。再从外部客户端访问VIP业务仍能正常打开说明高可用生效了。我在实测环境里使用默认advert_int1时从主节点故障到备节点绑定VIP大致在3秒左右。如果业务要求更快可以调advert_int0.5但切记两边保持一致并且确认网络足够稳定。4. 常见问题排查VIP不漂移、脑裂与ARP三者怎么破4.1 VIP迟迟不漂移的排查清单这是Keepalived问得最多的问题主节点都宕机了VIP怎么还在主节点上按下面顺序排查备节点上的Keepalived是否正常运行systemctl status keepalived。两台节点的virtual_router_id是否一致不一致就不是一个组自然没法接管。priority差值是否足够如果备机优先级写反了等于备机变成大哥主故障后备机可能不会理你。防火墙是否拦截了VRRP在备机上tcpdump -ni eth0 proto 112如果收不到组播或单播报文转排查安全组和iptables/firewalld。健康检查脚本是否一直在失败如果脚本配置了weight而主备两机的权级计算之后反而让备机更不想接管VIP就不会漂移。最直接的方法是看日志。CentOS系列看/var/log/messagesUbuntu系看journalctl -u keepalived。Keepalived日志里出现“Entering BACKUP STATE”或“Entering MASTER STATE”是角色转换的标志。一直不切换时注意有没有反复出现“script not found”或者“Unknown interface”这类提示很多时候是配置路径或网卡名写错了。4.2 脑裂与双主组播通不通是关键脑裂指的是两台节点同时认为自己是Master都绑定了VIP。出现这个情况时同一个VIP在网络上出现两个MAC地址来源后面的交换机MAC表会不停抖动客户端流量也会时通时断。根本原因通常是两节点之间的VRRP报文不通可能是防火墙拦截、组播在二层被限制、或者网卡层面没有开启multicast。排查思路是tcpdump -ni eth0 dst host 224.0.0.18如果两台机器完全收不到对方的VRRP包优先检查防火墙。还有一个很隐蔽的原因是两台节点之间走的是跨三层网络组播报文没被路由转发但这种部署本来就不合理VRRP要求节点在同一个二层网络内。我的经验是直接改用单播模式。只要在vrrp_instance里配置了unicast_src_ip和unicast_peerKeepalived就不依赖组播了。这个改动在云环境里尤其有效。如果你发现两节点一直处于双主状态但物理链路没问题十有八九是组播被网络设备丢弃了。4.3 切换后VIP通了但业务不通ARP问题不可忽视VIP漂移后Keepalived会自动发送免费ARP报文告诉同网段设备“192.168.1.100的MAC地址变了”。大多数情况下交换机很快会更新MAC表。但有些场景下客户端或交换机缓存没刷新流量依然发往旧主节点的MAC地址于是VIP虽然显示在备机上外部访问依然不通。解决办法不是手动清ARP缓存而是要确认Keepalived是否有权发送免费ARP以及网络里端口安全机制是否限制了MAC地址变化。有些办公网交换机开了端口安全策略允许的最大MAC数量是1VIP漂移会触发MAB限制直接把端口锁掉。这种就需要网络团队配合放行。如果是在LVS的DR模式下后端真实服务器上也会配置VIP到loopback接口并且必须开启ARP抑制echo 1 /proc/sys/net/ipv4/conf/all/arp_ignore echo 2 /proc/sys/net/ipv4/conf/all/arp_announce echo 1 /proc/sys/net/ipv4/conf/lo/arp_ignore echo 2 /proc/sys/net/ipv4/conf/lo/arp_announce否则真实服务器会以VIP的身份响应ARP请求导致客户端绕过负载均衡直接访问后端源IP策略和会话保持全部失效。这个点在做LVSKeepalived方案时非常典型我见过太多人对VIP广播问题毫无准备结果一挂LVS后端流量就乱了。4.4 健康检查脚本引发的问题速查现象常见原因解决思路节点频繁切换curl探测超时设得太短后端慢请求被判定失败调大timeout或增加fall3/fall4脚本执行报错脚本没有x权限或命令路径不在PATH里chmod x脚本内使用绝对路径主节点脚本失败后VIP不漂移weight绝对值小于主备优先级差值重新计算priority与weight保证扣减后主节点低于备节点rise/fall逻辑混乱对失联和恢复次数的理解不一致记住fall是连续失败判定次数rise是连续成功恢复次数即可切换后业务短时间内大量502备机Nginx配置和后端不完全一致或upstream没有预热保证两台Nginx配置完全同步必要时在notify_master里预热后端连接健康检查脚本是整个高可用方案的实际决策者比VRRP本身更容易出问题。我的建议是脚本里不要写太多复杂的业务逻辑职责就两个本机负载均衡进程是否活跃以及后端最低可用服务是否存活。把日志输出和决策判断分开脚本只负责返回0和非0Keepalived负责做切换不要在脚本里自己kill进程或者重启Keepalived。有一个我踩过的坑值得单独说脚本里如果用到pgrep、curl、systemctl等命令而Keepalived执行脚本时的PATH不包含/usr/local/bin那么明明手动执行脚本是好的Keepalived一调用就报command not found。所以脚本开头的PATH环境变量需要显式声明或者所有外部命令一律写绝对路径。5. 经验补遗升级、监控与切换动作设计文章写到这里核心内容其实已经讲完了。最后分享几个我在实际运维中沉淀下来的习惯。第一个是关于Keepalived版本差异。Keepalived 2.x之后很多老的配置写法仍然兼容但auth_type认证在部分版本中默认行为有变化如果你在配置里写了authentication { auth_type PASS auth_pass 1234 }要确保两台节点配置一致否则验证会失败。我现在的做法是尽量不配置认证段依靠网络ACL和安全组来控制VRRP报文的访问范围省去两边密钥不同步的麻烦。第二个是监控VIP而不是监控Keepalived进程。很多监控系统只盯着node_exporter或者系统服务的存活状态Keepalived进程还在但VIP已经因为某种原因消失了这种情况下业务已经断了监控却显示正常。我一般会加一个探活在每台节点上检查eth0:1是否绑定了VIP绑定在哪台节点就认为哪台是主再额外从探测节点用VIP去请求健康地址。这样“谁是主”“服务通不通”两个维度都能被监控覆盖。第三个是切换动作要做得有仪式感。所谓仪式感是指在notify_master和notify_backup脚本里别只写个echo日志还要做实际有用的动作。比如切换成主后清掉本机Nginx缓存、重新加载证书、预热upstream连接池切换成备份后主动断开部分外部连接释放资源。notify脚本里还可以把角色变化推送到内部IM群或者告警平台让每次故障切换都有记录。这不影响切换成功率但会直接影响故障响应效率。最后再回复到开头那个场景。我给那套官网集群加了Keepalived之后又一次遇到入口机器网卡故障流量自动切到了备机整个过程业务侧几乎没有感知。后来我发现很多团队不是不愿意做高可用而是被那些“高可用方案要引入一大堆组件”的说法吓住了。Keepalived这个工具足够轻量一台机器上部署一个服务、写一个配置文件、放一个检查脚本就行。关键是先把主备切换的逻辑想清楚把健康检查脚本做扎实再配合监控和通知机制入口这一层的可靠性就能上一个台阶。这套思路希望你也能在自己的环境里用上。