ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Keepalived+Haproxy高可用集群实战:从VIP漂移到故障转移完整记录

Keepalived+Haproxy高可用集群实战:从VIP漂移到故障转移完整记录 做高可用实验最怕的不是装不起来而是你花了一晚上把 Keepalived 和 Haproxy 都装好看到 VIP 落在某台机器上就理所当然地认为实验成功了。我最初也是这么想的直到在测试环境里故意把主节点的 haproxy 进程停掉发现 VIP 纹丝不动地停在原地外部请求还是全部打到旧节点上才意识到这套系统里最关键的其实不是安装配置而是故障到底怎么被感知、怎么被转移。这篇文章是我整理的一份 KeepalivedHaproxy 高可用集群实验记录。实验主线是用两台虚拟机搭建负载均衡层一台 Keepalived 负责 VIP 漂移和节点选举一台 Haproxy 负责七层流量分发然后完整验证主节点进程被杀、主机宕机、节点恢复抢占这几个常见故障场景。适合刚接触高可用集群、或者准备在生产环境落地 KeepalivedHaproxy 方案的运维朋友参考内容会从架构选型一路讲到故障实测再讲到几个我踩过的坑和对应的排查思路。1. 为什么下决心做KeepalivedHaproxy这个组合很多人在做负载均衡高可用实验时第一反应是 Keepalived 直接配合 Nginx或者干脆用 LVS 加 Keepalived。我不能说这些方案不对但如果你和我一样想在一个可控的实验环境里把故障转移机制看得清清楚楚KeepalivedHaproxy 这套组合确实有它独特的优势。1.1 市面上常见的三条技术路线先聊一下我在选型时对比过的三条路线。第一条是 LVSKeepalived这也是老牌高可用架构。LVS 跑在四层转发性能非常强适合超高并发场景。但 LVS 的 DR 模式要求后端服务器也需要绑定 VIP、抑制 ARP 响应后端节点要额外做一堆网络层配置TUN 模式又要处理隧道问题。做实验时这些细节很容易把注意力从高可用原理拉走变成网络调优排错。第二条是 KeepalivedNginx。Nginx 的配置大家很熟拿来做反向代理没有任何心理负担。但 Nginx 更擅长的是 Web 服务本身作为一个纯负载均衡代理它的健康检查能力、后端状态可视化不如 Haproxy 直观尤其是想在实验里实时看到某个后端节点从 UP 变成 DOWNHaproxy 的 stats 页面要比 Nginx 的日志检查方便好几个量级。第三条就是这篇实验的主角KeepalivedHaproxy。Keepalived 管 VIP、管节点状态选举Haproxy 管后端的流量分发和健康检查。两层职责非常清晰实验时你可以单独操作任何一层来观察另一层的反应。对于理解高可用集群来说这种模块边界清晰的架构是最好的教科书。1.2 这套组合要解决的问题从实际工作角度看这套组合解决的是接入层单点故障的问题。我有两台负载均衡器它们共同对外提供一个虚拟 IP用户只需要访问这个 VIP而不需要关心当前流量到底由哪台机器处理。正常情况下主节点承载流量当主节点整体宕机或者主节点上的 Haproxy 进程异常退出时Backup 节点需要无缝接管 VIP继续对外提供服务。注意这里我刻意区分了整机宕机和服务进程异常两种情况。Keepalived 本身作为一个进程它天然能感知到对端 Keepalived 是否还活着但它不会主动去感知你机器上的 Haproxy 是否健康。这个差异就是实验里最大的价值点——如果不做额外配置主节点的 Haproxy 挂掉VIP 并不会漂移对外服务照样会中断。这个结论我在不少团队里看到有人误判过所以这篇记录里会用专门一节展开讲。实验最终验证的目标有三个正常情况下流量能通过 VIP 均匀分发到后端主节点任意一种故障出现时VIP 能在几秒内漂移到备用节点主节点恢复后集群状态能回到预期设计并且不会因为频繁抢占造成服务抖动。2. 实验前置节点规划、初始化和后端准备实验环境不复杂但要先把网络规划和基础状态理清楚否则后面排查问题时根本分不清问题是出在 Keepalived、Haproxy 还是后端 Web 上。2.1 实验拓扑与IP地址分配我用两台虚拟机做负载均衡节点两台虚拟机做后端 Web 节点。操作系统用的是 RHEL 系发行版的最小化安装虚拟机网络全部放在同一个虚拟网段保证二层互通。具体规划如下节点角色主机名IP地址部署组件负载均衡主节点lb01192.168.56.101keepalived haproxy负载均衡备节点lb02192.168.56.102keepalived haproxyVIP虚拟IP-192.168.56.100随主备节点状态漂移后端Web节点1web01192.168.56.20httpd/nginx后端Web节点2web02192.168.56.21httpd/nginx之所以把两台负载均衡节点和两台后端放在同一个网段是因为 Keepalived 的 VRRP 协议默认通过组播方式交换心跳组播包在同一个二层网段里最稳定。如果你在实验环境里划分了复杂 VLAN那就要额外考虑组播穿越的问题这个我放到第六部分里单独讲。2.2 两台负载均衡节点的初始化清单两台 LB 节点的初始化动作完全一致我建议你全部做完再开始配置避免后面出现一边能通、一边不能通的玄学问题。首先关闭 SELinux实验环境里不关会引入很多权限层干扰尤其是脚本可执行权限和目录上下文问题。生产环境当然不能用这种方式处理但实验阶段图的是快速定位问题。setenforce 0 sed -i s/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config然后是防火墙。实验环境我直接关闭了 firewalld原因是 VRRP 组播包和 Haproxy 的健康检查请求经常会被防火墙策略挡住对新手来说排查起来很绕。生产环境不要照抄这个操作正确做法是放行 VRRP 协议和 80/443 端口。systemctl stop firewalld systemctl disable firewalld接下来同步时间。Keepalived 对时间同步不是特别敏感但如果你后面要用证书、审计日志对比节点事件时间不一致会让排查非常痛苦。顺手装一个常用工具包探活脚本里要用到 killall。yum install -y ntpdate psmisc ntpdate pool.ntp.org2.3 后端Web服务准备后端起一个简单的静态页面服务就行目的是让 Haproxy 的健康检查有明确目标。两个节点的内容要有区分度比如 web01 的 index.html 内容写入web01web02 写入web02这样通过 curl 访问 VIP 时能直观看到轮询效果。我建议在后端增加一个独立健康检查文件路径为/var/www/html/health内容只是一个状态关键词。为什么不直接检查/路径因为根路径可能受首页程序逻辑影响比如动态页面响应慢或者返回 500导致健康检查误判后端故障。独立的/health页面最稳定能明确表示这台机器的 Web 服务本身还活着。两个后端节点准备好后先在本地验证一下curl -s http://127.0.0.1/ curl -s -o /dev/null -w %{http_code}\n http://127.0.0.1/health看到各自标识和200状态码后端部分就算准备好了。3. Keepalived配置拆解VRRP通告、抢占策略和健康检查Keepalived 的核心作用不是负载均衡而是通过 VRRP 协议维护一个集群视角的 VIP。理解它的状态机是写配置的前提。3.1 主备节点配置对比先在主节点 lb01 上安装并编辑配置yum install -y keepalived vim /etc/keepalived/keepalived.conf主节点一份比较完整的配置长这样global_defs { router_id LB01 } vrrp_script chk_haproxy { script /etc/keepalived/check_haproxy.sh interval 2 weight -20 rise 2 fall 3 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.56.100/24 dev eth0 label eth0:1 } track_script { chk_haproxy } }备用节点 lb02 的配置结构完全一样区别只在四行router_id改成 LB02state改成 BACKUPpriority改成 90然后去掉virtual_ipaddress里的 tower 需求 不对两台机器都要保留virtual_ipaddress这一整段VIP 是集群共享的只是平时由 MASTER 持有。这里你要理解 VRRP 的几个核心参数。virtual_router_id是虚拟路由器编号同一个 VRRP 组里的所有节点必须一致不能和网段内其他集群冲突。priority是选举依据数字越大越优先成为 MASTER。advert_int是心跳间隔单位秒MASTER 每隔 1 秒往组播地址224.0.0.18发送 VRRP 通告Backup 节点连续三次没有收到通告就会认为 MASTER 失联然后按优先级重新选举。默认的抢占策略是优先级高的 BACKUP 一旦出现就会立刻抢占当前 MASTER这个行为在生产环境中不一定友好后面第七部分会讲怎么改成非抢占。auth_pass在实验环境里只是个轻量校验并不是安全机制。真实的 VRRP 报文中这串口令会以明文方式参与 HMAC 校验但它不能防止同网段内其他设备伪造 VRRP 报文只能挡一下配置粗心的误入者。3.2 check_haproxy.sh为什么探活脚本不能省略这是整个实验里最容易被忽略也最值得反复强调的部分。Keepalived 只检查对端 Keepalived 进程它根本不关心你的 Haproxy 是活着还是死了。如果不做track_script就一定会出现主节点 Haproxy 挂了VIP 却不走的问题。我的探活脚本放在/etc/keepalived/check_haproxy.sh#!/bin/bash if ! killall -0 haproxy 2/dev/null; then systemctl restart haproxy 2/dev/null sleep 2 if ! killall -0 haproxy 2/dev/null; then exit 1 fi fi exit 0脚本的逻辑是先检查 haproxy 进程是否存在。killall -0不是杀掉进程而是发送空信号检测进程状态存在返回 0不存在返回非 0。如果进程不存在先尝试自动拉起等待 2 秒后再检查一次如果拉起失败才返回退出码 1这个退出码会驱动 Keepalived 的权重切换机制。配合weight -20来看正常情况下主节点优先级是 100当脚本检测失败时优先级变成 80低于备用节点的 90于是备用节点抢占 VIP完成故障转移。这种做法的好处是 Keepalived 自身没有退出节点还处于集群内只是优先级被降级恢复后还能继续参与选举相比直接在脚本里systemctl stop keepalived这种方式更平滑也不会因为 Keepalived 崩溃造成日志和状态混乱。记得给脚本加上执行权限否则track_script会静默失败chmod x /etc/keepalived/check_haproxy.sh3.3 启动前必做检查配置文件改完不要直接systemctl start keepalived先用语法检查命令过一遍keepalived -t -f /etc/keepalived/keepalived.conf这条命令会解析配置并提示语法错误。Keepalived 的配置对关键字大小写敏感比如state必须是大写 MASTER/BACKUPinterface必须和系统实际网卡名一致这些错误通常不会在启动时报得很明显但会导致状态不正常。确认没问题后启动服务并设置开机自启systemctl enable --now keepalived systemctl status keepalived然后重点看一下 VIP 是否已经挂在主节点上ip addr show eth0正常情况主节点会出现eth0:1和192.168.56.100/24备用节点不会出现这个地址。只有确认 VIP 归属符合预期后面做故障转移实验才有基准。4. Haproxy负载均衡配置从前端到后端的一站式细节Keepalived 把 VIP 保持在了当前 MASTER 节点上那用户访问 VIP 的流量谁来处理Haproxy 就是干这个的。两个 LB 节点都要安装 Haproxy并且配置保持一致因为主节点故障后备用节点要能立刻提供同样的服务。4.1 一段能直接用的haproxy.cfg安装 Haproxy 后主配置文件在/etc/haproxy/haproxy.cfg我实验用的配置模板如下global maxconn 4096 log 127.0.0.1 local0 warn user haproxy group haproxy defaults mode http option httplog option redispatch timeout connect 3s timeout client 30s timeout server 30s frontend web_front bind *:80 default_backend web_back backend web_back balance roundrobin option httpchk GET /health server web01 192.168.56.20:80 check inter 3s fall 2 rise 3 server web02 192.168.56.21:80 check inter 3s fall 2 rise 3 listen stats bind *:9999 mode http stats enable stats uri /stats stats refresh 5s stats auth admin:lb123456这里几个关键点分别说。bind *:80用的是通配地址而不是直接绑定 VIP 的192.168.56.100:80。这样设计的好处是当 Keepalived 切换、VIP 还没落到本机时Haproxy 进程不会因为绑定失败而崩溃也不需要额外开启ip_nonlocal_bind。备机平时虽然没有 VIP但 Haproxy 进程照常运行一旦接管 VIP服务立即生效。option httpchk GET /health是健康检查方式。Haproxy 会每隔inter 3s向后端节点的/health路径发一次请求连续成功rise 3次标记为 UP连续失败fall 2次标记为 DOWN。后端被标记为 DOWN 后Haproxy 不会再把新请求转发过去这比 TCP 端口检测可靠得多因为端口通并不代表业务可用。balance roundrobin是轮询算法适合后端能力相近的场景。如果你的后端机器性能差异大后续可以换成leastconn或source等算法实验阶段轮询最直观。监听 9999 端口的listen stats段是 Haproxy 的统计页面访问http://192.168.56.100:9999/stats能看到后端节点实时状态。这个页面在实验里几乎是必需的因为你能直接看到 web01、web02 是绿色 UP 还是红色 DOWN不需要反复 curl 去猜。配置好后启动 Haproxysystemctl enable --now haproxy systemctl status haproxy4.2 健康检查路径和轮询结果验证在两个 LB 节点上都启动 Haproxy 后先在主节点上验证后端 IP 是否正确curl -s http://127.0.0.1/health正常情况下返回后端的 health 文件内容。然后再通过 VIP 访问for i in $(seq 1 10); do curl -s http://192.168.56.100/ | grep -o web[0-9]* done你会看到结果交替出现web01和web02说明轮询生效了。这时候顺手打开统计页面确认两个后端都是 UP。如果出现某个后端一直是 DOWN别急着调 Keepalived先去检查后端节点的/health文件权限和路径这个排查方向在第七部分我会再次强调。5. 故障转移实验实录杀进程、断电、恢复抢占配置全部就绪后就到了最有意思的部分故障注入实验。这部分我会按照实验前状态快照、进程级故障、整机故障、节点恢复四个步骤来记录每一步都给了验证命令和观察结果。5.1 实验前的状态快照做故障实验前先把集群的基准状态记录下来后面所有变化才有对照基础。在主节点 lb01 上查看ip -brief addr show systemctl status keepalived haproxy确认结果是lb01 持有192.168.56.100Keepalived 和 Haproxy 都是 active 状态。备用节点 lb02 上确认没有 VIPKeepalived 和 Haproxy 也都是 active 状态。这两台机器的 Haproxy 进程同时都在跑只是备用节点因为 VIP 不在本机暂时不接收来自 VIP 的流量而已。如果想看得更深入可以在两台机器上抓一下 VRRP 报文tcpdump -i eth0 host 224.0.0.18主节点方向能看到周期性的 VRRP 通告报文间隔正好 1 秒备用节点方向能看到它在持续监听这些报文。这一步可以让你从报文层面理解 Keepalived 的心跳机制比只看进程状态要扎实得多。5.2 第一轮实验主haproxy进程被杀第一轮模拟的是最常见的进程级故障主节点的 Haproxy 挂了Keepalived 还在跑。这时探活脚本就派上了用场。在主节点上执行systemctl stop haproxy然后立即观察集群变化。由于check_haproxy.sh会尝试自动重启 Haproxy所以实际效果取决于脚本逻辑。在我这个配置里手动systemctl stop haproxy后脚本会在下一个检测周期发现进程不存在然后拉起 Haproxy。如果进程被手动 stop 后可以正常被 systemd 拉起来你会看到 Haproxy 状态又恢复 active。为了真正模拟起不来的故障可以改成直接 kill 掉进程或者把 Haproxy 服务设置为 masked让脚本重启失败。我更推荐直接模拟脚本重启失败的场景因为你最终关心的是 VIP 会不会转移。操作如下systemctl mask haproxy pkill -9 haproxy大概 4 到 6 秒后观察 VIP 归属ip -brief addr show你会看到 lb01 上的192.168.56.100消失lb02 上出现了这个地址。同时从任意客户端访问http://192.168.56.100/请求依然能被 web01、web02 轮询回应说明 VIP 故障转移过程中服务没有中断。Haproxy 的统计页面这时也会正常展示 UP 的后端节点。这个实验的关键结论是Keepalived 的探活不是开箱即用的它需要你明确告诉它什么叫做主节点不健康。vrrp_script的行权机制让故障转移不再依赖进程崩溃这种极端事件而是把业务进程的健康状态纳入到了集群选举的权重体系里。5.3 第二轮实验直接关掉主节点第一轮是进程挂了第二轮是整机宕机。这个场景最直接也最容易理解——主节点彻底不工作了备用节点必须通过 VRRP 通告超时来感知故障。我先恢复第一轮的现场把 Haproxy 解除 mask确认主节点重新抢回 VIP集群回到初始状态。然后直接给主节点虚拟机执行关机命令模拟宕机poweroff此时主节点不再发送任何 VRRP 通告。备用节点连续三个心跳周期没有收到通告master_down_timer 超时就会从 BACKUP 切换到 MASTER。我在实验里观察到的漂移时间大概在 3 到 5 秒左右符合advert_int 1、连续丢失 3 个通告的设计预期。切换完成后在备用节点上执行ip -brief addr show journalctl -u keepalived -f可以看到 VIP 挂到了 eth0 上keepalived 日志会出现VRRP_Instance(VI_1) entering MASTER STATE的记录。这时候你从客户端访问 VIP服务依然可用。这里要注意一个细节Haproxy 的option redispatch配置在这种切换场景下很有帮助。它允许当某个后端连接出现问题、服务器没有及时响应时Haproxy 把请求重新分配给其他后端。故障转移瞬间如果有客户端恰好处在长连接上连接会被重置但新连接完全不受影响。5.4 第三轮实验主节点恢复后的抢占观察主节点重新开机后两台机器的 Keepalived 会重新通信。由于我配置里state MASTER和priority 100默认启用抢占策略lb01 一旦回来就会立刻抢占回 VIPlb02 会从 MASTER 重新降回 BACKUP。我在恢复主节点电源后等了两分钟然后分别查看两台机器的 VIP 归属发现 VIP 已经回到 lb01 上。lb02 的日志里会出现VRRP_Instance(VI_1) entering BACKUP STATE一切正常。但这个一切正常里藏着一个生产环境常见的问题抢占往往意味着一次不必要的网络切换。比如主节点只是短暂重启或者网络抖动恢复后立刻抢回 VIP会造成连接再次中断。对于无状态的 Web 服务来说影响不大但如果后面挂着 Redis、数据库代理或者长连接网关这种抢占抖动就可能影响业务。实验阶段你看到了默认行为生产环境就要针对这个行为做修正这部分我在第七部分给了具体做法。6. 实验里值得记录的三个坑和排查过程毕竟是实验不踩几个坑都说不过去。这一节我把整个实验里印象最深的三个问题记录下来每个都给出完整的排查链路而不是直接告诉你答案。6.1 两个MASTER同时出现先看组播通不通第一次做双节点实验时我遇到一个诡异现象两台机器状态都显示 MASTERVIP 都出现在各自网卡上。外部访问 VIP 时由于两台机器都声称拥有同一 IP网络表现完全随机时而通、时而不通。排查思路首先锁定 VRRP 通信。Keepalived 依赖组播报文让 Backup 感知 Master如果 Backup 收不到任何 Master 的通告它就会认为 Master 失联自己抢占 MASTER这就是典型的脑裂。我先在备用节点上抓包看能否收到主节点的 VRRP 报文tcpdump -i eth0 vrrp结果发现备用节点完全收不到任何 VRRP 报文。进一步排查发现是系统防火墙把 VRRP 组播给拦了。实验环境我直接关掉 firewalld 解决了但生产环境不能这么粗暴正确做法是放行 VRRP 协议firewall-cmd --permanent --add-protocolvrrp firewall-cmd --reload如果你所在网络环境不支持组播比如很多虚拟私有云环境那就需要改成单播模式在vrrp_instance里指定对端地址unicast_src_ip 192.168.56.101 unicast_peer { 192.168.56.102 }这个配置让 Keepalived 用单播方式交换心跳不再依赖组播。我后来在需要跨三层网络搭建 Keepalived 时也用到过这个方法实用性很强。6.2 haproxy进程挂了但VIP纹丝不动问题出在探活链路上这是我开头说过的那个问题配置完 Keepalived 但没配track_scriptHaproxy 进程挂掉后 VIP 不转移。当时我以为 Keepalived 作为高可用组件理所当然能保护所有本地服务事实证明并不是。排查时我先手动执行探活脚本确认脚本本身能正确返回非 0 退出码。然后查看 Keepalived 日志发现日志里根本没有脚本执行记录。再排查才发现脚本文件没有加执行权限track_script引用的脚本无法运行Keepalived 只能在日志里报错但不会同步到我的注意力范围内。这个坑提醒我对于 Keepalived 的探活链路每一环都要单独验证。脚本能不能执行、退出码是不是预期值、vrrp_script是否被track_script引用、weight值是否让主备产生优先级翻转这些环节缺一不可。排查顺序也建议从脚本本身往上层走不要在没验证脚本的情况下就去怀疑 Keepalived 配置。6.3 restart keepalived造成VIP瞬断第三个坑来自一次常规操作我在主节点上执行systemctl restart keepalived想应用新配置结果发现 VIP 出现了明显的瞬断外部 curl 访问在这几秒内失败。原因是 Keepalived 重启会先把网卡上的 VIP 摘掉再重新挂上。这个摘掉和挂上之间有一个很小的窗口VIP 不在任何节点上服务自然中断。实验里我用了三台客户端机器并发测试丢包时间在 1 到 3 秒左右与 Keepalived 重启耗时基本一致。生产环境如果要调整 Keepalived 配置我的建议是不要直接在主节点上重启而是先改备节点然后通过降低主节点优先级或者直接切换 VIP 的方式让流量先落到备节点再重启主节点。如果坚持原地重启那就要接受 VIP 瞬断的代价。这个操作层面的细节通常只有真实踩过坑才会放在心上。7. 从实验台到生产环境我做过的几处配置修正实验验证的是机制生产关注的是稳定。把这套架构从实验台搬到生产环境时我做了三处比较重要的配置修正这里一并分享。7.1 nopreempt 替代默认抢占默认抢占模式下主节点从短暂故障中恢复后会立刻抢回 VIP。这在很多业务场景里并不是最优解因为每次抢占都是一次无谓的网络切换。生产环境我建议改成非抢占模式。具体做法是两台节点的state都配置为 BACKUP并在vrrp_instance中加入nopreemptvrrp_instance VI_1 { state BACKUP nopreempt priority 100 ... }非抢占模式下高优先级节点不会因为自己恢复就立刻抢占当前 MASTER它会一直保持 BACKUP 状态直到当前 MASTER 发生故障才会重新参与选举。这样就把切换频率降到了最低对长连接类服务非常友好。7.2 双VIP互为主备让两台机器都有活干单主备模式下备机平时完全空闲有点浪费。生产环境我倾向于做双 VIP 互为主备定义两个vrrp_instance第一个 VIP 的主节点是 lb01第二个 VIP 的主节点是 lb02彼此互为备份。vrrp_instance VI_1 { state MASTER priority 100 virtual_router_id 51 virtual_ipaddress { 192.168.56.100/24 dev eth0 label eth0:1 } } vrrp_instance VI_2 { state BACKUP priority 90 virtual_router_id 52 virtual_ipaddress { 192.168.56.101/24 dev eth0 label eth0:1 } }lb02 上的配置正好反过来VI_1 是 BACKUPVI_2 是 MASTER。这样两个 VIP 同时对外提供服务正常情况下两台 LB 都在承接流量任何一台故障另一台会接管它的 VIP。这个模式充分利用了硬件资源也让故障切换时业务影响面更小是我在生产环境最推荐的一种落地方式。7.3 最后一条建议先单机跑再滚动引入冗余最后聊一个和配置无关、但同样重要的建议。不要一开始就把两台节点同时上线到生产流量里。先把一台 Haproxy 接入真实流量跑几天看日志、看统计页面的后端状态、看 Keepalived 的心跳稳定性确认没有任何异常后再把第二台节点以 BACKUP 身份加进去。在这个滚动过程中你会发现很多实验环境模拟不出来的问题比如后端健康检查路径在生产环境响应慢、防火墙策略对 VRRP 组播有隐性拦截、NAT 环境下客户端真实 IP 取不到等等。KeepalivedHaproxy 这套架构本身很成熟但真正让它在生产环境稳定运行的往往不是配置文件有多完美而是你愿意花多少时间去观察它在真实流量下的行为。实验台给你的是一套可复制的机制生产环境给你的才是最终答案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进