简介:Keepalived 1.2.13 源码包面向运维工程师、系统管理员及高可用架构学习者,围绕 VRRP 与 LVS 实现服务故障切换和负载均衡,适合搭建双机热备实验、研读健康检查逻辑及排查主备切换问题。压缩包共 188 个文件,约 334KB,以 62 个 .h 头文件和 61 个 .c 源文件为主体,便于追踪 VRRP 状态机、检查器与配置解析的实现;同时包含 .in 配置模板、init 启动脚本、pem 证书样例,以及构建脚本与说明文档,可辅助理解编译、部署及样例配置结构。已有 369 人学习下载,说明其对入门与进阶读者均具参考价值。通过源码与配套样例,读者可梳理 Keepalived 的模块划分、健康检查类型、虚拟服务器组配置方式,并积累故障排查与高可用方案设计思路。
1. 一个 2013 年的 tar.gz 值不值得拆:keepalived-1.2.13 的定位与适用面
机房搬迁那次,一台跑着老业务的机器必须在不停机的前提下换到新主机上,系统源里能装的只有 1.2 系列的包,最后从源码目录里翻出来的就是 keepalived-1.2.13.tar.gz。这个包解决的问题很具体:用 VRRP 协议在几台机器之间选出一台持有虚拟 IP,主节点挂了备节点在几秒内接管;顺带还能给 IPVS 后面的真实服务器做端口级健康检查,服务不通就把它从转发列表里摘掉。它不是新东西,但在存量系统、离线环境、需要交叉编译到特定内核的场景里,这个版本仍然是很多人的基线。适合三类人:要维护老配置却读不懂keepalived.conf里那些关键字的人、要在没有外网源的环境里从源码编出可执行文件的人、以及想知道主备切换到底为什么抖的人。
2. 从 tar.gz 到可执行文件:configure 开关与目录落点
源码包拿到手的第一件事不是./configure,而是先确认这个版本的开关清单。keepalived 的构建体系是 autotools,不同小版本之间开关增减很随意,网上抄来的编译命令经常在这台机器上直接报“unrecognized option”。我的习惯是先解包、进目录、把 help 打出来过滤一遍,再决定参数。
2.1 摸清依赖边界再动手
tar -xzf keepalived-1.2.13.tar.gz cd keepalived-1.2.13 # 先看这个版本到底认哪些开关,别照抄博客 ./configure --help | grep -Ei 'sysconf|lvs|vrrp|snmp|libnl|kernel'这条命令的输出决定了后面所有事。--disable-lvs、--disable-vrrp、--enable-snmp这几个开关在 1.2 系列里一直存在,而 netlink 相关的开关在不同小版本里名字会变,先grep一遍比事后对着报错猜要快得多。基础依赖只有三样:gcc、make、和目标内核版本匹配的头文件。netlink 和 openssl 的头文件缺失时,configure会在末尾打印checking for ... no然后直接退出,把最后 20 行日志看完基本就知道缺什么。
# 典型的最小构建,三个参数各管一件事 ./configure \ --prefix=/usr/local/keepalived \ --sysconf=/etc \ --localstatedir=/var三个参数的分工值得说清楚,因为后面所有的路径问题都从这里来。--prefix决定二进制和文档装到哪,主程序落在/usr/local/keepalived/sbin/keepalived。--sysconf=/etc决定编译进程序的配置文件搜索路径,最终是/etc/keepalived/keepalived.conf,这个路径是硬编码进二进制的,改了就得重编。--localstatedir=/var决定 PID 文件位置,落到/var/run/keepalived.pid,写 systemd unit 时要用到它。
如果这台机器只做纯 VRRP 的 VIP 漂移、不打算跑 IPVS,加--disable-lvs能少编进去一堆东西,也少一层内核模块依赖。反过来,如果编译阶段卡在 netlink 相关头文件上,先看--help里有没有--disable-libnl一类的开关,有就关掉走内置实现,没有就去装对应发行版的 libnl 开发包,别硬改源码。
make -j"$(nproc)" 2>&1 | tee build.log make installtee这一步不是形式主义。老代码在新版 gcc 上编译,报错往往埋在几千行输出中间,而且如果这个版本的 Makefile 默认带了-Werror,一行“警告”就能让整个构建中断,最后一行通常是cc1: all warnings being treated as errors。真遇到了,make CFLAGS="-O2 -g -Wno-error"覆盖一下就能过去,不用去改 Makefile。
2.2 装完先核对目录落点
| 路径 | 内容 | 由谁决定 |
|---|---|---|
/usr/local/keepalived/sbin/keepalived | 主程序 | --prefix |
/etc/keepalived/keepalived.conf | 主配置 | --sysconf |
/var/run/keepalived.pid | PID 文件 | --localstatedir |
/usr/local/keepalived/etc/rc.d/init.d/keepalived | 自带的 init 脚本样例 | --prefix |
/usr/local/keepalived/etc/sysconfig/keepalived | 启动参数文件 | --prefix |
装完先跑keepalived -v确认版本串对得上,再看/etc/keepalived/下有没有文件。有些版本会把样例装成keepalived.conf.sample,有些直接装成keepalived.conf,ls一眼就能分辨。配置文件不存在时,程序不是报“找不到配置”,而是按内置默认值把进程拉起来并保持空转,这是新手最容易误判“装成功了”的地方。
源码树里的keepalived/etc/keepalived/keepalived.conf就是官方样例,包含 VRRP 和 IPVS 两大段完整示例,值得留着当字典翻,比在网上搜半截配置可靠。
2.3 进程托管:手写 systemd unit 的几个关键行
1.2.13 不带 systemd unit,也没有 sd_notify 支持,所以只能用Type=forking配 PID 文件。
[Unit] Description=Keepalived VRRP daemon After=network-online.target Wants=network-online.target [Service] Type=forking PIDFile=/var/run/keepalived.pid EnvironmentFile=-/etc/sysconfig/keepalived ExecStart=/usr/local/keepalived/sbin/keepalived -D -f /etc/keepalived/keepalived.conf ExecReload=/bin/kill -HUP $MAINPID KillSignal=SIGTERM Restart=on-failure RestartSec=2 [Install] WantedBy=multi-user.target-D是详细日志,调试期必开,稳定后可以去掉。EnvironmentFile前面的减号表示文件不存在也不报错,避免老机器上没有/etc/sysconfig/keepalived时 unit 直接启动失败。KillSignal=SIGTERM是有意写死的:收到 TERM 时 keepalived 会走正常的“下线”流程,主动发一个优先级为 0 的通告让备机立刻接管,并且把 VIP 从网卡上摘干净;如果被 KILL 掉,VIP 会残留,备机虽然能接管,但主节点恢复后容易出现地址冲突。
提示:
systemctl reload触发的是 SIGHUP,进程会重读配置并重算 VRRP 状态,线上执行时主备可能瞬断一次,变更窗口要挑好。
3. VRRP 实例配置:六个必填字段和三个影响切换行为的参数
VRRP 的核心就一句话:多台机器共用一个虚拟路由器 ID,按优先级选出 Master,Master 定期发通告,备机一段时间收不到通告就认为对方死了,自己顶上。配置文件的难点不在语法,而在每个字段到底影响哪一步。
3.1 一个能跑起来的 vrrp_instance
global_defs { router_id node_a # 只用于日志识别和告警,不参与选举 } vrrp_instance VI_1 { state MASTER # 初始状态,最终身份由优先级和抢占决定 interface eth0 # 发通告和挂 VIP 的网卡 virtual_router_id 51 # 同一广播域内必须唯一,取值 1-255 priority 150 # 数值大者胜 advert_int 1 # 通告间隔,单位秒,两端必须一致 authentication { auth_type PASS auth_pass kp2024ab # PASS 模式最多 8 位,超了会被截断 } virtual_ipaddress { 192.0.2.100/24 dev eth0 } }备节点的配置除三处外完全相同:router_id改成node_b、state改成BACKUP、priority改成 100。virtual_router_id必须一字不差,advert_int必须一致,认证密码必须一致,这三项里任何一项对不上,两边都会认为对方不存在,各自举着 VIP 当 Master。
六个必填字段里最容易被忽视的是interface。它同时决定了两件事:VRRP 通告从哪张网卡发出去、VIP 挂到哪张网卡上。双网卡的机器上,如果通告走eth0而业务在eth1,就会出现“切换正常但业务不通”的现象,排查时先ip -4 addr show看 VIP 落在哪张卡上。
authentication里的密码长度是个硬限制,PASS 模式只取前 8 个字符,配了 12 位而两端只有一端“看起来”改过,实际上截断后仍然相同,反而掩盖了配置不一致的问题。
3.2 认证、通告间隔与抢占:三个决定切换行为的参数
advert_int决定故障感知速度。默认 1 秒,备机大约 3 个通告周期收不到就切换,也就是 3 秒左右。往小调能更快,但网络抖一下就误切,实际接管时间还不如稳定在 3 秒。
priority决定谁当选。主备之间建议至少差 20,留出脚本权重的调整空间。差值只有 1 的时候,一次脚本权重变化就能让两边来回抢,日志里会看到状态周期性翻转,很难查。
nopreempt决定原主恢复后是否抢回。默认行为是抢占:主节点恢复、优先级重新变高,就把 VIP 抢回来。在业务进程启动慢的机器上,这个行为很致命——主节点刚恢复,keepalived 起来了但后端服务还没 ready,VIP 已经抢回来,业务直接断一下。常见做法是备节点上加nopreempt,并且state必须写BACKUP(写 MASTER 时nopreempt不生效),让 VIP 留在当前持有者手里,直到它真的出问题。
注意:
nopreempt只影响“恢复后是否抢回”,不影响故障时的接管,别把它当成防止脑裂的手段。
如果环境里交换机默认关闭组播、或者跨网段部署,VRRP 通告根本发不出去。较新的 1.2 小版本支持单播,配置块里加unicast_peer { 192.0.2.11 }并把interface指向互通的那张卡即可。这个版本的配置解析器是逐行读的,先写上去跑一次,日志里没有unknown keyword就说明支持。
3.3 三件套验证:抓包、地址、日志
配置写完不要急着当它生效,三层验证依次做一遍,五分钟能省掉后面两小时。
# 第一层:通告有没有真的发出去、发给了谁 tcpdump -i eth0 -nn -c 10 proto 112 # 第二层:VIP 到底挂在哪张卡上 ip -4 addr show dev eth0 # 第三层:状态机怎么走的 tail -f /var/log/messages | grep -i keepalived第一条命令的预期输出是每秒一条来自本机、目的地址224.0.0.18的包,源地址是本机真实 IP,TTL 255。看到 TTL 不是 255,说明中间有设备在转发或者做了代理,VRRP 的选举逻辑会被打乱。一条都抓不到,问题在interface或组播,跟优先级无关。
第二条命令用来确认 VIP 的掩码和网卡。VIP 挂上了但掩码是/32,同网段的其他机器就认为它不在本地子网,报文会走网关,绕一圈回不来。
第三条命令的日志里,Entering MASTER STATE和Entering BACKUP STATE是状态迁移的标志,VRRP_Instance(VI_1) Received advert with higher priority表示收到了更高优先级的通告、主动让位。这几行是后面所有排障的起点。
4. 健康检查:vrrp_script 的权重算术与 real_server 的摘除逻辑
VIP 漂移只解决“机器活着但 keepalived 挂了”的情况。业务进程卡死、端口不响应、后端服务已经挂了,keepalived 默认是不知道的,VIP 照样在主节点上挂着,流量照样打进来。这一层要靠vrrp_script和real_server的健康检查补上。
4.1 vrrp_script 的权重要算,不能拍
vrrp_script chk_gateway { script "/etc/keepalived/chk_gateway.sh" interval 2 # 每 2 秒执行一次 fall 3 # 连续失败 3 次才判定为失败 rise 2 # 连续成功 2 次才判定为恢复 weight -60 # 失败时优先级减 60 } vrrp_instance VI_1 { # ... 前面的字段省略 track_script { chk_gateway } }vrrp_script的语义是“算出一个有效优先级”,而不是“直接触发切换”。有效优先级的算法是:脚本判定为成功时,有效优先级等于配置的priority;判定为失败时,等于priority + weight(weight 为负就是减)。所以切的不是脚本,是优先级。
这就带来第一个坑:weight 的绝对值必须大于主备的优先级差,否则脚本失败也不会切。主 150、备 100、差 50,weight 写-30,失败后有效优先级 120 仍然高于备机的 100,日志里能看到脚本失败但状态一动不动。写成-60,失败后 90 低于 100,才会真正切换。
#!/bin/bash # /etc/keepalived/chk_gateway.sh # 退出码 0 表示健康,非 0 表示异常,keepalived 只看退出码 curl -s -o /dev/null -m 2 http://127.0.0.1:8080/healthz脚本本身有三条纪律。第一,必须有可执行权限,chmod 755,否则 keepalived 调用时直接失败,日志里是Cannot execute script。第二,必须自己带超时,curl -m 2或timeout 2,脚本卡住会把检查线程挂死,interval形同虚设。第三,检查的必须是本机真实状态——查127.0.0.1或本地业务端口,不要去 ping 网关,外网抖动会导致全线切换。
fall和rise是抗抖动用的。fall 3意味着连续三次失败,配合interval 2,最坏情况下要 6 秒才判定失败,加上 VRRP 自身的 3 秒,整体接管时间接近 10 秒。这个数字要在上线前算清楚并写进预案,别等到故障时才发现比预期慢一倍。
提示:部分 1.2 小版本的
vrrp_script不认timeout关键字,写上去只会在日志里报unknown keyword然后跳过,脚本的自我保护必须自己做。
4.2 real_server 健康检查:端口级摘除
VIP 后面挂真实服务器时,健康检查的粒度要更细,谁挂了摘谁,不动 VIP。
virtual_server 192.0.2.100 80 { delay_loop 6 # 每 6 秒轮询一次所有 real_server lb_algo wrr # 加权轮询 lb_kind DR # 直接路由模式,响应不回经过调度器 persistence_timeout 50 # 会话保持 50 秒 protocol TCP real_server 192.0.2.11 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.0.2.12 80 { weight 1 HTTP_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 2 } } }TCP_CHECK只验证三次握手,端口开着就算健康,进程假死但监听还在的情况它看不出来。HTTP_GET会真的发一个请求并校验状态码,代价是多一点开销,但对“进程活着但不干活”这类故障有效。两种混用很常见:无状态服务用TCP_CHECK,有依赖的服务用HTTP_GET。
delay_loop和nb_get_retry的组合决定了摘除延迟。6 秒轮询、3 次重试、每次间隔 3 秒,最坏情况下要接近 15 秒才会把一台坏掉的机器摘掉。这个期间流量还在往它身上打,所以后端服务的健康检查接口本身要足够轻,别做数据库查询。
DR 模式有一个前置条件容易漏:真实服务器上要把 VIP 配到 loopback 并关掉 ARP 响应,否则它自己会去应答 VIP 的 ARP 请求,流量直接绕过调度器。
# 在每台 real_server 上执行,只加地址不发布 ARP ip addr add 192.0.2.100/32 dev lo sysctl -w net.ipv4.conf.all.arp_ignore=1 sysctl -w net.ipv4.conf.all.arp_announce=2 sysctl -w net.ipv4.conf.lo.arp_ignore=1 sysctl -w net.ipv4.conf.lo.arp_announce=2这四个 sysctl 要写进/etc/sysctl.conf,重启后依然生效。只改all不改lo的话,在某些内核上 loopback 的默认值会覆盖全局设置,表现为“配置都写了但 ARP 还是应答”。
4.3 notify 钩子:把状态机变成可观测事件
notify_*系列指令在状态迁移时执行外部脚本,是排查切换时间线最有效的手段。
vrrp_instance VI_1 { # ... 前面的字段省略 notify_master "/etc/keepalived/notify.sh master" notify_backup "/etc/keepalived/notify.sh backup" notify_fault "/etc/keepalived/notify.sh fault" notify_stop "/etc/keepalived/notify.sh stop" }#!/bin/bash # /etc/keepalived/notify.sh STATE="${1:-unknown}" # 配置里写死的状态,比依赖 keepalived 传参稳 INSTANCE="${2:-VI_1}" TS=$(date '+%F %T') # 1) 落盘,给监控采集器读 echo "$STATE" > "/var/run/keepalived.${INSTANCE}.state" # 2) 写系统日志,方便和 keepalived 自身日志对齐时间线 logger -t keepalived-notify "$INSTANCE -> $STATE at $TS" # 3) 只在接管时做业务侧动作,比如清会话、摘上游 if [ "$STATE" = "master" ]; then echo "$TS take over" >> /var/log/keepalived-notify.log fi脚本里有两条经验。状态直接用配置里写死的参数,不依赖 keepalived 追加的参数顺序,版本之间这个顺序变过。脚本里绝对不要手动ip addr addVIP——keepalived 自己会加,重复加会报RTNETLINK answers: File exists,而且下线时容易删不干净,留下地址冲突的隐患。
5. 避坑与排查:老版本 keepalived 最容易踩的五个坑
这一节的每一条都对应一次真实的排查过程,按“现象 → 原因 → 解决”记,遇到时可以直接对号。
5.1 双主:两台机器同时举着 VIP
现象:ip addr在两台机器上都能看到192.0.2.100,客户端访问时好时坏,ARP 表在两台机器的 MAC 之间反复切换,交换机日志里有 MAC 漂移告警。
原因有三个层次。最常见的是virtual_router_id冲突——同一广播域里还有另一组 VRRP 用了 51,两组通告互相干扰,选举结果随机。其次是认证不匹配,一端auth_pass改过而另一端没改,两边都认为对方的通告非法,各自认为自己是唯一 Master。再隐蔽一点的是advert_int不一致,一端 1 秒一端 2 秒,收通告的超时判断全乱。还有一种情况是交换机开了组播抑制或者端口隔离,两台机器根本收不到对方的通告,这种在容器化或云上环境里尤其常见。
解决:先tcpdump -i eth0 -nn proto 112在两端同时抓,看能不能抓到对方的通告。抓不到就是二层或组播问题,抓得到但状态还是双主,就逐字段比对virtual_router_id、auth_type、auth_pass、advert_int四项。环境不支持组播的,改用unicast_peer走单播。配置核对完还有双主,就要在交换机侧确认这两个端口在同一个 VLAN 且没有隔离策略。
5.2 VIP 加上了但业务不通
现象:切换正常,VIP 也能 ping 通,但通过 VIP 访问服务全部超时,直连真实 IP 一切正常。
原因:VIP 加在哪张网卡、掩码多长、后端有没有做 ARP 抑制,三件事里必有一件错。VIP 挂到eth0而业务监听在eth1的地址上,服务压根没监听这个地址。或者 VIP 掩码写成/32,同网段机器认为它不在本地子网,回包走了默认网关。再或者用了 DR 模式但真实服务器没关 ARP 响应,回包直接绕过了调度器,客户端收到的是真实服务器的源地址,三次握手对不上。
解决:ip -4 addr show确认 VIP 的设备和掩码,ss -lntp确认业务进程监听的地址。如果业务只能绑本机已有地址、启动时 VIP 还没挂上,加net.ipv4.ip_nonlocal_bind=1允许绑定非本地地址,否则进程会直接启动失败。DR 模式下回到 4.2 节那四条 sysctl,一台一台核对,别只看一台。
5.3 脚本权重设置了但切不回去
现象:主节点业务进程挂掉,vrrp_script日志里明明打了失败,VIP 却没有转移,主节点继续当 Master。
原因:weight 的绝对值小于主备优先级差,有效优先级仍然高于备机。或者fall设得太大,故障持续时间不够,计数器还没到阈值就恢复了,状态机根本没触发。还有一种情况是脚本检查的对象不对——脚本里curl的是外网地址,外网抖动导致全线误判,但故障当时外网是通的,脚本返回 0,永远不会失败。
解决:把优先级算术写在纸上,priority + weight算出来的数必须低于备机的priority。把fall/rise和interval乘一遍,算出最坏判定时间,写进切换预案。脚本里只检查本机状态,任何依赖外部网络的检查都应该放到监控系统里,不要放在 VRRP 的判定链路上。
5.4 配置写错了却“启动成功”
现象:systemctl start返回 0,进程在跑,但某个配置块完全没生效,比如track_script写了却没起作用。
原因:这个版本的配置解析器遇到不认识的关键字时,会在日志里打一行Unknown keyword,然后跳过这一行继续解析,进程照样启动、照样跑,退出码是 0。systemctl status看不出任何异常,只有翻/var/log/messages才能发现。
解决:每次改完配置,先grep -i 'unknown keyword' /var/log/messages,再grep -i keepalived看有没有Configuration file相关的告警。更稳的做法是前台跑一次:/usr/local/keepalived/sbin/keepalived -n -l -d -f /etc/keepalived/keepalived.conf,-n不 fork、-l输出到终端、-d把解析后的完整配置 dump 出来。dump 出来的内容和你写的对不上,问题就找到了。这个版本不一定支持较新版本里的-t配置检查选项,如果-n之类的开关报 usage,按它打印的选项列表挑。
提示:配置 dump 是排查配置类问题最快的手段,比逐行读文件可靠得多,因为它展示的是解析器真正理解的内容。
5.5 老代码在新环境上的编译与 PID 文件问题
现象:make在中途报错,错误信息是某个函数隐式声明,或者最后一行是cc1: all warnings being treated as errors。装完之后systemctl stop停不掉进程,或者停了之后重启提示 PID 文件已存在。
原因:老代码里的部分写法在新版 gcc 下会触发警告,而某些版本的 Makefile 默认带-Werror,警告即错误。PID 文件问题则来自--localstatedir没指对,或者进程被 KILL 掉时没来得及清理/var/run/keepalived.pid,unit 里的PIDFile指向一个过期文件,systemd 会认为服务状态异常。
解决:编译阶段用make CFLAGS="-O2 -g -Wno-error"覆盖,不要改源码。编译前确认./configure --help里列出的开关,别照抄网上的命令行。PID 问题先确认PIDFile路径和--localstatedir一致,再看单元文件里是不是缺了Restart=on-failure;手工清理残留 PID 文件时,一定先ps确认没有 keepalived 进程,否则新进程会因为绑定 VIP 冲突而反复退出。
6. 把切换行为测出来:三轮破坏性验证和一份时间线
配置写完、日志干净,只能说明它跑起来了,不能说明它会在该切的时候切。我一般会做三轮破坏性验证,每轮都掐着秒表看切换耗时,最后得到一份可以写进预案的时间线。
第一轮验证进程级故障。在主节点上systemctl stop keepalived,预期是立刻发出优先级 0 的通告、备机秒级接管,notify_stop和notify_master两个钩子都会触发。
# 主节点执行,同时另开一个窗口在备机上看状态文件 systemctl stop keepalived watch -n0.5 'cat /var/run/keepalived.VI_1.state'这一轮应该最快,通常在 1 秒以内,因为走的是主动下线而不是超时判定。如果这一轮就要等好几秒,说明notify脚本里有阻塞操作,keepalived 在等脚本返回。
第二轮验证链路级故障。用防火墙只丢弃 VRRP 通告,模拟交换机策略变更或链路单向故障,业务流量不受影响。
# 主节点上只丢 VRRP,别用默认的 DROP 全部,否则连不上机器 iptables -I INPUT -p 112 -j DROP # 观察备机日志,接管后把规则删掉,观察原主是否抢回 iptables -D INPUT -p 112 -j DROP这一轮的耗时应该接近advert_int × 3,配置成 1 秒就是 3 秒左右。测出来的数字明显大于 3 秒,去查advert_int两端是否一致。规则删掉之后原主抢回的时间点,正好用来验证nopreempt有没有生效——加了nopreempt的备节点不会把 VIP 还回去。
第三轮验证业务级故障。主节点上停掉业务进程但保留 keepalived,看vrrp_script触发切换需要多久。
# 停业务,观察脚本失败计数到 fall 的次数后是否切换 systemctl stop your-service grep -i 'chk_gateway' /var/log/messages | tail -20耗时应该是interval × fall加上 VRRP 的判定时间再加几秒余量。三轮测完,把三个数字填进一张表:
| 故障类型 | 触发条件 | 预期耗时 | 实测 |
|---|---|---|---|
| 进程退出 | systemctl stop keepalived | 1 秒内 | 待测 |
| 链路中断 | 丢弃 proto 112 | advert_int × 3 | 待测 |
| 业务假死 | 端口不响应 | interval × fall + 数秒 | 待测 |
三行都填完,这份表就是故障时的判断依据:实际切换时间超出表里的数字,说明有额外的阻塞;没到时间就切了
本文还有配套的精品资源,点击获取