跳转至

nginx + keepalived 主备高可用:VIP 漂移参数矩阵与 6 个真实故障排查

我去年帮一个朋友搭电商站的 nginx 高可用,配完一切正常,测试 VIP 漂移也成功。结果上线第三天凌晨三点,主节点 nginx 假死(CPU 100% 但端口还在监听),VIP 不漂移,客服打不通电话报了 20 分钟故障。后来复盘才意识到 keepalived 默认只监控自己的进程活着,不监控 nginx 进程活着——主备高可用的坑,九成不是配置错,是健康检查脚本没写对。

本文不是教你怎么配置 keepalived(网上教程很多),而是把参数矩阵、健康检查脚本、6 个我踩过的真实故障整理成一张速查表。

什么时候用 keepalived?先做决策

不是所有场景都需要 keepalived。装之前先花 60 秒想清楚:

场景 建议方案 理由
内部工具、单点容忍停机 1 分钟 单机 nginx + systemctl 自动重启 引入 keepalived 反而多了脑裂风险
对外服务、SLA ≥ 99.9% keepalived + nginx 主备 故障切换 3-5 秒,符合 SLA
对外服务 + 后端也是集群 keepalived + nginx 主备 + upstream keepalive 双层高可用,VIP + 后端双保险
已有 K8s / 云 LB 交给 LB,别自己搭 重复造轮子

唯一推荐场景:单机服务器(不是 K8s)跑 nginx 反代对外服务,需要 99.9% 以上可用性。

参数矩阵:6 个字段决定 keepalived 行为

keepalived.conf 里 90% 的字段都可以用默认值。只有下面 6 个字段必须改,其他都是可选:

字段 位置 MASTER 值 BACKUP 值 说明
state vrrp_instance MASTER BACKUP 仅初始状态,实际由 priority 决定
virtual_router_id vrrp_instance 51 51(必须一致) 不同组不通信,同组抢占
priority vrrp_instance 100 90 数值高者为 Master
advert_int vrrp_instance 1 1(必须一致) 心跳间隔(秒),建议保持 1
auth_pass authentication S3cret123 S3cret123(必须一致) VRRP 认证密码,主备一致
virtual_ipaddress vrrp_instance 10.0.0.100/24 10.0.0.100/24(必须一致) VIP,两台机器都不许绑定这个 IP 到网卡

踩坑警告:auth_pass 只取前 8 个字符,S3cret123456 实际生效的是 S3cret1。写密码时别超过 8 位,超过也不生效还容易迷惑人。

完整配置:MASTER 与 BACKUP

MASTER 节点(web1)

# 主机名与 IP
hostnamectl set-hostname web1
# web1 主机 IP: 10.0.0.11
# VIP: 10.0.0.100(两台机器上都不能预先绑定)

# 安装
dnf install -y nginx keepalived   # CentOS/AlmaLinux
# 或 apt install -y nginx keepalived  # Ubuntu
# /etc/keepalived/keepalived.conf

global_defs {
    router_id web1          # 本机标识,两台不能一样
    vrrp_skip_check_adv_addr
    vrrp_strict
}

# 健康检查脚本
vrrp_script chk_nginx {
    script "/usr/bin/systemctl is-active --quiet nginx"
    interval 2
    fall 3                   # 连续 3 次失败才降权
    rise 2                   # 连续 2 次成功才恢复
    weight -20               # 失败时 priority 减 20
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    nopreempt                # 关键:主节点恢复不抢占 VIP,防抖动
    authentication {
        auth_type PASS
        auth_pass S3cret12
    }
    virtual_ipaddress {
        10.0.0.100/24
    }
    track_script {
        chk_nginx
    }
}

BACKUP 节点(web2)

# /etc/keepalived/keepalived.conf
# web2 主机 IP: 10.0.0.12

global_defs {
    router_id web2          # 唯一不同
    vrrp_skip_check_adv_addr
    vrrp_strict
}

