Linux 基础体系 · 第 72/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux Keepalived 与 VRRP:虚拟 IP、健康检查、切换和脑裂边界

1. 先区分三个概念:VRRP、Keepalived 和虚拟 IP

**VRRP(Virtual Router Redundancy Protocol,虚拟路由器冗余协议)**是一种节点协作协议。多个真实主机构成一个逻辑上的“虚拟路由器”,对外使用同一个虚拟 IP(Virtual IP,VIP)和虚拟 MAC。正常情况下只有一个节点承担转发或服务入口,其他节点处于备用状态。

Keepalived是 Linux 上常用的用户态高可用程序。它实现了 VRRP,并在此基础上提供:

  • 虚拟 IP 的添加和删除;
  • VRRP 报文发送与接收;
  • 主备状态机;
  • 进程、端口、脚本和接口健康检查;
  • 状态变化通知;
  • 与 IPVS、Nginx 等服务协同。

虚拟 IP不是一个独立设备,也不是由交换机自动“漂移”的地址。它最终仍然表现为 Linux 内核接口上的一个地址,例如:

2: ens160:
    inet 192.0.2.10/24
    inet 192.0.2.100/24 scope global secondary ens160

其中 192.0.2.100 可以是 VIP。当前主节点通过内核把这个地址配置到本机网卡上,备用节点通常不配置它。主备切换时,Keepalived 实际上是在不同节点上执行添加或删除地址,并发送邻居缓存更新报文。

因此,VIP 高可用至少依赖三层机制共同成立:

  1. VRRP 控制面:节点知道谁是当前 Master;
  2. Linux 地址和邻居发现数据面:Master 真正持有 VIP,并让局域网更新 ARP 或 NDP;
  3. 上层服务:Nginx、应用进程或路由转发功能确实可用。

只实现其中一层并不能得到完整的高可用。


2. VRRP 解决的是什么问题

假设客户端访问:

http://192.0.2.100/

后端有两台入口服务器:

node-a: 192.0.2.11
node-b: 192.0.2.12
VIP:    192.0.2.100

客户端并不需要知道当前由哪台服务器处理请求。它只需要通过 ARP 查询:

谁拥有 192.0.2.100?

当前 Master 回答:

192.0.2.100 is-at <Master 的 MAC>

之后客户端把目的 MAC 写成 Master 的 MAC,IP 目的地址仍然是 VIP。

node-a 故障,node-b 成为 Master 后,它会发送 Gratuitous ARP(无偿 ARP,GARP),大意是:

192.0.2.100 is-at <node-b 的 MAC>

客户端和交换机的相关邻居缓存随后更新,新的连接就会到达 node-b

这里有一个容易被忽略的事实:VIP 切换主要改变的是二层邻居解析结果,不是客户端的 IP 路由表。同一子网中的客户端仍然访问同一个 IP,只是这个 IP 对应的 MAC 发生了变化。

IPv6 使用 NDP(Neighbor Discovery Protocol,邻居发现协议)代替 ARP,Keepalived 会通过相关的邻居通告让网络重新学习 VIP 所在节点。


3. VRRP 的报文与网络前提

VRRP 的典型网络属性如下:

  • IPv4 VRRP 使用组播地址 224.0.0.18
  • IPv6 VRRP 使用组播地址 ff02::12
  • IP 协议号是 112,不是 TCP,也不是 UDP;
  • VRRP 报文通常要求 TTL 或 Hop Limit 为 255;
  • 典型部署要求参与节点位于同一个二层广播域;
  • VIP 通常必须属于该二层网络所在的子网。

因此,下面这些条件必须单独验证:

ip addr show dev ens160
ip route
ip link show ens160

如果节点的地址是:

node-a: 192.0.2.11/24
node-b: 192.0.2.12/24
VIP:    192.0.2.100/24

它们看起来属于同一个 /24,但这并不自动证明二层连通。仍然可能存在:

  • 交换机端口隔离;
  • VLAN 配置错误;
  • 防火墙丢弃协议号 112;
  • 组播被交换机或云平台过滤;
  • VRRP 报文进入了错误的 network namespace;
  • 云环境禁止用户直接使用 GARP 或虚拟 MAC。

