[2026 Remastered · 01] HAProxy 3.0 + Keepalived 2.3 高可用四层/七层负载均衡实战(Debian 13 PVE 真机演练版)
🛸 站长重制导读(Anti Gravi's Remaster Note)
本文是 「来时路 · 2026 Debian 13 真机重制企划(1/12)」 的开篇之作,对应本站 2019 年 11 月发布的第 1 篇历史文章 《Haproxy+Keepalived搭建》(
CID: 3)。2019 年的原稿在 CentOS 7 虚拟机上跑通了人生第一个四节点高可用拓扑,但也留下了三个当时未曾察觉的生产级隐患:只盯网卡不盯进程导致
haproxy崩溃时 VIP 死锁黑洞、默认 VRRP 多播在云网桥下极易引发双主脑裂、以及 HAProxy 1.x 全局mode http耦合。七年后的今天,我们不再纸上谈兵——直接通过 Proxmox VE (
pve-script-test) 现场拉起 4 台全新的 Debian 13 (Trixie · Linux6.12.96+deb13-cloud-amd64) 虚拟机,在 HAProxy3.0.11+ Keepalived2.3.3+ Apache2.4.69上真刀真枪复现并根治全部隐患。
一、 2019 原稿 (CID: 3) 三大生产隐患复盘
在动手重构前,先直面当年“测试跑通就以为万事大吉”的三个致命盲区:
| 维度 | 2019 原稿 (CID: 3 · CentOS 7) 写法 |
生产环境真实后果 | 2026 Remastered (Debian 13) 根治方案 |
|---|---|---|---|
| 1. 进程探活缺失 | 仅配置 track_interface { ens33 },未写 vrrp_script;甚至误以为“关闭 DS1 的 Keepalived 后客户端请求完全中断”是正常现象 |
当 DS1 的 haproxy 进程 OOM 或崩溃退出、但 eth0 网卡仍处于 UP 时,Keepalived 不会降权,VIP 死死焊在挂掉的 DS1 上,全站流量 100% 黑洞 |
编写 vrrp_script chk_haproxy(每 2 秒执行 /usr/bin/killall -0 haproxy),连续失败 2 次自动扣减权重 weight -30(100 -> 70 < 90),3 秒内由 DS2 无缝接管 VIP |
| 2. 多播脑裂与单字符笔误 | 使用默认 VRRP 多播(224.0.0.18);且 RS2 文档中把 210 敲成了 /etc/Keepalived/201 |
云厂商 VPC 或虚拟化网桥禁多播时,DS1 与 DS2 互相收不到心跳,双双自封 MASTER 抢夺同一 VIP(Split-Brain 脑裂) |
显式声明 unicast_src_ip 与 unicast_peer 单播点对点心跳;设置 vrrp_strict 兼容与 script_security |
| 3. HAProxy 语法与非本地 VIP 绑定 | HAProxy 1.x 旧语法,在 defaults 强绑 mode http 与 option forwardfor,且 frontend 仅监听 :80 |
后续新增 MySQL/Redis 四层 TCP 转发时直接报错或污染字节流;若显式绑定 VIP 则在 BACKUP 节点因无 VIP 无法启动 | 升级至 HAProxy 3.0.11 规范配置,配合内核 /etc/sysctl.d/99-haproxy-vip.conf (net.ipv4.ip_nonlocal_bind = 1) 允许主备同时绑定漂移 VIP |
二、 2026 真机实验拓扑与 Debian 13 极速换源
1. Proxmox VE 四节点 Debian 13 实测拓扑
我们在本地 PVE 集群上基于 debian13 Cloud-Init 模板克隆出 4 台独立虚拟机,网络位于同一 10.0.0.0/24 网段,规划漂移虚拟 IP (VIP) 为 10.0.0.210/32:
+-------------------------------+
| Client (curl 10.0.0.210) |
+---------------+---------------+
|
Floating VIP: 10.0.0.210/32
|
+---------------------+---------------------+
| (VRRP Unicast Heartbeat + chk_haproxy) |
v v
+----------------------------+ +----------------------------+
| DS1 (VMID 9100 · MASTER) | | DS2 (VMID 9101 · BACKUP) |
| IP: 10.0.0.132/24 | <=========> | IP: 10.0.0.133/24 |
| Priority: 100 (Fail: 70) | Unicast | Priority: 90 |
| HAProxy 3.0 + Keepalived | | HAProxy 3.0 + Keepalived |
+-------------+--------------+ +--------------+-------------+
| |
+--------------------+----------------------+
|
Round-Robin HTTP Health Check
|
+--------------------+--------------------+
v v
+---------------------------+ +---------------------------+
| RS1 (VMID 9102) | | RS2 (VMID 9103) |
| IP: 10.0.0.135/24 | | IP: 10.0.0.134/24 |
| Apache 2.4.69 (:80) | | Apache 2.4.69 (:80) |
+---------------------------+ +---------------------------+
2. Debian 13 (Trixie) Cloud Image 换中科大源避坑指南
[!TIP]
Debian 13 Cloud 镜像专属踩坑提示:
Debian 13 官方 Cloud Image 的/etc/apt/sources.list.d/debian.sources不再直接写死http://deb.debian.org,而是采用了URIs: mirror+file:///etc/apt/mirrors/debian.list动态镜像列表,且默认开启了Types: deb deb-src(导致每次apt update都要拉取庞大的源码索引)。想要在国内环境实现秒级安装,正确的替换姿势是直接覆写
/etc/apt/mirrors/*.list并剔除deb-src:
# 在 DS1、DS2、RS1、RS2 四台 Debian 13 节点上统一执行:
echo "https://mirrors.ustc.edu.cn/debian" | sudo tee /etc/apt/mirrors/debian.list
echo "https://mirrors.ustc.edu.cn/debian-security" | sudo tee /etc/apt/mirrors/debian-security.list
sudo sed -i 's/Types: deb deb-src/Types: deb/g' /etc/apt/sources.list.d/debian.sources
sudo apt-get update -y
三、 后端真实服务器(RS1 & RS2)配置
在 RS1 (10.0.0.135) 和 RS2 (10.0.0.134) 上安装 Apache 2.4.69,并写入带有各自主机标识的测试页。
为什么四层/七层反向代理(HAProxy)不需要像 LVS-DR 那样在 RS 上绑
lo:vip和改arp_ignore?
2019 年原稿里把 LVS-DR 模式的后端lo环回绑 VIP 与内核 ARP 抑制照搬到了 HAProxy 教程里。实际上,HAProxy 工作在代理模式(Proxy Mode),它与后端RS1/RS2建立的是标准 TCP 连接(源 IP 是DS1/DS2的真实内网 IP,目的 IP 是RS1/RS2的真实 IP),后端服务器根本不需要感知VIP 10.0.0.210的存在!去掉这段冗余操作后,后端部署清爽了十倍。
# 1. 在 RS1 (10.0.0.135) 上安装 Apache 并写入测试页:
sudo apt-get install -y apache2
cat << 'EOF' | sudo tee /var/www/html/index.html
<html><body>
<div style="width:100%;font-size:40px;font-weight:bold;text-align:center;">
Test Page - RS1 (10.0.0.135)
</div>
</body></html>
EOF
sudo systemctl enable --now apache2
# 2. 在 RS2 (10.0.0.134) 上安装 Apache 并写入测试页:
sudo apt-get install -y apache2
cat << 'EOF' | sudo tee /var/www/html/index.html
<html><body>
<div style="width:100%;font-size:40px;font-weight:bold;text-align:center;">
Test Page - RS2 (10.0.0.134)
</div>
</body></html>
EOF
sudo systemctl enable --now apache2
四、 负载均衡主备节点(DS1 & DS2)标准化配置
1. 安装组件并开启 ip_nonlocal_bind
在 DS1 (10.0.0.132) 和 DS2 (10.0.0.133) 上安装 Keepalived 2.3.3、HAProxy 3.0.11 与提供 /usr/bin/killall 的 psmisc 包:
sudo apt-get install -y keepalived haproxy psmisc curl
# 允许 HAProxy 绑定当前尚未漂移到本机的 VIP 地址
cat << 'EOF' | sudo tee /etc/sysctl.d/99-haproxy-vip.conf
net.ipv4.ip_nonlocal_bind = 1
EOF
sudo sysctl --system
(注:Debian 13 普通用户默认 $PATH 不含 /usr/sbin,查看版本可执行 /usr/sbin/haproxy -v 与 /usr/sbin/keepalived -v。)
2. Keepalived 2.3.3 单播 + 进程降权配置
DS1 (10.0.0.132 · MASTER) 的 /etc/keepalived/keepalived.conf:
global_defs {
router_id lb-ds1
script_security
enable_script_security
}
# 核心修复 1:每 2 秒探活一次 haproxy 进程
# 若连续 2 次失败 (fall 2),当前节点有效优先级扣减 30 (100 - 30 = 70 < 90),主动让出 VIP!
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight -30
fall 2
rise 2
user root
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
# 核心修复 2:使用单播心跳替代多播,彻底杜绝云网桥丢包导致的双 MASTER 脑裂
unicast_src_ip 10.0.0.132
unicast_peer {
10.0.0.133
}
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.210/32 dev eth0 label eth0:vip
}
track_script {
chk_haproxy
}
}
DS2 (10.0.0.133 · BACKUP) 的 /etc/keepalived/keepalived.conf:
只需改动 4 处(router_id、state BACKUP、priority 90、对调 unicast_src_ip / unicast_peer):
global_defs {
router_id lb-ds2
script_security
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight -30
fall 2
rise 2
user root
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
advert_int 1
unicast_src_ip 10.0.0.133
unicast_peer {
10.0.0.132
}
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.210/32 dev eth0 label eth0:vip
}
track_script {
chk_haproxy
}
}
3. HAProxy 3.0 生产级配置(DS1 与 DS2 完全一致)
编辑 /etc/haproxy/haproxy.cfg,不再将 mode http 塞进全局 defaults,而是在 frontend / backend 中明确声明七层语义与 httpchk 探针:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
log global
option dontlognull
timeout connect 5s
timeout client 50s
timeout server 50s
frontend http_front
mode http
bind 0.0.0.0:80
option httplog
option forwardfor
default_backend web_cluster
backend web_cluster
mode http
balance roundrobin
option httpchk
http-check send meth GET uri / ver HTTP/1.1 hdr Host localhost
server rs1 10.0.0.135:80 check inter 2000 rise 2 fall 3
server rs2 10.0.0.134:80 check inter 2000 rise 2 fall 3
listen stats
mode http
bind 0.0.0.0:8404
stats enable
stats uri /stats
stats refresh 10s
在 DS1 与 DS2 上校验配置语法并启动服务:
sudo /usr/sbin/haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl enable --now haproxy keepalived
五、 PVE Debian 13 四阶段真机故障演练实录
以下输出 100% 采自 PVE 四台 Debian 13 (Linux 6.12.96+deb13-cloud-amd64) 虚拟机的真实终端与 journalctl -u keepalived 日志:
Phase 1:初始正常态(VIP 绑定在 DS1)
在 DS1 (10.0.0.132) 上执行 ip -4 a show dev eth0 与日志检查,可以看到 10.0.0.210/32 已挂载为 eth0:vip,且 chk_haproxy 探针返回成功:
=== [PHASE 1: NORMAL STATE - DS1 (10.0.0.132)] ===
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
altname enp0s18
inet 10.0.0.132/24 metric 100 brd 10.0.0.255 scope global dynamic eth0
inet 10.0.0.210/32 scope global proto 0x12 eth0:vip
valid_lft forever preferred_lft forever
Nov 04 11:52:53 localhost Keepalived[1990]: Starting Keepalived v2.3.3 (03/30,2025)
Nov 04 11:52:53 localhost Keepalived[1990]: Running on Linux 6.12.96+deb13-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.96-1 (2026-07-20)
Nov 04 11:52:53 localhost Keepalived_vrrp[1991]: VRRP_Script(chk_haproxy) succeeded
Nov 04 11:52:56 localhost Keepalived_vrrp[1991]: (VI_1) received lower priority (90) advert from 10.0.0.133 - discarding
Nov 04 11:52:56 localhost Keepalived_vrrp[1991]: (VI_1) Entering MASTER STATE
与此同时,DS2 (10.0.0.133) 收到来自 10.0.0.132 优先级为 100(高于自身 90)的单播通告,乖巧退居 BACKUP STATE:
=== [PHASE 1: NORMAL STATE - DS2 (10.0.0.133)] ===
Nov 04 11:52:53 localhost Keepalived_vrrp[1890]: VRRP_Script(chk_haproxy) succeeded
Nov 04 11:52:56 localhost Keepalived_vrrp[1890]: (VI_1) Master received advert from 10.0.0.132 with higher priority 100, ours 90
Nov 04 11:52:56 localhost Keepalived_vrrp[1890]: (VI_1) Entering BACKUP STATE
Phase 2:通过漂移 VIP (10.0.0.210) 验证 Round-Robin 轮询
连续 4 次访问 http://10.0.0.210/,流量在 RS1 与 RS2 之间完美按 1:1 交替分发:
=== [PHASE 2: ROUND ROBIN VIA VIP 10.0.0.210] ===
$ for i in 1 2 3 4; do curl -s http://10.0.0.210 | grep "Test Page"; done
Test Page - RS1 (10.0.0.135)
Test Page - RS2 (10.0.0.134)
Test Page - RS1 (10.0.0.135)
Test Page - RS2 (10.0.0.134)
Phase 3:模拟 DS1 故障(只停 haproxy 进程,不停网卡与 keepalived!)
这是检验 2019 原稿与 2026 重制版本质区别的决定性时刻——我们在 DS1 上执行 sudo systemctl stop haproxy(注意:此时 DS1 的机器没死、网卡 eth0 也没断,唯独负载均衡进程挂了):
=== [PHASE 3: SIMULATE DS1 HAPROXY CRASH (systemctl stop haproxy on DS1)] ===
--- DS1 after stop haproxy ---
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 10.0.0.132/24 metric 100 brd 10.0.0.255 scope global dynamic eth0
Nov 04 11:52:59 localhost Keepalived_vrrp[1991]: Script `chk_haproxy` now returning 1
Nov 04 11:53:01 localhost Keepalived_vrrp[1991]: VRRP_Script(chk_haproxy) failed (exited with status 1)
Nov 04 11:53:01 localhost Keepalived_vrrp[1991]: (VI_1) Changing effective priority from 100 to 70
Nov 04 11:53:04 localhost Keepalived_vrrp[1991]: (VI_1) Master received advert from 10.0.0.133 with higher priority 90, ours 70
Nov 04 11:53:04 localhost Keepalived_vrrp[1991]: (VI_1) Entering BACKUP STATE
看 DS1 的日志时间线:
11:52:59:第 1 次探测发现haproxy消失(返回1);11:53:01:达到fall 2阈值,VRRP_Script(chk_haproxy) failed,自动将DS1有效优先级从100降为70;11:53:04:DS2 (10.0.0.133)发现对端发来的通告优先级只有70(低于自己的90),立即升格为MASTER并接管10.0.0.210/32!
--- DS2 after stop haproxy (Backup takeover!) ---
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 10.0.0.133/24 metric 100 brd 10.0.0.255 scope global dynamic eth0
inet 10.0.0.210/32 scope global proto 0x12 eth0:vip
valid_lft forever preferred_lft forever
Nov 04 11:53:01 localhost Keepalived_vrrp[1890]: (VI_1) received lower priority (70) advert from 10.0.0.132 - discarding
Nov 04 11:53:02 localhost Keepalived_vrrp[1890]: (VI_1) received lower priority (70) advert from 10.0.0.132 - discarding
Nov 04 11:53:03 localhost Keepalived_vrrp[1890]: (VI_1) received lower priority (70) advert from 10.0.0.132 - discarding
Nov 04 11:53:04 localhost Keepalived_vrrp[1890]: (VI_1) Entering MASTER STATE
--- VIP curl during DS1 outage ---
Test Page - RS1 (10.0.0.135)
Test Page - RS2 (10.0.0.134)
在 DS1 的 haproxy 彻底停止期间,客户端访问 http://10.0.0.210 零报错、零中断,由 DS2 稳稳扛起全部流量!
Phase 4:拉起 DS1 的 haproxy,权重恢复并平滑回切
当我们在 DS1 上执行 sudo systemctl start haproxy 后,chk_haproxy 连续两次返回 0(rise 2),优先级从 70 恢复至 100,重新夺回 MASTER 席位:
=== [PHASE 4: RECOVER DS1 HAPROXY (systemctl start haproxy on DS1)] ===
Nov 04 11:53:07 localhost Keepalived_vrrp[1991]: Script `chk_haproxy` now returning 0
Nov 04 11:53:09 localhost Keepalived_vrrp[1991]: VRRP_Script(chk_haproxy) succeeded
Nov 04 11:53:09 localhost Keepalived_vrrp[1991]: (VI_1) Changing effective priority from 70 to 100
Nov 04 11:53:09 localhost Keepalived_vrrp[1991]: (VI_1) received lower priority (90) advert from 10.0.0.133 - discarding
六、 结语:从“能跑通”到“敢扛故障”
七年前写下 CID: 3 时,看到两个测试页面交替刷出就已经欣喜若狂;七年后再用 Debian 13 真机重制这一课,我们终于把当年漏掉的最后一环——“当进程比机器先死时,高可用还是否有效”——用真实的 Changing effective priority from 100 to 70 日志补上了圆满的句号。