Keepalived高可用实战:从VRRP原理到Nginx配置与故障排查
2026/9/15 23:55:54 网站建设 项目流程

Keepalived 这东西,我在生产环境里前前后后折腾了五六年,从最早给 LVS 做后端健康检查,到后来单独给 Nginx、MySQL 做高可用,可以说踩遍了各种各样的坑。它本质上就是 VRRP 协议的一个开源实现,靠虚拟 IP 漂移来实现主备切换。今天这篇就结合我自己的实际经验,把这个工具从原理到配置、从单实例到多实例、从正常运作到出问题排查,完整梳理一遍。不管你是刚接触高可用架构的新手,还是已经被脑裂折腾过的老手,这篇文章都能给你一些参考。

1. Keepalived 到底在解决什么问题

1.1 单点故障,运维手里最烫的山芋

先抛个场景:公司有个官网,流量虽然不算大,但老板要求 7x24 不能挂。你辛辛苦苦配了一台 Nginx 做反向代理,上游挂了几个 Tomcat,一切看起来都很完美。结果半夜两点,机房里那台 Nginx 服务器电源模块烧了,整个官网瞬间 502。

这就是典型的单点故障。所有流量都经过这一台机器,它一挂,业务就断。解决思路其实很简单:再准备一台一模一样备用机,让它时刻准备着接管流量。问题在于怎么让两台机器对外表现得像一台。这时候 Keepalived 就派上用场了。

Keepalived 干的事情,就是在这两台机器上虚拟出一个 IP 来,客户端访问这个虚拟 IP,而不是直接访问某一台物理机。正常情况下,虚拟 IP 绑在 A 机上,A 机挂了之后,虚拟 IP 自动漂移到 B 机,对客户端来说 IP 没变,服务没断,整个切换过程也就几秒钟。

注意:Keepalived 解决的是"服务可用性"问题,而不是"数据一致性"问题。它只管流量到哪台机器,不管业务数据怎么同步。数据库高可用如果只上 Keepalived 不同步数据,切换过去数据还是旧的,照样出事故。

1.2 VRRP 协议:让两台机器"看起来像一台"

Keepalived 的底层是 VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)。这个协议最早是思科搞出来的,目的是让一组路由器共同对外提供一个虚拟网关 IP,避免路由器单点故障。Keepalived 把这个思路搬到了服务器层面。

VRRP 的核心思想是选举。一组路由器/服务器组成一个虚拟路由器组,组里面有一个 Master(主)和一个或多个 Backup(备)。Master 周期性地发送 VRRP 广播报文,告诉 Backup 自己还活着。如果 Backup 在超时时间(通常 3-4 秒)内没有收到 Master 的报文,就会认为 Master 挂了,然后竞选成为新的 Master,把虚拟 IP 绑到自己身上,同时发送免费 ARP 告诉交换机:虚拟 IP 对应的 MAC 地址变了,以后到这个 IP 的流量请发给我。

我最初理解这块时有个误区:以为虚拟 IP 是两台机器共享的。其实不是,某一时刻 VIP 只存在于一台机器的网卡上,另一台机器只有配置,但网卡上没有这个 IP。这样交换机也不会把流量发给没有 VIP 的机器,避免了 IP 冲突。

1.3 Keepalived 不是万能的,要分清楚该不该用它

Keepalived 的定位非常清晰:做 IP 层的高可用。它适合的场景有这么几类。

第一类,给负载均衡器做高可用。比如 Nginx、HAProxy 前面挂一个 Keepalived,两台 Nginx 一台主一台备,VIP 绑定在主上,主挂了备接管。这是目前最常见的用法。

第二类,给 LVS 做高可用。LVS 本身支持 DR 模式、NAT 模式,但 LVS 路由器本身也是单点,需要 Keepalived 来保证冗余。实际上 Keepalived 最初就是配合 LVS 使用的,keepalived 名字里的"keep alive"就是保持 LVS 后端服务器存活的意思。

第三类,给数据库等有状态服务做 VIP 漂移。比如 MySQL 主从架构,主库挂了以后把 VIP 漂移到从库,应用层不用改配置。但这里有个前提:必须配合 MHA、Orchestrator 之类的工具做好数据提升和补偿,Keepalived 只负责让 IP 漂过去,不负责让数据一致。

不适合的场景也很明确:业务层多活的场景、需要会话保持的复杂场景、跨机房容灾场景。Keepalived 的 VRRP 广播是二层协议,一般工作在同一网络内。跨机房做的话延迟和网络分区会导致频繁脑裂,体验非常差。跨机房高可用请用 DNS 负载均衡或者 GSLB 那套,别拿 Keepalived 硬扛。

2. 核心概念与工作原理,掰开揉碎讲清楚

2.1 虚拟 IP 漂移:VIP 是怎么"跑"过去的

虚拟 IP 漂移是 Keepalived 最核心的动作,也是理解整个工具的关键。咱们拆解一下这个动作的完整过程。

正常情况下,Master 节点的 eth0 网卡上绑了两个 IP:物理 IP(比如 192.168.1.10)和虚拟 IP(比如 192.168.1.100)。客户端访问 192.168.1.100 时,交换机学习到的 MAC 地址是 Master 节点网卡的 MAC,所有流量都进 Master。