可以使用 tcpdump 检查是否真的看到 VRRP:

sudo tcpdump -ni ens160 'ip proto 112 or arp'

正常情况下,Master 周期性发送 VRRP 广告报文;切换时,除了 VRRP 状态变化,还应看到新的 Master 发送 ARP 更新。

Keepalived 支持单播 VRRP 配置,适用于某些不支持组播的网络,但单播只改变控制报文的传输方式,不能自动解决 VIP 的二层可达性。跨三层部署时,即使两个节点能够通过单播互相探测,客户端的 ARP、路由和云平台地址绑定仍然可能不成立。


4. VRRP 的状态机

VRRP 中最重要的角色是:

  • Master:当前拥有 VIP,并发送 VRRP 广告;
  • Backup:不主动拥有 VIP,监听 Master 的广告;
  • Init:尚未完成初始化,尚未进入稳定的 Master 或 Backup 状态。

一个简化的状态变化如下:

stateDiagram-v2
    [*] --> Init
    Init --> Master: 选举后优先级最高\n或配置为唯一可用节点
    Init --> Backup: 收到更高优先级 Master 广告
    Backup --> Master: Master_Down_Interval 超时
    Master --> Backup: 收到更高优先级节点广告且允许抢占
    Master --> Backup: 管理员停止或发送优先级 0
    Backup --> Master: 抢占延迟结束

4.1 Master 做什么

Master 的主要工作是:

  1. 在接口上配置 VIP;
  2. 发送 VRRP Advertisement;
  3. 响应局域网对 VIP 的 ARP 或 NDP 查询;
  4. 在服务检查失败时降低自己的有效优先级,或者主动退出;
  5. 在停止时尽量发送优先级为 0 的报文,通知 Backup 尽快接管。

优先级为 0 的报文表示当前 Master 正在离开服务。它不能保证一定送达,因为主机可能已经断电、内核已经崩溃或链路已经断开。

4.2 Backup 做什么

Backup 通常不应持有 VIP。它会:

  1. 监听来自 Master 的 VRRP 广告;
  2. 根据优先级和定时器判断 Master 是否仍然有效;
  3. 在 Master 超时后竞争成为新的 Master;
  4. 成为 Master 后添加 VIP,并发送邻居缓存更新;
  5. 如果发现更高优先级的 Master 且允许抢占,则让出 VIP。

4.3 如何选出 Master

优先级越高,越倾向于成为 Master。一个典型配置是:

node-a priority = 150
node-b priority = 100

当两台节点都正常通信时,node-a 成为 Master。

如果优先级相同,VRRP 还需要一个确定性的决胜规则,通常根据接口地址比较。工程上不应依赖“相同优先级也没关系”,应直接为节点设置不同优先级,使意图清晰。

优先级并不是永久固定的。健康检查可以调整有效优先级。例如:

基础优先级 = 150
Nginx 检查失败扣减 = 50
有效优先级 = 100

如果备用节点有效优先级仍为 120,它就可能接管 VIP。


5. 切换时间如何计算

VRRP 不是“收到心跳就永不切换”。Backup 在一段时间内没有收到合法广告后,才认为 Master 失效。

常见 VRRP 计算关系是:

Skew_Time=256Priority256×Advertisement_Interval\text{Skew\_Time} = \frac{256-\text{Priority}}{256} \times \text{Advertisement\_Interval}

Master_Down_Interval=3×Advertisement_Interval+Skew_Time\text{Master\_Down\_Interval} = 3\times \text{Advertisement\_Interval} + \text{Skew\_Time}

其中:

  • Advertisement_Interval 是 Master 发送广告的间隔;
  • Priority 是 Backup 的优先级;
  • Master_Down_Interval 是 Backup 等待 Master 的时间。

例如:

Advertisement_Interval = 1 秒
Backup Priority        = 100

则:

Skew_Time=256100256×10.609 秒\text{Skew\_Time} = \frac{256-100}{256}\times 1 \approx 0.609\text{ 秒}

所以:

Master_Down_Interval3+0.609=3.609 秒\text{Master\_Down\_Interval} \approx 3+0.609 =3.609\text{ 秒}

