恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Nginx高可用实践:Keepalived主从热备与故障切换详解
首页
资讯中心
/
Nginx高可用实践:Keepalived主从热备与故障切换详解
Nginx高可用实践:Keepalived主从热备与故障切换详解
发布时间:2026/10/12 22:30:25
我遇到过不止一次Nginx进程悄悄退出的情况。最难忘的是某天凌晨两点爆出的那通告警——某业务系统前端的Nginx突然完全无响应用户请求全部卡在连接阶段等我们摸黑登录服务器、检查日志、重启服务、确认恢复业务已经断了将近四十分钟。那台Nginx就是典型的单机部署没有任何冗余俗称单点。那次事故之后我所在的团队老老实实地把Nginx高可用集群排上了日程做的就是经典的NginxKeepalived主从热备方案。今天这篇文章我就把从方案选型、环境规划、配置落地到模拟故障切换、处理各种真实坑的完整过程整理出来希望对正在计划给Nginx加一层保障的运维或后端同学有实际参考价值。1. 单点事故的代价Nginx高可用到底在解决什么问题1.1 Nginx进程退出之后的连锁反应单点故障这件事听起来很简单——一台机器坏了服务就断了。但真正实施下来连锁反应远比想象中麻烦。那次事故里Nginx不只是负责转发请求它前面连着用户入口后面连着应用服务器。Nginx一挂所有请求进不来后端的应用其实还活着但用户根本够不着。业务侧反馈过来的时候我们连到底是网络问题、DNS问题、防火墙问题还是Nginx问题都排查了好一会儿。复盘时我们发现单点Nginx的问题不只是进程挂掉这一种可能。机器硬件故障、内核崩溃、磁盘写满导致的异常、被人误操作改了配置后reload失败、机房网络闪断……任何一种情况都能让Nginx从正常变为不可用。有的场景下服务器可能还开着但进程已经不在了有的场景下机器彻底失联。想要应对这一整类问题就必须从架构层面做冗余让故障发生时业务能自动切走而不是等工程师被叫起来手动恢复。1.2 主从热备模型虚拟IP与故障漂移主从热备的基本思路通俗地说就是让两台Nginx节点组成一个双人值守的小组。对外只暴露一个虚拟IPVIP平时由主节点MASTER持有这个VIP并处理全部流量备节点BACKUP在主节点正常时处于待命状态不接流量只通过心跳监控主节点的健康状况。一旦主节点失联或Nginx进程异常备节点会迅速把VIP抢到本机上接管服务整个过程对调用方基本不可见。这里最关键的设计就是虚拟IP。客户端访问的始终是这个固定的VIP而不是某一台具体服务器的IP。所以无论VIP落在主节点还是备节点上对用户来说入口没有变。这套思路可以类比小区配置两个值班保安住户进出只认门卫室这个位置今天A保安在岗还是B保安在岗住户不需要关心只要门卫室始终有人就行。两台Nginx之间怎么知道对方还活着这就是Keepalived的工作了。Keepalived基于VRRP协议实现节点间的状态同步和自动failover它会在两台设备之间周期性地发送心跳报文。心跳连续丢失达到阈值备节点就判定主节点故障立刻在本机绑定VIP、对外发送免费ARP通告让交换机把虚拟IP对应的流量切换到本机。1.3 为什么锁定了Keepalived而不是其他高可用方案很多人问过我高可用方案那么多为什么不直接用DNS轮询、云厂商的负载均衡或者干脆上LVS我的体会是方案没有绝对的好坏关键是看场景。DNS轮询的做法是给域名解析多个A记录指向不同Nginx节点。好处是配置简单但坏处也很明显——DNS解析结果存在客户端和Local DNS缓存里某一台节点宕机后仍然会有大量请求打到故障IP上。而且这种方案本质上是负载均衡不是主备容灾单个节点故障时受影响的范围不好控制。云负载均衡比如各种云SLB在公有云环境里确实好用托管维护、自带健康检查。但如果你所在的环境是自有机房或者客户明确要求内网闭环再或者出于成本考虑不想引入额外的LB层Keepalived这种轻量级方案就很有优势。它不需要额外硬件不改变现有Nginx架构两台服务器装上软件、写好配置就具备故障漂移能力学习和维护成本都不高。2. 环境规划与基础准备主备节点、VIP和系统参数2.1 节点与IP规划动手之前一定要画一张清晰的规划表别等配置写完了才发现VIP网段和业务网段冲突或者两台节点根本不在同一个二层网络里。以我们当时的部署为例两台Nginx节点跑在同一个交换机二层网络中使用私有地址段。规划如下角色主机名物理IP虚拟IP用途MASTERngx-node-01192.168.1.10192.168.1.100接收VIP流量主节点BACKUPngx-node-02192.168.1.11192.168.1.100主节点故障时接管VIP虚拟IP必须和两台节点处于同一广播域内Keepalived的VRRP报文依赖组播或多播广播跨了网段就会失效。生产环境中建议把Keepalived的心跳通信单独规划一条网线或独立网段避免业务流量高峰期把心跳报文也挤丢了这是后话。操作系统我们用的是CentOS 7.9内核版本3.10Keepalived版本1.3.5。当然你用其他主流Linux发行版也完全可以配置思路一致只是包管理命令和部分系统路径会有差异。2.2 安装Nginx并准备差异测试页Nginx的安装这里不展开细说yum源直接装即可。装完之后我给两台节点分别准备了一个不同的首页文件用于后面的切换测试。主节点的/usr/share/nginx/html/index.html内容h1Node: ngx-node-01/h1 pThis is MASTER nginx instance./p备节点的/usr/share/nginx/html/index.html内容h1Node: ngx-node-02/h1 pThis is BACKUP nginx instance./p同时把Nginx设为开机自启systemctl enable nginx systemctl start nginx这样设置的目的是为了在验证故障切换时能通过curl http://192.168.1.100返回的不同页面内容直观地判断当前VIP到底落在哪台节点上。2.3 系统基础配置防火墙、SELinux和时间同步这块最容易在部署时被忽略却在关键时刻拖后腿。首先是防火墙很多人的Keepalived一直报不出心跳排查了一圈发现是防火墙把VRRP协议挡了。VRRP使用的不是普通的TCP/UDP端口而是IP协议号112CentOS中需要放行对应的规则。firewall-cmd --permanent --add-protocolvrrp firewall-cmd --permanent --add-servicehttp firewall-cmd --reload如果是旧版iptables要放行VRRP协议可以参照iptables -A INPUT -p vrrp -i eth0 -j ACCEPT然后是SELinux。默认的enforcing模式下Keepalived调用外部脚本、绑定虚拟IP、发送通告都可能被拦截排查起来非常隐蔽。我们当时直接设置为permissive模式保证功能先跑通生产环境需要按自己公司的安全基线来权衡。setenforce 0 sed -i s/^SELINUXenforcing/SELINUXpermissive/ /etc/selinux/config时间同步也必须做好。Keepalived自身对时间不是特别敏感但整个集群里的日志时间线、证书有效期判断、业务系统时间戳都依赖一致的时间源。用chrony统一对时避免后面排查问题时日志时间对不上。3. Keepalived安装与核心配置拆解心跳、优先级和检测脚本3.1 VRRP的工作机制与Keepalived的角色切换Keepalived的核心是VRRP虚拟路由冗余协议。协议的思想很简单一组路由器这里就是Nginx节点共同对外提供一个虚拟IP其中优先级最高的节点成为Master负责响应VIP上的请求Master周期性发送VRRP广告报文其余节点收到后确认Master还活着继续待在Backup状态。如果Backup在连续几个广告周期内都没收到Master的报文就会认为Master已经不可用于是按照优先级最高的Backup顺序进入Master状态接管VIP。Keepalived在这个基础上又增加了track_script机制——不仅检测网络状态还能定期执行自定义脚本根据脚本返回值决定是否降低本节点优先级甚至主动移交VIP。理解这个机制之后再回来看配置就会非常清晰优先级、心跳间隔、检测脚本这三个要素决定了整个集群的切换行为。3.2 主节点Keepalived配置全解析安装Keepalivedyum install -y keepalived主节点的配置文件/etc/keepalived/keepalived.conf如下global_defs { router_id ngx-node-01 enable_script_security } vrrp_script check_nginx { script /usr/local/bin/check_nginx.sh interval 2 timeout 2 weight -20 fall 2 rise 2 } vrrp_instance VI_NGX { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 123456 } track_script { check_nginx } virtual_ipaddress { 192.168.1.100/24 dev eth0 } }逐段解释一下关键配置的含义。global_defs里面的router_id是当前节点的标识在两台节点上必须不同。enable_script_security用于限制Keepalived执行外部脚本时的安全性避免脚本环境变量被滥用官方文档建议开启。vrrp_script check_nginx定义了一个检测任务。script指定检测脚本绝对路径interval 2表示每2秒执行一次timeout 2表示脚本执行超过2秒视为超时weight -20表示脚本返回非0时当前节点优先级降低20fall 2和rise 2表示连续失败2次才判定服务异常连续成功2次才判定恢复这是为了防止脚本偶发抖动导致频繁切换。vrrp_instance VI_NGX是VRRP实例的配置。state MASTER声明该节点初始角色是主节点interface eth0指定心跳报文走哪块网卡一定是物理网卡而不是VIP所在的虚拟网卡virtual_router_id 51是一个虚拟路由器ID同一组主备节点必须保持一致priority 150是节点优先级数字越大越优先成为Masteradvert_int 1是心跳报文发送间隔单位秒通常保持默认1秒即可。authentication块配置节点间认证。auth_type PASS表示密码认证auth_pass是明文密码。注意Keepalived只取密码前8位所以密码不要超过8个字符否则超出部分会被忽略反而容易造成两台节点配置不一致。virtual_ipaddress配置虚拟IP。192.168.1.100/24 dev eth0表示VIP绑定在eth0上子网掩码24位。这里有个细节写成带掩码和dev的形式比只写一个IP更明确能避免某些系统下路由和ARP异常。3.3 备节点配置与主节点的差异备节点的配置大体复制主节点只需要改动四个地方router_id、state、priority和节点自身标识。备节点配置如下global_defs { router_id ngx-node-02 enable_script_security } vrrp_script check_nginx { script /usr/local/bin/check_nginx.sh interval 2 timeout 2 weight -20 fall 2 rise 2 } vrrp_instance VI_NGX { state BACKUP interface eth0 virtual_router_id 51 priority 120 advert_int 1 authentication { auth_type PASS auth_pass 123456 } track_script { check_nginx } virtual_ipaddress { 192.168.1.100/24 dev eth0 } }两个节点优先级相差30这个差值必须大于检测脚本的weight绝对值。主节点检测失败时优先级从150降到130仍然大于备节点的120主节点不会因为一次检测失败就把VIP让出去。但如果主节点整体宕机网络断开、机器失联备节点收不到任何VRRP报文就会直接接管VIP此时优先级差就不再重要。理解这个优先级与weight之间的数值关系才能灵活设计进程异常时切换、机器宕机时切换、轻微故障时不来回抖的预期行为。3.4 检测脚本Nginx是否活着的准确判断检测脚本是Keepalived配置中灵魂般的存在它决定了故障到底是整机失联还是Nginx进程挂了。我们的脚本如下#!/bin/bash # 检测 nginx master 进程是否存在 if pidof nginx /dev/null 21; then exit 0 else exit 1 fi脚本逻辑很简单pidof nginx能查到nginx进程就返回0否则返回1。写完一定要加上执行权限chmod x /usr/local/bin/check_nginx.sh这个脚本有两个常见的坑。一个是不要用ps -ef | grep nginx | grep -v grep | wc -l这类方式因为grep自身进程可能匹配到关键字而且统计数量容易误判另一个是脚本路径不要放在/etc/keepalived/目录下有些Keepalived版本在启动或重载时会清理该目录下的非配置文件内容脚本容易莫名其妙消失。放在/usr/local/bin这类系统路径里更安全。也可以做得更严格一点比如用Nginx的健康检查接口配合curl来判断进程是否真的在服务请求而不只是进程存在。但对于主从热备场景进程级检测通常够用过度复杂的健康检查反而会引入新的误判因素。4. 主从热备的验证实验模拟宕机、切换和回切全流程4.1 启动集群并确认VIP归属配置全部就绪后在两台节点上启动Keepalivedsystemctl enable keepalived systemctl start keepalived先看主节点上VIP是否绑定成功ip addr show eth0正常情况下主节点的eth0上除了物理IP192.168.1.10还会多出一个192.168.1.100/24的地址。备节点上不应该出现这个VIP。此时访问VIP应该返回主节点的页面curl http://192.168.1.100返回内容Node: ngx-node-01说明流量确实打在主节点上。再确认心跳状态systemctl status keepalived或者看消息日志tail -f /var/log/messages | grep VRRP能看到类似VRRP_Instance(VI_NGX) Entering MASTER STATE的日志说明节点正常进入了Master状态。4.2 场景一Nginx进程异常触发切换生产环境中进程被误杀、内存不足被OOM杀掉、配置文件错误导致worker进程退出这些都是比较常见的故障。我们先用最直接的方式模拟在主节点上停掉Nginx。systemctl stop nginx然后观察主节点的日志会看到检测脚本连续执行失败触发优先级降低最终退出Master状态。同时在备节点上能看到进入Master状态的日志。几条典型的日志片段VRRP_Script(check_nginx) failed VRRP_Instance(VI_NGX) Received lower prio advert, forcing new election VRRP_Instance(VI_NGX) Entering BACKUP STATE备节点日志VRRP_Instance(VI_NGX) Transition to MASTER STATE VRRP_Instance(VI_NGX) Sending gratuitous ARP on eth0 VRRP_Instance(VI_NGX) Entering MASTER STATE备节点进入Master后执行ip addr show eth0可以看到VIP192.168.1.100已经出现在备节点上而主节点上的VIP已经消失。再次访问VIPcurl http://192.168.1.100返回内容Node: ngx-node-02。整个切换过程从Nginx停止到VIP漂移完成大约3到5秒取决于检测脚本的间隔和失败次数设置。用户侧最多只会感觉到一次短暂的连接抖动不会出现长时间无法访问。4.3 场景二整机宕机与恢复后的抢占回切更严重的故障是主节点整台机器宕机比如断电、硬件故障、系统崩溃。这时主节点连心跳报文都不再发送备节点等待超时后直接接管VIP和上面观察到的现象基本一致。区别在于整机宕机时备节点完全收不到Master的VRRP报文所以它在advert_int乘以几倍的超时窗口之后就会切换通常也是一到三秒级别。接下来看回切行为。把主节点的Nginx和Keepalived都恢复起来systemctl start nginx systemctl start keepalived在默认配置下主节点启动后会重新以150的优先级参与选举备节点收到更高优先级的报文后自动退出Master状态VIP会回切到主节点。这个行为叫抢占preempt。从高可用角度来说抢占能保证流量回到配置更完善、资源更充裕的主节点上但也会带来一个问题如果主节点反复抖动每次回切都会造成一次连接中断。如果业务不希望主节点恢复后立刻抢回VIP可以在备节点配置中加入nopreempt让角色切换只在故障发生时触发正常情况下VIP稳定在当前节点。但要注意nopreempt的使用有讲究需要在vrrp_instance中配置并且主节点通常也要把state设为BACKUP单纯通过优先级区分谁是事实主节点否则抢占行为不会被完全抑制。4.4 切换过程中的连接保持问题主从热备能解决服务入口不中断但它解决不了正在进行的请求不中断。举个例子用户正在通过VIP下载一个大文件或者长轮询接口正在等待数据。切换发生时TCP连接的状态存储在主节点的内核协议栈上而备节点上并没有这些连接状态所以VIP漂移后这些存量连接会全部断开用户需要重新发起请求。对于大多数HTTP短连接业务来说影响的只是切换瞬间的少量请求重试一次就够了。如果业务对中断容忍度极低单纯NginxKeepalived是不够的还需要在更上层配合会话保持策略、客户端重试机制或者引入四层负载均衡方案把连接层的状态管理从Nginx节点中抽离出来。搞清楚主从热备的边界比盲目追求零切换更实际。5. 实际部署中容易踩的坑与调优建议5.1 那些隐蔽的坑防火墙、SELinux、脚本权限和脑裂每次帮别人排查Keepalived问题最后发现的大多不是配置理解问题而是这些不起眼的环境问题。防火墙不放行VRRP是最常见的一个。现象是两台节点都认为自己是Master或者备节点一直收不到心跳日志里没有任何切换动作。检查方法很简单在两台节点上tcpdump -i eth0 vrrp看能否抓到对方的VRRP报文。抓不到基本就是被防火墙或交换机策略挡住了。SELinux影响的是脚本执行。我遇到过检测脚本明明有执行权限手动跑也没问题但Keepalived执行时就是失败的情况日志里只显示脚本超时或返回异常。后来发现是SELinux拦截了对脚本文件的访问。要么把脚本所在目录的权限调整好要么直接临时改成permissive模式验证。脚本权限更基础却最容易犯。chmod x少了配置写错了路径脚本用了#!/bin/bash但文件格式是CRLF这些都会导致检测脚本不可用。建议写完脚本先手动跑一次确认返回码符合预期再交给Keepalived去调用。脑裂是双节点集群最怕的问题即两个节点都认为自己是MasterVIP同时出现在两台机器上。这种情况会导致流量在不同节点之间来回抖动甚至出现数据不一致。触发原因通常是心跳网络中断而业务网络正常或者防火墙半开只放行了部分方向。缓解措施包括心跳线用独立网段或直连网线检测脚本里加上对端IP连通性检查以及利用告警机制及时察觉双Master状态。5.2 调优参数与状态通知让集群可控可观测Keepalived配置文件里值得优化的参数不少我挑几个重点说说。advert_int决定心跳频率。默认1秒适合大多数场景调小到0.5秒能加快故障感知但会增加网络压力和误判概率调大到2秒以上则切换变慢适合心跳网络带宽受限的场景。这个值需要和检测脚本的interval配合考虑不要为了追求极端速度牺牲稳定性。weight的取值直接影响切换行为。如果你希望只有进程死掉才切换weight的绝对值必须小于主备优先级差值如果你希望检测失败就立即切换weight可以把主节点优先级压到备节点之下。我们配置里主备优先级相差30、weight是20就意味着进程异常时主节点还在只是优先级降到130继续留着。但如果加上fall 2的连续失败判定实际效果比单纯看weight更可靠因为它是基于连续失败次数的综合判断。状态通知是个容易被忽略但非常实用的功能。可以在vrrp_instance中配合notify_master、notify_backup、notify_fault在角色切换时触发自定义脚本发送钉钉、企业微信或邮件告警。实现故障发生时第一时间知道而不是等业务侧反馈才知道。一个简单的通知脚本示例如下#!/bin/bash # /usr/local/bin/notify_keepalived.sh case $1 in master) echo $(date) become MASTER /var/log/keepalived-notify.log # 在这里调用 curl 发送 webhook 告警 ;; backup) echo $(date) become BACKUP /var/log/keepalived-notify.log ;; fault) echo $(date) enter FAULT /var/log/keepalived-notify.log ;; esac配置中对应加入notify_master /usr/local/bin/notify_keepalived.sh master notify_backup /usr/local/bin/notify_keepalived.sh backup notify_fault /usr/local/bin/notify_keepalived.sh fault有了通知主从切换不再是一个黑盒事件每次切换都有迹可循。5.3 从主从到更复杂的形态双活与热备的扩展主从热备是一个很好的起点但不是终点。当两台Nginx机器跑在主从模式下时备机大部分时间都是空闲的从资源利用率角度看有点浪费。如果业务量增长可以考虑把架构演进为互为主备的双活模式两台节点各自带一个VIP分别承接一部分业务流量同时互为对方的备份。Keepalived配置里增加第二个vrrp_instance让A节点在实例1中是MASTER、在实例2中是BACKUPB节点则反着来这样两台机器的资源都利用起来了。还可以在Nginx前面加LVS或四层负载均衡形成两级的高可用体系。或者把Keepalived的检测脚本扩展成更完整的健康检查比如检查后端应用端口、检查磁盘空间、检查系统负载让集群的故障切换决策更聪明。但这些都属于有了主从热备之后的下一步延伸先把基础的主从热备稳定跑通再根据业务需要逐层加码才是稳妥的演进路径。最后分享一个我个人的习惯每半年至少手动做一次主从切换演练把切换脚本、通知脚本、备节点上的配置一致性全部验证一遍。人类是健忘的动物集群也一样——太久不切换真的到了需要切换的那天往往会发现备节点配置文件早就过期了或者脚本权限被人动过或者防火墙策略早就变了。演练不一定能完全避免事故但至少能保证在事故来临时你不需要一边盯着日志一边祈祷。