vrrp_script chk_nginx {
    script "/usr/bin/systemctl is-active --quiet nginx"
    interval 2
    fall 3
    rise 2
    weight -20
}

vrrp_instance VI_1 {
    state BACKUP            # 唯一不同
    interface eth0
    virtual_router_id 51    # 必须与 MASTER 一致
    priority 90             # 唯一不同,必须低于 MASTER
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass S3cret12  # 必须一致
    }
    virtual_ipaddress {
        10.0.0.100/24       # 必须一致
    }
    track_script {
        chk_nginx
    }
}

启动与验证

# 两台机器都执行
systemctl enable --now keepalived
systemctl status keepalived

你应该看到什么:Active: active (running),无红色 error。

# 看 VIP 在谁身上(MASTER 上应该能看到 10.0.0.100)
ip addr show eth0 | grep -w 10.0.0.100

你应该看到什么:web1 上返回 inet 10.0.0.100/24 brd ...,web2 上无输出。

# 看 VRRP 协议版本
vrrp_stat -s | head -20

你应该看到什么:显示 VRRPv3 或 VRRPv2,state: MASTER 或 BACKUP。

手动测试故障切换

# 在 MASTER 上停 nginx
systemctl stop nginx

# 等 6 秒(fall=3 × interval=2)
sleep 6

# 看 VIP 是否漂移
ssh web2 'ip addr show eth0 | grep 10.0.0.100'

你应该看到什么:web2 上出现 inet 10.0.0.100/24 ...,说明 VIP 漂移成功。

# 客户端通过 VIP 访问
curl http://10.0.0.100/

你应该看到什么:正常返回页面,说明流量已切到 web2。

6 个真实故障案例速查

案例一:VIP 不漂移(最常见)

症状:MASTER 停 nginx 或关机,VIP 10 秒后还在 MASTER 上,或者两台都没有。

排查:

# 1. 看两台防火墙是否放行 VRRP
iptables -L -n | grep -i vrrp
# 或
firewall-cmd --list-all | grep -i vrrp

你应该看到什么:应有 PROTOCOL vrrp 或 protocol value="vrrp" accept 规则。

修复:

firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
firewall-cmd --reload

根因:90% 的 VIP 不漂移是防火墙没放行 VRRP 协议(IP protocol 112)。云服务器还要看安全组。

案例二:脑裂(双 MASTER 同时持有 VIP)

症状:MASTER 恢复后,MASTER 和 BACKUP 都绑定 10.0.0.100,客户端请求随机失败。

排查:

# 两台机器同时看 VIP
ip addr show eth0 | grep 10.0.0.100

你应该看到什么:正常只有一台有这个 IP。两台都有 = 脑裂。

修复:

# 两台都加 nopreempt,避免主节点恢复后立刻抢占
vrrp_instance VI_1 {
    ...
    nopreempt
    ...
}

根因:默认 keepalived 是抢占模式,MASTER 恢复后立刻抢回 VIP,但 BACKUP 还没收到通知。加 nopreempt 让 BACKUP 保持接管,直到 BACKUP 也宕机或人工干预。

案例三:抢占抖动(VIP 频繁切换)

症状:客户端日志显示同一请求 30 秒内切换到不同节点 5 次。

排查:

# 看 keepalived 日志里 MASTER/BACKUP 切换频率
journalctl -u keepalived -n 100 --no-pager | grep -E 'MASTER|BACKUP|GOTO'

你应该看到什么:如果 5 分钟内出现 3 次以上 GOTO MASTER,说明抖动。

修复:

# 加 nopreempt + 调大 fall 次数
nopreempt
vrrp_script chk_nginx {
    script "/usr/bin/systemctl is-active --quiet nginx"
    interval 2
    fall 5          # 从 3 调到 5,减少误判
    rise 2
    weight -20
}

根因:健康检查太灵敏(fall=1)导致偶发失败就切换。加大 fall 容忍度 + nopreempt 关抢占。

案例四:多网卡选错 interface

症状:VIP 漂移到 BACKUP,但客户端访问不通。curl 10.0.0.100 超时。

