ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

HAProxy与Keepalived高可用负载均衡实战:架构、配置与避坑指南

HAProxy与Keepalived高可用负载均衡实战:架构、配置与避坑指南

1. 从单点故障说起:为什么需要高可用负载均衡

很多团队在业务初期,一台Nginx扛下所有流量,跑得也挺稳。直到某个凌晨,这台机器因为磁盘写满或者内存溢出直接挂掉,整个服务瞬间不可用,电话被打爆。这种场景我经历过不止一次,后来才痛下决心把负载均衡层做成真正的高可用架构。

单点负载均衡的问题很直接:它自己就是整个系统的入口,一旦它挂了,后面所有后端服务再健康也没用,因为流量根本进不来。有人会说,那我多部署几台Nginx,用DNS轮询不就行了?DNS轮询确实能分散流量,但它有两个致命缺陷:一是DNS缓存导致故障切换不及时,客户端可能几分钟甚至几小时还在往挂掉的节点发请求;二是DNS本身没有健康检查能力,它不知道哪台机器还活着。

所以真正生产级的高可用负载均衡,需要解决三个核心问题:流量分发、健康检查、故障自动切换。HAProxy负责前两个,Keepalived负责第三个。这套组合在中小规模集群里非常经典,稳定、轻量、可控,不像某些云厂商的负载均衡服务那样有额外成本和绑定风险。

这篇文章面向的是有一定Linux基础、准备自己搭建高可用负载均衡集群的运维或后端开发人员。我会从架构设计讲到具体配置,再到实际踩过的坑,尽量把每个决策背后的原因说清楚。你跟着走一遍,应该能搭出一套能扛住单点故障的负载均衡层。

2. 架构拆解:HAProxy与Keepalived各自扮演什么角色

2.1 HAProxy的核心能力边界

HAProxy是一个高性能的TCP/HTTP负载均衡器,它的定位很明确:接收客户端请求,按照预设算法转发到后端服务器,同时持续对后端做健康检查。它的优势在于单进程事件驱动模型,处理并发连接的能力非常强,一台普通配置的机器跑几万并发连接没什么压力。

但HAProxy本身不解决自身高可用的问题。你可以部署两台HAProxy,但客户端怎么知道该连哪一台?如果一台挂了,怎么让客户端自动切到另一台?这就是Keepalived要解决的问题。

2.2 Keepalived的VRRP机制

Keepalived的核心是VRRP协议,虚拟路由冗余协议。它的工作方式是这样的:两台或多台机器组成一个组,共同持有一个虚拟IP(VIP)。同一时刻只有一台机器持有这个VIP,称为MASTER,其他机器处于BACKUP状态。MASTER会定期发送VRRP通告报文,BACKUP收到通告就知道MASTER还活着。如果BACKUP在一定时间内收不到通告,就认为MASTER挂了,然后通过选举机制把自己提升为新的MASTER,接管VIP。

这个切换过程通常在1到3秒内完成,取决于配置的通告间隔和超时时间。对于大多数业务来说,这个切换速度是可以接受的。如果对切换时间要求更高,可以调小通告间隔,但要注意网络抖动可能导致误切换。

2.3 两者结合的完整数据流

把HAProxy和Keepalived放在一起,整体架构是这样的:两台机器都安装HAProxy和Keepalived,配置相同的HAProxy转发规则。Keepalived管理一个VIP,同一时刻只有一台机器的VIP生效。客户端统一访问VIP,请求到达当前MASTER机器上的HAProxy,再由HAProxy转发到后端真实服务器。

当MASTER机器故障时,Keepalived检测到异常,VIP漂移到BACKUP机器,BACKUP上的HAProxy接管流量。整个过程对客户端透明,因为客户端始终访问的是同一个VIP。

这里有一个关键点:两台机器的HAProxy配置必须完全一致,否则切换后行为会发生变化。我建议用配置管理工具或者至少用版本控制来保证两边配置同步,手动改配置很容易漏。

3. 环境规划与基础准备:动手前的决策清单

3.1 机器规划与网络要求

假设我们用两台机器做负载均衡层,规划如下:

