资讯动态

Keepalived高可用实战:VRRP虚拟IP双机热备与脑裂排查

发布时间:2026/10/14 22:38:32 来源:尧图企业网站定制
1. 为什么高可用需要Keepalived核心场景与架构定位1.1 从单机故障说起高可用的解决思路先说一次深夜故障。我接触Keepalived已经有几年时间第一次用是因为一套对外服务的Nginx入口只有一台机器某天凌晨它因为内存问题重启所有请求瞬间卡住监控告警刷了一屏。那次之后我才真正意识到单机部署再稳定也是概率事件高可用的第一要务不是让硬件永远不坏而是让故障发生时业务不中断。业务不中断说白了要解决三个问题多准备一台空闲节点、让备用节点知道主节点状态、对外提供一个不会因为单机故障而失效的访问入口。Keepalived恰好把这三件事都做了而且落地成本非常低不需要改造业务代码只要在两台Linux服务器上各装一个包、写一份配置文件把虚拟IP挂上去就能跑起来。这里面的核心是虚拟IPVIP客户端始终访问同一个地址主节点故障后备用节点秒级接管对用户来说就像什么都没发生过。可能你会问这不是负载均衡吗其实两者盯的是不同的问题。负载均衡做流量分发高可用做故障冗余。Keepalived既可以单独做高可用也能配合Nginx、LVS等组件一起用。最典型的组合是两台Nginx通过Keepalived绑定同一个VIP对外统一接收请求再由Nginx把流量反代到后端既承担入口职责又保证入口不挂。这篇文章适合正在做Web服务、网关、数据库代理等场景的后端开发和运维同学也适合想把项目从单点升级成高可用架构的独立开发者。1.2 VRRP协议与Keepalived的分工逻辑Keepalived的高可用能力本质上是VRRP协议在Linux平台上的落地实现。VRRP全称是虚拟路由冗余协议最初用在网络设备上解决网关单点问题同一网段里有多台路由器共用一个虚拟IP平时由优先级最高的Master响应ARP请求、转发数据包其余路由器处于Backup状态。一旦Backup在约定时间内收不到Master的VRRP通告报文就会按优先级顺序接替Master继续工作。Keepalived把这个思路搬到了服务器上让普通Linux机器也能对外漂移VIP。但它比原生协议多干了一件事业务级健康检查。VRRP本身只检查主机和网卡是否活着Keepalived可以通过自定义脚本去检查Nginx进程、后端端口、磁盘余量、健康检查接口然后根据检查结果决定是否降级、是否切换。从进程角度看Keepalived启动后有两个核心模块在协作一个是处理VRRP报文收发和状态切换的VRRP栈另一个是负责健康检查的Checkers模块。这里有个容易混淆的点Keepalived本身不做负载均衡它只负责“选主”和“漂IP”真正的流量转发还是由Nginx或LVS完成。整个链路是这样的Keepalived把VIP绑定到某台机器的网卡上这台机器上的Nginx监听VIP对应的端口客户端流量到达这台机器后由Nginx转发给后端。Keepalived一旦检测到Nginx异常就触发降级另一台机器接管VIP并继续对外服务。1.3 适用场景与选型对比很多人问已经有Nginx的upstream了还需要Keepalived吗分场景看。Nginx的upstream解决的是后端多台Web服务的负载均衡和故障摘除但Nginx这一层自己仍然是单点入口一旦挂掉后端再健康也无济于事。Keepalived补上的正是这一层缺口。相比之下Keepalived有几个明显优势配置简单核心就是一份keepalived.conf没有复杂的注册发现流程切换快默认通告间隔1秒结合超时机制多数场景下业务中断能控制在几秒内不依赖外部组件不需要DNS变更VIP漂移后在局域网内立即生效。缺点也明确它工作在三层和二层网络环境两台机器必须在同一个二层域跨机房场景需要额外打通网络有些网络环境会屏蔽组播报文也会造成误切换。选型上可以这样归纳Keepalived适合做VIP漂移和高可用兜底LVS适合大规模流量调度且需要把VIP和真实服务器分离HAProxy自身具备健康检查能力但仍常和Keepalived配合对外提供服务Consul这类注册中心方案适合微服务下的动态服务发现但切换依赖于客户端重新拉取列表做不到秒级漂移IP。我的建议是入门和高可用兜底先选Keepalived流量调度再往上叠加LVS或HAProxy。2. 环境规划与安装部署动手前的关键准备2.1 拓扑设计与IP规划清单先给一套最常用的拓扑。假设要为一套Web系统搭建高可用入口两台Linux服务器部署Nginx和Keepalived两台机器在同一交换机下的192.0.2.0/24网段内。角色主机名物理IP虚拟IP部署组件主节点kp-node1192.0.2.11/24192.0.2.100/24Nginx Keepalived备节点kp-node2192.0.2.12/24192.0.2.100/24Nginx Keepalived虚拟IP不要占用物理网段之外的地址也不要和已有DHCP分配范围冲突否则会出现IP冲突。实际操作中我会提前在交换机上确认这个IP没有被别的设备占用先把VIP手动绑定到物理网卡上ping一下发现没人应答再配进Keepalived。这里有一个容易忽略的事两台服务器的时钟必须同步。Keepalived的状态切换本身不依赖系统时间但排查问题时如果两台机器时间差太多日志对不上切换时间点也看不出先后顺序非常麻烦。建议部署前配好chrony或NTP主机名也尽量起得有意义kp-node1、kp-node2明显比node1、node2更容易在日志里区分。2.2 软件安装与内核参数调整CentOS/RHEL类系统上安装很简单直接执行yum安装即可yum install -y keepalived nginxUbuntu/Debian系则用apt安装。装完之后先别急着改配置建议先把内核参数调一下cat /etc/sysctl.conf EOF net.ipv4.ip_nonlocal_bind 1 net.ipv4.conf.all.promote_secondaries 1 net.ipv4.conf.default.promote_secondaries 1 EOF sysctl -p为什么需要这几个参数ip_nonlocal_bind允许进程绑定到本机尚未存在的IP这样Nginx可以预先监听VIP对应的端口即使VIP还没漂移到本机Nginx也能正常启动不会出现“Keepalived把VIP挂上去了但Nginx还没来得及监听”的空窗。promote_secondaries的作用是让辅助IP在删除主IP后能够自动晋升避免VIP在地址变更时出现临时的地址丢失。这里还有一个细节两节点的网卡名称要尽量一致。比如都是eth0或者都是ens192因为后面配置文件里要指定interface如果两台机器网卡名不一样换绑VIP时会绑定到错误的网卡上切换虽然显示成功但流量根本不从该网卡走。安装完成后用ip addr show确认网卡名称和物理IP再继续下一步。2.3 配置文件核心结构拆解Keepalived的默认配置文件在/etc/keepalived/keepalived.conf这个文件由三大部分组成global_defs定义全局参数vrrp_script定义健康检查脚本vrrp_instance定义VRRP实例。其中global_defs里常用的有notification_email和router_id。router_id只是本机标识不一定和主机名一致但两台机器不要配置成相同值否则看日志时根本分不清是哪台机器在发通告。vrrp_instance是核心段里面要配置state、interface、virtual_router_id、priority、advert_int、authentication、virtual_ipaddress、track_script、notify等参数。先解释几个最容易出问题的。virtual_router_id是一个数字范围1到255。同一个网段内如果有多个Keepalived集群不同集群的virtual_router_id必须不同同一集群内的两台机器必须相同。authentication里的auth_pass是认证字符串同一集群必须一致它的作用只是防止其他设备误加入VRRP组不是加密手段不要把它当安全措施。priority是选举优先级范围一般是1到254数值越大越优先成为MASTER。同一集群内不要设置成相等除非使用非抢占模式。advert_int是通告间隔默认1秒表示Master每隔1秒向组播地址发一次通告报文Backup根据这个报文判断Master是否存活。写配置时有条经验不要把注释写在参数后面Keepalived对部分参数做了严格解析注释位置不对可能导致启动失败。更稳妥的做法是用校验命令先检查一遍keepalived -t -f /etc/keepalived/keepalived.conf我第一部署时因为某个vrrp_instance少了一个花括号服务一直起不来后来靠这个校验命令定位了问题。配置文件里花括号嵌套层级多一定要严格对齐最好在编辑器里开启括号高亮。3. 核心配置实战单主/双主/业务健康检查3.1 单主模式Nginx双机热备的完整配置先看最常见的单主模式也就是一台Master、一台BackupVIP正常情况下始终由Master持有只有Master故障时才切换到Backup。kp-node1的配置如下global_defs { router_id kp-node1 } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 3 fall 2 rise 2 } vrrp_instance VI_WEB { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.0.2.100/24 dev eth0 label eth0:1 } track_script { check_nginx } }kp-node2的配置除了state改为BACKUP、priority改为80、router_id改为kp-node2之外其他保持一致。注意优先级不要设成100我用了80这样Master正常存活时即使发生轻微抖动Backup也不会轻易抢走VIP而当Master进程真正崩溃后Backup会在几秒内接管。这段配置里有几个关键点值得仔细看。state只是初始状态实际运行时决定谁是MASTER的依然是priority。比如你初始状态写MASTER但priority比另一台低启动后它也会让位给高优先级节点。virtual_ipaddress里用dev eth0 label eth0:1表示VIP以别名网卡的方式绑定在eth0上好处是物理IP和VIP在逻辑上分得清楚排查时ip addr show eth0:1一眼就能看到VIP是否存在。track_script引入check_nginx脚本Keepalived每2秒执行一次脚本连续失败2次认为服务异常连续成功2次恢复健康避免单次网络抖动引发误切换。3.2 双主模式利用VRRP实例实现负载分摊单主模式有个痛点第二台机器长期闲置资源利用率不高。双主模式又称互为主备用第二个VRRP实例也能让两台机器同时干活实例VI_WEB以kp-node1为主实例VI_API以kp-node2为主两台机器各自持有一个VIP分别承担不同业务的入口。当某一台故障时另一台会同时接管两个VIP。具体做法是在同一份keepalived.conf里写两个vrrp_instance第二个实例的virtual_router_id改成52priority反向设置virtual_ipaddress换成另一个VIP。配置示意如下vrrp_instance VI_API { state BACKUP interface eth0 virtual_router_id 52 priority 80 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.0.2.101/24 } track_script { check_nginx } }kp-node2上的VI_API实例则把priority改成100、state改为MASTER这样两个实例的主备关系刚好相反。注意两台机器上都要监听两个VIP对应的端口或者Nginx直接监听0.0.0.0:80否则切换过去后另一台机器上没有对应的虚拟主机流量进得来但处理不了。双主模式有个容易踩的坑一个网卡上绑定多个VIP时多个vrrp_instance可以共用同一个interface但如果第一个实例用了label eth0:1第二个实例又用label eth0:2切换时两个VIP会分别落在对应的别名上。大多数情况下没问题但某些基于网卡名的路由策略或Netfilter规则可能受影响。所以我建议在不需要区分流量的场景下直接写IP而不写label让Keepalived自动处理地址绑定。3.3 自定义健康检查脚本监听业务端口与文件Keepalived原生自带的检查能力很弱只检查主机存活和VRRP通信状态真正的业务健康检查要依赖vrrp_script。我第一次部署时只跟踪了系统进程没有检查业务端口结果某个后端进程还在但端口已经不监听了Nginx返回502Keepalived还认为节点健康VIP不切换用户全在报错。后来我写了一个包含两个判断的脚本检查Nginx进程和本地健康检查接口#!/bin/bash counter0 if ! pgrep -f nginx: master /dev/null; then counter$((counter1)) fi if ! curl -fsS --connect-timeout 3 http://127.0.0.1/healthz /dev/null 21; then counter$((counter1)) fi if [ $counter -gt 0 ]; then exit 1 fi exit 0为什么不用killall -0 nginx或单纯的pgrep -x nginx因为一台机器上可能跑着多个同名进程或者Nginx master进程存在但worker都崩了只看进程名太粗糙。我习惯把“进程存在”和“本地健康检查接口可用”结合起来curl请求的是127.0.0.1的/healthz这个接口可以是Nginx直接返回的静态页也可以由后端业务提供只要返回HTTP 200就认为健康。脚本的意义在于把应用的可用性纳入切换决策而不是只盯操作系统和进程列表。脚本配置为interval 2、timeout 3、fall 2、rise 2含义是每2秒执行一次超过3秒视为超时连续失败2次进入故障状态连续成功2次恢复健康。脚本要放在/etc/keepalived目录下并赋予执行权限chmod x /etc/keepalived/check_nginx.sh这里有个容易被忽略的坑Keepalived执行脚本时默认是root用户但部分环境里root的PATH不包含/usr/local/bin如果curl安装在/usr/local/bin下脚本里直接写curl可能报command not found脚本一直失败。解决办法是脚本开头写全路径或者先source /etc/profile。3.4 通知脚本与邮件告警高可用不是搭完就算完事故障切换发生时必须让人知道。Keepalived提供notify机制在状态变化时执行外部脚本notify_master /etc/keepalived/notify.sh MASTER notify_backup /etc/keepalived/notify.sh BACKUP notify_fault /etc/keepalived/notify.sh FAULT脚本内容大致如下#!/bin/bash TYPE$1 NAME$2 VIP$3 LOG/var/log/keepalived-notify.log echo $(date %F %T) $TYPE $NAME $VIP $LOG # 在这里调用团队的告警接口例如企业微信或钉钉机器人的webhook /usr/bin/curl -fsS -X POST http://你的告警网关/send \ -H Content-Type: application/json \ -d {\msg\:\Keepalived $TYPE on $(hostname), VIP$VIP\}注意notify脚本传入的第三个参数是该实例绑定的第一个IP地址第四个参数在部分版本里是状态原因。我一般会多打印一份到本地文件这样即使告警网关挂了事后也能通过日志看到历史切换记录。脚本同样要加执行权限而且第一次配置时不要直接上生产先手动跑一遍确认没有语法和权限问题否则真正切换时脚本执行失败等于白搭。4. 故障切换机制与脑裂问题排查实录4.1 状态转移过程详解Keepalived的VRRP状态机只有三种状态INIT、BACKUP、MASTER。服务启动后先进入INIT然后根据优先级和收到的通告报文进入BACKUP或MASTER。如果初始state是BACKUP或priority较低启动后必然先进入BACKUP并开始接收Master的通告如果初始state是MASTER且没有收到其他更高优先级的通告会在短暂延迟后进入MASTER并绑定VIP、发送VRRP组播报文。切换到MASTER的具体过程是Keepalived给网卡添加VIP对ARP表做一次主动通告让局域网里的交换机和客户端更新ARP缓存把指向旧Master的MAC地址更新为当前节点随后开始每1秒发送一次VRRP组播报文。切换到BACKUP的过程是把VIP从网卡上删除停止发送通告同时本机Nginx不再接收新请求但已经建立的TCP连接一般会在几十秒内自然老化极端情况下客户端会感觉到几秒的卡顿。理解了这一点就能解释一个常见现象切换发生后个别客户端仍然访问不通旧IP通常是客户端的ARP缓存没有及时更新。可以调小交换机或终端的ARP老化时间或者等它自然老化后重新学习。4.2 脑裂的本质与代价为什么不能让两端同时当MASTER脑裂是运维最怕的问题之一本质是主备节点之间的VRRP通信链路中断两个节点都认为对方已经死了各自从BACKUP晋升为MASTER同时绑定同一个VIP并对外响应ARP请求。结果局域网内同时出现两个拥有相同IP的MAC地址交换机MAC表在两个端口之间不停抖动客户端请求一会儿打到节点A一会儿打到节点B业务状态错乱严重时整个网段都会间歇性不可达。为什么脑裂代价这么大除了流量路径不稳定更危险的是双主同时写同一份数据或同时占用同一个资源。如果是无状态的Web入口双主还能勉强撑一会儿如果后面是数据库、消息队列或分布式锁两边同时抢写很容易造成数据不一致事后恢复成本极高。生产环境里必须对脑裂做兜底。一是尽量保证VRRP通信链路有多余路径比如交换机堆叠或双网卡绑定二是通过巡检脚本在MASTER状态确认前检查对端是否可达如果对端仍有业务响应或VRRP报文就拒绝晋升。4.3 脑裂排查脚本与实操手段排查脑裂时第一条命令是分别在两台机器上执行ip addr show | grep 192.0.2.100如果两台机器都能看到这个VIP基本可以确认已经脑裂。更自动化的做法是写一个巡检脚本#!/bin/bash VIP192.0.2.100 COUNT$(ip addr show | grep -c $VIP/) if [ $COUNT -ne 1 ]; then echo $(date %F %T) abnormal vip count: $COUNT /var/log/split-brain.log fi如果真发生脑裂操作顺序很重要。第一步先保住业务直接在备用节点上执行systemctl stop keepalived释放VIP让主节点恢复唯一的ARP响应。第二步再定位根因检查两台机器之间的物理链路和交换机端口看VRRP组播报文是否被阻断可以抓包确认tcpdump -i eth0 vrrp正常情况下应该能持续看到源地址或目的地址为224.0.0.18的VRRP报文如果完全抓不到说明组播被防火墙或交换机策略拦掉了。第三步检查两台机器防火墙按第5章提供的方式放行VRRP协议。脑裂最常见的触发原因无非三个防火墙没放行、交换机组播或IGMP策略没开、两台机器之间的二层链路断开但各自业务还活着。排查时按照物理连通性、防火墙规则、VRRP报文确认这个顺序走基本都能定位。4.4 常见故障速查表故障现象可能原因处理办法Keepalived启动失败配置文件语法错误执行keepalived -t -f 配置文件检查语法两台机器都持有VIP组播不通形成脑裂检查防火墙/交换机组播策略抓包确认VRRP报文Backup一直不接管健康检查脚本未通过手动执行脚本看退出码chmod x并检查PATHVIP绑定成功但Ping不通防火墙禁止了ICMP放行VIP对应的ping请求检查路由是否正确切换后Nginx端口不通Nginx没有监听VIP或监听端口遗漏确认Nginx listen 0.0.0.0:80或listen对应IP日志里没有任何切换记录脚本权限或路径错误查看/var/log/messages或journalctl -u keepalived这张表基本上覆盖了我这些年遇到的大多数Keepalived问题。每次排障时先对照故障现象找可能原因再按处理办法操作能少走很多弯路。5. 进阶优化与经验总结5.1 防火墙/安全组与VRRP协议适配Keepalived依赖VRRP协议通信VRRP的协议号是112目的组播地址是224.0.0.18。如果本机防火墙开启至少要放行这条规则iptables -I INPUT -p vrrp -i eth0 -d 224.0.0.18 -j ACCEPT也可以按源地址放行把对端的物理IP加进来这样更精细iptables -I INPUT -p vrrp -s 192.0.2.12 -j ACCEPT iptables -I INPUT -p vrrp -s 192.0.2.11 -j ACCEPT云服务器有一个额外的注意点除了系统防火墙云平台安全组也要放行VRRP协议或至少放行两台机器之间的组播通信。这一步经常被忽略很多环境里安全组只放行了TCP和UDPVRRP报文被拦截后两台机器互相感知不到启动后各自成为MASTER直接脑裂。遇到这种情况就像我上面说的配置单播模式是更可控的替代方案unicast_peer { 192.0.2.11 }两个节点都要把对方的物理IP写进unicast_peer并且同时放行协议112。单播模式在云环境里很常用因为很多云网络对组播支持有限甚至完全不支持写单播比折腾组播可靠得多。5.2 抢占模式与非抢占模式的选择默认情况下Keepalived的VRRP是抢占模式高优先级节点启动后会立刻把低优先级节点的VIP抢过来。这在某些场景很有用比如你希望故障恢复后把业务切回性能更好的主力机器。但在另外一些场景里抢占反而会带来无谓抖动尤其是两台配置相近的机器主节点因为短暂网卡抖动切过去恢复后又抢回来中间产生好几次几十毫秒甚至几百毫秒的请求中断对长连接业务不友好。对这类场景可以用非抢占模式配置里加上nopreempt参数。使用nopreempt时有个非常关键的细节两台机器的state必须都写成BACKUP。原因是非抢占模式通过关闭抢占逻辑实现如果某台机器初始state是MASTER它会认为自己是主永远不会把VIP让出去故障恢复后切换就失效了。实际配置中我通常不管哪台机器真正承担流量都把state写成BACKUPpriority仍然区分主次同时加上nopreempt。这样初始阶段priority高的节点成为MASTER但由于nopreempt生效在低优先级节点已经接管后高优先级节点恢复时不会强行抢回VIP。代价是切换时间会略长但换来的是更平稳的流量路径这个取舍我觉得很值。5.3 参数调优VRRP通告间隔与超时计算advert_int参数直接决定Master发送通告的频率默认1秒它会直接影响故障切换灵敏度。Keepalived判断Master失联的时间可以这样近似理解Backup在3倍的advert_int时间内没有收到通告就会认为Master挂了同时还要加一个基于priority算出的偏移时间。经验公式大致是超时时间 (3 * advert_int) (256 - priority) / 256秒。比如advert_int1priority100超时大约是3加0.6秒再叠加脚本检查时间整个切换过程从Master故障到VIP更换完成通常在3到6秒之间。有人把advert_int调小到0.2秒以追求更快切换但代价是组播报文数量成倍增加弱交换机环境下反而容易造成报文丢失和误切换。我的建议是优先保持1秒默认值先优化健康检查脚本的耗时比如只做TCP端口检查而不是复杂的HTTP请求。如果要针对高延迟链路调优可以把advert_int适当提高到2秒减少组播报文占用但切换时间也会相应拉长。还要提一下master_down_delay参数它表示Backup启动后多久才开始计算Master失联时间默认0秒。在回切场景下给这个参数留一点缓冲可以防止Master重启瞬间Backup抢VIP又立刻让回去造成二次抖动。5.4 我踩过的几个坑坑一脚本权限和PATH。这是新手最容易踩的坑。Keepalived执行vrrp_script时对脚本执行权限要求严格脚本没有执行权限或者解释器不对会瞬间失败Keepalived直接认为业务异常并触发切换。我遇到过一台机器上脚本里用了管道配合crontab -l结果由于PATH缺失命令找不到脚本返回127两台机器来回切了好几次。最后把脚本里的每条命令都改成绝对路径才消停。坑二VIP和物理IP同网段的ARP问题。如果VIP和物理IP同网段绑定VIP时Linux会自动更新ARP表把物理IP对应的MAC地址也改成VIP的MAC地址这会导致物理IP在换绑时被“带走”影响监控和SSH登录。建议把VIP规划成与物理IP不同网段或者使用别名标签并正确配置arp_ignore和arp_announce参数。至少要把监控系统对物理IP的告警阈值放宽减少误报。坑三notify脚本未按状态分流。很多人配置notify后脚本对所有状态都执行同一个动作结果BACKUP状态时错误地启动或重启了业务进程造成两边同时有服务在跑。我的建议是脚本第一行就按状态参数分情况处理而且脚本本身要轻量如果执行失败或耗时过长优先放弃通知而不是阻塞VRRP状态机。Keepalived整体上是很可靠的组件但它是通过“脚本健康检查加VRRP通信”完成高可用的任何一个环节失效都可能让HA变成负优化。所以测试时必须同时验证四类场景正常情况、服务假死情况、主机断电情况、网络隔离情况全部通过后再交付。最后再分享一个小技巧当线上出现VIP能Ping通但业务打不开时先不要急着重启Keepalived在客户端机器上执行arp -a | grep 192.0.2.100看看ARP表里VIP对应的MAC地址是哪台服务器的。VRRP切换后如果客户端ARP缓存还指向旧节点流量就会持续打到已经没有监听端口的旧机器上表现就是VIP通但服务不通。这个现象和Keepalived本身关系不大但非常容易误判排查优先级一定要放在重启服务之前。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