当 Master 挂了,Backup 节点在 Master 失效定时器(Master Down Timer)超时后,进入 Master 状态,然后执行三个动作:一是把 VIP 配置到自己的 eth0 网卡上;二是立即发送免费 ARP 报文(Gratuitous ARP),告诉局域网内的所有设备,192.168.1.100 这个 IP 对应的 MAC 地址变了,以后给我发;三是开始周期性地发送 VRRP 广播报文,向其他 Backup 声明我是新的 Master。

免费 ARP 报文这个细节很重要,很多新手认为 VIP 漂移是交换机自动感知的,其实不对。如果没有免费 ARP,交换机缓存里还是旧 MAC 地址,流量照样往已经宕机的机器上送,VIP 漂移就失败了。所以 Keepalived 在状态切换时发免费 ARP 是一个关键动作。

这里有个实际经验:有些老旧交换机的 MAC 地址表更新不积极,即使发了免费 ARP 也可能需要等几秒。所以生产环境里 Keepalived 的状态切换时间虽然理论上是秒级,但真正让业务感知不到中断,除了 Keepalived 本身,还要看二层交换机的表现。我遇到过一台很老的水星交换机,切一次要十几秒,后来果断换了华为的。

2.2 Master/Backup 选举机制:优先级说了算

VRRP 组里的角色不是配置死了的,而是通过优先级动态选举出来的。每个节点在配置里有一个 priority 参数,范围是 0-255,数值越大优先级越高。

选举规则是这样:启动的时候,优先级高的节点会成为 Master,优先级低的成为 Backup。Master 会周期性发送 VRRP 报文,里面带着自己的优先级。Backup 收到后会比较:如果自己的优先级比收到的 Master 优先级高,并且配置了抢占模式(nopreempt 没有开启),就会立刻接管成为新的 Master。

这里有个容易踩坑的设计:VRRP 报文的比较不只看 priority,还有 IP 地址大小作为 tiebreaker。如果两个节点的 priority 设成一样,那么 IP 地址较大的那个会成为 Master。这个在配置时必须意识到,不然你以为谁主谁备是随机的,其实是有明确规则的。

还有一点:Backup 节点的优先级是可以动态调整的。Keepalived 支持通过 vrrp_script 里的 weight 参数,根据业务健康状况给优先级加减分。举个例子,Master 节点的 Nginx 挂了,健康检查脚本返回失败,配置的 weight 是 -20,那 Master 的优先级从 100 降到 80,低于 Backup 的 90,于是 Backup 接管,VIP 漂移过去。这样一来,不再是"机器挂了才切换",而是"服务不行了就切换",灵活性和可用性都上了一个台阶。

2.3 抢占模式与非抢占模式:不是所有场景都适合抢占

Keepalived 默认是抢占模式。所谓抢占,就是原先的 Master 从故障中恢复后,会立刻把自己的优先级恢复到 100,然后通过 VRRP 报文让 Backup 发现自己优先级更高,从而把 VIP 抢回来。

抢占模式的优点在于"主备角色明确",业务上如果依赖主节点的特殊配置或资源,恢复后能及时回到主节点。缺点也很明显:频繁地主备切换会导致 VIP 不停地漂移。比如因为网络抖动,主备切换了两次,每次切换都会触发免费 ARP,对客户端连接产生潜在影响。

非抢占模式(配置 nopreempt)适合主备之间没有本质区别的场景,比如两台配置一样的 Nginx,谁当 Master 都无所谓。非抢占模式下,哪个节点先启动,哪个就是 Master。Master 挂了,Backup 接管;Master 恢复了,也只是变成 Backup 待命,不会发生 VIP 再漂移回去的动作。

实际生产里,我大多数场景用的是默认抢占模式,但会配合健康检查脚本把切换逻辑做得相对保守,避免因为一时抖动误切换。如果业务对 VIP 漂移非常敏感,那建议选非抢占模式,前提是两台机器硬件配置、软件配置完全对等。

注意:非抢占模式不是没有抢占动作。它的意思是"恢复后不抢回 VIP",但故障时 Backup 该接管还是会接管。另外,nopreempt 只在两个节点都配置该选项时才生效,如果你只在 Backup 上配了,Master 上没配,那 Master 恢复后照样抢回,配置会失效。

2.4 健康检查脚本:Keepalived 的"心跳脉搏"

Keepalived 的 VRRP 广播只是告诉其他节点"这台机器还活着",但机器活着不代表业务活着。如果 Nginx 进程僵死,端口还在监听但不响应请求,VRRP 报文照样能发出去,Master 不会切换,业务照样挂。

所以 Keepalived 提供了 vrrp_script 机制,让我们可以自定义健康检查脚本,通过脚本返回值动态调整优先级或直接触发切换。这是 Keepalived 配置中最关键也是最需要动脑子的部分。

脚本的检查逻辑可以很丰富:可以检查端口是否监听,可以 curl 一下本地服务看响应码,可以检查磁盘空间、负载、后端存活数。返回值分三种:0 表示成功,1 表示失败,2 表示异常但不切换(保留这个功能可以在特殊场景下用)。