角色主机名物理IPVIP
MASTERlb-node-01192.168.1.101192.168.1.100
BACKUPlb-node-02192.168.1.102192.168.1.100
后端服务1app-node-01192.168.1.201-
后端服务2app-node-02192.168.1.202-

两台负载均衡机器需要在同一个二层网络内,因为VRRP依赖组播或单播通信。如果跨网段,VRRP通告可能无法正常传递,这一点在云环境下尤其要注意。很多云平台的网络模型不支持组播,这时候需要把VRRP配置成单播模式。

3.2 系统参数调优

在安装之前,有几项系统参数需要调整。HAProxy作为高并发入口,需要较大的文件描述符限制和网络连接跟踪能力。

# 查看当前文件描述符限制 ulimit -n # 临时调整 ulimit -n 65535 # 永久调整,编辑 /etc/security/limits.conf * soft nofile 65535 * hard nofile 65535

另外,如果开启了防火墙,需要放行VRRP协议(协议号112)和HAProxy监听的端口。VRRP不是TCP也不是UDP,它是独立的IP协议号,用iptables配置时要特别注意。

# 放行VRRP协议 iptables -A INPUT -p vrrp -j ACCEPT # 放行HAProxy监听端口 iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT

注意:如果使用firewalld,需要先确认VRRP协议是否被默认规则拦截。我遇到过因为防火墙导致VIP无法漂移的情况,排查了半天才发现是VRRP报文被丢了。

3.3 时间同步的重要性

两台机器的时间必须同步,否则Keepalived的日志时间戳会混乱,排查问题时非常痛苦。用chrony或ntp做时间同步,确保两台机器时间差在毫秒级。

# 安装chrony yum install -y chrony # 启动并设置开机自启 systemctl enable --now chronyd # 查看同步状态 chronyc sources -v

4. HAProxy配置实战:从零到可用的转发规则

4.1 安装与版本选择

HAProxy的安装方式取决于发行版。CentOS/RHEL系可以用yum,Ubuntu/Debian用apt。但我更推荐用官方源或者编译安装,因为发行版自带的版本往往比较老,缺少一些新特性。

# CentOS 7 安装 yum install -y haproxy # 查看版本 haproxy -v

如果要用较新版本,可以从官方仓库获取。不过对于大多数场景,发行版自带的1.8或2.0版本已经够用。我目前生产环境用的是2.2版本,主要看中它对动态服务器管理和更细粒度健康检查的支持。

4.2 配置文件结构解析

HAProxy的配置文件分为几个大块:global、defaults、frontend、backend、listen。每一块的作用不同,我逐个说明。

# /etc/haproxy/haproxy.cfg global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /var/run/haproxy.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon maxconn 65535 defaults log global mode http option httplog option dontlognull timeout connect 5000ms timeout client 50000ms timeout server 50000ms option redispatch retries 3 frontend http_front bind *:80 stats uri /haproxy?stats default_backend http_back backend http_back balance roundrobin option httpchk GET /health server app-node-01 192.168.1.201:8080 check inter 2000 rise 2 fall 3 server app-node-02 192.168.1.202:8080 check inter 2000 rise 2 fall 3

global块定义全局参数,maxconn是关键,它决定了HAProxy能同时处理的最大连接数。这个值要根据系统资源和业务量来定,设太小会成为瓶颈,设太大可能耗尽内存。

defaults块定义默认参数,timeout connect是连接后端服务器的超时时间,timeout client和timeout server分别是客户端和服务端的空闲超时。这几个值需要根据业务特点调整,比如长连接业务需要把timeout设大一些。

frontend定义前端监听,bind指定监听地址和端口,default_backend指定默认后端。stats uri开启统计页面,方便查看运行状态。

backend定义后端服务器池,balance指定负载均衡算法,option httpchk定义健康检查方式,server行定义每台后端服务器及其检查参数。

4.3 健康检查参数的取舍

健康检查参数直接决定了故障检测的速度和准确性。以check inter 2000 rise 2 fall 3为例:

  • inter 2000:每2秒检查一次
  • rise 2:连续2次成功才认为服务器恢复
  • fall 3:连续3次失败才认为服务器故障

