1. 为什么单台Nginx扛不住真实流量
很多团队在项目初期都是单台Nginx打天下,配置简单、维护省心,直到某天凌晨被告警电话叫醒——Nginx进程挂了,整个业务入口全断。这种事故我经历过不止一次,后来复盘发现,单点Nginx的问题远不止"进程崩溃"这么简单。
单台Nginx的脆弱性体现在三个层面。第一是硬件层面,网卡、内存、磁盘任何一个部件故障都会导致服务不可用,这不是调优能解决的。第二是软件层面,Nginx虽然以稳定著称,但配置热加载失败、worker进程异常退出、连接数打满等情况依然会发生。第三是运维层面,系统内核升级、安全补丁安装、证书更换这些操作都需要重启服务,单机环境下每次操作都是一次业务中断风险。
有人会说,那用DNS轮询或者LVS不就行了?DNS轮询的问题在于故障切换依赖TTL缓存,客户端可能几分钟内还在往挂掉的节点发请求,而且它没法做健康检查。LVS确实强,但配置复杂度高,对于中小规模业务来说有点杀鸡用牛刀。Keepalived配合Nginx双机主备这个方案,恰好卡在一个甜点位上——足够简单,又能实现秒级故障切换,还能顺带解决IP漂移问题。
这个方案的核心思路是:两台服务器都装Nginx和Keepalived,同一时刻只有一台(MASTER)持有虚拟IP(VIP)对外提供服务,另一台(BACKUP)待命。MASTER上的Keepalived通过VRRP协议持续向BACKUP发送心跳,一旦BACKUP收不到心跳,就认为MASTER挂了,自己接管VIP成为新的MASTER。整个过程对客户端完全透明,因为它们访问的一直是那个VIP。
适合参考这套方案的人群很明确:手里有两台服务器、业务入口是Nginx、希望实现高可用但不想引入太复杂组件的运维或后端开发。如果你只有一台机器,或者业务已经上了Kubernetes,那这套方案可能不是最优解。但如果你正处在"单机扛不住、集群又太重"的尴尬阶段,双机主备就是那个刚刚好的选择。
2. VRRP协议与Keepalived的工作机制拆解
2.1 VRRP到底在干什么
VRRP全称Virtual Router Redundancy Protocol,翻译过来叫虚拟路由冗余协议。名字听着唬人,本质其实很简单:多台机器组成一个组,共同虚拟出一个IP地址,同一时刻只有一台机器响应这个IP的请求。
打个比方,就像公司前台。平时只有A前台在岗,访客都找A。A突然请假了,B前台立刻顶上,访客还是找"前台"这个位置,不需要知道具体是谁在坐。VIP就是那个"前台"的位置,MASTER和BACKUP就是A和B。
VRRP组内通过多播地址224.0.0.18发送通告报文,默认每1秒发一次。BACKUP如果连续3秒(3倍通告间隔)没收到MASTER的通告,就启动选举流程。优先级高的成为新MASTER,优先级相同则比较IP地址大小。这个"3倍间隔"的设计是为了容忍网络抖动,避免误切换。
2.2 Keepalived的角色与状态机
Keepalived是VRRP协议在Linux上的实现,但它比纯VRRP多了健康检查功能。这是它比LVS自带VRRP更实用的地方——Keepalived不仅能检测对方机器是否活着,还能检测本机的Nginx进程是否正常。
Keepalived的状态机有三个核心状态:MASTER、BACKUP、FAULT。MASTER持有VIP并持续发通告;BACKUP收通告并随时准备接管;FAULT是健康检查失败后的状态,此时会主动放弃VIP。状态转换的触发条件包括:收到更高优先级的通告、心跳超时、健康检查脚本返回失败。
这里有个容易踩的坑:Keepalived默认只检查自己的心跳,不检查Nginx。也就是说,如果Nginx进程挂了但Keepalived还在跑,MASTER依然持有VIP,但用户请求全部502。所以必须配置vrrp_script来监控Nginx状态,这是整套方案里最关键的配置项之一。
2.3 抢占模式与非抢占模式的选择
Keepalived默认是抢占模式:原MASTER恢复后,会重新抢回VIP。这在某些场景下是好事,比如你希望性能更强的机器一直当主力。但抢占会导致一次额外的切换,如果原MASTER反复重启,就会反复抢占,造成VIP频繁漂移。
非抢占模式(nopreempt)下,原MASTER恢复后不会抢回VIP,而是保持BACKUP状态,直到当前MASTER出问题。这个模式适合两台机器配置对等、不希望频繁切换的场景。配置方式是在vrrp_instance里加一行nopreempt,但注意这个参数只在state为BACKUP的节点上生效。
我的建议是:如果两台机器配置完全一样,用非抢占模式更稳;如果一台明显强于另一台,用抢占模式让强机当主力。这个选择没有绝对对错,取决于你的实际部署情况。
3. 双机环境的前置准备与网络规划
3.1 机器规划与IP分配
动手之前先把IP规划清楚,这是后面所有配置的基础。假设你有两台服务器,规划如下:
| 角色 | 主机名 | 物理IP | VIP | 优先级 |
|---|---|---|---|---|
| MASTER | nginx-01 | 192.168.1.101 | 192.168.1.100 | 100 |
| BACKUP | nginx-02 | 192.168.1.102 | 192.168.1.100 | 90 |
VIP必须和物理IP在同一网段,否则VRRP通告无法正常收发。优先级数值越大越优先,MASTER设100、BACKUP设90是常见做法,留出空间方便以后加机器。
注意:VIP不要和DHCP地址池重叠,否则可能出现IP冲突。如果内网有DHCP服务器,务必把VIP排除在地址池之外。
3.2 系统参数调整
两台机器都需要做几项系统层面的调整,否则Keepalived可能无法正常工作。
第一项是关闭防火墙对VRRP的拦截。VRRP使用多播地址224.0.0.18和协议号112,很多默认防火墙规则会把它挡掉。最省事的做法是直接放行:
# 如果使用firewalld firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept' firewall-cmd --reload # 如果使用iptables iptables -A INPUT -p vrrp -j ACCEPT iptables -A OUTPUT -p vrrp -j ACCEPT第二项是开启IP转发。虽然纯Nginx场景下不一定需要,但如果后续要做七层转发或者配合其他组件,这个参数迟早要开:
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf sysctl -p第三项是关闭NetworkManager对VIP的干扰。某些发行版的NetworkManager会接管网卡配置,导致Keepalived添加的VIP被清除。稳妥做法是在网卡配置里加上NM_CONTROLLED=no,或者直接停用NetworkManager。
3.3 Nginx的安装与基础配置
两台机器的Nginx配置必须保持一致,否则切换后行为不一致会出问题。安装方式看个人习惯,yum/apt装或者源码编译都行,关键是版本要统一。
Nginx的核心配置里,有几项和双机主备直接相关。首先是worker进程数,建议设为CPU核心数;其次是连接数上限,根据业务量调整;最重要的是日志格式,要能区分请求最终落到了哪台机器,方便排查问题:
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'upstream_addr=$upstream_addr'; access_log /var/log/nginx/access.log main;配置完成后,用nginx -t检查语法,然后启动服务。两台机器都确认Nginx能正常响应请求后,再进入Keepalived的配置环节。
4. Keepalived配置文件的逐项拆解
4.1 全局配置段
Keepalived的主配置文件通常在/etc/keepalived/keepalived.conf。全局段主要定义邮件通知和路由规则,双机主备场景下最需要关注的是通知功能:
global_defs { notification_email { ops@example.com } notification_email_from keepalived@example.com smtp_server 127.0.0.1 smtp_connect_timeout 30 router_id nginx-01 }router_id建议设为唯一值,通常用主机名。这个字段会出现在日志里,方便定位是哪台机器发的通告。邮件通知功能在切换时能及时告警,但需要本机有邮件服务,如果没配邮件服务,这部分可以省略,改用其他监控手段。
4.2 健康检查脚本定义
这是整套配置里最关键的段落。vrrp_script定义了一个检查脚本,Keepalived会按指定间隔执行它,根据返回码决定是否切换状态:
vrrp_script chk_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 weight -20 fall 2 rise 2 }逐项解释:interval 2表示每2秒执行一次;weight -20表示脚本失败时优先级减20,这样MASTER优先级从100降到80,低于BACKUP的90,触发切换;fall 2表示连续失败2次才判定为故障;rise 2表示连续成功2次才恢复。
对应的检查脚本内容:
#!/bin/bash # 检查Nginx进程是否存在 if ! kill -0 $(cat /var/run/nginx.pid) 2>/dev/null; then exit 1 fi # 进一步检查Nginx是否能正常响应 if ! curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/health | grep -q "200"; then exit 1 fi exit 0脚本要加执行权限:chmod +x /etc/keepalived/check_nginx.sh。这里我建议同时检查进程和端口响应,因为进程活着但服务卡死的情况并不少见。如果只检查进程,Nginx假死时Keepalived不会切换,用户照样访问不了。
4.3 vrrp_instance实例配置
实例配置定义了VRRP组的具体行为,MASTER和BACKUP的配置只有state和priority两处不同:
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.1.100/24 } track_script { chk_nginx } notify_master "/etc/keepalived/notify.sh master" notify_backup "/etc/keepalived/notify.sh backup" notify_fault "/etc/keepalived/notify.sh fault" }virtual_router_id是VRRP组的标识,同一组内两台机器必须一致,不同组之间不能重复。advert_int 1是通告间隔,默认1秒。authentication是简单密码认证,防止误加入其他VRRP组,密码最多8位。
notify_master等脚本在状态切换时触发,可以用来做告警、日志记录或者调整其他服务。比如切换时给监控系统发个事件,或者更新DNS记录,这些都可以在notify脚本里实现。
4.4 配置文件的差异对比
把两台机器的配置差异整理成表格,部署时对照检查:
| 配置项 | MASTER节点 | BACKUP节点 |
|---|---|---|
| state | MASTER | BACKUP |
| priority | 100 | 90 |
| router_id | nginx-01 | nginx-02 |
| virtual_router_id | 51 | 51 |
| virtual_ipaddress | 192.168.1.100/24 | 192.168.1.100/24 |
| auth_pass | 123456 | 123456 |
除了这几项,其他配置必须完全一致。部署时建议先配好一台,用scp复制到另一台,再改差异项,这样能避免手误导致的配置不一致。
5. 启动验证与故障切换实测
5.1 服务启动与状态确认
两台机器都配置好后,先启动MASTER的Keepalived,再启动BACKUP的:
systemctl start keepalived systemctl enable keepalived启动后在MASTER上执行ip addr show eth0,应该能看到VIP 192.168.1.100已经挂在网卡上。在BACKUP上执行同样的命令,不应该看到VIP。同时用systemctl status keepalived确认服务状态正常,日志里没有报错。
查看Keepalived日志可以用journalctl -u keepalived -f,正常运行时MASTER会周期性打印"VRRP_Instance(VI_1) Sending gratuitous ARP",BACKUP会打印"VRRP_Instance(VI_1) Entering BACKUP STATE"。
5.2 手动触发切换的完整过程
验证切换最直接的方式是停掉MASTER的Keepalived:
# 在MASTER上执行 systemctl stop keepalived然后在BACKUP上观察,几秒内应该能看到VIP漂移过来。用ip addr show eth0确认VIP出现,同时日志里会打印"VRRP_Instance(VI_1) Transition to MASTER STATE"。
此时用另一台机器ping VIP,应该能正常通。访问VIP上的Nginx服务,也应该正常响应。整个过程通常在3秒内完成,具体取决于advert_int和fall的配置。
5.3 模拟Nginx故障的切换测试
停Keepalived测的是进程级故障,更贴近真实场景的是Nginx挂掉但Keepalived还在跑。这时候就要靠健康检查脚本了:
# 在MASTER上停掉Nginx systemctl stop nginx等待几秒(取决于interval和fall的乘积),MASTER的优先级会因为脚本失败而降低,BACKUP检测到优先级变化后接管VIP。此时在MASTER上ip addr应该看不到VIP了,而BACKUP上能看到。
这个测试很关键,因为它验证的是健康检查脚本是否真正生效。我见过不少配置,脚本路径写错、权限不对、或者脚本逻辑有bug,导致Nginx挂了Keepalived却毫无反应,这种配置等于白做。
5.4 切换后的状态恢复
测试完成后,把MASTER的Nginx和Keepalived重新启动。如果是抢占模式,MASTER会重新抢回VIP;如果是非抢占模式,VIP会留在BACKUP上,直到BACKUP出问题。
恢复后要检查几件事:MASTER的VIP是否回来(抢占模式)、Nginx是否正常响应、日志里有没有异常报错。如果发现VIP没回来,检查优先级配置和抢占模式设置。
6. 生产环境踩过的坑与优化建议
6.1 脑裂问题的成因与防范
脑裂是双机主备最危险的情况:两台机器都认为自己是MASTER,同时持有VIP,导致IP冲突和请求混乱。成因通常是心跳链路中断,比如网线松动、交换机故障、或者防火墙误拦了VRRP报文。
防范脑裂有几个手段。第一是用独立网卡跑心跳,业务网卡和心跳网卡分开,避免业务流量拥塞影响心跳。第二是配置unicast_peer,把多播改成单播,减少对网络设备的依赖:
unicast_src_ip 192.168.1.101 unicast_peer { 192.168.1.102 }第三是加双活检测脚本,在notify脚本里检查对端是否可达,如果不可达就主动降级。这些手段组合使用,能把脑裂概率降到很低。
6.2 VIP漂移后ARP缓存不更新
有些场景下VIP漂移了,但客户端还是往旧机器发请求,原因是ARP缓存没更新。Keepalived在切换时会发送免费ARP(gratuitous ARP)来刷新周边设备的ARP表,但某些交换机或客户端可能不响应。
解决办法是在notify脚本里手动触发ARP更新:
#!/bin/bash case "$1" in master) arping -c 3 -A -I eth0 192.168.1.100 ;; esacarping -A发送ARP应答包,强制刷新对端ARP缓存。这个操作在切换后执行一次即可,能显著减少切换后的请求丢失。
6.3 健康检查脚本的常见陷阱
健康检查脚本写不好,整套高可用就是摆设。我踩过的坑包括:脚本里用了相对路径导致找不到命令、curl超时时间没设导致脚本卡死、脚本返回码逻辑写反了。
几个实用建议:脚本里所有命令用绝对路径;curl加--max-time 2限制超时;脚本开头加set -e让错误及时暴露;测试时手动执行脚本确认返回码符合预期。另外,脚本的检查逻辑要贴合业务,比如你的Nginx主要做反向代理,那就检查后端是否可达,而不是只检查Nginx进程。
6.4 日志与监控的配套建设
双机主备上线后,没有配套监控等于裸奔。至少要监控三项:VIP当前在哪台机器、Keepalived进程状态、Nginx健康检查结果。
VIP位置可以通过在每台机器上跑脚本检测ip addr里有没有VIP,然后上报到监控系统。Keepalived状态可以用systemctl is-active keepalived判断。健康检查结果可以解析Keepalived日志里的关键字。
告警策略上,VIP漂移事件必须告警,因为每次漂移都意味着一次故障。但要注意区分正常维护和异常切换,避免维护窗口期被告警轰炸。可以在notify脚本里加个标记,维护期间不触发告警。
7. 从双机主备到更复杂场景的延伸
双机主备解决了可用性问题,但它有个天然局限:BACKUP平时是闲置的,资源利用率只有50%。业务量增长后,你可能会想能不能让两台机器同时干活。
这就引出了双机双活的概念。Nginx本身支持负载均衡,两台机器可以同时持有VIP(需要DNS轮询或者上层负载均衡器配合),或者用Keepalived的lvs_sync_daemon做连接同步。但双活的复杂度明显上升,会话保持、数据一致性、脑裂处理都要重新设计。
另一个延伸方向是多机集群。Keepalived支持多个BACKUP节点,优先级依次递减,MASTER挂了之后优先级最高的BACKUP接管。这种架构适合对可用性要求极高的场景,但要注意VRRP通告在多节点下的风暴问题。
不过话说回来,方案选型要看实际需求。如果你的业务量单台Nginx就能扛住,双机主备已经足够;如果单台扛不住,那要考虑的就不只是高可用,还有负载均衡和水平扩展,这时候可能Kubernetes或者专业的负载均衡器更合适。技术方案没有银弹,适合当前阶段的才是最好的。
我个人在实际操作中的体会是,双机主备这套方案的价值不在于技术多先进,而在于它用最小的复杂度解决了最核心的问题。配置一次,稳定运行几年,这种性价比是很多复杂方案比不了的。但前提是配置要扎实,健康检查要可靠,监控要到位,否则高可用就是纸面上的高可用。