
1. 为什么要搭建Nginx高可用集群1.1 单点故障的现实困境很多人刚开始接触Nginx时都是在单台服务器上部署做反向代理、静态资源托管、负载均衡转发。这套玩法在开发环境和中小流量场景下没什么问题但当它真正扛起生产流量时一个最现实的问题就会浮出水面这台Nginx挂了整个服务入口也就没了。我见过不少线上事故Nginx所在服务器因为磁盘写满、内核参数被调崩、或者单纯是云服务商底层物理机维护导致重启结果前端页面直接白屏接口全部超时。最难受的是这种故障往往不是业务代码的问题而是基础设施层面的单点瓶颈。你明明有十台应用服务器在后面跑得好好的却因为最前面这一台代理挂了整个集群对外不可用。这就是单点故障Single Point of Failure的本质整个链路中只要有一个环节不具备冗余能力它就成为整个系统的短板。而Nginx作为流量入口承担着反向代理、负载均衡、SSL终止、静态资源响应等职责它的可用性直接决定了用户能不能访问到你的服务。所以搭建Nginx高可用集群并不是“大公司才需要做的事”而是任何对可用性有要求的业务都应该具备的基础能力。哪怕你的业务日活只有几千一次Nginx宕机导致的半小时故障也足以让口碑受损。1.2 高可用集群能解决什么问题高可用集群的核心目标只有一个当集群中的某台机器出现故障时系统能在尽可能短的时间内自动恢复服务且对用户无感知或仅感知极短的闪断。具体到Nginx场景高可用集群要解决三个层面的问题自动故障转移主节点宕机后备用节点能自动接管流量不需要人工登录服务器去改DNS、改路由或者手动切换。入口IP的漂移对外提供服务的入口地址虚拟IP不发生变化。客户端访问的还是同一个IP但背后处理请求的物理机器已经悄悄换了。健康感知与自愈集群不仅能感知“进程死了”还能感知“服务不可用”——比如Nginx进程还在但后端应用全部超时这时候也需要触发切换。这三件事做扎实了你的Nginx入口就从“一台机器”变成了“一台有备胎的机器”可用性从单机的99.9%向集群的99.99%迈进。1.3 Nginx高可用在整个架构中的位置我们需要先理清一个概念Nginx高可用集群不等于业务高可用。它解决的是入口层的冗余问题而业务层的高可用还需要应用集群、数据库主从、缓存集群等共同配合。以一个典型的Web架构为例用户 → DNS → 虚拟IPVIP→ Nginx主/备集群 → 应用服务器集群 → 数据库/缓存在这个链路中Nginx高可用集群处于流量入口的位置。它对外暴露一个虚拟IPVIP通过Keepalived等工具实现主备节点的自动切换。当主Nginx宕机时备用Nginx自动绑定VIP并接管流量。这里有一个很重要的认知Nginx高可用集群不仅仅解决了“进程挂了”的问题它还让整个架构具备了滚动升级的能力。比如你想升级Nginx版本可以直接在备用节点上先升级、测试、然后主动触发切换再把旧主节点升级。整个过程业务不中断这就是高可用架构带来的额外红利。明白了这些我们下面直接进入实操。2. 高可用集群的方案设计与环境准备2.1 方案选型Keepalived双机热备为什么是主流Nginx高可用的实现方案有很多常见的包括方案原理优点缺点Keepalived VRRP通过虚拟IP漂移实现主备切换配置简单、切换快、成熟稳定主备模式下备用节点闲置Nginx Plus商业版官方提供的集群与健康检查能力功能完善、支持配置同步商业授权费用较高LVS KeepalivedLVS做四层负载均衡Keepalived保障高可用性能极高、支持大规模流量架构复杂、运维成本高DNS轮询 多Nginx多个Nginx入口通过DNS负载均衡无需VIP、天然多活DNS缓存导致切换不彻底Consul Nginx动态配置服务发现驱动Nginx动态更新配置灵活、配合微服务架构需要额外部署组件、复杂度高综合来看Keepalived 双机热备是目前最主流的方案。为什么因为它用最简单的机制解决了最核心的问题。Keepalived基于VRRP虚拟路由冗余协议实现多个节点在同一个虚拟组内竞选角色Master节点持有VIP并对外提供服务Backup节点持续监控Master的状态。一旦Master失联Backup节点晋升为Master并绑定VIP。整个过程在秒级到毫秒级完成配置也仅仅是几个文本文件。我倾向使用Keepalived方案还有一个原因是它的“无侵入性”——它不修改Nginx本身的任何行为只是为Nginx加了一层“保护罩”。无论你用Nginx做反向代理、负载均衡还是静态服务器Keepalived都能在旁边稳定地守护着。2.2 环境规划与网络拓扑设计在动手之前先把环境规划好。以最常见的双节点主备架构为例节点角色IP地址说明node1Master192.168.1.10安装Nginx Keepalivednode2Backup192.168.1.11安装Nginx KeepalivedVIP虚拟地址192.168.1.100对外提供服务的入口地址在这个设计中VIP不对应任何物理网卡而是由Keepalived动态绑定在Master节点的网卡上。正常情况下客户端访问192.168.1.100实际请求被node1处理。当node1故障时Keepalived在node2上自动绑定同一VIP实现秒级切换。网络层面的要求两节点之间需要能够互通并且建议配置独立的心跳线路比如单独的内网网卡或直连线避免业务流量和心跳流量互相干扰。VIP需要和节点IP处于同一广播域内这样Keepalived发送VRRP组播报文时才能被对端正常接收。防火墙需要放行VRRP协议报文。Keepalived默认使用组播地址224.0.0.18协议号为112。操作系统方面我建议使用同一版本的Linux发行版避免底层网络栈行为不一致。如果在云环境下搭建需要注意云厂商是否支持VRRP组播或需要改用单播模式这也是后面要重点讲的内容。2.3 核心原理解读虚拟IP与健康检查虚拟IPVIP的本质虚拟IP的本质是一个不属于物理网卡但被系统绑定到网卡上的IP地址。它和普通IP一样可以被路由、被访问但绑定它的节点是可变的。你可以把VIP理解成一个酒店的“前台电话号码”。客户只需要记住这一个号码不管接待员今天是A还是B只要电话能打通就行。Keepalived负责保证“电话永远有人接”——Master在由Master接Master不在Backup自动接。健康检查的两种层级Keepalived对Nginx的健康检查分为两个层级进程级检查Keepalived定期检查Nginx进程是否存在。如果进程不存在就认为节点不健康。服务级检查通过自定义脚本主动访问Nginx的服务端口或健康检查URL判断服务是否真正可用。这两者的核心区别在于nginx进程可能还活着但工作进程已经死锁端口已经不监听了。只靠进程检查发现不了这种问题。所以生产环境我强烈建议使用自定义脚本做服务级检查后面会给出脚本模板。掌握了这些原理下面就是纯实操环节了。3. 从零搭建Nginx高可用集群完整实操3.1 基础准备关闭防火墙干扰并统一环境在开始安装之前先把两个节点的系统基础环境统一起来。以下操作需要在两台机器上都执行。配置主机名映射# node1 上执行 hostnamectl set-hostname nginx-node1 # node2 上执行 hostnamectl set-hostname nginx-node2然后在两台机器的/etc/hosts中添加192.168.1.10 nginx-node1 192.168.1.11 nginx-node2主机名映射的作用是方便Keepalived配置中对端地址的解析也方便后续排查问题时快速区分节点身份。防火墙规则如果开启了防火墙需要放行Keepalived所需的VRRP协议和安全组端口。以firewalld为例# 放行VRRP协议组播或单播 firewall-cmd --add-rich-rulerule protocol value112 accept --permanent firewall-cmd --reload也要确保Nginx的监听端口在防火墙规则中放行。如果用的是云主机还要在安全组中确认同样的规则。注意防火墙配置是网络层面切换失败的最常见原因。如果你发现两台机器互相ping不通VIP、Keepalived收不到对方的VRRP报文第一时间检查防火墙。同步系统时间使用chrony或ntpd同步两台机器的时间。VRRP报文带有时间戳信息时间偏差过大会导致Keepalived认为对端状态异常。我在实际运维中遇到过因为系统时间差了5分钟导致主备节点频繁误判切换的案例。3.2 安装并标准配置Nginx在node1和node2上分别安装Nginx。不同Linux发行版的安装命令不同这里以常用的CentOS/RHEL系列为例# 安装Nginx以EPEL源为例 yum install -y nginx # 或者使用官方源 rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.rpm yum install -y nginx安装完成后先做一份标准的反向代理配置让两台Nginx都能正常代理后端的应用服务。以代理到本机的8080端口应用为例# /etc/nginx/conf.d/proxy.conf upstream backend_app { server 127.0.0.1:8080 max_fails3 fail_timeout30s; # 如果有多个后端节点在这里添加 # server 192.168.1.20:8080 max_fails3 fail_timeout30s; # server 192.168.1.21:8080 max_fails3 fail_timeout30s; } server { listen 80; server_name _; location / { proxy_pass http://backend_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 健康检查端点 location /healthcheck { access_log off; return 200 OK\n; } }这里留了一个重要的伏笔我添加了一个/healthcheck端点。这个端点的作用是给Keepalived的健康检查脚本用的——它不依赖任何后端应用如果Nginx本身正常它就能返回200。这样我们就把“Nginx是否正常”和“后端应用是否正常”解耦了避免后端应用偶发故障导致Nginx被误判为宕机。验证Nginx配置并启动nginx -t systemctl start nginx systemctl enable nginx启动后访问http://本机IP/healthcheck如果看到OK说明Nginx正常。3.3 安装Keepalived在node1和node2上分别安装Keepalivedyum install -y keepalived安装完成后Keepalived的默认配置文件在/etc/keepalived/keepalived.confvrrp脚本目录在/etc/keepalived/下。我们先备份默认配置再按下面的配置进行修改。注意Keepalived的配置语法很苛刻变量名拼写错误、括号层级不对、行尾多了空格都可能导致服务启动失败。每次修改配置后务必执行keepalived -t检查语法。3.4 配置主节点Master先来看node1的完整配置global_defs { router_id NGINX_HA_1 enable_script_security } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 2 rise 2 fall 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 nopreempt authentication { auth_type PASS auth_pass 1234 } unicast_src_ip 192.168.1.10 unicast_peer { 192.168.1.11 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:0 } track_script { check_nginx } notify_master /etc/keepalived/notify.sh MASTER notify_backup /etc/keepalived/notify.sh BACKUP notify_fault /etc/keepalived/notify.sh FAULT }这段配置的核心要点我来逐项拆解vrrp_script段落定义了健康检查脚本interval 2表示每2秒执行一次脚本rise 2表示连续2次成功后节点才判定为健康fall 2表示连续2次失败后判定为不健康。这个参数组合可以有效避免网络抖动带来的误判。state MASTER表示节点启动时以Master角色参与竞选。但注意这个值只是初始状态。真正决定谁是Master的是priority值值越大越优先。priority 100是主节点的优先级。备用节点需要设置一个更低的数值比如90。virtual_ipaddress定义了VIP地址label eth0:0表示将VIP绑定在网卡eth0的别名接口上。这样通过ip addr show能清晰地看到VIP是绑定在哪个物理网卡上的。nopreempt是一个容易被忽略但很重要的参数它表示当Master从故障中恢复后不主动抢占VIP。也就是说只要备用节点还活着VIP就继续留在备用节点上。这可以避免频繁的主备切换导致连接抖动。这个策略叫“非抢占模式”。3.5 配置备用节点Backupnode2的配置和主节点几乎一致只是改了三个关键值global_defs { router_id NGINX_HA_2 enable_script_security } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 timeout 2 rise 2 fall 2 } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 90 advert_int 1 nopreempt authentication { auth_type PASS auth_pass 1234 } unicast_src_ip 192.168.1.11 unicast_peer { 192.168.1.10 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:0 } track_script { check_nginx } notify_master /etc/keepalived/notify.sh MASTER notify_backup /etc/keepalived/notify.sh BACKUP notify_fault /etc/keepalived/notify.sh FAULT }三个关键差异是router_id、state BACKUP、priority 90。需要强调一点两个节点的virtual_router_id必须一致这里都是51。这个ID是VRRP组播报文中的身份标识两边的ID不一致它们就不会认为彼此属于同一个虚拟路由器组VIP就无法实现漂移。3.6 编写健康检查与状态通知脚本Keepalived之所以能实现“服务级健康检查”靠的是它调用的外部脚本。现在我们来编写最关键的检查脚本。在两台机器的/etc/keepalived/check_nginx.sh中写入#!/bin/bash # 定义检测URLNginx的本地健康检查端点 CHECK_URLhttp://127.0.0.1/healthcheck CHECK_PORT80 # 检测Nginx端口是否存在 if ! /usr/sbin/ss -tln | grep -q :$CHECK_PORT; then exit 1 fi # 用curl获取HTTP状态码判断服务是否可用 HTTP_CODE$(curl -s -o /dev/null -w %{http_code} --connect-timeout 2 --max-time 3 $CHECK_URL 2/dev/null) if [ $HTTP_CODE 200 ]; then exit 0 else exit 1 fi赋予执行权限chmod x /etc/keepalived/check_nginx.sh脚本逻辑很简单先检查端口是否在监听再用curl请求健康检查端点。只有两个条件都满足脚本才返回0Keepalived才认为节点健康。使用curl而不是直接检查进程关键原因在于网络协议栈可能已经异常端口虽然listen但无法正常响应请求。直接检查进程发现不了这类半死不活的状态。再编写通知脚本/etc/keepalived/notify.sh#!/bin/bash STATE$1 case $STATE in MASTER) echo $(date %Y-%m-%d %H:%M:%S) [notify] switch to MASTER, start logging... /var/log/keepalived-notify.log # 这里可以扩展告警通知、动态更新DNS、预热缓存等 ;; BACKUP) echo $(date %Y-%m-%d %H:%M:%S) [notify] switch to BACKUP /var/log/keepalived-notify.log ;; FAULT) echo $(date %Y-%m-%d %H:%M:%S) [notify] enter FAULT state /var/log/keepalived-notify.log ;; esac赋予执行权限。这个脚本的价值在于它把主备切换的瞬间记录下来成为日后排查故障时最重要的时间线索。你可以在MASTER分支里追加企业微信/钉钉/邮件告警逻辑让每次切换都能实时通知到人。3.7 启动并验证集群先在node1上启动Keepalivedsystemctl start keepalived systemctl enable keepalived sleep 5 ip addr show eth0此时应该能看到eth0上多了一个eth0:0别名接口IP为192.168.1.100。说明node1成功绑定了VIP。然后在node2上执行同样操作并用ip addr show eth0检查正常情况下node2上是不会有VIP的。验证VIP漂移的过程如下在node1上执行# 强制关闭Keepalived模拟节点故障 systemctl stop keepalived等待3-5秒然后在node2上执行ip addr show eth0如果配置正确此时node2的eth0上已经出现了192.168.1.100的绑定信息。此时再用同一台客户端机器访问http://192.168.1.100/healthcheck应该依然返回OK。再启动node1的Keepalived因为配置了nopreemptVIP不会自动漂回node1而是继续留在node2上。这个行为是符合预期的——非抢占模式就是为了避免切换抖动。3.8 将Keepalived配置为开机自启systemctl enable keepalived同时我们需要确认一个细节Keepalived和Nginx的启动顺序。如果服务器重启理论上应该先启动Nginx再启动Keepalived因为Keepalived的健康检查脚本在Nginx不可用时会返回失败节点无法绑定VIP。但如果Nginx挂了而Keepalived还活着健康检查脚本会连续失败导致Keepalived降级为FAULT状态VIP会漂移到备用节点。这个行为是我们需要的。问题在于如果Nginx还没起来Keepalived就起来了节点无法立即接管VIP会导致服务短暂不可用。解决办法是在Keepalived的systemd服务中添加依赖关系# 编辑 /etc/systemd/system/keepalived.service.d/dependency.conf [Unit] Afternginx.service Requiresnginx.service然后执行systemctl daemon-reload4. 生产环境的高可用进阶与避坑指南4.1 脑裂问题最隐蔽的高可用杀手脑裂Split-Brain是高可用集群中绕不开的话题。简单来说当主备节点之间的心跳通信中断但两个节点都认为对方已经死了于是同时绑定VIP对外提供服务造成IP冲突和流量分配混乱。脑裂的产生条件非常苛刻主节点本身是健康的只是它发送的VRRP报文无法到达备用节点。常见诱因包括心跳线路物理断开或网络拥塞交换机端口配置错误导致组播报文被丢弃云环境中底层网络策略限制了组播防火墙规则意外拦截VRRP预防脑裂的手段主要有三层使用独立心跳线路不要让心跳流量和业务流量共享同一链路。条件允许时用直连线或独立交换机连接两个节点。配置单播而非组播云环境特别推荐使用unicast_peer方式发送VRRP报文绕开组播依赖。前文中已经配置了unicast_src_ip和unicast_peer这正是针对云环境的适配。引入仲裁机制Fencing这是更高级的手段。当节点无法确认对端状态时主动找第三方仲裁节点如数据库或单独的探测机确认对端状态再决定是否接管VIP。Keepalived本身没有内置仲裁可以通过notify_fault脚本结合外部探测来实现。在实际生产环境中我最常推荐的方案是单播 独立心跳 仲裁探测脚本。三者配合可以把脑裂概率降到极低。4.2 健康检查参数调优避免切换过度敏感很多人在配置健康检查时喜欢把interval设得很小比如0.5秒以为这样切换更快。但实际上过于激进的检查策略会带来严重问题。举个真实例子某后端数据库因为GC停顿了2秒导致后端应用接口响应超时结果Nginx的健康检查脚本也超时了。生产上有两个节点都健康但因为健康检查脚本短暂超时Keepalived误判Master故障触发了VIP切换。切换过程中部分TCP连接被中断反而影响了业务。调优的核心原则是健康检查的灵敏度要匹配后端服务的抖动容忍度。参数推荐值说明interval2-3秒检查频率过小会增加误判风险timeout2-3秒脚本执行超时时间fall2-3次连续失败达到该次数才判定故障rise2次连续成功达到该次数才判定恢复对于严格依赖单点状态的服务可以把fall设小一点对于允许短暂抖动的服务建议fall设2-3次interval设2秒这样整体故障切换时间在4-6秒左右对大部分业务是完全可以接受的。4.3 会话保持与数据一致性的补充说明Nginx Keepalived高可用集群解决了“入口可用性”的问题但有一个很容易被忽略的坑如果后端只有一台应用服务器Nginx高可用只是把“入口挂了”变成了“入口没挂”当实际调后端时还是只有一个后端节点。你在做Nginx高可用架构时必须明确两个层面的高可用是独立的入口层高可用由Keepalived保障业务层高可用由应用集群 数据库主从等保障另外如果业务有会话保持需求Nginx配置了ip_hash或sticky会话模块那么在主备Nginx切换时会话信息并不会自动同步到备用节点。这意味着用户可能被重新分配到新的后端节点如果后端没有做Session共享用户会突然掉线。解决方案有两种后端应用做Session外置存储到Redis或数据库中使用Nginx的sticky模块将用户会话绑定到同一后端节点并配合后端Session复制我在实际项目中倾向于前者因为Session外置的扩展性更好与高可用架构的配合也更自然。4.4 高频故障速查表故障现象可能原因解决思路VIP始终不出现Keepalived配置语法错、防火墙拦截VRRP、auth_pass不匹配先keepalived -t检查语法再看/var/log/messages中Keepalived日志Master和Backup同时绑定VIP脑裂心跳中断但双方存活检查心跳链路引入仲裁机制使用单播模式切换后业务访问不通备用节点Nginx未启动或配置不一致统一Nginx配置管理确保备用节点的upstream配置和Master一致健康检查脚本频繁误判脚本中超时时间太短或后端应用抖动放宽curl的--max-time增加fall次数重启后VIP无法漂移Keepalived启动时健康检查未通过确认Nginx先于Keepalived启动或设置systemd依赖关系keepalived进程活着但VIP不生效网卡名称不对、virtual_ipaddress中的dev指定错误用ip addr show确认真实的网卡名称这张速查表是我在多次排障中沉淀下来的高频项。实际线上故障大多数不会超出这个范围。5. 个人实操体会与扩展建议5.1 我踩过的最深的坑如果只能分享一条经验我想说不要在主备节点上手工修改Nginx配置文件一定要引入配置同步机制。我遇到过这样一次事故。某天晚上需要临时加一个API的反向代理规则我直接在当时的Master节点上改了配置并reload当时一切正常。第二天早高峰时Master机器因硬件告警重启VIP漂移到Backup节点上——结果Backup节点上根本没有我昨晚新加的规则映射到新API的流量全部502。用户端表现为部分接口不可用排查了接近二十分钟才发现是配置不一致的问题。从那之后我就建立了三个习惯所有Nginx配置纳入版本管理通过配置同步脚本如rsync或ansible在两个节点间自动同步。每次配置变更后在两台机器上都执行nginx -t验证语法。每隔一段时间主动做一次主备切换演练确保备用节点真的能扛起流量。主备切换演练尤其重要。很多高可用集群看着配置完美但从未实际切换过。等到真正需要切换时才发现备用节点连VIP都抢不过来。我建议至少每季度做一次主动切换演练。5.2 后续扩展方向搭建完这套Nginx高可用集群之后你的架构已经具备了一定的容灾能力。但如果流量继续增长可以考虑以下几个扩展方向多节点集群Keepalived天然支持一主多备模式你可以在配置文件中添加多个BACKUP节点进一步提高可用性。需要注意的是Keepalived的VRRP组播模式下多节点要合理规划优先级。混合云架构不同云厂商或自建机房之间VIP方案可能受制于网络环境。可以考虑把Keepalived与全局负载均衡如DNS GSLB结合实现跨机房的高可用。Nginx配置自动化当Nginx实例越来越多时手工维护配置文件会变得非常痛苦。可以引入Ansible、SaltStack等自动化工具管理Nginx和Keepalived的部署与配置。可观测性建设给Keepalived的切换事件增加告警、日志采集、甚至自动化工单创建。高可用不只是“能切换”还要“知道什么时候切换了、为什么切换”。最后分享一个我自己非常受用的习惯每次搭建完高可用集群我都会在手机备忘录里记一笔“切换演练操作清单”内容包括停哪个服务、观察什么指标、如何回滚。这个清单在真正出故障时能帮你省下最宝贵的十分钟。高可用架构的核心从来不是“配置一套看起来很完美的集群”而是“在出问题时能冷静、快速、正确地处理”。希望这篇实操笔记能帮你在搭建Nginx高可用集群的路上少踩一些坑。