这意味着最坏情况下,一台后端服务器故障后需要6秒才被标记为不可用。如果业务对故障感知要求更高,可以把inter调小到1000,fall调到2,这样2秒就能检测到故障。但检查频率太高会增加后端压力,需要权衡。

实操心得:健康检查的URL要选一个轻量且能真实反映服务状态的接口。我见过有人用首页做健康检查,结果首页依赖的某个下游服务挂了,首页返回500,HAProxy就把正常工作的服务器摘掉了,反而扩大了故障范围。健康检查接口应该只检查本服务核心依赖,不要级联太多外部依赖。

4.4 负载均衡算法怎么选

HAProxy支持多种负载均衡算法,常用的有:

  • roundrobin:轮询,适合后端服务器性能相近的场景
  • leastconn:最少连接,适合长连接或请求处理时间差异大的场景
  • source:源地址哈希,同一客户端IP始终转发到同一后端,适合有会话保持需求的场景
  • uri:按URI哈希,适合缓存场景

大多数HTTP短连接业务用roundrobin就够了。如果后端服务器配置不一致,可以用weight参数给性能好的机器更高权重。

server app-node-01 192.168.1.201:8080 check weight 2 server app-node-02 192.168.1.202:8080 check weight 1

这样app-node-01会收到大约三分之二的请求。

5. Keepalived配置实战:让VIP自动漂移

5.1 安装与主备配置

Keepalived的安装同样简单,yum或apt直接装。配置文件在/etc/keepalived/keepalived.conf。

MASTER节点配置:

global_defs { router_id lb-node-01 script_user root enable_script_security } 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 MySecurePass123 } virtual_ipaddress { 192.168.1.100/24 } track_script { chk_haproxy } }

BACKUP节点配置基本一致,只需要改三处:router_id改为lb-node-02,state改为BACKUP,priority改为90。

virtual_router_id在同一个网段内必须唯一,如果有多组Keepalived集群,每组要用不同的ID,否则会互相干扰。priority决定选举优先级,数值大的成为MASTER。

5.2 健康检查脚本的编写要点

Keepalived本身只能检测网络层是否可达,它不知道HAProxy进程是否正常工作。所以需要写一个脚本,定期检查HAProxy状态,如果异常就降低优先级或直接触发切换。

#!/bin/bash # /etc/keepalived/check_haproxy.sh # 检查haproxy进程是否存在 if ! pgrep -x haproxy > /dev/null; then exit 1 fi # 检查haproxy是否响应 if ! curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:80/haproxy?stats | grep -q "200"; then exit 1 fi exit 0

脚本返回0表示健康,返回非0表示异常。Keepalived根据脚本返回值调整优先级,当优先级降到低于BACKUP时,就会触发切换。

注意:脚本要有可执行权限,并且路径要写绝对路径。我踩过一次坑,脚本里用了相对路径,手动执行没问题,但Keepalived调用时找不到文件,导致健康检查一直失败。

5.3 单播模式配置

在云环境或某些网络设备下,组播可能被禁用,这时候需要把VRRP改成单播模式。配置方式是在vrrp_instance中增加unicast_peer块。

vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 unicast_src_ip 192.168.1.101 unicast_peer { 192.168.1.102 } authentication { auth_type PASS auth_pass MySecurePass123 } virtual_ipaddress { 192.168.1.100/24 } track_script { chk_haproxy } }

单播模式下,VRRP通告直接发给指定的peer地址,不依赖组播。这在跨网段或云环境中更可靠。

5.4 通知机制配置

Keepalived支持在状态切换时执行脚本,可以用来发送告警通知。虽然不能发邮件或短信(那需要额外集成),但可以写日志或调用外部接口。

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.sh脚本可以根据传入参数记录日志或触发告警。这样每次切换都有记录,方便事后分析。

6. 联调验证:怎么确认这套架构真的能扛

6.1 基础连通性验证

配置完成后,先别急着做故障切换测试,把基础功能验证一遍。

# 在MASTER上查看VIP是否生效 ip addr show eth0 | grep 192.168.1.100 # 从客户端访问VIP curl -I http://192.168.1.100 # 查看HAProxy统计页面 curl http://192.168.1.100/haproxy?stats