这只是 VRRP 控制面开始接管的时间。真实业务恢复时间还包括:

  • Keepalived 状态切换;
  • VIP 添加;
  • GARP 或 NDP 更新;
  • 客户端邻居缓存刷新;
  • Nginx 或应用是否已经启动;
  • TCP 连接重新建立;
  • 上游服务连接池恢复。

因此不能把“VRRP 超时约 3.6 秒”直接等同于“业务在 3.6 秒内完全恢复”。

降低广告间隔可以缩短检测时间,但会增加控制报文频率,并不能消除网络收敛、客户端缓存和应用恢复时间。生产环境应通过故障注入测量实际结果,而不是只根据公式估计。


6. 抢占、回切与连接影响

假设:

node-a priority = 150
node-b priority = 100

node-a 原来是 Master,后来故障,node-b 接管。此时 node-a 恢复:

  • 如果允许抢占,node-a 通常会重新成为 Master;
  • 如果禁止抢占,node-b 可以继续持有 VIP;
  • 如果设置抢占延迟,node-a 会等待一段时间再回切。

抢占有两个常见风险。

第一,节点刚恢复时服务可能尚未完全准备好,但由于优先级更高,它立即夺回 VIP。可以使用 preempt_delay 或启动阶段的健康检查降低这种风险。

第二,频繁故障会导致 VIP 来回切换,也就是 flap。假设健康检查周期很短、没有 fallrise 稳定次数,服务瞬时超时就可能触发切换。切换配置应当包含一定的滞后:

第一次失败:记录失败
连续多次失败:降低优先级或触发切换
连续多次成功:恢复有效优先级

VIP 切换通常不会迁移已有 TCP 连接。旧 Master 上的连接状态、Nginx 工作进程状态和内核连接跟踪状态不会自动复制到新 Master。结果可能是:

  • 已建立连接被重置;
  • 长连接断开;
  • 客户端自动重试后恢复;
  • 连接到后端的会话丢失。

VRRP 解决的是入口地址的可用性,不是连接状态复制。


7. Keepalived 配置示例

以下示例假设:

接口:ens160
node-a:192.0.2.11
node-b:192.0.2.12
VIP:192.0.2.100
VRID:51

在两台机器上安装 Keepalived。不同发行版包名通常相同,但服务名称和配置路径应以发行版为准:

sudo apt install keepalived
# 或
sudo dnf install keepalived

配置文件通常是:

/etc/keepalived/keepalived.conf

7.1 node-a 配置

global_defs {
    router_id LVS_NODE_A
    script_user root
    enable_script_security
}

vrrp_script chk_nginx {
    script "/usr/local/sbin/check-nginx.sh"
    interval 2
    timeout 1
    fall 3
    rise 2
    weight -50
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens160
    virtual_router_id 51
    priority 150
    advert_int 1

    virtual_ipaddress {
        192.0.2.100/24 dev ens160
    }

    track_script {
        chk_nginx
    }

    notify_master "/usr/local/sbin/keepalived-master.sh"
    notify_backup "/usr/local/sbin/keepalived-backup.sh"
    notify_fault  "/usr/local/sbin/keepalived-fault.sh"
}

7.2 node-b 配置

node-b 的主体配置相同,但修改:

global_defs {
    router_id LVS_NODE_B
    script_user root
    enable_script_security
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens160
    virtual_router_id 51
    priority 100
    advert_int 1

    virtual_ipaddress {
        192.0.2.100/24 dev ens160
    }

    track_script {
        chk_nginx
    }
}

将两台节点都设置为 state BACKUP 通常比一台写 MASTER、一台写 BACKUP 更不容易产生误解,因为真正的选举仍由优先级决定。state MASTER 主要影响启动初始状态,不能代替 VRRP 选举。

配置文件权限必须限制为 root 可写:

sudo chown root:root /etc/keepalived/keepalived.conf
sudo chmod 600 /etc/keepalived/keepalived.conf

启动前检查配置。Keepalived 的具体检查选项可能随版本变化,常见方式是:

sudo keepalived -t -f /etc/keepalived/keepalived.conf

然后启动:

sudo systemctl enable --now keepalived
sudo systemctl status keepalived

验证 VIP:

ip -br addr show dev ens160

预期只有当前 Master 显示:

ens160 UP 192.0.2.11/24 192.0.2.100/24

Backup 不应显示 VIP。

auth_pass 等 VRRP 认证配置在一些 Keepalived 配置中仍然可见,但不能把它当作现代网络安全边界。它不是对 VRRP 报文进行通用加密,也不能替代网络隔离、访问控制和节点身份保护。


8. 健康检查到底检查什么

Keepalived 的健康检查不是一个单一功能,而是多个层次。

8.1 进程检查

检查 Nginx 进程是否存在:

pgrep -x nginx >/dev/null

这只能证明某个进程存在,不能证明:

  • Nginx 正在监听正确地址;
  • 配置已经加载成功;
  • TLS 证书可用;
  • upstream 可达;
  • 应用返回正确内容。

8.2 端口检查

可以检查 TCP 端口:

timeout 1 bash -c '</dev/tcp/127.0.0.1/443'

它比进程检查更接近服务入口,但仍然不能证明 HTTP 请求能够成功处理。

8.3 应用检查

一个更有意义的检查可以请求本地健康接口:

#!/bin/sh
set -eu

code=$(
    curl --fail --silent --show-error \
         --max-time 1 \
         --output /dev/null \
         --write-out '%{http_code}' \
         http://127.0.0.1/healthz
)

[ "$code" = "200" ]

保存为:

/usr/local/sbin/check-nginx.sh

并设置:

sudo chown root:root /usr/local/sbin/check-nginx.sh
sudo chmod 700 /usr/local/sbin/check-nginx.sh

脚本必须具有确定的退出码:

  • 0:检查成功;
  • 0:检查失败。

fall 3 表示连续失败若干次后才认为故障,rise 2 表示连续成功若干次后才恢复。具体有效优先级如何因权重变化,应结合当前 Keepalived 版本的手册验证;使用负权重降低故障节点的竞争优先级是常见配置方式。

8.4 检查对象的边界

如果 Nginx 返回 200,但 Nginx 到后端的所有请求都失败,那么“检查 Nginx 进程”可能误判为健康。反过来,如果检查接口依赖一个偶发不稳定的外部系统,又可能造成不必要的 VIP 切换。

检查应尽量回答这个问题:

当前节点是否能够承担通过 VIP 进入的真实业务?

但检查脚本也不能执行过重的业务流程,否则健康检查本身会成为负载来源。


9. 健康检查失败后的因果链

node-a 为当前 Master、node-b 为 Backup 为例:

sequenceDiagram
    participant C as Client
    participant A as node-a
    participant B as node-b
    participant S as Backend

    C->>A: 请求 VIP
    A->>S: 转发请求
    B-->>B: 健康检查失败
    B-->>A: 仍收到 VRRP 广告
    A-->>B: VRRP 广告
    Note over A,B: node-a 的 Nginx 也可能失败
    A-->>B: 广告停止或有效优先级降低
    B->>B: Master_Down_Interval 到期
    B->>B: 添加 VIP
    B-->>C: GARP/NDP 更新
    C->>B: 新连接访问 VIP
    B->>S: 转发请求

这里有两种不同的失败路径:

路径一:Master 主机直接宕机

Master 不再发送广告,Backup 等待超时后接管。这个路径通常比较可靠,因为故障节点已经不能继续处理请求。

路径二:Master 主机仍然活着,但服务异常

例如:

  • Nginx 进程仍在,但 worker 全部卡死;
  • 本机到后端的路由异常;
  • TLS 配置加载错误;
  • 服务端口仍在监听,但返回 503。

这时必须依赖健康检查。健康检查失败后,Keepalived 可以:

  • 降低有效优先级,使另一个节点抢占;
  • 进入故障状态;
  • 执行通知脚本;
  • 删除 VIP,防止继续接收流量。

如果健康检查只检查 VRRP 进程或网卡,服务异常可能不会触发切换。


10. 与 Nginx 反向代理的配合

