1. Keepalived集群的定位与整体设计思路
1.1 什么场景下真正需要Keepalived
先说结论:Keepalived解决的不是性能问题,而是可用性问题。它不会让你的Nginx、MySQL或业务接口跑得更快,但能在这些服务意外宕机时,让整个系统在外界看来“什么都没发生”。
我做高可用集群这些年,见过不少团队把Keepalived当成万能药——业务做不了横向扩展就上Keepalived,数据库扛不住压力也上Keepalived。其实这个思路需要严格区分。Keepalived最擅长的是解决“入口漂移”和“单点故障”这两件事。如果你有一组后端服务器,它们本身是无状态的、可以同时对外提供服务的,这时候真正需要的可能是LVS、Nginx负载均衡,而不是Keepalived。但如果你的架构里存在一个必须对外暴露固定地址的入口节点,比如数据库主节点、反向代理主节点、消息队列主节点,那么Keepalived就是最成熟、最稳妥的选型之一。
以我们线上最常见的MySQL主从架构为例。业务侧当然可以配置多个数据库地址做读写分离,但一旦主库发生故障,业务侧通常只有一个写入地址。这时候配置Keepalived,在MySQL主节点上绑定VIP,客户端只需要连接这个VIP。主库宕机后,VIP漂移到备库,业务层几乎无感知,应用只会在这一瞬间看到连接断开的报错,重连之后自动恢复到新主库。这就是Keepalived最核心的价值:把服务器故障对外表现为“一阵短暂的网络抖动”,而不是“服务不可用”。
1.2 为什么VIP漂移是最朴素也最可靠的高可用方案
很多人一谈到高可用,第一反应是上K8s、上分布式协调、上服务网格。但现实中大量的核心业务系统,尤其是政企项目、传统金融项目、中小互联网公司的基础架构,受限于人员维护成本和系统复杂度,不可能把所有服务全部容器化。在这些环境里,Keepalived这种基于虚拟路由冗余协议(VRRP)的VIP漂移方案,反而是最轻量、最可靠的高可用实现。
之所以说它可靠,是因为VRRP协议本身非常成熟。它通过多播报文进行主备心跳检测,没有复杂的脑裂仲裁逻辑,也没有依赖外部存储或协调组件。主节点周期性广播VRRP通告报文(通常一秒一次),备节点收到通告后保持BACKUP状态;当备节点连续多个通告周期收不到报文,就会认为主节点已经失联,自动切换为MASTER状态并接管VIP。这个检测机制极其简单,但恰恰是简单才让它稳定——不需要等数据库恢复、不需要等容器重新调度,只要网卡还在、协议栈还能收发报文,漂移就能在几秒内完成。
另外,Keepalived的依赖面很小。它本身是Linux上的轻量级守护进程,对系统资源占用几乎可以忽略不计,配置也是一份纯文本的keepalived.conf。我见过一个维护了七八年的老集群,系统从CentOS 6一路升级到CentOS 8,Keepalived配置几乎不用改,稍微调整一下语法就能继续工作。这一点在追求稳定压倒一切的生产环境里,是最重要的加分项。
2. 核心机制拆解:VRRP协议、状态切换与脑裂防护
2.1 VRRP协议是如何实现主备协商的
Keepalived的底层核心是VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议),它模拟了一组路由器共用一个“虚拟路由器”的效果。多台物理设备配置相同的VRRP实例,对外表现为同一个虚拟IP和虚拟MAC地址。局域网内的客户端把网关指向这个VIP,无论数据包由哪台物理设备处理,从客户端视角看都是到达了同一个“虚拟路由器”。
协议运行的标准流程是这样的:每台参与VRRP的设备上都有一个1到255之间的优先级数字,默认配置通常是MASTER节点优先级为100,BACKUP节点优先级为90。优先级最高的设备会成为MASTER,负责响应VIP上的ARP请求,并周期性发送VRRP通告报文。这个通告报文的默认发送间隔是1秒,目的地址是组播地址224.0.0.18。备节点收到通告后,会重置一个定时器。如果连续三个通告周期(即3秒)内都没有收到来自MASTER的通告,备节点就认为MASTER已经不可达,于是进行状态切换,自己变成MASTER。
这里有一个关键细节:备节点升为MASTER后,会立即发送一次免费ARP(Gratuitous ARP),把它自己的物理MAC地址绑定到VIP上,通知局域网内所有交换机和主机刷新ARP缓存。这一步决定了业务切换的感知时间窗口。所以这里提醒一下,如果你的网络里有设备ARP老化时间设置过长,可能会在VIP漂移后的一段时间内继续往旧MAC地址发数据,导致部分请求超时。生产环境我一般建议把交换机端口的STP和ARP老化时间调快一些,减小切换后的缓存延迟影响。
2.2 状态机、优先级策略和抢占模式的取舍
Keepalived的运行状态机非常清晰,只有INIT、BACKUP、MASTER三个阶段。进程启动后先进入INIT,紧接着根据配置的本机优先级和当前VRRP通告情况,决定进入BACKUP还是MASTER。如果本机优先级最高,直接变MASTER;否则进入BACKUP,开始监听MASTER的通告。
这里要重点说一下抢占(preempt)和非抢占(nopreempt)的区别。默认情况下Keepalived是抢占模式,也就是说当原MASTER节点故障恢复后,只要它的优先级高于当前MASTER,就会立刻抢回VIP。这个机制在某些场景下是灾难。想象一下MySQL主库节点A宕机,VIP漂移到备库B,业务已经切换完成。这时候A节点恢复,如果开着抢占模式,A会立刻把VIP抢回来,但A节点上的MySQL可能还在恢复中,业务写入再次中断,会造成二次抖动。
所以对于数据库这类需要数据恢复时间的服务,我通常建议把两端的优先级配成一致,并开启nopreempt模式,让故障恢复后的原主节点以BACKUP身份重新加入集群,避免来回切换。对于Nginx反代这种无状态服务,抢占模式影响不大,甚至可以说让原主节点快速回归对运维更友好,因为它的配置和证书都是原始标准,可以避免长期以备节点状态运行带来的偏差。
2.3 脑裂问题:为什么明明只有两台机器也会裂
脑裂(Split-Brain)是所有HA架构里绕不开的魔鬼。Keepalived的脑裂场景通常是这样的:主备两台服务器其实都活着,但两者之间的网络或心跳链路断了。备节点收不到MASTER的通告,于是把自己升级为MASTER,也绑定了同一个VIP。于是同一网段内出现两台设备同时响应VIP的ARP请求,数据包一会发给A、一会发给B,整个服务立刻陷入混乱。
一种常见原因是防火墙挡了VRRP的多播报文。VRRP使用的协议号是112,很多默认策略会拦截非TCP/UDP的无连接协议报文。我遇到过一台服务器上自带的NF_TABLES规则把所有非标准端口的入站流量都拒了,另外一台服务器却没问题,结果主备之间看起来通,实际VRRP报文根本没到对方。排查这种问题最快的方式是在两台机器上分别执行tcpdump -i eth0 vrrp,看能不能抓到对方的通告包。
另一种脑裂原因比较隐蔽,是两台机器之间的实际数据链路存在单向通信故障。我们用网线或光纤互联时,个别情况下会出现A能发到B、B发不到A的单向链路问题。这会导致A始终认为自己是MASTER,B也认为自己是MASTER。防范脑裂没有一招制敌的办法,我的习惯是双重保险:Keepalived的心跳网卡尽量用独立的物理网卡和独立交换机,同时业务程序层面再做一次仲裁判断。比如MySQL高可用场景,可以编写notify脚本,在发生MASTER切换时检查本节点是否真正持有数据库的写锁或半同步复制是否正常,如果发现异常立即降级为BACKUP。
3. 实操配置:环境规划、安装与基础keepalived.conf详解
3.1 节点规划与网络参数设计
以最经典的双节点主备架构为例,假设我们要为一组Nginx反向代理做高可用。先规划环境:
| 参数 | 主机A(主) | 主机B(备) |
|---|---|---|
| 操作系统 | Rocky Linux 9.2 | Rocky Linux 9.2 |
| 业务IP | 192.168.10.11 | 192.168.10.12 |
| VIP | 192.168.10.100 | 192.168.10.100 |
| 心跳网卡 | eth1 | eth1 |
| 优先级 | 100 | 90 |
| 抢占模式 | 关闭 | 关闭 |
这里VIP必须和业务网卡在同一广播域内,也就是说交换机上这个VLAN要允许VIP地址的数据包通过。如果你把VIP配成和业务网段不相同的地址,就需要额外配置ARP代理或者路由逻辑,生产环境我建议不要自己给自己找麻烦,VIP直接从业务网段里规划一个空闲IP即可。
网络层面还有一个容易被忽略的点:需要确认两节点之间VRRP多播报文能够互通。如果你用的是云服务器或者云VPC,有些云平台默认不支持VRRP多播协议,这时候用Keepalived的unicast(单播)配置模式可以绕过限制。具体方法是在keepalived.conf的vrrp_instance段里写unicast_src_ip和unicast_peer,将主备节点地址以单播方式互发VRRP报文,效果和多播一致。我帮同事排过不少云上无法切换的案例,最后都是用单播配置解决的。
3.2 安装Keepalived并加载基础配置
Rocky Linux / CentOS / Ubuntu都提供了Keepalived的软件源包,不需要编译源码。直接执行:
# Rocky Linux / CentOS系 sudo dnf install -y keepalived # Ubuntu / Debian系 sudo apt install -y keepalived安装完成后,Keepalived的主配置文件路径是/etc/keepalived/keepalived.conf。这个文件默认存在但内容是空的或者只包含注释。我习惯先使用一个最精简的配置验证集群通信,再逐步叠加健康检查脚本和业务联动。
主节点A的基础配置如下:
global_defs { router_id LVS_HA_01 enable_script_security } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 100 advert_int 1 nopreempt authentication { auth_type PASS auth_pass 8a9kZxQp } virtual_ipaddress { 192.168.10.100/24 dev eth0 label eth0:1 } }主节点B的配置文件只需把router_id改成LVS_HA_02,priority改成90,其它内容完全一致。这里有一个细节值得说明:我把两台机器的state都写成BACKUP,然后依靠priority来区分主备。这是官方推荐的“平等模式”,好处是配合nopreempt后,启动顺序不会影响最终的角色归属。如果写成state MASTER + state BACKUP的传统配置,并且同时开启nopreempt,可能会出现在MASTER节点尚未启动时,BACKUP抢先占用了VIP,之后MASTER启动却不抢占的情况,角色和预期会不一致。
vrrp_instance里每个参数的含义我们要心里有数:
- state:本节点的初始状态,推荐都写BACKUP。
- interface:VRRP通告报文从哪个物理网卡发出,必须和业务网卡一致。
- virtual_router_id:同一个VRRP组标识,范围0到255。两台机器上这个值必须一致,否则组不成集群。
- priority:优先级,范围1到254。越大越容易成为MASTER。
- advert_int:VRRP通告间隔秒数。默认1秒表示1秒发一次通告,切换检测时间为3秒。如果需要更快切换可以改成0.5甚至0.2,但会使网络报文量增大且对抖动更敏感。
- authentication:认证方式。生产环境建议使用PASS口令认证,防止同网段内其他设备恶意加入VRRP组。auth_pass最多8位字符,这8位字符应该设置得有随机性。
3.3 启动验证:看状态转换和VIP绑定
配置完成后,两台机器分别启动服务:
sudo systemctl enable --now keepalived sudo systemctl status keepalived启动后用ip命令检查VIP是否按预期绑定在主节点A上:
ip addr show eth0在主节点A上应该能看到eth0:1这个子接口绑定了192.168.10.100,而在备节点B上不应该看到。查看Keepalived的日志更能确认状态机的流转:
tail -f /var/log/messages | grep Keepalived # 或 journalctl -u keepalived -f日志会依次输出类似这样几行:
Keepalived_vrrp[1234]: VRRP_Instance(VI_1) Entering BACKUP STATE Keepalived_vrrp[1234]: VRRP_Instance(VI_1) Entering MASTER STATE Keepalived_vrrp[1234]: VRRP_Instance(VI_1) using locally configured address 192.168.10.100从出现Entering MASTER STATE到ARP通告发出,间隔通常在1秒内。此时可以主动停掉主节点的keepalived服务模拟故障:
sudo systemctl stop keepalived # 主节点A的VIP被释放,备节点B在几秒后自动接管在节点B上看到MASTER状态且VIP已绑定,说明最基础的主备漂移链路已经打通。这是后续所有生产配置的“地基”,地基不稳一切都白搭。
4. 业务联动:健康检查脚本与多场景组合实战
4.1 Nginx反向代理场景:健康检查决定成败
上面配置完成之后,会出现一个尴尬的情况:Keepalived本身是正常的,但如果Nginx进程挂了,Keepalived依然认为节点健康,VIP不会漂移,业务依然失败。这就是为什么说健康检查脚本才是Keepalived实用价值的关键。
Nginx场景的健康检查思路是:检测Nginx进程是否存活,以及端口是否能正常响应。我通常写一个脚本,放在/etc/keepalived/check_nginx.sh,并赋予执行权限:
#!/bin/bash # 检查Nginx进程是否存在 if ! pgrep -x nginx > /dev/null 2>&1; then exit 1 fi # 再检查本机80端口是否能正常访问 curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/healthz 2>/dev/null | grep -q "200" if [ $? -ne 0 ]; then exit 1 fi exit 0然后在keepalived.conf里用vrrp_script定义这个检查脚本,并把它和vrrp_instance关联起来:
vrrp_script chk_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 timeout 2 fall 2 rise 2 } vrrp_instance VI_1 { ... track_script { chk_nginx } }interval表示每隔2秒执行一次脚本。timeout表示单次执行超过2秒就视为失败。fall表示连续失败2次节点就降级,rise表示连续成功2次节点恢复可用。这里请你注意fall和rise的配合:fall不能太小,否则网络抖动或脚本执行偶发超时会触发无谓切换;fall也不宜太大,否则业务真的挂了还要等太久才切换。我经验上是2秒间隔配合fall=2,整体故障感知时间约4到6秒,业务接受度比较好。
这个健康检查机制还有一层隐藏逻辑值得说明:当备节点执行健康检查失败时,它不会影响自己的BACKUP状态,只是不能升MASTER。当主节点健康检查失败时,会立刻降级为BACKUP,VIP随即漂移到备节点。通过这种方式,业务的高可用粒度从“机器活着”细化到了“Nginx真的能响应请求”,这才是高可用集群该有的样子。
4.2 LVS负载均衡场景:Keepalived + LVS DR模式
Keepalived的另一个高频使用场景是配合Linux自带的IPVS模块实现四层负载均衡高可用。我们常说Keepalived天生就是为LVS设计的,它的全名Keepalived里包含了对虚拟服务的直接支持,不需要额外脚本就能把LVS规则同步到备节点。
LVS DR(直接路由)模式的拓扑是这样的:两台LVS服务器组成Keepalived集群,共享一个VIP。客户端访问VIP,请求由LVS服务器转发给后端的多台真实服务器(RS),而RS的响应数据不经过LVS,直接返回给客户端。这种模式的转发性能极高,后端RS只需在lo网卡上绑定VIP地址并关闭ARP响应即可。
Keepalived负责LVS规则的部分配置示例如下:
virtual_server 192.168.10.100 80 { delay_loop 6 lb_algo rr lb_kind DR protocol TCP sorry_server 127.0.0.1 8080 real_server 192.168.10.21 80 { weight 1 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } real_server 192.168.10.22 80 { weight 1 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } }这个配置的含义是:Keepalived每6秒检查一次RS的健康状态,用轮询算法在多个RS之间分配请求,如果某个RS的健康检查失败,Keepalived会自动将它从LVS转发列表里摘除。当所有RS都不可用时,请求会转发给sorry_server,也就是一个本地的备用维护页面,告诉用户当前服务暂时不可用。
很多人会忽视下面这个细节:在DR模式下,后端RS服务器必须抑制对VIP的ARP响应。因为VIP绑定在LVS服务器上,同时也需要在每台RS的lo网卡上绑定VIP用于接收请求,但是不能让RS对外回答这个VIP的ARP请求,否则网关会把流量直接发给RS导致转发异常。RS上的标准操作是设置sysctl参数,然后绑定VIP到lo接口:
# 在每台RS机器上执行 sudo sysctl -w net.ipv4.conf.lo.arp_ignore=1 sudo sysctl -w net.ipv4.conf.lo.arp_announce=2 sudo sysctl -w net.ipv4.conf.all.arp_ignore=1 sudo sysctl -w net.ipv4.conf.all.arp_announce=2 # 在lo网卡上绑定VIP地址 sudo ip addr add 192.168.10.100/32 dev lo这个组合场景下Keepalived的核心价值是两个:一是LVS本身的HA,故障自动漂移;二是对RS的健康管理,动态摘除和加回故障节点。两者缺一不可。
4.3 MySQL主从场景:脚本联动的进阶实现
MySQL主从这类有状态服务,配置Keepalived要比Nginx复杂得多。主库宕机后,VIP漂移到备库只是第一步,如果备库数据还没追平主库的binlog,切换过去就会丢数据。所以健康检查脚本不能只看进程存活,还要做数据同步延迟的判断。
我的常用做法是在健康检查脚本里执行如下判断逻辑:检查MySQL端口是否可连接,检查从库的Seconds_Behind_Master是否过大。如果延迟超过阈值,认为当前节点不适合做MASTER,脚本返回失败,让Keepalived不把VIP分配给该节点。
配合MySQL半同步复制使用时,Keepalived的notify脚本能发挥更大价值。在主库切换完成后,我们需要自动修改备库的复制状态、更新应用账号的应用地址权限等。Keepalived提供了notify_master、notify_backup、notify_fault三个钩子脚本,分别在节点变为MASTER、变为BACKUP、发生故障时执行。我在notify_master脚本里通常做这些事情:把自身MySQL的read_only参数改为OFF,写入恢复完成后启动半同步复制;在notify_backup里则相反,打开read_only,确保备库不会被客户端误写入。
#!/bin/bash # /etc/keepalived/notify_master.sh mysql -uroot -p'yourpass' -e "SET GLOBAL read_only=OFF;" mysql -uroot -p'yourpass' -e "STOP SLAVE; CHANGE MASTER TO ... ; START SLAVE;" # 其中CHANGE MASTER参数需要根据实际复制拓扑填写这套联动的核心思想是:Keepalived负责“让VIP漂移”,而notify脚本负责“让业务真正可用”。两者结合,才能组成一套完整的高可用切换方案。
5. 常见问题与故障排查实录
5.1 VIP漂移失败?先看这六个检查点
我排查Keepalived问题时有一套固定的排查顺序,分享出来供你参考:
| 排查项 | 操作方法 | 预期结果 |
|---|---|---|
| 进程状态 | systemctl status keepalived | 两端均为active (running) |
| 主备日志 | journalctl -u keepalived -f | 能看到状态切换和通告日志 |
| VRRP报文 | tcpdump -i eth0 vrrp | 主节点每秒发出源/目的224.0.0.18的报文 |
| 认证配置 | 对比两端auth_pass | 必须完全一致 |
| 防火墙 | iptables -L / firewall-cmd --list-all | 放行协议号为112的报文 |
| 网卡错误 | ip -s link show eth0 | 无大量RX errors和dropped |
其中防火墙这道坎我踩过最多次。Keepalived的VRRP通告协议不是TCP也不是UDP,它直接承载在IP协议号112上。firewalld默认的富规则或者iptables的默认DROP策略很容易把它拦掉。如果发现主节点正常发送通告、备节点却收不到,优先检查是不是防火墙策略把112协议丢了。
使用firewalld时,放行VRRP的命令如下:
sudo firewall-cmd --permanent --add-rich-rule='rule protocol value="112" accept' sudo firewall-cmd --reload5.2 日志层面如何定位状态切换异常
Keepalived的日志对于排查问题非常重要。默认它在/var/log/messages里输出日志(Ubuntu上在/var/log/syslog),如果日志量太大,也可以配置单独的日志文件。
一条正常的切换日志长这样:
VRRP_Instance(VI_1) Transition to MASTER STATE VRRP_Instance(VI_1) Entering MASTER STATE VRRP_Instance(VI_1) Sending gratuitous ARP on eth0 for 192.168.10.100比较常见的异常日志是:
failed to bind to interface eth0: No such device这说明配置文件里的interface名字写错了。Linux系统网卡命名可能是ens18、ens33,不一定叫eth0,用ip addr命令确认后再填。
还有一类异常是:
sending an invalid vrrp instance number通常是virtual_router_id取值范围不对,或者两端不一致。VRRP组的ID必须在两端完全一致,而且同一网段内不同的VRRP组不能使用相同的ID,否则互相混淆。
5.3 脑裂快速定位:用一次ARP请求来验证
当你怀疑发生脑裂时,最快的确认方法是在一台独立主机上分别ping VIP,然后查看VIP对应的MAC地址。正常情况下,VIP应该始终解析到当前MASTER节点的物理MAC。如果在连续几次请求中MAC地址在两个值之间交替出现,或者明显不是通过Keepalived多播同步出来的那个MAC,那大概率就是脑裂了。
更严谨一点的做法,可以在主备节点上分别执行:
ip addr show | grep 192.168.10.100如果两个节点上都显示VIP已绑定,脑裂就坐实了。此时第一步不是杀进程,而是保留现场。分别抓取两端的VRRP报文,确认谁能收到谁的通告。我遇到过一种情况:主节点A的通告能到B,但B到A的单向链路断了,导致A一直认为自己是唯一MASTER,B却认为A不可达而升级。这种情况需要检查交换机端口配置或者链路光模块是否异常,单纯重启Keepalived无法根治。
从长期运维角度看,Keepalived高可用集群的日常维护量其实很小,但一定要把日志采集和告警联动做好。我习惯在主备两个节点上都配置notify_fault脚本,一旦节点进入FAULT状态就把告警推到钉钉或企业微信机器人。集群很少出问题,但出了问题如果30秒内没有告警,高可用就失去了意义。
6. 实战总结与个人经验补充
文章写到这里,核心逻辑已经完整了。Keepalived高可用集群的基本功可以归纳为三句话:VRRP协议搞懂,健康检查写好,脑裂防护想清。这三件事放在任何场景都不会过时。
我个人在多次生产落地过程中最深刻的体会,是两个容易被轻视的细节。第一个是健康检查脚本的健壮性。脚本在Keepalived的track_script里被执行时,执行环境是受限的,PATH可能被裁剪。所以脚本里最好全路径写命令,比如/bin/bash、/usr/bin/curl,而不是裸写curl。另外脚本本身一定要有执行权限和正确的shebang,否则Keepalived会直接判定脚本执行失败。我见过一个团队排查了整整一个下午,最后发现是check脚本忘了chmod +x,Keepalived根本没办法执行。
第二个体会是切换速度要符合业务预期。Keepalived默认的通告间隔是1秒,加上fall=2的检查周期,整体故障切换时间大约5到8秒。如果你的业务只能容忍3秒内的中断,就需要把advert_int调小、检查间隔缩短。但同时也要评估网络抖动带来的误切换风险。我更倾向于在慢和稳之间做个平衡,把切换时间控制在5秒上下,同时用多路径网络提高主备之间链路的稳定性。高可用系统本身的目标不是追求绝对不挂,而是让故障的影响窗口足够短、恢复路径足够清晰。
最后分享一个小技巧。在验证Keepalived配置改动时,可以先在不重启服务的情况下用keepalived的配置检查语法:
sudo keepalived -t -f /etc/keepalived/keepalived.conf这个命令只会做语法解析,不会影响运行中的实例。等确认无误后再systemctl restart keepalived,能最大限度降低因为配置手误带来的业务中断风险。常用的虚拟服务器配置、健康检查脚本、notify脚本也建议纳入版本管理,和业务配置一起打标签发布,这样才能让高可用集群在长年运维中始终处于可控状态。