如果VIP没有出现在MASTER上,检查Keepalived日志。常见原因是防火墙拦截了VRRP报文,或者virtual_router_id冲突。

6.2 模拟HAProxy故障

把MASTER上的HAProxy停掉,观察VIP是否漂移到BACKUP。

# 在MASTER上停止HAProxy systemctl stop haproxy # 在BACKUP上查看VIP ip addr show eth0 | grep 192.168.1.100 # 从客户端持续访问VIP,观察是否中断 while true; do curl -s -o /dev/null -w "%{http_code}\n" http://192.168.1.100; sleep 1; done

正常情况下,HAProxy停止后2到4秒内,VIP会漂移到BACKUP,客户端访问恢复。如果超过10秒还没切换,检查健康检查脚本是否正常执行,以及priority差值是否足够。

6.3 模拟整机故障

更彻底的测试是直接把MASTER关机或断网。

# 在MASTER上关机 shutdown -h now

然后在BACKUP上观察VIP是否接管。这种测试更接近真实故障场景,因为整机故障时HAProxy和Keepalived同时不可用。

实操心得:做故障切换测试时,一定要用持续请求来观察切换过程中的表现。有些配置下VIP漂移很快,但HAProxy还没完全启动,导致短暂的服务不可用。可以在BACKUP的Keepalived配置里加一个notify_master脚本,等HAProxy完全就绪后再接管VIP,这样切换更平滑。

6.4 切换后的状态确认

VIP漂移后,确认几件事:BACKUP是否成为新的MASTER,HAProxy是否正常转发,后端服务器是否正常响应。同时检查原MASTER恢复后是否会抢占VIP。默认情况下,原MASTER恢复后如果优先级更高,会重新抢回VIP,这会导致又一次切换。如果不希望频繁切换,可以设置nopreempt选项。