最简单的做法是让 Nginx 在两台机器上都监听通配地址:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_pool;
    }
}

即使 Backup 没有 VIP,Nginx 也能启动,但它不会收到发往 VIP 的流量。成为 Master 后,VIP 添加到接口,流量即可进入 Nginx。

如果配置为:

server {
    listen 192.0.2.100:80;
}

则 Backup 在没有 VIP 时可能无法绑定地址。可以选择:

  1. 只在持有 VIP 的节点启动 Nginx;
  2. 使用 net.ipv4.ip_nonlocal_bind=1,允许绑定本机当前不存在的地址;
  3. 使用通知脚本在 Master 状态启动服务,在 Backup 状态停止服务;
  4. 让 Nginx 监听 0.0.0.0,通过 VIP 负责流量入口。

ip_nonlocal_bind 的示例:

sudo sysctl -w net.ipv4.ip_nonlocal_bind=1

持久化配置:

net.ipv4.ip_nonlocal_bind = 1

但这不是无条件更优的选择。它会允许进程绑定本机尚未拥有的地址,可能掩盖 VIP 未正确接管的问题。配置变化后应验证:

ss -ltnp
curl --resolve example.com:80:192.0.2.100 http://example.com/healthz

Keepalived 的通知脚本适合执行服务编排,但脚本必须幂等、快速并且不依赖 VIP 自己才能访问。例如,notify_master 中启动 Nginx 时,应先检查配置:

#!/bin/sh
set -eu

/usr/sbin/nginx -t
systemctl start nginx

如果 nginx -t 失败,脚本不应假装服务已经恢复。生产环境还需要明确:健康检查失败时是仅降低 VRRP 优先级,还是同时停止服务、删除 VIP。两者的故障表现不同,不能只依赖默认行为。


11. VIP 切换时 Linux 内核做了什么

成为 Master 后,Keepalived 通常通过 Netlink 请求内核执行类似操作:

ip addr add 192.0.2.100/24 dev ens160

离开 Master 时则相当于:

ip addr del 192.0.2.100/24 dev ens160

管理员可以观察地址事件:

sudo ip monitor address

也可以检查当前地址:

ip -details addr show dev ens160

注意,手工执行 ip addr add 只能改变本机地址,不能构成完整的 VRRP 接管。还需要:

  • 通知局域网更新 ARP 或 NDP;
  • 确认交换机允许新的 MAC;
  • 确认上游路由能够把流量送到该二层网络;
  • 确认应用已经监听并能够处理请求。

手工添加 VIP 还可能与 Keepalived 状态机冲突。测试时若执行过:

sudo ip addr add 192.0.2.100/24 dev ens160

应在测试结束后删除,或者重启 Keepalived 重新收敛:

sudo ip addr del 192.0.2.100/24 dev ens160

生产环境不要把临时 ip 命令当作长期配置管理手段。


12. GARP、ARP 缓存与“切换成功但访问仍失败”

Keepalived 切换后,即使 ip addr 已经显示 VIP,客户端也可能短时间继续把流量发给旧 Master。这是因为客户端或交换机仍然缓存着旧的:

192.0.2.100 -> old MAC

GARP 的作用是主动刷新这些缓存,但它并不保证所有设备都立即接受:

  • 某些交换机对 MAC 移动有保护策略;
  • 某些云平台不允许用户直接宣告地址;
  • 客户端可能忽略或延迟更新 ARP;
  • 中间设备可能有静态 ARP;
  • 多个网络设备可能存在不同的缓存生命周期。

诊断时可以分别查看本机和远端:

ip neigh show 192.0.2.100
sudo tcpdump -ni ens160 arp

从客户端执行:

arp -n
# 或
ip neigh show

如果本机已经持有 VIP,但客户端仍访问旧节点,应重点检查 GARP、交换机 MAC 表和云平台网络限制,而不是继续修改 VRRP 优先级。


13. IPv4、IPv6 和路由边界

IPv4 的 VIP 依赖 ARP,IPv6 的 VIP 依赖 NDP。两者都要求地址的前缀、接口和上游网络设计正确。不要因为 IPv4 VRRP 已经工作,就假设 IPv6 会自动工作。

