keepalived双机热备这个名字在运维圈子里几乎和Nginx反向代理一样常见但我发现真正把它配置得明明白白的人并不算多。多数情况是照着网上的教程复制粘贴一份配置VIP能漂移就算成功至于为什么漂、怎么漂、漂完怎么回来遇到故障时能不能靠得住很多人心里其实没底。这篇东西我不想写成一份简单的配置文件罗列教程。我会从一次让我印象深刻的凌晨故障讲起把keepalived双机热备的适用场景、VRRP协议的切换原理、一份能上生产的配置怎么写、切换实测数据是多少、以及线上环境里那些不踩不知道的坑完整地过一遍。适合正在搭主备架构的运维新人也适合已经用keepalived但想搞清楚底层逻辑、想排查疑难问题的同行参考。1. 双机热备到底在解决什么问题以及为什么最终选了keepalived1.1 单点故障的真实代价先说一次真实经历。某天凌晨两点多某业务系统的数据库主节点物理机内存报错系统直接重启。这台机器同时承担着前端接入的虚拟IP也就是说所有外部请求都打到这台机器上。服务一断线上告警瞬间刷屏等我爬起来连上跳板机的时候业务已经中断了近二十分钟。恢复的过程倒是简单——机器重启完成后VIP自动回来服务随之恢复。但那次故障带来的直接后果是夜间核心时段业务完全不可用运维团队第二天写复盘报告写到手软。这个例子非常典型。它是典型的单点故障而单点故障的代价并不是机器坏了这么简单而是机器坏了之后所有依赖它的东西全部瘫痪。你可以在架构层面把数据库做成主从把应用层做成多实例但如果没有一种机制来接管对外服务的入口IP那么前面所有的冗余都白做。入口挂了后面再结实也没用。这就是双机热备的核心价值让两台机器共享一个虚拟IP平时一台负责干活另一台状态同步待命一旦主节点异常备节点迅速接管这个IP并继续提供服务整个切换过程对外部客户端尽可能透明。1.2 主备方案对比从DNS轮询到VRRP解决入口高可用这件事业界有不少思路。简单粗暴的做法是DNS轮询把同一个域名解析到多个IP上让客户端轮流访问不同机器。这个方法的好处是无须额外组件但缺点也很致命DNS缓存导致切换不可控某个机器宕机后已经被解析到该IP的客户端会持续访问失败直到缓存过期。session一致性也是个麻烦A请求打到第一台机器B请求打到第二台机器应用层的会话状态很容易乱套。再往后是负载均衡器方案比如在应用前面加一层LVS或Nginx来分发流量。这个方案能解决问题但它本身又成了新的单点。为了消除这个单点就需要在两台负载均衡器之间做热备——而这正是keepalived的典型用武之地。keepalived基于VRRP协议比DNS轮询的优势在于它有一个明确的主和备主节点通过周期性通告保证自己的存活备节点一旦在指定时间内收不到通告立即接替主节点的虚拟IP。整个过程是自动的而且因为虚拟IP始终在同一个网段内对外表现就是IP不变对客户端完全透明。1.3 keepalived和heartbeat/pacemaker的取舍说到热备方案还会有人提到heartbeat和pacemaker。早期heartbeat配合DRBD做双机也很流行但相比之下keepalived有它独特的适配场景。heartbeat和pacemaker更侧重的是资源级别的管理比如文件系统、服务进程、IP资源的统一控制适合做复杂的集群资源编排。keepalived则轻量得多它核心解决的是网络层IP的接管问题。如果你只是想给一组业务提供一个自动漂移的VIPkeepalived是首选。从实践角度说keepalived胜在三个方面配置简单核心就一个配置文件依赖少安装完基本就能跑对业务无侵入它只管VIP不关心你后面跑的是什么服务。我个人的经验是如果需求复杂度低、就是想要一个可靠的VIP漂移不要一开始就上pacemaker那套重型方案那是给自己找麻烦。架构设计永远是越简单越可靠keepalived这种够用的轻量方案在绝大多数主备场景下都是最优解。2. 核心机制拆解VRRP协议、虚拟IP漂移与状态通告2.1 VRRP协议的工作原理keepalived的底层核心是VRRPVirtual Router Redundancy Protocol虚拟路由冗余协议。这个协议的思路其实特别朴素可以类比成一个小组里有组长和副组长组长定期喊一嗓子我还在组员们听到就安心待命如果大家连续好几次听不到组长喊话副组长就站出来顶替位置。具体到协议层面VRRP将一组路由器在keepalived场景里就是两台服务器组织成一个虚拟路由器组对外呈现一个虚拟IP。组内的每台设备有不同优先级优先级最高的成为Master其余作为Backup。Master会周期性发送VRRP通告报文Backup收到后便知道Master还活着。当Backup连续一段时间收不到通告就会认为Master挂了然后竞选成为新的Master。这里有两个关键时间参数必须搞清楚。一个是通告间隔advert_int用于设定Master每隔多久发送一次通告默认一般是1秒。另一个是Master失效时间通常是通告间隔乘以3加上一些偏移。这意味着从Master真正宕机到Backup开始接管至少会有几秒的空窗期这是VRRP机制固有的、无法避免的延迟。理解这个机制后你对高可用的期望就会更理性。所谓热备并不等于零切换延迟而是把延迟从人工介入的几十分钟压缩到协议自动发现的几秒钟。在大多数业务场景下这个级别的恢复速度已经足够。如果你的业务连一两秒中断都无法接受那不是keepalived的问题而是需要从架构层面做多活改造。2.2 VIP漂移的具体过程VIP漂移是keepalived最核心的功能也是大家最关心的部分。我画不出图就用文字把整个过程推演一遍这样你心里会有个清晰的时间线。假设主节点优先级为150备节点优先级为100虚拟IP为192.168.10.100通告间隔为1秒。正常状态下主节点持续发送VRRP通告备节点收到后确认自己还是BackupVIP绑定在主节点的网卡上。外部访问VIP的流量全部进入主节点。当主节点突然宕机过程是这样展开的主节点停止发送通告备节点等待Master失效时间这个时间大约是3秒。3秒过后备节点状态变更为Master开始在自己的网卡上绑定虚拟IP同时向交换机发送免费ARPGratuitous ARP报文通知局域网内的交换机和路由器这个IP现在在我这里请把发往该IP的报文送到我的MAC地址。交换机更新MAC地址表新的流量被引导到备节点VIP漂移完成。这个过程里最容易忽略的细节是免费ARP。即使备节点把IP绑到了网卡上如果交换机的MAC地址表没有刷新流量依然会往旧地址送业务还是不通的。VRRP协议的设计中专门包含了这一步同时keepalived在状态切换时也会主动发送免费ARP帮助网络设备快速更新。这也是为什么配置里有一项garp_master_delay相关的参数它与免费ARP的发送时机和频率有关。2.3 抢占模式与非抢占模式的区别配置keepalived时有一个经常被误解的选项nopreempt也就是非抢占模式。很多人弄不清它到底控制什么行为。默认情况下keepalived的工作模式是抢占式的。假设主节点某次故障后恢复备节点正在充当Master此时主节点因为有更高的优先级会重新发送通告并夺回VIP。这听起来合理但在实际业务中可能导致一个问题主节点频繁抖动、又频繁恢复的情况下VIP会在两台机器之间来回切换客户端连接反复断掉比一直用备节点还糟糕。非抢占模式正好解决这个问题。配置了nopreempt之后即使原来的主节点恢复正常它也不会主动抢回VIP而是继续以Backup身份待命直到当前Master再次故障。这样可以显著减少不必要的切换对保持连接稳定性很有帮助。我的建议是如果你的两台服务器性能完全对等希望减少切换次数就开启非抢占模式如果主备之间存在明确的性能差异比如主节点配置明显更强、且必须承担主要流量那就保持默认的抢占模式。另外还要注意一个细节在keepalived中设置非抢占模式时两台机器的role都必须设置为BACKUP且其中一台的优先级更高否则配置无法正常生效这一点很多人第一次配置时都会踩到。3. 环境准备从网络规划到软件安装3.1 网络拓扑规划动手配置之前先把网络环境想清楚。keepalived对网络有硬性要求两台机器必须在同一个二层广播域内也就是通常说的同一网段因为VRRP协议依赖组播报文通信。跨网段跑VRRP是个很麻烦的事情尽量不要自找麻烦。以一个典型的主备架构为例我习惯这样规划主节点网卡eth0IP为192.168.10.11管理IP不动备节点网卡eth0IP为192.168.10.12管理IP不动虚拟IPVIP192.168.10.100不预先配置在任意一台机器上由keepalived运行时动态绑定对外网关192.168.10.1也就是交换机上的三层网关地址规划VIP时有一些讲究。VIP不能与管理IP冲突这是废话但实际中确实有人把VIP设成网关地址或者另一台服务器的管理IP结果一启动keepalived整个网络就乱了。另外VIP建议使用网段内较为固定的地址段便于防火墙规则和监控识别。如果服务器有多个网卡只在一张网卡上配置keepalived实例不要在多张网卡上都绑定VIP否则ARP通告会互相干扰。多网卡场景下需要为每个实例指定interface参数并且每个实例使用不同的VRID这块后面细说。3.2 安装keepalived安装过程没什么难度但两个主流Linux发行版的安装方式稍有区别我分开写一下。先看RedHat系CentOS或Rocky Linuxyum install -y keepalivedUbuntu和Debian系apt update apt install -y keepalived装完之后先确认版本不同版本在配置语法上略有差异尤其是脚本相关的配置项keepalived --version这里有个容易被忽视的点。keepalived通过VRRP协议收发报文时如果本机开启了防火墙且没有放行VRRP组播包那么备节点永远收不到主节点的通告会出现两台机器都是Master、同时绑定一个VIP的问题也就是脑裂。关于防火墙后面章节我会专门讲这里先记住一个原则装完keepalived先别急着配业务先把防火墙对VRRP的放行规则处理好。另外如果你的机器用的是NetworkManager服务建议在配置keepalived之前先确认NetworkManager不会篡改网卡配置。实际遇到过几次这样的情况keepalived把VIP绑定到网卡后NetworkManager自动刷新了接口配置把VIP冲掉了导致当时误以为软件不稳定查了很久才发现是网管服务的锅。3.3 内核参数与日志配置keepalived运行本身依赖的内核参数不多但有一个需要关注反向路径过滤即rp_filter。Linux内核默认启用了rp_filter机制用于防止IP欺骗它会检查收到的数据包源地址是否可以通过本机的路由表回包。在VRRP的Master和Backup同网段的场景下大多数情况没问题但在某些多网卡或多路由的复杂网络环境中rp_filter可能导致VRRP通告包被丢弃造成备节点误判Master故障。如果确认网络拓扑简单可靠可以在配置网卡时将该参数设为宽松模式。临时生效的命令如下sysctl -w net.ipv4.conf.all.rp_filter0永久生效需要写入sysctl配置文件。要注意的是我并不是推荐所有人关掉这个安全机制只是提醒你如果遇到两台机器都认为自己是Master或者说备节点收不到主节点通告这类问题rp_filter是排查点之一。日志方面keepalived默认会把状态切换信息写入系统日志。在RedHat系上你可以这样查看journalctl -u keepalived -f tail -f /var/log/messagesUbuntu上常见的是journalctl -u keepalived -f每次切换都会记录下从Backup变Master或从Master变Backup的状态变化信息以及优先级变化的原因。养成看日志的习惯很重要排查问题时第一条线索永远在日志里。4. 配置实战手写一套可上生产的主备配置4.1 主节点配置逐行解析这里我带大家完整写一份主节点配置并逐段解释含义。配置在/etc/keepalived/keepalived.conf先看全局定义global_defs { router_id LVS_MASTER vrrp_skip_check_adv_addr vrrp_strict }router_id只是一个标识可以任意取但不能重复建议根据角色命名主节点叫LVS_MASTER备节点叫LVS_BACKUP。vrrp_strict这个选项比较关键它会让keepalived严格执行VRRP协议规范包括检查报文合法性。如果不开这个选项某些网络环境下可能会有兼容性问题但开了之后对防火墙上放行的要求也更严格。我的经验是生产环境建议开启但务必确保防火墙已经放行VRRP协议。然后是VRRP实例的定义vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.10.100/24 dev eth0 label eth0:1 } }逐项解释一下。state设置为MASTER但这个值主要影响启动初期的状态即使配置成BACKUP只要优先级最高启动后也会成为Master。interface eth0指定VIP绑定的物理网卡。virtual_router_id是VRID取值范围0到255同一组热备的两台机器必须一致用于标识同一个虚拟路由器组。如果同一台机器上有多个keepalived实例每个实例的VRID必须不同。priority是优先级主节点150备节点100数字越大越优先。advert_int是VRRP通告间隔单位秒1是常见设置。如果业务要求更快的切换可以调整为更小的值比如0.5但请注意过于频繁的通告在拥塞链路上可能反而导致误判这一点后面讲。authentication是认证信息同一组内的两台机器必须完全一致。早期VRRP协议里这算一道简单的访问控制但它并不是强安全机制只是防止无关设备乱发通告。生产环境建议设置不要留空。virtual_ipaddress就是重点了这里定义VIP。我通常会在VIP后面加上dev eth0 label eth0:1显式指定绑定到eth0并且使用eth0:1这个别名接口。这样做的好处是一眼就能看出VIP是虚拟的、独立的。如果多个VIP可以在同一段落里一行一个。加上监控脚本后的完整配置如下vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 2 fall 2 rise 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.10.100/24 dev eth0 label eth0:1 } track_script { check_nginx } }track_script的作用是将脚本的检查结果关联到VRRP实例上如果脚本检测到业务异常keepalived会降低本机的优先级从而触发切换。这里我预留了一个check_nginx.sh脚本稍后专门展开讲。4.2 备节点配置差异备节点的配置和主节点只有两个关键差异state改成了BACKUPpriority改成了100。其余包括VRID、认证信息、VIP、script配置必须和主节点保持一致这一点无比重要。global_defs { router_id LVS_BACKUP vrrp_skip_check_adv_addr vrrp_strict } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 2 fall 2 rise 2 } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.10.100/24 dev eth0 label eth0:1 } track_script { check_nginx } }这里要特别提醒一个配置误区。很多人以为主节点和备节点的virtual_router_id必须不同这其实是错的。VRID的意义恰恰是让两台机器标识自己是同一个虚拟路由器必须保持一致。同理authentication_conf里的auth_pass在两端也必须一致否则备节点无法验证主节点发来的VRRP报文会直接把合法通告丢弃掉表现为备台账一直收不到消息、最终两台都绑上VIP的脑裂状态。如果你希望启用非抢占模式两台机器的state都应设置为BACKUP然后在主节点优先级更高的基础上两台机器的实例里都加上nopreempt这一行。注意如果一台是MASTER另一台是BACKUP然后只在这个实例里写了nopreempt那是不会生效的。这类配置习惯问题往往要等现场出了问题回来翻文档才会真正记住。4.3 不要忽略的脚本和日志配置track_script指向的脚本直接决定了keepalived对业务的感知能力。我见过不少配置里写了track_script但脚本本身写得很随意导致keepalived完全无法检测到业务真的已经不可用的情况。一个相对可靠的业务检查脚本应该包含两部分进程是否存在以及服务端口是否真正响应。以Nginx为例我常用的脚本如下#!/bin/bash check_count0 if ! pgrep -f nginx /dev/null 21; then echo nginx process not found exit 1 fi if command -v curl /dev/null 21; then code$(curl -I -s -o /dev/null -w %{http_code} http://127.0.0.1/ --connect-timeout 2 --max-time 3 2/dev/null) if [ $code ! 200 ]; then echo nginx response abnormal, http code: $code exit 1 fi fi echo check ok exit 0注意几个细节。首先是脚本对curl超时设置了上限避免脚本本身因为服务无响应而挂死。其次是当curl不可用时直接放行只以进程作为判断依据这在最小化安装的机器上很实用。最后是脚本必须具备执行权限并且建议在shellcheck里过一遍一个语法错误的脚本会让track_script完全失效。还有一个容易被忽略的点keepalived自身对脚本执行超时的处理。如果脚本执行时间过长会直接影响VRRP的通告节奏极端情况下甚至导致备node认为主节点故障而误切换。因此脚本里命令都加了超时这是经验之谈。配置检查还有个快速验证方法启动keepalived之前先跑一遍keepalived -t -f /etc/keepalived/keepalived.conf这个命令会做语法检查。如果配置文件有问题它会直接报错并给出行号这比启动后在日志里扒错误信息高效得多。每次改动配置后我都先跑一遍这个命令确认无误后再重启服务。5. 故障切换实测从kill进程到模拟服务器宕机5.1 测试场景一主节点keepalived进程被kill配置搭好之后真正重要的是验证切换行为这部分我建议分三步做测试。第一步只kill掉主节点的keepalived进程用来模拟软件层面的故障。在主节点上执行kill -9 $(cat /var/run/keepalived.pid)或者直接pkill -9 keepalived接下来在备节点上持续观察系统日志journalctl -f正常情况下你会看到类似下面的日志输出(VI_1): entering BACKUP STATE (initial) (VI_1): received an invalid ip address ... (VI_1): Master shutdown received, entering MASTER STATE (VI_1): Entering MASTER STATE日志出现后立刻在备节点上确认VIP是否已经绑定ip addr show eth0你会看到eth0:1这个接口上出现192.168.10.100/24地址。此时用另外一台测试机器去ping这个VIP应该能通。从kill进程到VIP在备节点上生效整个时间取决于Master失效时间我实测在1秒通告间隔默认参数下大约为3秒。这里面有个细节值得关注。即使Main的keepalived被强杀但业务进程本身还活着备节点接管VIP后流量会先进入备节点。如果备节点上没有部署同样的业务服务就会出现VIP通了但业务仍然不可用的情况。这就是为什么热备不是单独keepalived的事两台机器必须部署相同的业务数据层面也要保持同步。架构上必须整体考虑不能只盯着VIP。5.2 测试场景二主节点整机断电第二步测试更有说服力直接给主节点断电模拟物理机宕机。这个场景和kill进程不同kill进程时主节点可能还会发出最后的通告但断电时一切中断得猝不及防。断电后备节点同样要等Master失效时间然后在3到4秒后进入MASTER状态并绑定VIP。实测数据和kill进程场景基本一致因为机制相同。这个测试会暴露一个之前提到的问题交换机MAC地址表的刷新速度。虽然keepalived会发送免费ARP但在某些交换机上如果端口状态变化处理较慢或者有安全策略限制ARP报文切换后的一小段时间内流量可能仍然被转发到已宕机的主节点端口导致业务中断时间比VIP生效时间更长。我的经验是在机房里有条件的话尽量使用支持快速收敛的交换机。如果网络设备较老就要做好切换后可能还有几秒流量黑洞的心理准备。另外断电测试时要特别注意备节点上的业务服务是否真的能扛住流量。一次断电测试某业务依赖的本地缓存没有预热备节点接管后业务虽然能连通但响应时间飙升直到缓存逐渐重建才恢复。这提醒我们备节点不只是装了同一个软件就算数它的状态、数据、缓存都要提前做好准备这也是演练存在的意义。5.3 测试场景三恢复后的回切机制第三步重新把主节点开机观察回切行为。如果配置的是默认抢占模式主节点启动并加入VRRP组后由于优先级150高于备节点的100它会在下一轮通告中重新夺回VIP。恢复过程通常会有如下表现(VI_1): entering MASTER STATE (VI_1): sending gratuitous ARP on eth0此时VIP会重新绑定回主节点备节点退回BACKUP状态。这个回切过程同样伴有免费ARP以更新网络设备的MAC表。我的建议是在大多数业务场景下开启非抢占模式会更好。很多线上事故的复盘都指向同一个原因主节点因为小幅波动异常恢复后立即回切但此时应用还在慢慢热身业务反而比故障时更不稳定。开启nopreempt后一切切过去就切过去了主节点恢复了也只是静默待命不给业务增加额外干扰。至于什么时候回切最好我的策略是在业务低峰期手动执行一次优雅回切比如在凌晨两点操作切换脚本将VIP迁移回原主节点再观察确认无误。既保留了主节点的性能优势又避免了自动回切的风险。5.4 丢包率实测数据最后给一组我实测中的数据供你参考。测试环境为同一交换机下两台虚拟机千兆内网通告间隔1秒默认抢占模式压测工具在第三台机器上持续ping VIP。在正常状态下ping VIP的丢包率为0平均延迟低于1毫秒。执行kill keepalived测试时在切换瞬间出现连续丢包数了一下大约丢失3到4个包即3到4秒的中断和预期相符之后立刻恢复。实测中如果加上track_script检测业务超时导致的主动降级切换会在业务脚本超时判定后开始通常需要额外加上脚本间隔、失败次数和超时时间整体切换时间大约在5到8秒。如果把通告间隔调整为0.5秒并配合更短超时的track_script切换时间能压缩到2秒左右。但通告频率过快也有代价如果网络偶尔抖动哪怕丢一两个包备节点也可能在连续几个通告周期后误判Master故障引发不必要的切换。这就是为什么我不建议为了追求极限切换速度而无脑调小advert_int。高可用架构的终极目标是可用性而不是切换速度频繁的误切换本身就是一种不可用。6. 线上踩过的坑与排查思路6.1 脑裂问题两个节点同时持有VIP脑裂是整个双机热备架构里最经典、也最危险的问题。所谓脑裂就是主备两台机器同时把自己认定为Master同时绑定了同一个VIP后果是流量负载混乱、ARP通告互相覆盖、整个网络逻辑上出现脏状态业务表现时好时坏最严重时可能彻底不可用。我遇到的一次比较典型的脑裂场景是这样产生的。某次网络割接后主备两台机器仍然能互通管理IP但VRRP组播包被上层防火墙策略拦截了。备节点迟迟收不到Master的通告于是判定Master不可用主动晋升为Master并绑定了VIP。这时候主节点还活着也在绑着VIP两台机器同时对外宣告自己拥有这个IP交换机MAC表不断更新整个网段里的ARP流量异常升高业务间歇性中断排查起来非常痛苦。排查脑裂的第一步是登录两台机器都去执行ip addr show | grep 192.168.10.100如果两台都绑着VIP先冷静别急着kill进程或重启。正确的处理是先保留主节点不动手动在备节点上停掉keepalived服务并解绑VIP恢复单一Master状态再从网络层面查组播阻断的原因。如果反过来先把主节点停了那备节点会把VIP继续挂下去网络同样混乱。脑裂的根因通常两种VRRP通告因为防火墙或ACL被阻断或者网络拥塞导致通告丢失过多。排查时要先确认二层连通性再用tcpdump抓包看组播报文是否确实到达备节点tcpdump -i eth0 vrrp -n这个命令能在备节点上直接看到是否收到了来自Master的VRRP报文。如果抓包能看到、但keepalived状态仍然误判那就是配置问题重点检查VRID、auth_pass是否一致。如果抓包完全看不到那就是网络层问题去搞防火墙策略。6.2 防火墙导致VRRP组播不通防火墙问题是我见过最多的坑值得单独拿出来说。RedHat系服务器通常默认开着firewalldUbuntu上也有ufw如果你不主动放行VRRP协议keepalived的通告就可能被丢得干干净净。VRRP使用的协议号是112多播地址是224.0.0.18。RedHat系上放行的写法firewall-cmd --permanent --add-protocolvrrp --zonepublic firewall-cmd --reload如果使用iptables可以加这样一条规则iptables -A INPUT -p vrrp -j ACCEPT iptables -A OUTPUT -p vrrp -j ACCEPT关键是两个方向都要放行。注意VRRP是一种IP层的协议它不是TCP也不是UDP所以不能用普通的端口放行规则去处理必须在防火墙里显式允许协议号为112的流量或者直接使用vrrp这个协议名。还有一个容易忽略的地方是云环境。很多公有云的安全组默认不支持VRRP组播协议即使你把安全组规则放到了最宽松组播报文也出不了安全组边界。如果机器跑在云上使用keepalived之前建议先确认底层网络是否支持VRRP。不支持的话要在同一张网卡上测试抓包确认能从对端收到VRRP报文再继续后面的部署。6.3 网络拥塞导致的频繁切换这个坑比防火墙更隐蔽。若干次线上见鬼现象最后追查下来都指向网络拥塞导致的通告丢失。背景是这样的主备两台机器之间只有一条千兆链路业务高峰期流量达到了链路的90%以上。网络设备在转发高负载流量时对组播包的优先级处理未必很高偶发情况下VRRP通告被延迟甚至丢弃。备节点连续几次没有按时收到通告后判定Master故障切换发生。但切换之后主节点其实活得好好的于是又开始抢占VIP在几分钟内来回漂移业务在两次切换之间反复中断。这个问题的根因不是keepalived本身而是网络环境太差。但这个问题暴露了keepalived的一个天然局限性依赖网络通路的稳定性。在网络本就不稳定的条件下VRRP的高可用机制反而可能变成新的故障源。处理这类问题思路有几个层面一是提升主备之间链路的可靠性用多网卡bond或直接更换网络设备减少丢包。二是调整通告参数适当增大超时阈值让它对偶发抖动不那么敏感。三是在架构上接受切换并非零延迟的事实把监控和告警做细一旦出现频繁切换就去查网络指标而不是反复重启服务。6.4 配置了track_script却没有任何效果最后一个坑其实最让人抓狂track_script配了脚本也会执行但业务挂掉后VIP就是不切换。我排查过一次类似的案例最后发现脚本逻辑写错方向了。当时某开发者配置了一个ping远端网关的检测脚本用来判断本机网络是否正常。脚本的逻辑是如果ping不通网关就返回1表示故障。但运维在脚本里忘了把返回值正确传给keepalived而是让脚本最终执行了一个exit 0。结果是无论业务好还是坏keepalived拿到的脚本结果始终是健康于是VIP永远不切换。keepalived对track_script的判定规则其实很明确脚本退出码为0表示正常非0表示失败。所以你必须严格保证业务正常时退出码为0业务异常时退出码为非0。另外脚本的执行结果对优先级的影响并不是立刻生效的它会通过降低本机priority来触发切换而本机priority降低后对端需要经过一个通告交互周期才能真正完成切换这个过程通常在几秒范围内。还有一类错误是脚本文件权限不够keepalived以root运行脚本本身却不可执行导致脚本完全没跑起来keepalived也感知不到。检查方法是手动执行看结果再直接观察系统日志里的VRRP状态变化。如果track_script反复显示异常优先手动执行脚本看看脚本是否真的能返回0。7. 进阶思考结合Nginx/LVS的负载均衡热备7.1 双机热备Nginx的思路讲完基础的热备聊一个更贴近业务的组合玩法用keepalived给Nginx集群做入口热备同时用Nginx做四层或七层负载均衡。在这个架构里两台Nginx机器共同拥有VIP一台Master一台Backup。外部流量先访问VIP到达Nginx后由Nginx将请求转发到后端的应用服务器池。这样一来keepalived解决的是负载均衡层的高可用Nginx解决的是流量分发和业务层的高可用两层叠加之后整个入口的可靠性就有了明显提升。配置上不用做太大改动只要把之前配置里的check_nginx.sh换成同时检查Nginx进程、端口和关键业务URLkeepalived就能更准确地感知到业务层的健康状态。如果VIP所在的机器连Nginx进程都停了那么这台机器就不再有资格持有VIP流量应全部转向对端。track_script在这里的价值就完全体现出来了。7.2 一主多备和双主模式keepalived不仅支持两台机器还可以扩展到一主多备甚至双主模式。一主多备的概念很好理解一台主节点多台备节点优先级依次递减。主节点挂掉后备节点们按照优先级竞选优先级最高的接任。这种模式适合对可用性要求更高、容错能力更强的场景。双主模式则更复杂一些。两个节点同时都在跑业务各自持有不同的虚拟IP互为主备。比如节点A持有VIP1正常服务业务1节点B持有VIP2正常服务业务2。当节点A宕机后VIP1漂移到节点B节点B上的keepalived实例会同时持有两个VIP。这个方案充分利用了两台机器的资源避免了传统主备模式下备节点长期闲着的问题。不过双主模式对业务有比较高的要求备机必须能完全接管对方的所有业务数据、配置、容量都要提前准备到位。如果一台机器扛不住两台机器的流量双主模式就只是个花架子。我在生产环境上的经验是如果业务量不大、又希望节省资源双主模式很合适如果业务规模较大老老实实一台主一台备备机平时可以做不重要的开发任务但要有随时接管的能力。7.3 后期可以考虑的演进方向keepalived双机热备只是高可用架构的起点不是终点。搭好之后后面还可以考虑几个方向的演进。第一个方向是引入更完善的服务发现和流量接入层。比如把Kubernetes集群的Ingress放到两台Node上配合keepalived提供VIP这样在容器化改造时入口的高可用方案可以自然延续下来。第二个方向是建立定期的故障演练机制。不要等真正出了故障才去验证你的热备方案每隔一段时间手动切一次熟悉操作流程同时验证备节点是否真的能顶住流量。第三个方向是把监控告警体系建立起来重点监控VIP的所在位置、master状态变化次数、切换期间的丢包情况这样每次故障都有数据可溯。但也要泼一点冷水如果业务已经发展到多机房、多活的规模keepalived这种基于二层VRRP的方案会逐渐力不从心因为VIP的漂移范围受限于广播域。多机房的高可用通常要靠DNS级别的流量调度、全局负载均衡或者云原生的多集群方案而不是单纯靠两台机器抢一个IP。认清这个边界能帮你避免在错误的道路上越走越远。回到最初的那个凌晨故障。那次事故后我花了一个周末把keepalived双机热备重新搭了一遍不再满足于配置能跑而是把VRRP协议的细节、脚本检查的边界、防火墙的放行规则、非抢占模式的选择全部过了一遍。后来又一次遇到物理机硬件告警我直接在夜间流量低谷进行了切换操作业务几乎无感知。如果说有什么心得可以分享那就是高可用方案的价值不在于它听起来多高大上而在于你真正理解了它的机制并且定期验证过它。最后再给一个小技巧。如果你需要在keepalived配置里使用多个业务脚本不要写成一个又长又复杂的脚本尽量拆分多个独立的检查脚本每个脚本只负责一件事。然后在track_script里按顺序引用。这样任何一个脚本出问题定位都很快也不会因为一个脚本阻塞其他检查项的执行。这个小习惯在线上排查时会给你节省大量时间。