Keepalived 配置排查:VRRP 虚拟 IP 漂移原理与 permanent error 实战
运维这行干久了最怕的不是业务突然上量而是半夜监控电话打进来——登录上去发现主服务器还活着但 keepalived 已经“躺平”了虚拟 IP 不知道飘到了哪台机器上。查日志一行字冷冰冰地摆在那Keepalived exited with permanent error config。这个场景我见过太多次也帮不少人排查过。Keepalived 这个软件名声很大但真正把它配好、配稳、出了问题能快速定位的人不多尤其是第一次接触高可用的小伙伴很容易栽在配置文件的细节上。这篇文章我会从 VRRP 的原理讲起覆盖安装、双机热备配置、最典型的permanent error config排查链路最后聊聊网络侧和 LVS 配合的进阶玩法希望能让新手少走弯路也让老手复盘一下自己的习惯。1. Keepalived 到底守的是什么“活”虚拟 IP 漂移背后的真相1.1 虚拟 IP 和 VRRP看似玄学实则是“投票选举”很多刚接触高可用的人对 VIP虚拟 IP漂移的理解都停留在“主挂了IP 自动跑过去”这个层面但为什么能自动过去机制是什么并不清楚。Keepalived 的核心依赖是 VRRP 协议全称 Virtual Router Redundancy Protocol虚拟路由冗余协议。它的诞生初衷其实是为了解决路由器单点故障的后来被拿来做服务器的高可用。你可以把一组 keepalived 节点想成一个“小区物业团队”VIP 是小区门口那个统一地址业主只需要认准这个门牌号就行。小区里有多个保安节点它们之间通过周期性的“喊话”VRRP 组播报文确认彼此活着。所有保安的嗓门priority 优先级不一样嗓门最大的那个就成为“代理保安”也就是 Master由它持有 VIP 并对外提供服务。其他保安就是 Backup正常时候只听不干但会一直监听 Master 的喊话。这些喊话不是随便喊的。VRRP 报文里有几个关键字段virtual_router_idVRID标识这是一组 vrid 内互相竞争的节点、priority优先级0 到 255、advert_int通告间隔秒。Master 每隔一个通告间隔就会发一次报文Backup 收到后知道 Master 还活着。如果 Backup 在超时时间内没收到报文就会认为 Master 出了状况开始重新选举优先级最高的节点顶上把 VIP 绑到自己的网卡上。这个超时时间一般接近 3 倍通告间隔还会根据优先级算一个抖动偏移目的是避免多个 Backup 同时抢主时冲突太剧烈。1.2 主备切换的边界它高可用的是“IP”不是你的业务这是我一直想先讲清楚的一个认知问题。Keepalived 只负责一件事让 VIP 始终在某个活着的节点上。它并不负责帮你拉起 Nginx、把 Java 进程重启、处理数据库主从同步。很多初次使用的人以为“装了 keepalived应用挂了它就会自动恢复”这是巨大的误会。你想想看如果主服务器上的 Nginx 进程因为内存泄漏僵死了但操作系统还活着网卡也正常keepalived 进程也正常VRRP 报文照常发那 Master 依然认为自己活得好好的VIP 不会漂移到 Backup。这时候用户访问 VIP连到的还是一个假死的应用。要解决这个问题不能靠 keepalived 本身的“高可用”要靠健康检查脚本。后面第 3 章我会专门讲vrrp_script那才是让 VIP 真正服务于业务的关键。还有一个边界要认清Keepalived 的切换不是无延迟的。从 Master 失联到 Backup 接管至少经过一个探测周期通常一两秒加上协议层面的超时时间实际会有一小段服务中断。如果业务要求 RPO/RTO 趋近于零那就不是简单双机能解决的需要更复杂方案。所以别高估 keepalived它是一个成熟可靠的高可用工具但不是魔法。2. 装对 Keepalived不同平台的安装姿势与依赖坑2.1 Ubuntu / Debian 系一条命令装完但要留意启动参数多数测试环境和小型生产集群我推荐直接用发行版自带的 keepalived 包省心且和 systemd 集成好。以 Ubuntu 22.04 为例sudo apt update sudo apt install -y keepalived装完先别急着systemctl start先看一眼版本和编译特性keepalived -v如果只是做标准 VRRP 双机热备发行版自带的版本完全够用。配置文件默认在/etc/keepalived/keepalived.conf日志会走 syslog/journald。有一点要注意Ubuntu 上 keepalived 的 systemd 服务默认读取/etc/default/keepalived中的环境变量。你可以在里面加DAEMON_ARGS-D -S 0-D表示输出详细日志到 syslog-S 0是设置日志级别。但我的建议是前期调试尽量别加太多参数否则日志噪音很大反而干扰排查。等部署稳定了再决定要不要开 verbose。2.2 CentOS / RHEL 系yum 背后的包版本问题CentOS 7、8、9 上最常规的安装方式sudo yum install -y keepalived # 或者新版系统 sudo dnf install -y keepalivedCentOS 7 默认源里的 keepalived 版本比较老我记得是 1.3.x功能上做基础双机热备没问题但有一些 newer 的特性比如vrrp_script里某些参数、更严格的配置校验和 2.x 版本会有差异。如果你是从网上抄了一份 2.x 的配置扔到 CentOS 7 上很可能启动直接失败。这种跨版本抄配置导致的“permanent error”案例我在项目里见过不少。解决思路有两个一是调低预期按老版本的语法写配置二是引入 EPEL 源或者用源码编译安装新版本。我不建议一上来就追求最新版生产环境稳定优先发行版自带的版本是经过基础测试的够用就行。2.3 源码编译什么时候值得自己折腾说实话大多数场景不需要源码编译。但我遇到过必须编译的情况定制了内核编译参数、需要开启 SNMP 支持、或者发行版自带的版本太旧且没有 EPEL 源。源码编译的核心依赖有三个方向OpenSSL 开发库libssl-devlibnl 开发库libnl-3-dev、libnl-genl-3-devpopt 开发库libpopt-devUbuntu 上可以用 apt 装齐CentOS 对应openssl-devel、libnl3-devel、popt-devel。基本编译流程wget https://www.keepalived.org/software/keepalived-2.2.8.tar.gz tar xf keepalived-2.2.8.tar.gz cd keepalived-2.2.8 ./configure --prefix/usr --sysconfdir/etc make -j$(nproc) sudo make install编译安装最隐蔽的坑是系统里原本已经有 systemd 的 keepalived 单元它默认会去/usr/sbin/keepalived寻找二进制而你如果没把编译产物放到那个路径或者--prefix指到了/usr/local启动服务时就会ExecStart找不到文件。我建议编译安装时直接--prefix/usr --sysconfdir/etc省得后面一堆麻烦。装完用which keepalived和keepalived -v确认一下实际路径和版本。2.4 安装完之后先别急着写配置检查 systemd 单元装完包之后无论哪个发行版第一件事应该是看一眼 unit 文件内容知道它启动时到底执行了什么命令systemctl cat keepalived常见的 centos 系统 unit 大概是[Service] Typeforking EnvironmentFile-/etc/sysconfig/keepalived ExecStart/usr/sbin/keepalived $KEEPALIVED_OPTIONS PIDFile/run/keepalived.pidUbuntu 则可能读/etc/default/keepalived。我的经验是不要轻易改动 unit 文件本身而是通过环境变量文件去调参数。需要调试时可以在 /etc/sysconfig/keepalived 里设置KEEPALIVED_OPTIONS-f /etc/keepalived/keepalived.conf这样至少能确定它加载的是你预期的配置文件而不是因为某个环境变量没配好进程起不来却找不到原因。这种“看似 keepalived 配置错误其实是 systemd 启动参数不一致”的情况排查起来特别耗时间所以安装阶段花十分钟把 unit 和环境变量梳理清楚非常值得。3. 双机热备配置逐行拆解从 global_defs 到 vrrp_instance3.1 最小可用配置一台 Master、一台 Backup我以一个最常见的场景为例两台 Nginx 服务器VIP 是192.168.1.100平时由 master 节点提供服务master 故障时 VIP 漂移到 backup。先看 master 节点的配置文件! Configuration File for keepalived global_defs { router_id NGINX_HA enable_script_security } vrrp_script chk_nginx { script /etc/keepalived/chk_nginx.sh interval 2 fall 2 rise 1 weight -20 } vrrp_instance VI_WEB { 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 } track_script { chk_nginx } }backup 节点几乎一样只有三个地方不同state改成BACKUPpriority改成90router_id可以不同但建议也区分开方便日志识别其他如virtual_router_id、interface、auth_pass、virtual_ipaddress必须保持一致。这个“一致性”非常关键我下面逐个解释。3.2 优先级、抢占与 auth_pass让谁当主可不是随便写个 100priority的范围是 0 到 255数值越大越容易被选成 Master。常见做法是主节点 100备节点 90 或更低。很多人会问那 state 写 MASTER 和 BACKUP 还有什么意义其实 state 只是初始状态真正决定谁是主人靠的是优先级。即使两台都写了state MASTER只要优先级不同高优先级的那台最终还是会胜出。但我不建议这么玩应该严格按照“一主一备”来写否则日志里会出现不该有的抢主过程看起来心惊胆战。nopreempt是另一个容易误解的参数。默认情况下抢占是开启的如果主节点故障备节点接管后来主节点恢复它又会通过更高优先级把 VIP 抢回来。这样一次故障会造成两次切换业务抖动是二次的。如果对连续性敏感可以在两台配置里都加上nopreempt意思是“我不主动抢除非我现在是唯一活着的”。注意这个参数要两台一起配才有效果只配一边很容易出问题。authentication块里的auth_type通常是 PASSauth_pass是两台之间互通消息的暗号。这里有两个坑。第一auth_pass在主备两台上必须完全一致大小写、空格都算一旦不一致VRRP 报文会被丢弃表现为“永远选不出主”或“VIP 飘来飘去”而不是直接报错。第二VRRPv2 下 PASS 模式的认证码理论上不超过 8 个字符新版 keepalived 对超长配置会更加严格有可能直接判为配置错误老版本则可能默默地截断。主备两台如果用了不同长度或不同内容的认证码不会崩但就是“互相看不见”排查起来特别容易忽略。3.3 健康检查脚本真正决定“该不该让 VIP 飘走”的东西现在重点说vrrp_script。看名字就明白这是 VRRP 层面专门跑健康检查的钩子。上文的配置里我写了一个chk_nginx对应的脚本路径是/etc/keepalived/chk_nginx.sh内容很朴素#!/bin/bash if pgrep -x nginx /dev/null 21; then exit 0 else exit 1 fi这个脚本的逻辑是Nginx 进程存在返回 0表示健康进程不存在返回非 0表示不健康。vrrp_script里的interval 2表示每 2 秒跑一次脚本fall 2表示连续 2 次失败才确认“不健康”rise 1表示只要出现 1 次成功就算恢复。weight -20的意思最巧妙健康检查失败时本机优先级减去 20 分。举个例子master 优先级 100backup 优先级 90。master 上 Nginx 挂了chk_nginx连续失败 2 次之后master 的实际参与选举的优先级变成了 80。这时候 backup 的 90 分反而更高下一轮 VRRP 选举后流量就会平滑切换到 backup。一旦 master 的 Nginx 恢复脚本变正常优先级回到 100又会把 VIP 抢回来。这种“降权让位”的设计比直接杀掉 keepalived 优雅得多因为你不需要关心 keepalived 进程本身只需要让业务故障转化为优先级变化。脚本本身还有一些细节要处理必须加上执行权限chmod x /etc/keepalived/chk_nginx.sh脚本内最好别用复杂逻辑和长时间阻塞的管道比如curl -s要加超时否则脚本卡住会影响检测enable_script_security开启后keepalived 会限制脚本运行时的用户身份如果脚本需要访问受保护资源可能需要在 vrrp_script 里加user root之类的配置。这里有个常见认知错误检测脚本只负责“发现故障”不负责“修复故障”。千万别在脚本里写 restart nginx 这种操作业务拉起应该交给 systemd 或 supervisorkeepalived 的角色是裁判不是教练。4. “exited with permanent error config”实战排查一次配置灾难的全过程4.1 报错先看日志journalctl 和 -t 参数的用法热搜词里有keepalived exited with permanent error config这个报错对不熟悉的人来说非常唬人。我第一次见到这个日志时也愣了几秒心想“permanent error”是什么鬼天才级别的错误。后来懂了它其实没有特别深奥就是 keepalived 在启动阶段解析配置文件失败判断这个错误无法恢复所以直接退出。对比运行期可能出现的 transient error比如暂时联系不上对端、某个监控脚本超时配置错误是“永久性”的不修正配置就永远起不来。看到这类报错的自然反应是打开日志看细节journalctl -u keepalived -n 100 --no-pager通常能看到类似这样几行Starting Keepalived v2.2.8 (10/15,2023) Opening file /etc/keepalived/keepalived.conf Unknown keyword: vip Keepalived exited with permanent error config.真正有价值的是它带出来的那一句具体语法问题。如果 journald 里的日志不够细还有一个杀手锏命令keepalived -t -f /etc/keepalived/keepalived.conf-t是配置检测模式它不会真实启动 VRRP 和 LVS 组件只解析配置文件。正常时输出一大串读入的行号最后会出现Configuration OK。有错误时它会明确告诉你第几行哪个 keyword 不认识、缺少什么符号、哪个块没闭合。我在实际排查中几乎都靠这个命令解决比盯着 systemd 状态高效得多。4.2 我踩过的三类 config 错误第一类是“结构性错误”花括号不配对、少了引号、block 命名写错。Keepalived 的配置是典型的“块状 花括号”语法一个vrrp_instance VI_1 { ... }块漏了右花括号后面所有内容都会被吞进去报错行号会和真实问题位置相差很远容易误导人。我的习惯是用支持括号高亮的编辑器打开配置文件配置结构复杂时宁可多写几段注释也不要让大括号风险潜伏。第二类是“接口不存在”配置里写interface eth0但服务器实际网卡是ens33或者有多个网卡名启动时 keepalived 找不到对应接口直接报永久错误退出。尤其现在云环境里网卡命名非常不统一一定要先执行ip link或ip addr看清楚自己的接口名称再填interface。第三类是“引用未定义的东西”或“重复定义”track_script里引用的脚本名没有先在vrrp_script块里定义或者某个virtual_router_id在一套配置里用了两次。这类错误在人工编辑配置文件时很容易出现特别是在一个文件里管理多个 vrrp_instance 的时候。我遇到过一个案例同事复制了一个 vrrp_instance 块忘了把virtual_router_id从 51 改成 52程序启动后不报错但两个实例互相打架VIP 不停抖动。这种逻辑错误比语法错误更隐蔽启动时反而是正常的。所以排查任何 keepalived 故障都不要只盯报错还要看运行时的实际状态。4.3 单播场景下更隐蔽的配置问题公共云和部分托管机房是不支持组播的而 VRRP 默认通过组播地址224.0.0.18通信。如果组播被禁两台 keepalived 根本收不到彼此的报文会出现“两个节点都认为自己是 Master”的脑裂。解决办法是启用单播模式vrrp_instance VI_WEB { state MASTER interface eth0 virtual_router_id 51 priority 100 unicast_src_ip 192.168.1.10 unicast_peer { 192.168.1.11 } ... }注意unicast_src_ip填本机网卡 IPunicast_peer里填对端 IP且两台要对称填写互为对方的源和对端。如果这里填错了其中一个 IP日志不会出现五彩斑斓的报错最多是提示对端不可达然后 keepalived 正常启动却一辈子选不出主。单播模式下还经常叠加安全组/防火墙问题你放行了 TCP/UDP 端口但忘了 VRRP 是协议号 112不是端口号照样被拦死。遇到这种情况抓包最能说明问题tcpdump -ni eth0 proto 112如果只有自己发出的报文而收不到对端的大概率是防火墙或者安全组在中间作梗。总之排查permanent error config时先别慌按“配置检测 - 日志 - 抓包”三层走90% 的问题都能定位到具体行号或网络策略上。5. 网络侧的隐形障碍防火墙、ARP 与交换机5.1 VRRP 报文这不是普通 UDP/TCP防火墙要按协议放行第一次在 CentOS 上配完 keepalived发现两台明明是通的VIP 就是不漂移最后查到原因firewalld 默认 INPUT 链把 VRRP 报文拦掉了。VRRP 的 IP 协议号是 112不是 80、443、22 这种 TCP/UDP 端口所以很多人在防火墙里加端口规则完全没用。iptables 的放行示例iptables -I INPUT -p 112 -d 224.0.0.0/8 -j ACCEPT iptables -I INPUT -p 112 -s 192.168.1.0/24 -j ACCEPTfirewalld 的富规则也可以firewall-cmd --permanent --add-rich-rulerule protocol valuevrrp accept firewall-cmd --reload注意如何在/etc/sysconfig/iptables或者iptables-save里持久化这取决于你的系统。抓包验证的话用tcpdump -ni eth0 vrrp或tcpdump -ni eth0 proto 112能同时看到主备的发包就说明网络层没毛病了。5.2 交换机 MAC 表漂移与 VIP 的虚拟 MACVRRP 默认情况下 VIP 会绑在真实网卡上使用的是物理网卡的 MAC 地址。当 VIP 从 master 切到 backup 时交换机端口对应的 MAC 表项会发生变化需要重新学习。某些交换机端口如果开启了 sticky MAC、端口安全、或者其他防 MAC 漂移策略会拒绝这种切换导致 VIP 虽然已经绑到了新节点但从交换机到上层网关这一段的路径还是旧的数据包仍然被送到老端口。这种问题在虚拟化环境里尤其容易出现表现为“VIP 看着已经漂过去了业务却不通”。解决思路有两个方向。一是开启 keepalived 的vmac功能让 VIP 使用虚拟 MAC00:00:5e:00:01:XX其中 XX 是 VRID 的十六进制值。这样无论漂移到哪台机器MAC 地址固定不变交换机只需要学习一次。但 vmac 在部分网络环境或云平台上兼容性一般需要测试。二是协调网络团队在交换机端口上关闭 MAC 地址动态迁移限制。这里没有放之四海而皆准的配置但记住“VIP 漂移不只是 IP 的事还牵扯 MAC 学习”这一点能让你少绕很多弯路。5.3 云环境/跨三层组播失灵就老实上 unicast_peer现在很多公司已经把核心业务放到云主机上VPC 网络里经常不支持组播这是开发 keepalived 的人可能都想不到的“现代难题”。遇到这种情况unicast_peer单播模式就是唯一出路。前面第 4.3 节已经给了配置示例这里再补充一点网络策略细节单播模式下 VRRP 报文目标地址变成对端 IP但 IP 协议号仍然是 112。很多云安全组控制台只允许你填写 TCP/UDP 端口不支持协议号 112那 keepalived 的组播单播路径都走不通。我用的办法是找云厂商售后确认是否支持“自定义协议号放行”或者改成用 TCP/UDP 健康探测云负载均衡的高可用方案。技术选型永远要跟着部署环境走别在一个不支持 VRRP 的环境里死磕。6. Keepalived 的另一半配合 LVS 实现负载均衡高可用6.1 为什么说 Keepalived 天生就是 LVS 的搭档很多人在 Nginx 双机热备里认识 Keepalived但它最早其实是专门为 LVS 设计的高可用组件项目名称“keepalived”直译就是“让 LVS 的调度器一直活着”。LVSLinux Virtual Server负责把流量分发到后端 real serverKeepalived 负责两件事一是通过 VRRP 保证 LVS 调度器本身高可用二是通过健康检查把后端故障节点从转发列表里摘除。这两个组件一配合才是一个真正完整的负载均衡方案。单个 keepalived 配置里可以同时出现两类内容一类是给 LVS 调度器用的virtual_server一类是给系统 IP 热备用的vrrp_instance。它们不是互斥的可以同时存在。下面是一个简化的虚拟服务配置virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo rr lb_kind DR protocol TCP real_server 192.168.1.11 80 { weight 1 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 } } real_server 192.168.1.12 80 { weight 1 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 } } }delay_loop是轮询后端节点的间隔lb_algo是调度算法rr 轮询、wrr 加权轮询、lc 最少连接等lb_kind是转发模式常见是 DR直接路由和 NAT。两个real_server块各自有weight和健康检查方式。keepalived 通过 HTTP_GET 探测/healthz如果某个后端连续失败自动把它从负载池里摘掉恢复正常再加回来。这套机制比单纯用 Nginxupstream自带的 passive 健康检查要主动得多。6.2 不建议一开始就上全套如果你的目标只是让两个 Nginx 互为热备听我一句劝先别急着搞 LVS Keepalived 全家桶。LVS 的 DR 模式需要后端 real server 也在 lo 接口上绑定 VIP 并且配置 ARP 抑制涉及arp_ignore、arp_announce这些内核参数一旦没配好下面转发不稳定排查难度直线上升。先把最基础的 VRRP 双机热备跑通、切换验证过再逐步引入 LVS 转发否则你会在“VIP 怎么不通”“后端怎么摘不掉”两个坑里来回跳。真到了需要扩展并发和负载均衡的阶段再考虑 LVS。那时候 keepalived 会同时扮演两个角色VRRP 负责调度器高可用virtual_server负责后端探活。两个角色在地域和职责上不同别混为一谈配置优先级、故障切换逻辑时分开思考会清晰很多。我个人维护了几年 keepalived 环境之后最大的体会是这个软件本身不难难的是对网络模型的理解和配置细节的一致性。很多故障表面上是 keepalived 报错根子却在防火墙策略、两端参数不一致或者配置语法结构上。我现在每改一次配置都固定走一遍流程先备份再keepalived -t检测再重启服务最后在对端节点用ip addr确认 VIP 状态如果是大版本切换还会专门约一次业务低峰期的双机切换演练。这个习惯帮我挡掉过至少三次凌晨电话。希望这篇文章也能帮你建立一个可靠的 keepalived 使用框架让 VIP 在关键时刻真的能飘得过去。