脚本通过 weight 和 track_script 组合决定行为。一个较复杂的策略是:脚本失败时 weight 减掉的分数必须让优先级跌破 Backup 的优先级,VIP 才会漂移。比如 Master 优先级 100,Backup 优先级 90,脚本失败 weight 设为 -20,则 Master 降到 80,低于 90,触发切换。如果 weight 设为 -5,Master 降到 95,还是高于 Backup,就不会切换,这样设计的意图是"降级不切换",适合某些场景。

3. 安装和基础配置:先从最小可用开始

3.1 环境规划和基本要求

在动手装之前,先把环境规划好。Keepalived 对硬件要求很低,虚拟机、物理机、云主机都能跑,但有几个硬性前提需要注意。

同网段:两台机器必须在同一个二层网络内。VRRP 报文是基于多播或单播的 IP 协议包,跨三层就没法直接互通。互联网上那种跨地域的两台云主机绝对不适合直接跑 Keepalived,延迟太高。

VIP 与物理 IP 同网段:虚拟 IP 需要和物理 IP 在同一个子网内,这样交换机才能通过 ARP 正确转发流量。

通信端口和协议:VRRP 使用 IP 协议号 112,走多播地址 224.0.0.18(如果配置了 unicast_peer 则用单播)。需要保证防火墙放行这个流量,很多厂家的云安全组默认不知道这个协议,这是云上使用 Keepalived 最常见的问题。

3.2 安装 Keepalived:包管理器装完也不是万事大吉

Keepalived 在大多数 Linux 发行版上都有软件包,CentOS/RHEL 用 yum 装,Ubuntu/Debian 用 apt 装,非常方便。

# CentOS/RHEL yum install -y keepalived # Ubuntu/Debian apt update && apt install -y keepalived # 查看版本 keepalived --version

装完以后,系统会生成一个默认配置 /etc/keepalived/keepalived.conf,还有一个 systemd 服务。很多新手直接启动服务会报错,因为默认配置里没有实例内容。可以先看一下默认文件,然后改成自己的配置。

如果是源码编译安装,步骤会多一些:下载源码、安装依赖(openssl-devel、libnl3-devel 等)、./configure、make、make install。源码编译的好处是可以自定义编译参数,但对绝大多数场景没有必要,包管理器安装足够。

经验之谈:如果你在 CentOS 7 上用 yum 装,默认版本可能比较旧(比如 1.2.x 和 1.3.x 在某些源里版本有差异)。新版本的 Keepalived 对配置文件的解析更严格,有些老写法会报错。我建议装完后直接看版本,旧版本要么升级源,要么源码编译,别在生产环境用太旧的版本,有些 bug 影响还挺大。

3.3 最小可用配置:一主一备最简模型

我们先把一个最小的可用配置跑起来。假设两台机器:

  • 节点 A:物理 IP 192.168.1.10,主
  • 节点 B:物理 IP 192.168.1.11,备
  • 虚拟 IP:192.168.1.100

先看 A(Master)的配置:

global_defs { router_id LVS_DEVEL_A vrrp_skip_check_adv_addr vrrp_strict } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } }

再看 B(Backup)的配置:

global_defs { router_id LVS_DEVEL_B vrrp_skip_check_adv_addr vrrp_strict } vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 90 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } }

关键参数逐个说。

state 声明初始角色,MASTER 或 BACKUP。注意这只是初始状态,真正起作用的是 priority。

virtual_router_id 是关键标识,同一个 VRRP 组里的所有节点必须保持一致。不同业务实例要用不同的 router_id。如果你在一个网络里跑了多组 Keepalived,router_id 千万别重复,否则两个 VRRP 实例会互相串扰,产生各种奇怪现象。我曾遇到过线上两组业务由于 router_id 都配成了 51,结果 VIP 来回漂移,查了好久才定位到这个低级错误。

interface 指定 VRRP 报文走哪块网卡,务必和有 VIP 的网卡一致。多网卡机器尤其得小心,选错网卡会导致报文从一个网卡出去,VIP 却绑定在另一个网卡上,逻辑会乱。

authentication 是区域认证,PASS 是明文口令,同一个实例里所有节点口令必须一致。生产环境不建议用默认的 1234,改成一个难猜的口令,避免同网段里其他 Keepalived 实例干扰你。还有一种 AH 认证方式,但不太兼容某些设备,通常不用。

advert_int 是 VRRP 报文发送间隔,单位秒。1 表示每 1 秒发一次。这个值决定了故障检测速度。advert_int 设为 1 时,Dead Timer 大约为 3 * 1 + 偏移 = 3.x 秒,也就是说最长约 4 秒完成切换。如果想更快,改成 0.5,但过小的间隔会增大网络广播压力和误判概率。我见过很多人把 advert_int 调成 0.2 来追求毫秒级切换,结果网络一个抖动,主备来回切,业务被搞得比不切还惨。

virtual_ipaddress 这一段列 VIP 地址。可以用 /24 带掩码格式,也可以不带掩码,Keepalived 会根据网卡现有配置推断。label 选项可以给 VIP 绑定一个别名接口名,比如 eth0:1,这样 ip addr 输出里能一眼看到 VIP 属于哪个实例,方便排查。

启动服务:

systemctl start keepalived systemctl enable keepalived systemctl status keepalived

启动后,在 A 上执行 ip addr show eth0,应该能看到 192.168.1.100 已经出现在 eth0 上。在 B 上看 eth0,应该没有这个 IP。然后测试切换:在 A 上执行 systemctl stop keepalived,几秒后到 B 上看,VIP 应该过来了。