例如,IPv6 配置可能包含:

virtual_ipaddress {
    2001:db8:10::100/64 dev ens160
}

是否需要显式设置 VRRP 版本、是否允许 IPv6 组播、邻居通告行为如何,取决于 Keepalived 版本和发行版打包方式,部署前应检查本机 Keepalived 手册及实际日志。

VIP 也不等于路由高可用。对于反向代理入口:

客户端 -> VIP -> Nginx -> 后端

VIP 只处理客户端到入口的寻址。后端方向仍然受到:

  • 默认路由;
  • 策略路由;
  • 多路由表;
  • VRF;
  • ECMP;
  • 反向路径检查;
  • 防火墙状态跟踪;

的影响。

例如,备用节点虽然成功接管 VIP,但它到后端没有正确路由,结果是客户端请求已经到达新 Master,却仍然全部失败。此时应检查:

ip route get <backend-ip> from 192.0.2.100
ip rule
ip route show table main
ip route show table all

如果存在非对称路由,Linux 的严格反向路径检查可能丢弃报文。可以查看:

sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.ens160.rp_filter

调整 rp_filter 必须基于实际路由设计进行,不能把关闭反向路径检查作为普遍修复手段。相关问题应结合策略路由和接口级配置验证。


14. 脑裂是什么

**脑裂(split brain)**是指多个节点都认为自己是当前 Master,并同时持有同一个 VIP。

它不是“两个节点优先级一样”这么简单。脑裂的必要条件是:节点之间失去足够的控制面通信,但它们仍然能够继续运行并服务客户端。

典型过程如下:

正常:
node-a Master,持有 VIP
node-b Backup,不持有 VIP

控制链路中断:
node-a 收不到 node-b,但继续服务
node-b 收不到 node-a,等待超时

错误结果:
node-a 继续认为自己是 Master
node-b 也升级为 Master
两者同时添加 VIP

在同一二层网络中,两个节点都发送 GARP,客户端可能看到:

192.0.2.100 -> MAC_A
192.0.2.100 -> MAC_B
192.0.2.100 -> MAC_A
...

于是出现:

  • ARP 缓存来回变化;
  • 连接随机进入不同节点;
  • 一些客户端访问成功,另一些失败;
  • Nginx 日志分散在两台主机;
  • 长连接和会话表现不稳定;
  • 交换机可能报告 MAC flapping。

更危险的是,如果入口服务后面连接的是有状态系统,两个节点同时执行写操作可能造成数据冲突。VRRP 本身不会保护数据库、文件系统或应用状态。


15. 为什么优先级不能解决脑裂

优先级只有在节点能够互相看到 VRRP 广告时才有效。

设:

node-a priority = 150
node-b priority = 100

在通信正常时,node-a 能够让 node-b 知道自己仍然是 Master。若链路分区使两者都收不到对方报文:

  • node-a 不会收到更高优先级竞争者;
  • node-b 也不会收到任何 Master 广告;
  • node-b 超时后会成为 Master;
  • node-a 继续保持 Master。

因此,优先级解决的是可见节点之间的选举顺序,不能解决节点互相不可见时谁有资格继续服务

单播 VRRP 也不能从根本上解决脑裂。它可能让控制报文绕过组播限制,但如果网络存在单向故障:

node-a 能发给 node-b
node-b 不能发给 node-a

两个节点对对方状态的判断仍可能不一致。更复杂的多路径网络还可能产生部分可达、部分不可达的情况。


16. 同一二层和不同二层中的脑裂表现不同

如果两个脑裂节点位于同一二层:

  • 相同 VIP 会产生 ARP 冲突;
  • MAC 地址可能在交换机端口之间快速移动;
  • 故障通常比较明显,但影响面可能很大。

如果两个节点位于不同二层或不同数据中心:

  • 它们可能都持有相同 VIP;
  • 各自本地网络可能都认为该 VIP 合法;
  • 是否冲突取决于上游路由、BGP、云平台地址绑定和客户端路径;
  • 两个区域可能同时接收真实流量。