排查:

# 看 BACKUP 上 VIP 绑定在哪个网卡
ip addr show | grep -B 2 10.0.0.100

你应该看到什么:如果 VIP 绑定在 eth1(内网管理网)而不是 eth0(业务网),客户端自然访问不到。

修复:把 vrrp_instance 里的 interface eth0 改成正确的业务网卡。

根因:ip addr 排序按接口名,eth1 常常在 eth0 前面。多网卡机器容易配错。

案例五:健康检查脚本永远成功

症状:nginx 进程真的挂了,但 keepalived 不切换,VIP 不动。

排查:

# 手动跑健康检查脚本,看返回值
/usr/bin/systemctl is-active --quiet nginx; echo $?

你应该看到什么:nginx 正常返回 0,挂了返回 3。

修复:如果返回 0 但 nginx 其实挂了,说明 nginx 进程假死(比如 CPU 100% 卡死但进程还在)。改用主动探测:

# 用 curl 主动探测 nginx 端口
cat > /etc/keepalived/check_nginx.sh << 'EOF'
#!/bin/bash
if ! curl -sf --max-time 3 http://127.0.0.1/ > /dev/null 2>&1; then
    exit 1
fi
exit 0
EOF
chmod +x /etc/keepalived/check_nginx.sh
vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 2
    fall 3
    rise 2
    weight -20
}

根因:systemctl is-active 只看 systemd 记录的状态,进程挂但 systemd 未察觉(比如被 OOM Kill 后 systemd 延迟更新)就会假成功。主动 curl 更可靠。

案例六:keepalived 升级后 VRRP 版本不兼容

症状:一台机器升级 keepalived 到 2.2.7+,另一台还是 1.4.x,VIP 不漂移。

排查:

keepalived -V

你应该看到什么:两台版本号。如果差异大,看 keepalived 日志:

journalctl -u keepalived | grep -i vrrp

你应该看到什么:VRRPv3 与 VRRPv2 混用报错。

修复:

# 强制指定协议版本,两台一致
global_defs {
    ...
    vrrp_version 2    # 或 vrrp_version 3
}

根因:keepalived 2.x 默认 VRRPv3,1.x 默认 VRRPv2,两者不互通。要么都升 2.x,要么显式设 vrrp_version 2。

Cheat Sheet:15 条速查命令

# 命令 用途
1 keepalived -V 看版本
2 systemctl status keepalived 看服务状态
3 keepalived -t -f /etc/keepalived/keepalived.conf 测试配置语法
4 ip addr show eth0 \| grep 10.0.0.100 看 VIP 在哪
5 vrrp_stat -s 看 VRRP 实例状态
6 journalctl -u keepalived -f 实时看日志
7 iptables -L -n \| grep vrrp 看防火墙 VRRP 规则
8 ss -tlnp \| grep :80 看 nginx 是否监听 80
9 curl -sf http://127.0.0.1/ && echo OK 主动探测 nginx
10 curl -v http://10.0.0.100/ 通过 VIP 访问
11 tcpdump -i eth0 -nn vrrp 抓 VRRP 心跳包
12 ping 10.0.0.100 客户端测 VIP 连通性
13 systemctl stop keepalived 停 keepalived(VIP 释放)
14 systemctl stop nginx 停 nginx 测漂移
15 nginx -T 输出合并后的 nginx 配置

收尾

keepalived 的配置字段没几个,但出问题的时候排查路径固定:先看防火墙 VRRP → 再看主备 priority 与 virtual_router_id 是否一致 → 再看健康检查脚本是否真的生效 → 最后看 nopreempt 与 fall 参数。九成故障都能在这四步里定位。

我个人不推荐在生产环境用 keepalived 搭 nginx 高可用——K8s 环境下应该交给 Service/LB,非 K8s 场景如果预算允许应该上云 LB。但如果没有云 LB、又必须自建高可用,keepalived 仍然是唯一可靠的选择。别为了高可用把复杂度引入不必要的地方。

延伸阅读