3.4 防火墙放行和系统参数调整

这里必须单独立一节讲,因为坑太多。

首先是防火墙。CentOS 7+ 默认 firewalld,Ubuntu 有 ufw。VRRP 的协议号是 112,不是常见的 TCP/UDP 端口,所以要用 ip_protocol 来放行。

# firewalld firewall-cmd --add-rich-rule='rule protocol value="vrrp" accept' --permanent firewall-cmd --reload # ufw ufw allow proto 112 from any

如果你懒得折腾防火墙,可以直接关掉再测试。但生产环境请务必写明白规则,别图省事。

再看 SELinux。CentOS 默认 SELinux enforcing 模式时,Keepalived 写配置文件、绑定 VIP 可能被拦截。要么把 SELinux 改成 permissive,要么给 Keepalived 做一个正确的策略。实际生产里很多人选择直接关闭 SELinux,我觉得如果安全要求不苛刻,这是最省事的做法。如果公司安全要求严格,那得写策略,这个比较费劲,超出了本文范围。

另外还有一个系统参数:net.ipv4.ip_nonlocal_bind。Backup 节点在接管 VIP 之前,内核默认不允许绑定一个本机不存在的 IP。有些服务提前 bind 了 VIP 地址(比如 Nginx 里的配置),在 Backup 上服务会起不来。解决办法是让内核允许 non-local bind:

sysctl -w net.ipv4.ip_nonlocal_bind=1 echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.conf

但这个参数只在某些场景需要,不是标配。Keepalived 管 VIP 绑定的时候是在内核层面操作的,不受此限制。主要是你自己的业务服务需要 bind VIP 时才要开。

4. 实战:Nginx 高可用方案完整落地

这一节从零到一,部署一个生产可用的 Nginx + Keepalived 高可用架构。不管你之前有没有接触过,跟下来都能跑得通。

4.1 架构设计和需求约定

业务场景设定:一台对外提供 HTTP 服务的 Nginx,需要消除单点故障。设计如下。

  • 两台服务器 node1(192.168.1.10)和 node2(192.168.1.11),配置对等
  • 虚拟 IP 192.168.1.100 对外提供服务
  • node1 为主节点,node2 为备节点
  • 健康检查策略:Nginx 进程挂了或 HTTP 探测失败,VIP 漂移到对端
  • 故障恢复策略:node1 恢复后,VIP 回到 node1(抢占模式)
  • Nginx 在两端都启动,但只有 Master 节点的 VIP 有流量

这套架构适合静态网站、前端页面、API 网关入口等大多数 Web 场景。因为两台 Nginx 都是无状态组件,所以根本不需要复杂的数据同步逻辑。

4.2 安装配置 Nginx,两边的配置尽量保持完全一致

先在两台机器上都装好 Nginx:

yum install -y nginx systemctl enable nginx

nginx.conf 里监听 80 端口,这里的监听地址要注意:既可以监听 0.0.0.0:80,也可以监听 VIP 192.168.1.100:80。我建议监听 0.0.0.0:80,这样两台机器都会启动 Nginx 并监听 80 端口。当 VIP 从 A 漂到 B 时,B 上的 Nginx 已经在监听 80 了,流量到达后立刻能处理,不用等 Nginx 冷启动。

如果监听 VIP 地址,就会依赖 ip_nonlocal_bind,而且 Nginx 必须等 VIP 绑定后才能正常启动,启动时机和 Keepalived 之间会有先后依赖,容易出问题。所以无状态服务监听任意地址是更稳妥的选择。