“不同网段所以不会脑裂”是错误理解。不同网段可能减少 ARP 冲突,却不能阻止双主,也不能阻止两个节点同时写入共享状态。

跨三层或跨数据中心的高可用通常需要其他机制,例如:

  • 路由协议撤销和发布;
  • 云平台的浮动 IP 或 API 绑定;
  • 分布式仲裁;
  • 共享存储 fencing;
  • 应用层租约;
  • 数据库自身的主节点选举。

Keepalived 可以参与其中,但 VRRP 单独不提供法定人数(quorum)或强制隔离(fencing)。


17. 脑裂检测与恢复

日志是第一证据:

sudo journalctl -u keepalived -f

重点观察:

  • 当前状态是否在 MASTERBACKUP 之间频繁切换;
  • 是否出现多个节点都宣布成为 Master;
  • 健康检查是否反复失败;
  • 是否能收到 VRRP 报文;
  • 是否发生优先级变化;
  • 是否执行了通知脚本。

同时在两台机器上执行:

ip -br addr show dev ens160
ip neigh
sudo tcpdump -ni ens160 'ip proto 112 or arp'

若两台机器都显示 VIP,先不要继续重启或反复修改优先级。应先确认哪一台具备真实服务能力,再隔离错误节点:

sudo systemctl stop keepalived

必要时还应停掉 Nginx 或应用,避免双主继续处理写请求。恢复网络后,确认:

# 只有预期 Master 持有 VIP
ip addr show dev ens160

# VRRP 报文方向恢复
sudo tcpdump -ni ens160 'ip proto 112'

# 客户端的邻居缓存指向正确 MAC
ip neigh show <VIP>

如果脑裂期间存在数据库或文件写入,VIP 恢复并不意味着数据已经一致。必须单独检查应用状态、数据库复制状态和业务幂等性。


18. 防火墙和安全边界

防火墙至少要考虑:

  • VRRP 的 IP 协议号 112
  • VRRP 组播地址;
  • 管理和监控端口;
  • 健康检查依赖的本地或后端访问;
  • Nginx 对外监听端口。

可以用以下命令查看本机是否实际收到 VRRP:

sudo tcpdump -ni ens160 'ip proto 112'

如果完全没有报文,优先检查接口、VLAN、组播和防火墙,而不是先修改 priority

VRRP 控制报文不应被认为是强身份认证信道。攻击者如果能够注入或伪造 VRRP 报文,可能影响选举;如果攻击者能够访问同一广播域,还可能直接干扰 ARP。因此生产环境需要:

  • 限制二层接入;
  • 控制管理权限;
  • 限制 Keepalived 配置和检查脚本的写权限;
  • 监控异常 VRRP 和 MAC 变化;
  • 不在健康检查脚本中拼接未经验证的外部输入。

enable_script_security 和 root 脚本权限控制可以降低脚本路径被替换等风险,但不能替代主机加固。


19. 一个可验证的故障演练

不要只通过“拔电源”验证。应分别测试控制面、服务面和数据面。

19.1 测试正常状态

在两台节点上执行:

systemctl is-active keepalived
ip -br addr show dev ens160
curl --max-time 2 http://192.0.2.100/healthz

预期:

  • 一台节点是 Master 并拥有 VIP;
  • 另一台节点是 Backup 且没有 VIP;
  • VIP 请求能够成功。

19.2 测试服务故障

在当前 Master 上停止 Nginx:

sudo systemctl stop nginx

观察:

sudo journalctl -u keepalived -f
ip -br addr show dev ens160

如果健康检查正确,经过 fall 次数和选举时间后,Backup 应接管 VIP。若没有切换,应检查:

  • 健康脚本是否可执行;
  • 脚本退出码是否正确;
  • Keepalived 是否加载了 track_script
  • 权重变化是否足以让对端优先级更高;
  • Nginx 停止后本地检查是否仍然命中其他服务。

19.3 测试主机故障

安全方式是停止 Keepalived:

sudo systemctl stop keepalived

这通常会触发优先级为 0 的通知,备用节点应更快接管。然后恢复:

sudo systemctl start keepalived

再观察是否发生预期回切。

19.4 测试网络分区

