ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Keepalived高可用集群实战:VRRP协议、VIP漂移与脑裂防护详解

Keepalived高可用集群实战:VRRP协议、VIP漂移与脑裂防护详解 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 8Keepalived配置几乎不用改稍微调整一下语法就能继续工作。这一点在追求稳定压倒一切的生产环境里是最重要的加分项。2. 核心机制拆解VRRP协议、状态切换与脑裂防护2.1 VRRP协议是如何实现主备协商的Keepalived的底层核心是VRRPVirtual Router Redundancy Protocol虚拟路由冗余协议它模拟了一组路由器共用一个“虚拟路由器”的效果。多台物理设备配置相同的VRRP实例对外表现为同一个虚拟IP和虚拟MAC地址。局域网内的客户端把网关指向这个VIP无论数据包由哪台物理设备处理从客户端视角看都是到达了同一个“虚拟路由器”。协议运行的标准流程是这样的每台参与VRRP的设备上都有一个1到255之间的优先级数字默认配置通常是MASTER节点优先级为100BACKUP节点优先级为90。优先级最高的设备会成为MASTER负责响应VIP上的ARP请求并周期性发送VRRP通告报文。这个通告报文的默认发送间隔是1秒目的地址是组播地址224.0.0.18。备节点收到通告后会重置一个定时器。如果连续三个通告周期即3秒内都没有收到来自MASTER的通告备节点就认为MASTER已经不可达于是进行状态切换自己变成MASTER。这里有一个关键细节备节点升为MASTER后会立即发送一次免费ARPGratuitous 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始终认为自己是MASTERB也认为自己是MASTER。防范脑裂没有一招制敌的办法我的习惯是双重保险Keepalived的心跳网卡尽量用独立的物理网卡和独立交换机同时业务程序层面再做一次仲裁判断。比如MySQL高可用场景可以编写notify脚本在发生MASTER切换时检查本节点是否真正持有数据库的写锁或半同步复制是否正常如果发现异常立即降级为BACKUP。3. 实操配置环境规划、安装与基础keepalived.conf详解3.1 节点规划与网络参数设计以最经典的双节点主备架构为例假设我们要为一组Nginx反向代理做高可用。先规划环境参数主机A主主机B备操作系统Rocky Linux 9.2Rocky Linux 9.2业务IP192.168.10.11192.168.10.12VIP192.168.10.100192.168.10.100心跳网卡eth1eth1优先级10090抢占模式关闭关闭这里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_02priority改成90其它内容完全一致。这里有一个细节值得说明我把两台机器的state都写成BACKUP然后依靠priority来区分主备。这是官方推荐的“平等模式”好处是配合nopreempt后启动顺序不会影响最终的角色归属。如果写成state MASTER state BACKUP的传统配置并且同时开启nopreempt可能会出现在MASTER节点尚未启动时BACKUP抢先占用了VIP之后MASTER启动却不抢占的情况角色和预期会不一致。vrrp_instance里每个参数的含义我们要心里有数state本节点的初始状态推荐都写BACKUP。interfaceVRRP通告报文从哪个物理网卡发出必须和业务网卡一致。virtual_router_id同一个VRRP组标识范围0到255。两台机器上这个值必须一致否则组不成集群。priority优先级范围1到254。越大越容易成为MASTER。advert_intVRRP通告间隔秒数。默认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 21; 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秒间隔配合fall2整体故障感知时间约4到6秒业务接受度比较好。这个健康检查机制还有一层隐藏逻辑值得说明当备节点执行健康检查失败时它不会影响自己的BACKUP状态只是不能升MASTER。当主节点健康检查失败时会立刻降级为BACKUPVIP随即漂移到备节点。通过这种方式业务的高可用粒度从“机器活着”细化到了“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_ignore1 sudo sysctl -w net.ipv4.conf.lo.arp_announce2 sudo sysctl -w net.ipv4.conf.all.arp_ignore1 sudo sysctl -w net.ipv4.conf.all.arp_announce2 # 在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 -pyourpass -e SET GLOBAL read_onlyOFF; mysql -uroot -pyourpass -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-rulerule protocol value112 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一直认为自己是唯一MASTERB却认为A不可达而升级。这种情况需要检查交换机端口配置或者链路光模块是否异常单纯重启Keepalived无法根治。从长期运维角度看Keepalived高可用集群的日常维护量其实很小但一定要把日志采集和告警联动做好。我习惯在主备两个节点上都配置notify_fault脚本一旦节点进入FAULT状态就把告警推到钉钉或企业微信机器人。集群很少出问题但出了问题如果30秒内没有告警高可用就失去了意义。6. 实战总结与个人经验补充文章写到这里核心逻辑已经完整了。Keepalived高可用集群的基本功可以归纳为三句话VRRP协议搞懂健康检查写好脑裂防护想清。这三件事放在任何场景都不会过时。我个人在多次生产落地过程中最深刻的体会是两个容易被轻视的细节。第一个是健康检查脚本的健壮性。脚本在Keepalived的track_script里被执行时执行环境是受限的PATH可能被裁剪。所以脚本里最好全路径写命令比如/bin/bash、/usr/bin/curl而不是裸写curl。另外脚本本身一定要有执行权限和正确的shebang否则Keepalived会直接判定脚本执行失败。我见过一个团队排查了整整一个下午最后发现是check脚本忘了chmod xKeepalived根本没办法执行。第二个体会是切换速度要符合业务预期。Keepalived默认的通告间隔是1秒加上fall2的检查周期整体故障切换时间大约5到8秒。如果你的业务只能容忍3秒内的中断就需要把advert_int调小、检查间隔缩短。但同时也要评估网络抖动带来的误切换风险。我更倾向于在慢和稳之间做个平衡把切换时间控制在5秒上下同时用多路径网络提高主备之间链路的稳定性。高可用系统本身的目标不是追求绝对不挂而是让故障的影响窗口足够短、恢复路径足够清晰。最后分享一个小技巧。在验证Keepalived配置改动时可以先在不重启服务的情况下用keepalived的配置检查语法sudo keepalived -t -f /etc/keepalived/keepalived.conf这个命令只会做语法解析不会影响运行中的实例。等确认无误后再systemctl restart keepalived能最大限度降低因为配置手误带来的业务中断风险。常用的虚拟服务器配置、健康检查脚本、notify脚本也建议纳入版本管理和业务配置一起打标签发布这样才能让高可用集群在长年运维中始终处于可控状态。
返回列表