ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Keepalived高可用实战:从VRRP原理到Nginx配置与故障排查

Keepalived高可用实战:从VRRP原理到Nginx配置与故障排查 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 的底层是 VRRPVirtual 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 优先级 100Backup 优先级 90脚本失败 weight 设为 -20则 Master 降到 80低于 90触发切换。如果 weight 设为 -5Master 降到 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备虚拟 IP192.168.1.100先看 AMaster的配置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 } }再看 BBackup的配置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。如果你在一个网络里跑了多组 Keepalivedrouter_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 默认 firewalldUbuntu 有 ufw。VRRP 的协议号是 112不是常见的 TCP/UDP 端口所以要用 ip_protocol 来放行。# firewalld firewall-cmd --add-rich-rulerule protocol valuevrrp 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 bindsysctl -w net.ipv4.ip_nonlocal_bind1 echo net.ipv4.ip_nonlocal_bind 1 /etc/sysctl.conf但这个参数只在某些场景需要不是标配。Keepalived 管 VIP 绑定的时候是在内核层面操作的不受此限制。主要是你自己的业务服务需要 bind VIP 时才要开。4. 实战Nginx 高可用方案完整落地这一节从零到一部署一个生产可用的 Nginx Keepalived 高可用架构。不管你之前有没有接触过跟下来都能跑得通。4.1 架构设计和需求约定业务场景设定一台对外提供 HTTP 服务的 Nginx需要消除单点故障。设计如下。两台服务器 node1192.168.1.10和 node2192.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两边的配置尽量保持完全一致先在两台机器上都装好 Nginxyum install -y nginx systemctl enable nginxnginx.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 21; then exit 1 fi # 2. 再检查 HTTP 本地响应连续 3 次失败才报故障 FAIL_COUNT0 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.confglobal_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 addrVIP 应该回来了。第五步验证自动告警和通知。查看 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/messagesCentOS/RHEL或 /var/log/syslogUbuntu用 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 的优先级改低然后 reloadsed -i s/priority 100/priority 80/ /etc/keepalived/keepalived.conf systemctl reload keepalived另一种更干脆直接 stop keepalivedVIP 自然漂移到 Backup。维护结束后再 start 回来抢占模式下 VIP 会自动漂回来。这个方法在测试环境很常用生产环境建议用前者因为不用重启进程更平滑。注意恢复 Master 节点后VIP 漂移过来需要 1-3 秒加上免费 ARP 刷新交换机 MAC 的时间业务可能有一个很短暂的双主或无主状态。很多生产事故就发生在这个时间窗口。如果你在维护过程中有长连接比如 WebSocket切换会导致连接断开这是所有 VIP 漂移方案的固有代价应用层需要考虑重连机制。5.4 环境相关的奇怪问题我整理了一下这些年踩过的环境类坑每个都很有代表性。云服务器的 VRRP 限制是最常见的问题。阿里云、腾讯云默认的 VPC 网络并不支持标准的 VRRP 广播安全组和 VPC 网络本身会丢弃多播报文。如果你是云服务器要么换成单播模式要么直接用云厂商的负载均衡 SLB/CLB 做高可用省心得多。强行在云主机上玩 Keepalived最后多半会踩到各种网络限制的坑。多网卡机器上接口选错也很常见。如果机器有两块网卡一块走内网一块走外网VRRP 报文必须和业务流量走同一块网卡。很多新手把 interface 配成 eth1VIP 却配置在 eth0 上结果 VRRP 报文和业务流量不在一个网络里切换行为非常怪异。VIP 冲突这个问题有隐蔽性。如果你的 VIP 被同网段的其他设备占用了Keepalived 启动时会检测到并进入 FAULT 状态。排查方法先 ping VIP如果通说明 IP 已经被占用再查 ARP 表看看是哪个 MAC。ping -c 3 192.168.1.100 arp -a | grep 192.168.1.100CPU 高负载导致误切换也是一个容易被忽略的问题。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 又抢占回来。结果就是业务流量被来回切换明显感知到抖动。正确的做法是用 reloadsystemctl reload keepalivedKeepalived 支持 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优先级 100VIP 192.168.1.100VI_MYSQL优先级 90VIP 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 都能对外提供服务。比如node1VI_1 是 MASTERVIP1VI_2 是 BACKUPVIP2node2VI_1 是 BACKUPVIP1VI_2 是 MASTERVIP2外部流量一半走 VIP1一半走 VIP2。正常情况下两台机器同时干活利用率翻倍。node1 挂了以后VIP1 漂到 node2但 VIP2 还留在 node2node2 需要扛起全部压力这是双主模式的一个需要考虑的点。所以双主模式对单台机器的性能和容量要求是可以扛住全部流量而不是扛住一半流量否则故障发生时照样撑不住。双主模式更适合无状态服务比如两个 Nginx 集群互为备份、两个 API 网关入口交叉承接流量。如果是有状态服务双主模式会带来数据一致性问题复杂度直线上升不建议轻易尝试。6.3 Keepalived LVS从后端健康检查到负载均衡高可用Keepalived 最初设计的黄金搭档是 LVS。LVS 提供四层负载均衡Keepalived 负责 LVS 路由器的高可用和后端真实服务器的健康检查。这套组合在很长一段时间里几乎是电商、门户网站的标准架构。架构大概是这样的两台 LVS 服务器一主一备跑 KeepalivedVIP 对外提供服务。后端挂多台真实服务器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 参数。这里先留个引子后续可以单独写一篇 LVSKeepalived 的完整部署指南。Keepalived LVS 这个组合的高可用能力主要体现在几个方面一是 LVS 路由器自身高可用避免负载均衡器单点二是后端真实服务器健康检查自动剔除故障节点三是有连接跟踪同步功能LVS 连接状态可以在主备之间同步切换时不丢连接。连接同步是个大杀器传统的 TCP 连接在 LVS 主备切换时一般都会断因为连接状态是保存在单个 LVS 节点内存里的。Keepalived 提供了连接同步机制让主 LVS 定期把连接状态同步给备 LVS备 LVS 接管后可以直接接管已有连接。配置方法是在 global_defs 里加上静态同步选项。不过连接同步对内存和带宽有一定消耗通常只有在业务对长连接敏感时才启用。写在最后的几条实在建议搞了这么多年高可用我觉得 Keepalived 这个工具确实设计得挺巧十几行的配置就能解决一个生产级别的单点问题。但也正因为它简单很多人轻视了其中的坑。配置语法看一遍就会但真正能跑得好、扛得住故障需要踩过很多次才能明白。我的核心建议就三条。第一条健康检查脚本是灵魂是决定高可用质量的核心。脚本写得越贴近业务切换的准确率越高。强烈建议脚本里带上业务层的探测逻辑而不是只看进程在不在。但与此同时探测逻辑也要尽量简单快速避免引入复杂度反噬自身。第二条切换演练不能停在部署那天。Keepalived 部署是开始不是结束。每半年到一年做一次故障演练最好用真正拔网线、停内核这种硬核方式验证整套方案是真的能扛故障。很多部署了高可用的系统第一次发生真故障时才发现备机有问题这种场景不要让它出现在你的生产环境里。第三条告警别停。Keepalived 切换不可怕可怕的是切换了你不知道。无论多忙务必保证 notify 脚本能正常发告警。我在生产环境里把 notify 和监控系统做成强绑定Keepalived 一有状态变化立刻能收到企业微信通知第一时间知道发生了什么。在高可用系统里可观测性比高可用本身更重要因为你永远不知道下一次故障会在什么时候、以什么方式到来。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进