1. 从一次凌晨三点的告警说起:为什么单靠一个组件扛不住
凌晨三点,监控大屏突然飘红,入口流量全部打到一台已经半死不活的节点上,业务侧开始报 502。值班的同事第一反应是重启服务,重启完确实好了几分钟,然后又挂。事后复盘才发现,问题根本不在业务进程本身,而是入口层只有一台机器在扛,它一挂,整条链路就断了。这个场景我猜很多做运维或者后端的朋友都遇到过,也正是从那时候起,我开始认真研究keepalived 加 haproxy 的主备切换这套组合拳。
先把话说清楚:这套方案解决的核心问题,是让入口层既有高可用(HA),又有负载均衡(LB)。haproxy 负责把流量分发到后端多台真实服务器,keepalived 负责让两台(或多台)haproxy 之间有一个"主"在对外服务,主挂了备能自动顶上。它适合谁?适合那些业务规模还没大到需要上云厂商四层负载均衡、但又绝对不能接受入口单点故障的团队;也适合想自己掌控整条链路、把成本压到最低的自建机房场景。
很多人第一次接触会犯一个错:以为装了 keepalived 就万事大吉。实际上 keepalived 只解决"谁是主"的问题,它本身不转发流量;haproxy 只解决"流量怎么分"的问题,它自己挂了没人接管。两者必须配合,而且配合的细节里藏着大量坑。下面我按自己实际搭过好几套环境的经验,把整套逻辑拆开讲透,包括为什么这么设计、每一步在干什么、以及那些文档里不会写的坑。
2. 拆解这套架构的底层逻辑:VIP 漂移与健康检查如何咬合
2.1 keepalived 到底在忙什么:VRRP 协议与虚拟 IP
keepalived 的核心是VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)。你可以把它想象成两台机器在抢一个"对外电话号码"——这个号码就是VIP(Virtual IP,虚拟 IP)。谁抢到了,谁就负责接听所有打进来的电话,另一台就在旁边待命,时不时问一句"你还活着吗"。
具体机制是这样的:两台机器配置同一个virtual_router_id,并设置不同的priority(优先级)。优先级高的那台成为MASTER,它会周期性地向组播地址发送 VRRP 通告报文。备机(BACKUP)如果连续几个周期收不到通告,就认为主机挂了,于是自己升为 MASTER,并把 VIP 绑到自己的网卡上。这个过程叫VIP 漂移,通常在 1 到 3 秒内完成。
这里有个关键点很多人忽略:VIP 漂移是"抢占式"还是"非抢占式"取决于state和nopreempt配置。默认是抢占式,也就是主机恢复后会抢回 VIP。但在某些场景下,频繁抢占反而会造成抖动,这时候就要用nopreempt让它安分待着。我个人的经验是,如果主机是因为短暂网络抖动掉线,抢占式会导致 VIP 来回跳,业务侧连接被反复打断,所以生产环境我更倾向于配置非抢占,配合合理的advert_int和auth_pass。
2.2 haproxy 的角色:四层还是七层,转发逻辑怎么选
haproxy 是真正干活的那个,它监听端口,接收流量,然后按规则转发给后端。它支持四层(TCP)和七层(HTTP)两种模式,选哪种直接决定了你的配置复杂度和能力边界。
四层模式(mode tcp)只看 IP 和端口,不解析应用层内容,性能极高,适合数据库代理、TCP 长连接这类场景。七层模式(mode http)能解析 HTTP 头,可以做基于域名、路径、Cookie 的精细路由,还能改写请求头、做 SSL 卸载。我一般建议:如果是 Web 业务,直接上七层,因为后面排查问题、做灰度、加限流都方便得多;如果是纯 TCP 转发,别硬上七层,白白增加 CPU 开销。
haproxy 的健康检查是它和 keepalived 咬合的关键。它会定期探测后端真实服务器,发现某台挂了就把它从负载列表里摘掉。这个检查和 keepalived 的 VRRP 检查是两层独立的机制:keepalived 管的是"haproxy 这台机器活不活",haproxy 管的是"后端业务活不活"。两层都健康,整条链路才稳。
2.3 两者如何咬合:一张图看清数据流与控制流
虽然不能用图,但我可以用文字把数据流讲清楚。假设你有两台入口机 A 和 B,VIP 是 10.0.0.100,后端有两台业务机 C 和 D。
正常状态下:A 是 MASTER,VIP 在 A 上。用户请求打到 10.0.0.100,实际落到 A 的 haproxy,haproxy 按轮询或最少连接算法分给 C 或 D。B 是 BACKUP,VIP 不在它身上,它的 haproxy 也在跑,但没流量进来。
故障状态下:A 的 keepalived 挂了,或者 A 整机宕机。B 在几个心跳周期内没收到 VRRP 通告,立刻把 VIP 绑到自己网卡上,同时发一个免费 ARP(Gratuitous ARP)告诉交换机"这个 IP 现在在我这"。用户请求继续打到 10.0.0.100,这次落到 B 的 haproxy,B 接着分给 C 或 D。整个过程用户几乎无感知,最多丢一两个连接。
这里有个细节:B 的 haproxy 必须一直处于运行状态,不能等切换了才启动。因为 VIP 漂移是秒级的,如果 haproxy 还要现启动,那几秒的空白期业务就断了。所以两台机器的 haproxy 配置要完全一致,且都常驻。
3. 动手前的环境规划:别急着敲命令,先把这几件事定下来
3.1 机器与网络规划:VIP、网卡、组播地址怎么定
动手之前,先把这几样东西列清楚,不然后面配置改来改去很容易乱:
| 项目 | 示例值 | 说明 |
|---|---|---|
| 主机 A 内网 IP | 192.168.1.10 | 真实物理 IP |
| 主机 B 内网 IP | 192.168.1.11 | 真实物理 IP |
| VIP | 192.168.1.100 | 对外提供服务,必须同网段 |
| 网卡名 | eth0 | 用ip addr确认,别想当然 |
| virtual_router_id | 51 | 同一网段内不能重复 |
| 认证密码 | 自定义字符串 | 两台必须一致 |
VIP 必须和两台机器的真实 IP 在同一个二层网络里,否则免费 ARP 发不出去,漂移就失败。这一点在跨网段或者云主机环境里特别容易踩坑——很多云平台的网络模型不支持随意漂移 VIP,需要走平台自己的高可用方案,这个后面会专门讲。
网卡名一定要用命令确认。我见过有人照着老教程写eth0,结果新系统是ens33或者enp0s3,keepalived 起不来还找不到原因。ip addr或者ifconfig看一眼,几秒钟的事。
3.2 系统参数:内核转发与 ARP 行为必须调
这一步是新手最容易漏的,漏了之后表现为"配置看着都对,就是不通"。需要改两个内核参数:
# 开启 IP 转发,让机器能转发非本机 IP 的包 net.ipv4.ip_forward = 1 # 控制 ARP 响应行为,避免 VIP 漂移时的 ARP 冲突 net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.all.arp_announce = 2arp_ignore = 1的意思是:只有当 ARP 请求的目标 IP 是接收网卡上的 IP 时才回应。arp_announce = 2的意思是:发送 ARP 时总是使用最佳本地地址。这两个参数配合,能避免备机在没拿到 VIP 时也去响应 VIP 的 ARP 请求,导致流量错乱。
改完执行sysctl -p生效。别嫌麻烦,这两个参数是整套方案能稳定工作的地基。
3.3 软件安装:版本选择与依赖确认
keepalived 和 haproxy 在主流 Linux 发行版的仓库里都有,直接装就行:
# Debian/Ubuntu 系 apt-get install -y keepalived haproxy # RHEL/CentOS 系 yum install -y keepalived haproxy版本上,keepalived 建议 2.0 以上,haproxy 建议 2.0 以上。老版本在健康检查脚本和配置语法上有差异,容易踩坑。装完用keepalived --version和haproxy -v确认一下。
有个细节:haproxy 默认配置文件在/etc/haproxy/haproxy.cfg,keepalived 在/etc/keepalived/keepalived.conf。有些发行版装完会自动启动服务并占用 80 端口,如果你后面要用 haproxy 监听 80,记得先把系统自带的 web 服务停掉,不然端口冲突。
4. 配置实战:keepalived 与 haproxy 的完整落地
4.1 haproxy 配置:前端后端与健康检查怎么写
先看 haproxy 的核心配置,两台机器保持一致:
global log /dev/log local0 maxconn 4096 daemon defaults log global mode http option httplog option dontlognull retries 3 timeout connect 5s timeout client 30s timeout server 30s frontend web_front bind *:80 default_backend web_back backend web_back balance roundrobin option httpchk GET /health server web1 192.168.1.20:8080 check inter 2000 rise 2 fall 3 server web2 192.168.1.21:8080 check inter 2000 rise 2 fall 3逐段解释一下为什么这么写。maxconn 4096是单进程最大连接数,按机器内存和业务量调,太小会拒绝连接,太大可能把内存吃爆。mode http选了七层,因为后面要做 HTTP 健康检查。balance roundrobin是轮询算法,简单均匀;如果后端机器性能不一致,可以换成leastconn按连接数分。
健康检查那行是重点:option httpchk GET /health表示 haproxy 会定期向后端发一个 GET /health 请求,返回 2xx 或 3xx 才算健康。inter 2000是每 2 秒检查一次,rise 2是连续成功 2 次才标记为可用,fall 3是连续失败 3 次才标记为不可用。这几个参数决定了故障发现和恢复的速度,调太激进会误判,调太保守故障发现慢,2 秒加 3 次失败大概 6 秒发现,是个比较平衡的值。
注意:
/health这个接口需要后端业务自己实现,返回 200 即可。如果后端没有这个接口,haproxy 会一直认为它挂了,流量永远分不过去。这是新手最常见的"配置都对但没流量"的原因。
4.2 keepalived 主备配置:state、priority 与 track_script
主机 A 的 keepalived 配置:
global_defs { router_id node_a } vrrp_script chk_haproxy { script "/etc/keepalived/check_haproxy.sh" interval 2 weight -20 fall 2 rise 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass MyPass123 } virtual_ipaddress { 192.168.1.100/24 } track_script { chk_haproxy } }备机 B 的配置几乎一样,只改三处:router_id改成node_b,state改成BACKUP,priority改成 90。优先级差 10 是惯例,保证主机优先。
vrrp_script这段是精髓。它定义了一个检查脚本,每 2 秒跑一次,如果脚本返回非 0(失败),就给当前节点的优先级减 20(weight -20)。主机优先级 100 减 20 变成 80,低于备机的 90,于是备机抢走 VIP。这样即使 keepalived 进程本身活着,但 haproxy 挂了,也能触发切换。这就是"keepalived 检查 haproxy"的咬合点。
检查脚本内容:
#!/bin/bash # 检查 haproxy 进程是否存在 if killall -0 haproxy 2>/dev/null; then exit 0 else exit 1 fikillall -0只发信号不杀进程,用来判断进程是否存在。比ps加grep更干净,不会误匹配到 grep 自己。脚本要给执行权限:chmod +x /etc/keepalived/check_haproxy.sh。
4.3 启动顺序与验证:怎么确认真的切换成功了
启动顺序有讲究:先起 haproxy,再起 keepalived。因为 keepalived 一起来就会跑检查脚本,如果 haproxy 还没起,脚本失败,可能直接触发切换。
systemctl start haproxy systemctl start keepalived systemctl enable haproxy keepalived验证分三步。第一步,在主机 A 上ip addr看 VIP 在不在,在就对了。第二步,在备机 B 上ip addr看 VIP 不在,也对。第三步,模拟故障:在 A 上systemctl stop keepalived,然后立刻在 B 上ip addr,几秒内应该能看到 VIP 漂过来。同时用另一台机器持续ping 192.168.1.100,观察丢包情况,正常应该只丢 1 到 2 个包。
我实测下来,advert_int 1加fall 2的配置,切换时间大概在 2 到 3 秒。如果对切换时间要求更高,可以调小advert_int,但太小会增加网络负担,1 秒是个比较稳妥的值。
5. 那些文档不会告诉你的坑:我踩过的五个真实问题
5.1 脑裂:两台都以为自己是主
脑裂(Split Brain)是这套架构最危险的故障。表现是两台机器同时持有 VIP,流量乱窜,后端收到重复请求。根因通常是心跳网络断了——两台机器之间的 VRRP 通告收不到,各自都以为对方挂了,于是都升为 MASTER。
怎么发现?在任意一台机器上ip addr看 VIP,如果两台都有,就是脑裂了。怎么防?第一,心跳网络要独立,别和业务网络混在一起,避免业务流量把心跳挤掉。第二,配置nopreempt减少抢占抖动。第三,加一个双 ping 检测,在vrrp_script里同时 ping 网关和对端,两个都不通才认为真故障。
我遇到过一次脑裂,原因是交换机做了端口隔离,两台机器的组播报文过不去。后来改成单播模式(unicast_peer)才解决。所以如果你的环境组播有问题,直接上单播,别死磕。
5.2 VIP 漂移了但 ARP 没更新
这个坑很隐蔽:keepalived 日志显示切换成功,VIP 也确实在备机上了,但用户请求还是打到主机。原因是交换机或者客户端的 ARP 缓存没更新,还记着旧的 MAC 地址。
解决办法是确保 keepalived 发送免费 ARP。默认它是发的,但如果网卡或者交换机禁用了相关功能,就发不出去。可以在配置里加garp_master_delay和garp_master_repeat参数,控制免费 ARP 的发送时机和次数。另外,有些云环境的安全组会拦截 ARP,这种就只能走云平台自己的方案。
5.3 haproxy 健康检查把正常后端判死
前面提过,option httpchk依赖后端实现/health接口。如果后端返回 302 跳转,或者需要认证,haproxy 可能判定为不健康。这时候要么改后端接口,要么用httpchk的完整语法指定期望的状态码:
option httpchk GET /health HTTP/1.1\r\nHost:\ localhost http-check expect status 200还有一种情况是后端响应慢,超过了timeout check的默认值,被误判。这时候要调大检查超时,别让慢请求背锅。
5.4 配置文件语法对但服务起不来
keepalived 的配置对格式很敏感,少个花括号、多个空格都可能起不来。而且它报错信息很不友好,经常只说"配置错误"不说哪错。我的习惯是改完配置先keepalived -t -f /etc/keepalived/keepalived.conf做语法检查,通过了再重启。haproxy 同理,用haproxy -c -f /etc/haproxy/haproxy.cfg检查。
另外,auth_pass长度不能超过 8 个字符,超了会被截断,导致两台认证不一致,切换失败。这个坑我踩过,排查了半天才发现是密码太长。
5.5 云主机上 VIP 漂移的天然限制
如果你在云平台上跑,很多平台不允许随意漂移 VIP,因为底层网络是 SDN 架构,免费 ARP 发不出去。这时候有几个选择:一是用平台自带的高可用方案;二是用keepalived的unicast模式配合平台的 API 动态改路由;三是干脆放弃 VIP,用 DNS 轮询或者外部负载均衡。这个要提前确认,别等搭完了才发现漂移不了。
6. 让这套方案更稳的几个进阶思路
6.1 双主模式:让两台机器都不闲着
标准主备模式下,备机平时是闲置的,资源浪费。双主模式的思路是配置两个vrrp_instance,互为主备。比如实例 1 中 A 是主 B 是备,实例 2 中 B 是主 A 是备,两个实例绑定不同的 VIP。这样两台机器都在干活,资源利用率翻倍。
配置上就是在每台机器上写两个vrrp_instance,virtual_router_id不同,priority反过来。haproxy 那边监听两个 VIP 或者用同一个配置。这个模式适合流量较大、不想浪费机器的场景,但配置复杂度上升,要确保两个实例的检查脚本和故障逻辑不互相干扰。
6.2 健康检查脚本的精细化
前面那个killall -0的脚本太粗糙,只能判断进程在不在。更精细的做法是检查 haproxy 的统计接口:
#!/bin/bash # 通过 haproxy stats socket 检查 echo "show stat" | socat stdio /var/run/haproxy.sock | grep -q "FRONTEND"或者直接发一个 HTTP 请求到 haproxy 监听的端口,看能不能正常响应。这样能发现"进程活着但已经卡死"的情况。脚本越精细,切换判断越准,但也要注意脚本本身别太重,否则每 2 秒跑一次会拖累系统。
6.3 日志与监控:切换了得知道
keepalived 默认日志走 syslog,在/var/log/messages或/var/log/syslog里能看到状态变化。建议单独配置日志文件,方便排查:
global_defs { log_file /var/log/keepalived.log }监控上,至少要监控三样:VIP 当前在哪台机器、haproxy 后端健康数、切换事件次数。切换次数突然增多,说明链路不稳定,要赶紧查。我一般会在监控里设个告警,5 分钟内切换超过 3 次就通知,避免频繁抖动没人发现。
7. 收尾:几个我反复验证过的实操心得
这套 keepalived 加 haproxy 的组合,我从最早的单机入口一路搭到多机房,踩的坑基本都在这了。最后分享几个我反复验证过的点。
第一,配置一定要版本化。两台机器的配置文件用 git 管起来,改之前先提交,出问题能快速回滚。我见过太多人手改配置改乱了,最后两台不一致,切换就出问题。
第二,切换测试要定期做。别等真出故障了才发现切换不好使。我一般每个月做一次演练,手动停掉主机的 keepalived,看备机能不能在预期时间内接管,顺便验证监控告警是否正常。
第三,别过度设计。如果业务量不大,单台 haproxy 加 keepalived 做本机进程守护就够了,不一定非要两台。方案复杂度要和业务规模匹配,简单可靠比花哨重要。
第四,文档写给自己看。把 VIP、优先级、检查脚本、切换时间这些关键信息写成一页纸,贴在运维手册里。真出事的时候,没人有时间去翻配置文件猜逻辑。
这套方案不新,但足够稳。只要把 VIP 漂移、健康检查、脑裂防护这三件事做扎实,入口层的高可用基本就稳了。剩下的就是根据自己业务的特点慢慢调优,没有一劳永逸的配置,只有不断迭代的实践。