server { listen 80; server_name example.com; location / { proxy_pass http://backend_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

两边 Nginx 配置尽量保持完全一致,这样可以避免切换后行为不一致。

验证 Nginx 能正常启动:

nginx -t systemctl start nginx curl -I http://127.0.0.1/ # 应该返回 200 或 502 但至少 Nginx 在响应

4.3 编写健康检查脚本:稳、准、不误报

健康检查脚本是这套方案里最关键的一个文件,写得好不好直接决定了故障切换的质量。

先说设计原则。脚本要足够灵敏,服务一挂就能发现;但也不要过于激进,避免网络抖动就误切换。常见做法是连续探测多次失败才认为故障,而不是探测一次失败就下结论。

我写的一个 Nginx 健康检查脚本,/etc/keepalived/check_nginx.sh:

#!/bin/bash # 检查 Nginx 进程和 HTTP 响应 # 返回 0 表示健康,返回 1 表示不健康 # 1. 先查进程 if ! pgrep -x nginx > /dev/null 2>&1; then exit 1 fi # 2. 再检查 HTTP 本地响应,连续 3 次失败才报故障 FAIL_COUNT=0 for i in 1 2 3; do HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 3 http://127.0.0.1/ 2>/dev/null) if [ "$HTTP_CODE" != "200" ]; then FAIL_COUNT=$((FAIL_COUNT + 1)) sleep 1 else break fi done if [ "$FAIL_COUNT" -ge 3 ]; then exit 1 else exit 0 fi

给脚本加上执行权限:

chmod +x /etc/keepalived/check_nginx.sh

这个脚本的逻辑值得说一下。第一步用 pgrep 查进程,这一步很快很轻。但进程存在不代表服务正常,所以第二步再用 curl 探测本地 HTTP 响应。用三次探测而不是一次,是为了避免偶发超时导致误判。curl 的 --connect-timeout 和 --max-time 也做了限制,避免脚本本身卡死。

注意:脚本里的临界值设计要符合你的业务容忍度。三次探测,每次最多 3 秒,最坏情况一个完整的检查周期要 9 秒左右。Keepalived 里配套的检查间隔要设在 3-5 秒之间,这样整体故障切换时间可以控制在 10-15 秒内。如果你的业务对 RTO(恢复时间目标)有更高要求,可以把探测次数降为 2 次,或者把 sleep 去掉。但风险也相应上升,需要你在误判率和切换速度之间做权衡。

4.4 配置 Keepalived 完整版,策略型脚本联动

接下来是完整版配置。A、B 两台机器的 global_defs 保持一致,vrrp_instance 配置里除了 state 和 priority 以外保持相同。

A 节点 /etc/keepalived/keepalived.conf:

global_defs { router_id nginx_ha_a vrrp_skip_check_adv_addr vrrp_strict vrrp_garp_interval 0 vrrp_gna_interval 0 } vrrp_script check_nginx { script "/etc/keepalived/check_nginx.sh" interval 3 timeout 10 fall 3 rise 2 weight -20 } vrrp_instance VI_NGINX { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass NginxHA123 } virtual_ipaddress { 192.168.1.100/24 dev eth0 } 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" }

B 节点配置大体一样,改这几处:

router_id nginx_ha_b state BACKUP priority 90

再看几个关键参数的设计思路。

interval 3 表示每隔 3 秒跑一次检查脚本。要注意 interval 得大于脚本的最大执行时长,否则多个脚本实例会重叠执行。我这个脚本最坏执行时间接近 9 秒,如果 interval 设 3,就可能出现脚本还没跑完,下一次又开始了。这里其实有个细节:Keepalived 的脚本执行不是严格的定时任务,它在上一次脚本执行结束后开始计时,所以 interval 3 实际的含义是"下一次执行在本次结束后 3 秒"。即使这样,脚本最坏执行 9 秒,而 fall 3 需要连续 3 次失败才生效,整体判定失败的时间会比较长。实际操作中我通常把 curl 的超时调小,让单次脚本最坏控制在 3 秒左右,interval 设 3,这样检测周期紧凑可控。

fall 3 表示连续 3 次失败才确认故障,rise 2 表示连续 2 次成功判定恢复。这两个参数配合脚本内部的三次探测,实际上是一个"双重保险":脚本内部挫掉小抖动,Keepalived 层面再挫掉偶发故障。

weight -20 表示脚本失败一次就把优先级减 20。策略判断:Master 优先级 100,减 20 变成 80,低于 Backup 的 90,于是 VIP 漂移到 Backup。如果脚本恢复,Master 优先级加回来变成 100,抢占模式下 VIP 又会漂移回来。

notify 脚本主要用于对接告警系统。当 Keepalived 状态切换时,会自动执行对应状态的脚本,可以往企业微信、钉钉、邮件发告警,或者做自动化标记。里面可以根据 $1 参数判断状态,然后做差异化处理。

notify 脚本简单示例,/etc/keepalived/notify.sh:

#!/bin/bash echo "$(date '+%F %T') switch to $1" >> /var/log/keepalived-notify.log # 这里可以加 curl 发告警、调 API 等逻辑

4.5 启动验证和完整的切换演练

所有配置文件就位后,依次在 A、B 上启动服务:

systemctl start keepalived systemctl enable keepalived

然后按下面的顺序做一遍完整验证。

第一步:确认初始状态。

# 在 A 上 ip addr show eth0 | grep 192.168.1.100 ip addr show eth0 | grep "state UP" # 应该能看到 VIP 在 A 上 # 在 B 上 ip addr show eth0 | grep 192.168.1.100 # 应该没有任何输出

第二步:从外部访问 VIP,确认服务正常。

curl -I http://192.168.1.100/ # 返回 200 OK

第三步:模拟 Nginx 故障。

# 在 A 上停掉 Nginx systemctl stop nginx

等待健康检查脚本连续失败 3 次(约 9 秒 + 检查间隔),再到 B 上看:

ip addr show eth0 | grep 192.168.1.100 # 现在 B 上应该有 VIP 了

外部再次 curl VIP,服务应该还是正常响应。但注意:请求实际上已经由 B 上的 Nginx 处理了。

第四步:恢复 Nginx。

# 在 A 上重新启动 Nginx systemctl start nginx

等健康检查连续两次成功,A 的优先级恢复到 100,抢占模式下 VIP 会很快回到 A。再到 A 上看 ip addr,VIP 应该回来了。

第五步:验证自动告警和通知。查看 notify 日志:

cat /var/log/keepalived-notify.log

里面应该记录了从 MASTER 到 BACKUP 再到 MASTER 的状态变化。

整个流程走完,这套高可用方案才算真正可以上线。

实操提醒:千万不要在没做切换演练的情况下直接上生产。我见过太多部署完 Keepalived 看起来一切正常,结果一发生故障就发现备机上 Nginx 配置有问题、脚本没执行权限、防火墙拦截 VRRP 等一堆低级坑。每半年做一次切换演练应该作为运维团队的常规动作,我甚至见过大厂的做法:每周随机时间手动拔掉一台机器网线,检验高可用系统的自动化程度。

5. 常见问题与排查技巧实录

5.1 脑裂:高可用最怕的事

脑裂指两台机器同时认为自己是 Master,都绑定了 VIP,网络出现混乱。脑裂一旦发生,VIP 在 A、B 两台机器上同时存在,客户端发到 VIP 的流量会被交换机在 MAC 地址表里反复横跳,业务表现时好时坏,延迟暴增。

脑裂的常见原因有以下几种。

第一,VRRP 报文被防火墙拦截,Backup 收不到 Master 的报文,超时后认为 Master 挂了,于是自己接管 VIP。此时 Master 并没有挂,于是两边同时有 VIP。

第二,网络抖动或临时拥塞,导致 VRRP 广播报文丢失,触发 Backup 接管。

第三,交换机上开启了 IGMP Snooping 或组播过滤,多播报文没被正确转发到对应端口。

如何检测脑裂?最简单的办法:定期检查本机的 VIP 是否正常,然后通过另一个通道(比如监控系统的 Agent)去查对端的 VIP。我在生产环境是这么做的:用 zabbix 或自研监控脚本,每 30 秒分别查 A 和 B 的 VIP 状态,如果发现两边同时都有 VIP,就立刻告警。如果两边同时都没有 VIP,那也是严重故障,说明没有任何节点提供服务。

如何尽量避免脑裂?

一是检查防火墙,把 VRRP 协议的流量放行规则写正确,特别是华为云、阿里云这类云环境,安全组规则不但要放行端口,还要放行协议号为 112 的 IP 层协议。

二是如果网络抖动严重,可以适当增大 advert_int。虽然会影响切换速度,但能减少误判概率。

三是如果网络环境不支持多播,可以开启单播模式,配置 unicast_peer,让 VRRP 报文通过单播直连对端,绕开组播问题。配置方法如下:

vrrp_instance VI_NGINX { ... unicast_src_ip 192.168.1.10 unicast_peer { 192.168.1.11 } }

单播模式除了解决网络组播问题,另一个好处是更安全,不会让无关机器接收到 Keepalived 的 VRRP 报文。

5.2 日志排查:从 journalctl 里读出真相

Keepalived 的日志一般会写到 /var/log/messages(CentOS/RHEL)或 /var/log/syslog(Ubuntu),用 systemd 管理时也可以用 journalctl 看。

常用的排查命令:

tail -f /var/log/messages | grep Keepalived journalctl -u keepalived -f # 更精确地过滤实例 journalctl -u keepalived | grep "VI_NGINX" journalctl -u keepalived | grep -E "(Master|Backup|Fault)"

一条典型的 Master 状态切换日志长这样:

Apr 5 14:23:45 node2 Keepalived_vrrp[12345]: VRRP_Instance(VI_NGINX) Transition to MASTER STATE Apr 5 14:23:46 node2 Keepalived_vrrp[12345]: VRRP_Instance(VI_NGINX) Entering MASTER STATE Apr 5 14:23:46 node2 Keepalived_vrrp[12345]: VRRP_Instance(VI_NGINX) sending gratuitous ARP on eth0 for 192.168.1.100

注意日志里有没有 "sending gratuitous ARP" 字样,这个说明机器已经从 Backup 状态切换到 Master 状态,并且在发送免费 ARP 刷新交换机缓存。如果状态已经切换但业务还是不通,问题大概率在交换机的 MAC 表刷新上。

还有一个很常见的误导性日志:VRRP_Instance(VI_NGINX) Dropping received VRRP packet。这个通常说明收到了一个 VRRP 报文,但因为校验失败被丢弃了。原因可能是 authentication 密码不一致、virtual_router_id 不一致,或者报文来源 IP 不在预期的网段。排查思路依次检查这三个配置。

5.3 状态查看和主动触发的调试命令

有时候你需要手动确认当前哪个节点是 Master,或者强制让 VIP 漂移。比较有用的命令如下。

# 查看 keepalived 进程 ps aux | grep keepalived # 查看当前 VRRP 状态(通过进程输出或日志) ip -d addr show eth0 # 查看日志里当前实例状态 journalctl -u keepalived --since "10 minutes ago" | grep "VRRP_Instance"

注意 keepalived 有一个命令行参数 --dont-fork,大多数发行版的服务文件里已经加了。没用的话建议加上,因为前台模式方便调试,比如你能直接在控制台看到 VRRP 报文收发情况。

强制切换的玩法:生产环境下有时需要手动把流量切到备用节点做维护。两种方法:

一种是临时把 Master 的优先级改低,然后 reload:

sed -i 's/priority 100/priority 80/' /etc/keepalived/keepalived.conf systemctl reload keepalived

另一种更干脆:直接 stop keepalived,VIP 自然漂移到 Backup。维护结束后再 start 回来,抢占模式下 VIP 会自动漂回来。这个方法在测试环境很常用,生产环境建议用前者,因为不用重启进程,更平滑。

注意:恢复 Master 节点后,VIP 漂移过来需要 1-3 秒,加上免费 ARP 刷新交换机 MAC 的时间,业务可能有一个很短暂的"双主"或"无主"状态。很多生产事故就发生在这个时间窗口。如果你在维护过程中有长连接(比如 WebSocket),切换会导致连接断开,这是所有 VIP 漂移方案的固有代价,应用层需要考虑重连机制。

5.4 环境相关的奇怪问题

我整理了一下这些年踩过的环境类坑,每个都很有代表性。

云服务器的 VRRP 限制是最常见的问题。阿里云、腾讯云默认的 VPC 网络并不支持标准的 VRRP 广播,安全组和 VPC 网络本身会丢弃多播报文。如果你是云服务器,要么换成单播模式,要么直接用云厂商的负载均衡 SLB/CLB 做高可用,省心得多。强行在云主机上玩 Keepalived,最后多半会踩到各种网络限制的坑。

多网卡机器上接口选错也很常见。如果机器有两块网卡,一块走内网,一块走外网,VRRP 报文必须和业务流量走同一块网卡。很多新手把 interface 配成 eth1,VIP 却配置在 eth0 上,结果 VRRP 报文和业务流量不在一个网络里,切换行为非常怪异。

VIP 冲突这个问题有隐蔽性。如果你的 VIP 被同网段的其他设备占用了,Keepalived 启动时会检测到并进入 FAULT 状态。排查方法:先 ping VIP,如果通说明 IP 已经被占用,再查 ARP 表看看是哪个 MAC。

ping -c 3 192.168.1.100 arp -a | grep 192.168.1.100

CPU 高负载导致误切换也是一个容易被忽略的问题。Keepalived 是单线程模型,虽然它本身很轻量,但如果机器负载特别高,VPPR 报文发送和处理会延迟,可能导致 Master 报文发布不及时,Backup 误以为 Master 挂了而接管。这种情况的典型特征是:主备都进入 MASTER 状态(脑裂),但两边负载都不高。排查时看日志里有没有 "Master Down Timer" 以及与之对应的网络延迟。

还有一个我印象特别深的坑:VIP 上配置了多个业务网段。早期我把两个网段的 VIP 写在一个 virtual_ipaddress 块里,比如 192.168.1.100 和 10.10.1.100,结果发现主节点重启 keepalived 时,10.10.1.100 那个网段的下游设备经常丢包。后来查资料发现,Keepalived 在帮多个 VIP 发送免费 ARP 时是逐个执行的,间隔时间可以配置,默认中间没有间隔,交换机来不及刷新所有条目。解决方法是把两个网段的 VIP 分别放在不同的 vrrp_instance 里,或者调大 vrrp_garp_interval 和 vrrp_gna_interval,给 ARP 刷新流出时间。

5.5 Keepalived 作为服务,如何优雅地管理和重启

很多人改完配置后直接 systemctl restart keepalived,这样做风险不小。在抢占模式下,restart 会让 keepalived 短暂停掉,VRRP 报文停止发送,对端的 Backup 会在几秒内接管 VIP,然后你 restart 完成后,Master 又抢占回来。结果就是业务流量被来回切换,明显感知到抖动。

正确的做法是用 reload:

systemctl reload keepalived

Keepalived 支持 SIGHUP 信号重载配置,不会中断 VRRP 状态。但 reload 有一个坑:如果你的配置改错了,reload 可能会让服务直接挂掉或者进入不可用状态。所以我建议在 reload 之前先用 keepalived 的配置测试语法:

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

这条命令会解析配置文件并报出语法错误,特别好用。配完测试通过再加 reload,可以大幅降低改配置导致的事故概率。

6. 进阶玩法:多实例、双主模式和 LVS 联动

基础搞完以后,我们看看 Keepalived 还能怎么玩出花来。这三个方向是生产环境里最常见的进阶需求。

6.1 多 vrrp_instance:一台机器跑多组高可用

如果一台机器上有多个业务系统需要做高可用,不希望它们混在一个 VRRP 组里,可以配置多个 vrrp_instance。每个实例有自己的 VIP、自己的 virtual_router_id、自己的优先级和健康检查脚本。

举个例子,一台机器同时是 Nginx 主节点和 MySQL 备节点。可以这样设计:

  • VI_NGINX:优先级 100,VIP 192.168.1.100
  • VI_MYSQL:优先级 90,VIP 192.168.2.100

这样 Nginx 的 VIP 在这台机器上是主,MySQL 的 VIP 在这台机器上是备,两台机器交叉承载不同业务,流量负载也能分担,不再是一台闲着,一台累死。

多实例配置时需要注意,virtual_router_id 必须全局唯一。哪怕两个实例在不同的业务里,只要在同一个二层网络里,router_id 冲突就会导致相互干扰。我建议在运维文档里构建一张表,记录每个实例的 router_id、VIP、业务方,便于排查问题。

另外还有一个细节:告警通知脚本里要根据实例名区分处理。notify 脚本第一个参数是状态,如果想判断实例,可以在配置里给 notify 脚本传第二个参数,让脚本能识别是哪个实例触发了切换。

notify_master "/etc/keepalived/notify.sh MASTER VI_NGINX" notify_backup "/etc/keepalived/notify.sh BACKUP VI_NGINX"

这样告警信息里能明确显示哪个实例发生了切换,排查起来快很多。

6.2 双主模式:把资源利用率拉满

标准的一主一备模式有个天然的浪费:备用机器正常情况下不承担业务流量,性能冗余白白闲置。双主模式就是为了利用起这台"闲置"机器。

双主模式的核心思路是:配置两个 vrrp_instance,在一台机器上 A 实例是 Master、B 实例是 Backup,在另一台机器上反过来。每个实例有自己的 VIP,两个 VIP 都能对外提供服务。比如:

  • node1:VI_1 是 MASTER(VIP1),VI_2 是 BACKUP(VIP2)
  • node2:VI_1 是 BACKUP(VIP1),VI_2 是 MASTER(VIP2)

外部流量一半走 VIP1,一半走 VIP2。正常情况下两台机器同时干活,利用率翻倍。node1 挂了以后,VIP1 漂到 node2,但 VIP2 还留在 node2,node2 需要扛起全部压力,这是双主模式的一个需要考虑的点。所以双主模式对单台机器的性能和容量要求是"可以扛住全部流量",而不是"扛住一半流量",否则故障发生时照样撑不住。

双主模式更适合无状态服务,比如两个 Nginx 集群互为备份、两个 API 网关入口交叉承接流量。如果是有状态服务,双主模式会带来数据一致性问题,复杂度直线上升,不建议轻易尝试。

6.3 Keepalived + LVS:从后端健康检查到负载均衡高可用

Keepalived 最初设计的黄金搭档是 LVS。LVS 提供四层负载均衡,Keepalived 负责 LVS 路由器的高可用和后端真实服务器的健康检查。这套组合在很长一段时间里几乎是电商、门户网站的标准架构。

架构大概是这样的:两台 LVS 服务器一主一备跑 Keepalived,VIP 对外提供服务。后端挂多台真实服务器(Real Server),跑 Nginx 或 Tomcat。Keepalived 的配置文件里除了 vrrp_instance,还有 virtual_server 段,用它来定义 LVS 的转发规则和后端服务器组。

在 Keepalived 的配置文件里,LVS 配置是独立段落,和 vrrp_instance 平级。virtual_server 常见配置长这样:

virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR persistence_timeout 600 protocol TCP real_server 192.168.1.21 80 { weight 1 HTTP_GET { url { path / status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.1.22 80 { weight 1 HTTP_GET { url { path / status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }

这一段的含义:对 192.168.1.100:80 做负载均衡,算法是加权轮询(wrr),转发方式是 DR 模式,后端有两台真实服务器。Keepalived 每隔 6 秒探测一次后端 HTTP 服务,如果某台机器 3 次探测失败,就自动从 LVS 转发池里剔除。恢复后自动加回。这样一来,Keepalived 不但在 LVS 路由器层面做到了高可用,在后端健康检查层面也做足了保障。

LVS 的 DR 模式有个特点:真实服务器也需要配置 VIP,但要抑制对 VIP 的 ARP 响应。后端真实服务器的配置相对繁琐,需要修改内核参数避免 IP 冲突。这个领域如果展开讲,又是一整篇文章的篇幅,基本思路是通过 sysctl 调整 arp_ignore 和 arp_announce 参数。这里先留个引子,后续可以单独写一篇 LVS+Keepalived 的完整部署指南。

Keepalived + LVS 这个组合的高可用能力,主要体现在几个方面:一是 LVS 路由器自身高可用,避免负载均衡器单点;二是后端真实服务器健康检查,自动剔除故障节点;三是有连接跟踪同步功能,LVS 连接状态可以在主备之间同步,切换时不丢连接。连接同步是个大杀器,传统的 TCP 连接在 LVS 主备切换时一般都会断,因为连接状态是保存在单个 LVS 节点内存里的。Keepalived 提供了连接同步机制,让主 LVS 定期把连接状态同步给备 LVS,备 LVS 接管后可以直接接管已有连接。配置方法是在 global_defs 里加上静态同步选项。不过连接同步对内存和带宽有一定消耗,通常只有在业务对长连接敏感时才启用。

写在最后的几条实在建议

搞了这么多年高可用,我觉得 Keepalived 这个工具确实设计得挺巧,十几行的配置就能解决一个生产级别的单点问题。但也正因为它简单,很多人轻视了其中的坑。配置语法看一遍就会,但真正能跑得好、扛得住故障,需要踩过很多次才能明白。

我的核心建议就三条。

第一条:健康检查脚本是灵魂,是决定高可用质量的核心。脚本写得越贴近业务,切换的准确率越高。强烈建议脚本里带上业务层的探测逻辑,而不是只看进程在不在。但与此同时,探测逻辑也要尽量简单快速,避免引入复杂度反噬自身。

第二条:切换演练不能停在部署那天。Keepalived 部署是开始,不是结束。每半年到一年做一次故障演练,最好用真正拔网线、停内核这种硬核方式,验证整套方案是真的能扛故障。很多部署了高可用的系统,第一次发生真故障时才发现备机有问题,这种场景不要让它出现在你的生产环境里。

第三条:告警别停。Keepalived 切换不可怕,可怕的是切换了你不知道。无论多忙,务必保证 notify 脚本能正常发告警。我在生产环境里把 notify 和监控系统做成强绑定,Keepalived 一有状态变化,立刻能收到企业微信通知,第一时间知道发生了什么。在高可用系统里,可观测性比高可用本身更重要,因为你永远不知道下一次故障会在什么时候、以什么方式到来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询