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
}
}
启动与验证
你应该看到什么:Active: active (running),无红色 error。
你应该看到什么:web1 上返回 inet 10.0.0.100/24 brd ...,web2 上无输出。
你应该看到什么:显示 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 漂移成功。
你应该看到什么:正常返回页面,说明流量已切到 web2。
6 个真实故障案例速查
案例一:VIP 不漂移(最常见)
症状:MASTER 停 nginx 或关机,VIP 10 秒后还在 MASTER 上,或者两台都没有。
排查:
你应该看到什么:应有 PROTOCOL vrrp 或 protocol value="vrrp" accept 规则。
修复:
根因:90% 的 VIP 不漂移是防火墙没放行 VRRP 协议(IP protocol 112)。云服务器还要看安全组。
案例二:脑裂(双 MASTER 同时持有 VIP)
症状:MASTER 恢复后,MASTER 和 BACKUP 都绑定 10.0.0.100,客户端请求随机失败。
排查:
你应该看到什么:正常只有一台有这个 IP。两台都有 = 脑裂。
修复:
根因:默认 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 超时。
排查:
你应该看到什么:如果 VIP 绑定在 eth1(内网管理网)而不是 eth0(业务网),客户端自然访问不到。
修复:把 vrrp_instance 里的 interface eth0 改成正确的业务网卡。
根因:ip addr 排序按接口名,eth1 常常在 eth0 前面。多网卡机器容易配错。
案例五:健康检查脚本永远成功
症状:nginx 进程真的挂了,但 keepalived 不切换,VIP 不动。
排查:
你应该看到什么: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 日志:
你应该看到什么:VRRPv3 与 VRRPv2 混用报错。
修复:
根因: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 仍然是唯一可靠的选择。别为了高可用把复杂度引入不必要的地方。
延伸阅读
- nginx 反向代理实战:502 / 504 三种根因决策树 — VIP 漂过去之后 nginx 本身出问题怎么排查
- TCP 排障决策树:ss + tcpdump 三件套定位 — VRRP 心跳包抓不出来时抓 TCP 层
- Wireshark 抓包入门 — 需要图形化看 VRRP 报文时用