网络分区是脑裂测试,风险最高。不要在没有隔离客户端、写入流量和恢复方案的生产环境中直接执行。测试目标不是“VIP 最终能否访问”,而是确认:

  • 哪些故障会导致双主;
  • 监控能否识别双主;
  • 应用是否有写入保护;
  • 恢复时谁被安全隔离;
  • 客户端 ARP 和交换机 MAC 表多久收敛。

20. 常见误解

误解一:VIP 在两台机器上预先都配置好就能高可用

如果两台机器同时持有同一个 VIP,就已经绕过了 VRRP 的主备语义。除非上层明确设计了 Anycast 或其他多活模型,否则这通常会造成地址冲突。

误解二:Keepalived 能保证服务可用

Keepalived 只能根据可观察信号改变 VIP 归属。健康检查写错、检查层次过低或后端不可达时,可能出现“VIP 正常但业务失败”。

误解三:切换后已有连接会自动继续

VIP 和 TCP 连接状态不是同一个对象。地址可以切换,连接状态不会自动迁移。

误解四:两个节点能 ping 通,就说明 VRRP 正常

ping 测试的是 ICMP 可达性,不等于协议号 112 的 VRRP 报文、组播、ARP 更新和服务流量都正常。必须使用 tcpdumpip monitor 和实际 VIP 请求分别验证。

误解五:脑裂只是 VIP 冲突

VIP 冲突是可见症状,真正的风险是两个节点同时认为自己有服务资格。对于无状态 Nginx,表现可能是连接抖动;对于数据库、队列或共享文件,可能演变为数据破坏。


21. 诊断顺序

遇到“VIP 不切换”时,可以按故障路径检查:

# 1. Keepalived 是否运行
systemctl status keepalived

# 2. 配置和日志是否显示错误
journalctl -u keepalived -b

# 3. VIP 当前在哪台机器
ip -br addr

# 4. 是否收到 VRRP 报文
tcpdump -ni ens160 'ip proto 112'

# 5. 是否发生 ARP 更新
tcpdump -ni ens160 arp

# 6. 服务是否真正可用
curl --max-time 2 http://127.0.0.1/healthz
curl --max-time 2 http://<VIP>/healthz

# 7. 到后端的路由是否正确
ip route get <backend-ip> from <VIP>

# 8. 是否存在策略路由或反向路径检查问题
ip rule
ip route show table all
sysctl net.ipv4.conf.all.rp_filter

不同现象对应的故障层次不同:

  • 没有 VRRP 报文:接口、VLAN、组播、协议号 112、防火墙;
  • VIP 已添加但客户端仍访问旧节点:GARP、ARP 缓存、交换机 MAC 学习;
  • VIP 已切换但 HTTP 失败:Nginx、监听地址、证书、后端路由或防火墙;
  • 两台都持有 VIP:控制面分区、单向链路、错误的手工配置或脑裂;
  • 频繁回切:健康检查抖动、抢占、权重和 rise/fall 参数不匹配。

22. 设计边界

Keepalived 与 VRRP 很适合以下场景:

同一二层网络
两台或少量入口节点
一个稳定的 VIP
无状态或可重试的反向代理
应用状态存储在独立的高可用系统中

它不单独解决以下问题:

  • 数据库主从一致性;
  • 跨数据中心仲裁;
  • 共享存储脑裂保护;
  • TCP 连接状态迁移;
  • 云平台对浮动 IP 的控制;
  • 上游和后端路由的自动收敛;
  • 两个节点同时成为 Master 时的强制隔离。

因此,一个完整的高可用设计应把因果链写清楚:

VRRP 广告正常
    -> Backup 知道 Master 存活
    -> Master 持有 VIP
    -> ARP/NDP 指向 Master
    -> Nginx 监听并通过健康检查
    -> 后端路由和策略允许转发
    -> 客户端能够重试或恢复连接

其中任意一环失败,最终现象都可能是“VIP 在,但业务不可用”。而当 VRRP 控制面分区时,真正的边界是:VRRP 可以选举可见节点,却不能在不可见节点之间提供仲裁和 fencing。这正是 Keepalived 与 VRRP 能力的核心边界。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。