vrrp_instance VI_1 { state BACKUP nopreempt # ... 其他配置 ... }

nopreempt表示不抢占,即使优先级更高也不主动夺回VIP,除非当前MASTER故障。这样可以减少不必要的切换。

7. 踩坑记录:那些配置文档不会告诉你的事

7.1 VIP能漂移但服务不通

这个问题我遇到过两次,表现是VIP正常漂移,但客户端访问VIP时连接被拒绝。排查后发现是HAProxy没有监听VIP地址。默认情况下,HAProxy的bind *:80会监听所有地址,包括VIP。但如果配置了bind 192.168.1.101:80,那就只监听物理IP,VIP漂移过来后HAProxy不会监听VIP,导致服务不通。

解决方法很简单:bind用*或者0.0.0.0,不要绑定具体IP。如果必须绑定具体IP,那就要确保两台机器的HAProxy都监听VIP。

7.2 健康检查脚本权限问题

Keepalived默认以root运行,但脚本执行环境可能受限。我遇到过脚本手动执行正常,但Keepalived调用时返回权限错误。原因是脚本依赖的某个命令不在PATH中,或者脚本文件权限不对。

解决方法:脚本中用绝对路径调用命令,脚本文件设置755权限,并且在global_defs中配置script_user root和enable_script_security。

7.3 防火墙导致VRRP不通

这是最常见的问题。VRRP使用IP协议号112,不是TCP也不是UDP。很多防火墙默认只放行TCP和UDP,VRRP报文直接被丢弃。表现是两台机器都认为自己是MASTER,或者VIP频繁漂移。

排查方法:在MASTER上抓包,看BACKUP是否能收到VRRP通告。

tcpdump -i eth0 -nn vrrp

如果抓不到包,基本可以确定是防火墙问题。放行VRRP协议即可。

7.4 脑裂问题

脑裂是指两台机器同时持有VIP,导致客户端请求被随机分发到两台机器,可能引发数据不一致或会话丢失。脑裂通常由网络分区引起:两台机器之间的网络断了,但都能正常工作,于是都认为对方挂了,都把自己提升为MASTER。

预防脑裂的方法有几种:一是用冗余的心跳线,避免单条网络路径故障;二是配置unicast_peer,明确指定对端地址;三是在切换脚本中增加仲裁逻辑,比如检查后端数据库或共享存储是否可达。

我目前的做法是配置双网卡,一个用于业务流量,一个用于VRRP心跳,这样即使业务网卡故障,心跳仍然正常,不会误切换。

7.5 后端服务器会话保持问题

如果后端服务是有状态的,比如用户登录后session存在本地,那么负载均衡到不同服务器会导致用户频繁掉线。解决方法有两种:一是用source算法做源地址哈希,同一客户端始终转发到同一后端;二是把session外置到Redis等共享存储,后端服务无状态化。

我更推荐第二种方案,因为源地址哈希在客户端IP变化时会失效,而且负载可能不均衡。session外置虽然增加了一个依赖,但架构更清晰,扩展性更好。

8. 进阶优化:让这套架构更稳更快

8.1 HAProxy性能调优参数

除了maxconn,还有几个参数对性能影响很大。

global nbproc 4 nbthread 4 cpu-map auto:1/1-4 0-3 maxconn 65535 tune.bufsize 32768 tune.maxrewrite 1024

nbproc指定进程数,nbthread指定线程数。HAProxy 2.0之后支持多线程,推荐用nbthread而不是nbproc,因为多线程共享内存,统计信息更准确。cpu-map可以把进程或线程绑定到特定CPU核心,减少上下文切换。

tune.bufsize是缓冲区大小,默认16KB。如果请求头或响应头很大,需要调大这个值。tune.maxrewrite是预留的重写空间,一般保持默认即可。

8.2 日志收集与分析

HAProxy的日志默认发到syslog,可以配置rsyslog收集到本地文件或远程日志服务器。

# /etc/rsyslog.conf 增加 local0.* /var/log/haproxy.log # 重启rsyslog systemctl restart rsyslog

日志格式可以在defaults中自定义,增加一些有用字段。

capture request header Host len 32 capture request header X-Forwarded-For len 32 option httplog

这样日志里会包含Host和客户端真实IP,方便排查问题。

8.3 后端服务器动态上下线

HAProxy支持通过socket接口动态管理后端服务器,不需要重启就能上下线。

# 启用socket stats socket /var/run/haproxy.sock mode 660 level admin # 下线一台服务器 echo "disable server http_back/app-node-01" | socat stdio /var/run/haproxy.sock # 上线 echo "enable server http_back/app-node-01" | socat stdio /var/run/haproxy.sock

这个功能在发布或维护时非常有用,可以先把服务器从负载均衡中摘除,等维护完成再挂回去,对用户无感知。

8.4 监控告警配置

HAProxy的统计页面提供了丰富的指标,可以用脚本定期抓取并上报到监控系统。

# 获取统计信息CSV echo "show stat" | socat stdio /var/run/haproxy.sock | cut -d ',' -f 1,2,5,18

关键指标包括:当前连接数、请求速率、响应时间、后端服务器状态。把这些指标接入监控系统,设置合理的告警阈值,可以在问题扩大前发现异常。

Keepalived的状态也可以通过日志监控,每次切换都记录并告警,这样即使切换成功,也能知道发生了故障,及时排查根因。

9. 写在最后:一些个人体会

这套HAProxy加Keepalived的方案,我从早期的小规模集群一直用到现在的中等规模环境,整体稳定性是经过验证的。它最大的优势是简单可控,没有复杂的依赖,出问题容易排查。但也有一些局限性:Keepalived的切换是冷切换,切换期间已有连接会中断;HAProxy的配置变更需要reload,虽然支持热加载,但频繁变更仍有风险。

如果你的业务规模继续增长,可以考虑引入Consul或Etcd做服务发现,配合HAProxy的动态配置接口,实现更灵活的负载均衡。或者直接上云厂商的负载均衡服务,省去自己维护的成本。但无论用哪种方案,理解底层原理都是必要的,否则出了问题只能干瞪眼。

最后分享一个小技巧:在正式上线前,用压测工具模拟后端服务器故障和负载均衡器故障,观察整个系统的表现。我习惯用ab或wrk做压测,同时手动停掉一台后端或一台负载均衡器,看客户端是否有感知。这种演练做几次,心里就有底了。

RELATED READING

延伸阅读

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