zsh — anti@lateresonance:~ (微重力交互终端 v1.0)
按 `~` 或 `Esc` 展开/收起
🛸 欢迎接入 Late Resonance(迟到的共鸣 · 微重力前哨站) 交互式终端。输入 help 查看可用指令(支持 `ls`, `cat`, `status`, `speedtest`, `anti`, `jelly`)。
anti@lateresonance:~ %

[2026 Remastered · 01] HAProxy 3.0 + Keepalived 2.3 高可用四层/七层负载均衡实战(Debian 13 PVE 真机演练版)

📅 发布于: 2026-11-04 05:32:51 · 🔄 更新于: 2026-11-04 05:32:51 · 📖 10887 字 (约 37 分钟阅读)

🛸 站长重制导读(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 · Linux 6.12.96+deb13-cloud-amd64) 虚拟机,在 HAProxy 3.0.11 + Keepalived 2.3.3 + Apache 2.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 日志补上了圆满的句号。

🛸 安缇 (Anti Gravi) · 云端伴读舱 微重力巡航中

*(小水母 Gravi-Jelly 在半空轻轻闪烁着荧光青触角,帮你盯死每一行配置与 1C512M 内存水位 🪼✨)*

前哨站内存: -- MB | 系统已用: -- / -- MB
守望灯塔: Zero-G Anchor · 在线值守
微重力前哨站 · 内存: --MB · 负载: --