Linux运维15 min read

HAProxy + Keepalived 负载均衡与高可用实战

为什么是 HAProxy + Keepalived

Nginx 擅长七层反向代理,但在纯 TCP 负载均衡、连接级健康检查、会话保持等场景下,HAProxy 才是专业选择。Keepalived 提供的 VRRP 虚拟 IP 漂移则解决了单点故障问题。两者组合是 Linux 下最经典的四层/七层负载均衡 + 高可用方案。

维度 Nginx HAProxy LVS
工作层 七层为主 四层+七层 四层(内核态)
健康检查 被动 主动+被动 有限
会话保持 cookie/ip_hash cookie/source/conn source
动态后端 需 reload 运行时 socket ipvsadm
性能 极高 极高 最高(内核)
高可用配对 upstream + keepalived keepalived keepalived/heartbeat

选型建议:Web/API 七层用 Nginx,数据库/Redis/MQ 等 TCP 负载用 HAProxy,超大规模四层用 LVS。

架构总览

                    ┌──────────────────────────────────────────┐
                    │           Virtual IP (VIP)               │
                    │          192.168.1.100                   │
                    │     Keepalived VRRP 主备漂移              │
                    └─────────┬──────────────┬─────────────────┘
                              │              │
                    ┌─────────▼────┐  ┌──────▼───────┐
                    │  HAProxy-Master │  │ HAProxy-Backup │
                    │  192.168.1.11   │  │ 192.168.1.12  │
                    │  Active (MASTER)│  │ Standby(BACKUP)│
                    └────────┬────────┘  └───────┬───────┘
                             │                   │
              ┌──────────────┼───────────────────┼──────────────┐
              │              │                   │              │
        ┌─────▼────┐   ┌────▼────┐       ┌─────▼────┐   ┌────▼────┐
        │ Backend1 │   │ Backend2│       │ Backend3 │   │ Backend4│
        │  .21:80  │   │  .22:80 │       │  .23:80  │   │  .24:80 │
        └──────────┘   └─────────┘       └──────────┘   └─────────┘

安装部署

环境准备

# 两台负载均衡器均执行
# CentOS/RHEL
yum install -y haproxy keepalived

# Ubuntu/Debian
apt install -y haproxy keepalived

# 开启 IP 转发(四层模式需要)
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
sysctl -p

# 关闭 SELinux(或配置策略)
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

HAProxy 配置

# /etc/haproxy/haproxy.cfg
global
    log         127.0.0.1 local2
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     20000
    user        haproxy
    group       haproxy
    daemon
    stats socket /var/lib/haproxy/stats mode 660 level admin
    tune.ssl.default-dh-param 2048

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         5s
    timeout client          30s
    timeout server          30s
    timeout http-keep-alive 10s
    timeout check           5s
    maxconn                 10000

# ─── 统计页面 ───
listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /haproxy-stats
    stats realm HAProxy\ Statistics
    stats auth admin:StrongPass!2026
    stats refresh 10s

# ─── 七层 HTTP 负载均衡 ───
frontend web-frontend
    bind *:80
    bind *:443 ssl crt /etc/haproxy/certs/scrcx.cn.pem alpn h2,http/1.1
    http-request redirect scheme https unless { ssl_fc }
    
    # ACL 规则:按路径分流
    acl is_api    path_beg /api/
    acl is_static path_beg /static/ /images/ /css/ /js/
    acl is_blog   path_beg /posts/ /tags/ /about
    
    use_backend api_servers    if is_api
    use_backend static_servers if is_static
    default_backend web_servers

# ─── 后端服务器池 ───
backend web_servers
    mode http
    balance     roundrobin
    # 主动健康检查
    option      httpchk GET /healthz
    http-check  expect status 200
    # 会话保持:cookie 注入
    cookie SERVERID insert indirect nocache httponly samesite Strict
    # 慢启动(新服务器逐步加流量)
    server web1 192.168.1.21:80 check cookie s1 weight 10 slowstart 30s
    server web2 192.168.1.22:80 check cookie s2 weight 10 slowstart 30s
    server web3 192.168.1.23:80 check cookie s3 weight 10 backup

backend api_servers
    mode http
    balance     leastconn
    option      httpchk GET /api/health
    http-check  expect status 200
    # 连接复用
    option      http-keep-alive
    server api1 192.168.1.31:8080 check maxconn 500
    server api2 192.168.1.32:8080 check maxconn 500

backend static_servers
    mode http
    balance     roundrobin
    option      httpchk GET /static/health.txt
    # 缓存友好:尽量复用连接
    http-reuse  safe
    server st1 192.168.1.41:80 check
    server st2 192.168.1.42:80 check

# ─── 四层 TCP 负载均衡(数据库示例) ───
listen mysql-cluster
    bind *:3306
    mode tcp
    balance source
    option tcp-check
    tcp-check connect
    tcp-check send PING\r\n
    tcp-check expect string *pong*
    # 最大连接限制
    server mysql-master 192.168.1.51:3306 check inter 2000 rise 2 fall 3 maxconn 200
    server mysql-slave  192.168.1.52:3306 check inter 2000 rise 2 fall 3 maxconn 200 backup

listen redis-cluster
    bind *:6379
    mode tcp
    balance first
    option tcp-check
    tcp-check send PING\r\n
    tcp-check expect string +PONG
    server redis1 192.168.1.61:6379 check inter 1000 fall 2
    server redis2 192.168.1.62:6379 check inter 1000 fall 2

Keepalived 配置

主节点(Master)配置:

# /etc/keepalived/keepalived.conf — 主节点
global_defs {
    router_id LB_MASTER
    # 启用脚本安全权限
    enable_script_security
}

# ─── HAProxy 健康检查脚本 ───
vrrp_script check_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2
    weight -30          # 脚本失败则优先级减 30
    fall 2
    rise 2
}

# ─── VRRP 实例 ───
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1

    # 抢占模式:Master 恢复后自动夺回 VIP
    preempt
    preempt_delay 5     # 恢复后等 5 秒再抢占

    authentication {
        auth_type PASS
        auth_pass MyStr0ngVRRP
    }

    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:vip
    }

    # 跟踪 HAProxy 进程状态
    track_script {
        check_haproxy
    }

    # 状态变更通知脚本
    notify_master "/etc/keepalived/notify.sh master"
    notify_backup "/etc/keepalived/notify.sh backup"
    notify_fault  "/etc/keepalived/notify.sh fault"
}

备节点(Backup)配置:

# /etc/keepalived/keepalived.conf — 备节点
global_defs {
    router_id LB_BACKUP
    enable_script_security
}

vrrp_script check_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2
    weight -30
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP          # 关键差异
    interface eth0
    virtual_router_id 51  # 必须与 Master 一致
    priority 90            # 低于 Master
    advert_int 1

    preempt
    preempt_delay 5

    authentication {
        auth_type PASS
        auth_pass MyStr0ngVRRP
    }

    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:vip
    }

    track_script {
        check_haproxy
    }

    notify_master "/etc/keepalived/notify.sh master"
    notify_backup "/etc/keepalived/notify.sh backup"
    notify_fault  "/etc/keepalived/notify.sh fault"
}

辅助脚本

HAProxy 进程检查脚本:

#!/bin/bash
# /etc/keepalived/check_haproxy.sh
# 检查 HAProxy 进程是否存活
if ! killall -0 haproxy 2>/dev/null; then
    # 尝试自动拉起
    systemctl start haproxy
    sleep 2
    if ! killall -0 haproxy 2>/dev/null; then
        echo "HAProxy is DOWN" >&2
        exit 1
    fi
fi
exit 0

状态变更通知脚本:

#!/bin/bash
# /etc/keepalived/notify.sh
# VRRP 状态变更时发送告警

STATE=$1
HOSTNAME=$(hostname)
VIP="192.168.1.100"
TIME=$(date '+%Y-%m-%d %H:%M:%S')

case "$STATE" in
    master)
        MSG="⚠️ ${HOSTNAME} 已成为 VRRP MASTER,VIP ${VIP} 已漂移到本机 [${TIME}]"
        ;;
    backup)
        MSG="ℹ️ ${HOSTNAME} 已切换为 VRRP BACKUP,VIP ${VIP} 已释放 [${TIME}]"
        ;;
    fault)
        MSG="🔴 ${HOSTNAME} VRRP 进入 FAULT 状态,请检查网络和 HAProxy [${TIME}]"
        ;;
esac

# 发送到企业微信/钉钉/飞书 Webhook
curl -s -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MSG}\"}}"

# 同时记录本地日志
logger -t keepalived "$MSG"

启动服务

# 两台均执行
chmod +x /etc/keepalived/check_haproxy.sh /etc/keepalived/notify.sh
chown root:root /etc/keepalived/*.sh

systemctl enable haproxy keepalived
systemctl start haproxy
systemctl start keepalived

# 验证 VIP
ip addr show eth0 | grep 192.168.1.100

常用负载均衡算法

算法 HAProxy 配置 适用场景
轮询 roundrobin 后端性能均匀,无状态服务
最少连接 leastconn 长连接、请求耗时差异大
源地址哈希 source 需要会话保持但无 cookie
URI 哈希 uri 缓存命中率优化
带权重轮询 roundrobin + weight N 异构后端(CPU/内存不同)
首个可用 first 连接池场景(先打满一台再分下一台)
动态权重 roundrobin + dynamic 运行时通过 socket 调整

动态后端管理

HAProxy 的运行时 socket 是其核心优势之一——无需 reload 即可增删后端服务器:

# 连接 admin socket
socat /var/lib/haproxy/stats -

# 查看所有后端状态
show servers state

# 优雅下线一台服务器(标记为 DRAIN,等待连接排空)
set server web_servers/web1 state drain

# 彻底下线
set server web_servers/web1 state maint

# 上线恢复
set server web_servers/web1 state ready

# 运行时调整权重
set server web_servers/web1 weight 5

# 查看某后端服务器详情
show servers state web_servers

# 统计信息
show info
show stat

生产建议:封装上述命令为脚本,集成到 CI/CD 发布流程中——发布时先 drain → 等待连接排空 → 部署新版本 → ready → 逐台滚动。

健康检查策略

七层检查

# 基础 HTTP 检查
option httpchk GET /healthz
http-check expect status 200

# 高级:检查响应体
option httpchk GET /api/health
http-check expect string "ok"

# 检查响应头
http-check expect rheader "X-Service-Status: healthy"

# 连续 N 次成功才标记 UP,M 次失败才标记 DOWN
server web1 192.168.1.21:80 check inter 2000 rise 2 fall 3
#           inter=检查间隔  rise=连续成功次数  fall=连续失败次数

四层检查

# TCP 连接检查
option tcp-check
tcp-check connect

# Redis 协议检查
tcp-check send PING\r\n
tcp-check expect string +PONG

# MySQL 协议检查(需要使用 mysql-check)
option mysql-check
mysql-check user haproxy_check
# 需在 MySQL 创建只读检查用户:CREATE USER 'haproxy_check'@'%' IDENTIFIED BY '';

被动健康检查

# 基于响应状态的被动检查
observe layer7
# 当后端返回 503/502 时自动降权
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http

# 连续错误计数
default-server inter 2s rise 2 fall 3 observe layer7 on-error mark-down

双主模式(Active-Active)

标准主备模式浪费一半算力。利用 VRRP 双实例可实现双主——两台 HAProxy 同时承载流量,各持有一个 VIP:

# 主节点 keepalived.conf — 同时是 VI_1 的 Master 和 VI_2 的 Backup
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    virtual_ipaddress { 192.168.1.100/24 dev eth0 }
}

vrrp_instance VI_2 {
    state BACKUP
    interface eth0
    virtual_router_id 52
    priority 90
    virtual_ipaddress { 192.168.1.101/24 dev eth0 }
}
# 备节点 keepalived.conf — 同时是 VI_1 的 Backup 和 VI_2 的 Master
vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 90
    virtual_ipaddress { 192.168.1.100/24 dev eth0 }
}

vrrp_instance VI_2 {
    state MASTER
    interface eth0
    virtual_router_id 52
    priority 100
    virtual_ipaddress { 192.168.1.101/24 dev eth0 }
}

DNS 层面用轮询将 www.example.com 同时解析到 .100.101,实现两台同时对外服务。任一宕机,另一个 VIP 自动漂移到存活节点。

监控与告警

Prometheus 指标采集

# HAProxy 2.x 原生导出 Prometheus 指标
# haproxy.cfg 中添加
listen prometheus
    bind *:8405
    mode http
    http-request use-service prometheus-exporter if { path /metrics }
# prometheus.yml
scrape_configs:
  - job_name: 'haproxy'
    static_configs:
      - targets: ['192.168.1.11:8405', '192.168.1.12:8405']

关键告警规则

# 告警规则示例
groups:
  - name: haproxy
    rules:
      - alert: HaproxyBackendDown
        expr: haproxy_backend_status != 1
        for: 1m
        labels: { severity: critical }

      - alert: HaproxyBackendHighErrorRate
        expr: rate(haproxy_backend_http_responses_total{code=~"5.."}[5m]) / rate(haproxy_backend_http_responses_total[5m]) > 0.05
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "后端 5xx 错误率超过 5%"

      - alert: HaproxySessionQueueHigh
        expr: haproxy_backend_current_queue > 100
        for: 2m
        labels: { severity: warning }

      - alert: KeepalivedVRRPChanged
        expr: changes(keepalived_vrrp_state[1m]) > 0
        labels: { severity: critical }
        annotations:
          summary: "VRRP 状态发生切换"

故障切换验证

# 1. 正常状态:VIP 在 Master 上
# Master 节点
ip addr show eth0 | grep "192.168.1.100"
# → inet 192.168.1.100/24 scope secondary eth0

# 2. 模拟 HAProxy 故障
systemctl stop haproxy
# → check_haproxy.sh 尝试拉起失败 → weight -30 → 优先级降到 70 < Backup 90
# → Backup 抢占 VIP

# 3. 验证 VIP 漂移
# Backup 节点
ip addr show eth0 | grep "192.168.1.100"
# → inet 192.168.1.100/24 scope secondary eth0

# 4. 恢复 Master 的 HAProxy
systemctl start haproxy
# → check_haproxy.sh 成功 → 优先级恢复到 100 > Backup 90
# → preempt_delay 5 秒后 VIP 漂回 Master

# 5. 模拟网络分区(拔网线)
# Master: VRRP 广播超时 → Backup 在 3*advert_int 秒后超时接管
# 双方都认为自己是 Master → split-brain 风险!
# 解决方案见下方避坑清单

避坑清单

# 症状 解决
1 VRRP 脑裂 两个节点同时持有 VIP 配置 vrrp_strict + 交叉监控脚本 + 多播/单播确认网络连通
2 HAProxy reload 丢连接 reload 时活跃连接中断 使用 haproxy -c 验证配置 + systemctl reload 平滑重载(fork 新进程接管 socket)
3 健康检查端口不通 后端全部标红,502 风暴 确认 check 端口与后端监听端口一致;后端防火墙放行 HAProxy IP
4 Cookie 会话保持不生效 请求轮询到不同后端 确认 cookie SERVERID insert + 后端 cookie sN 标签配置正确
5 SSL 证书更新不生效 证书过期后仍用旧证书 HAProxy 不会自动重载证书,需 systemctl reload haproxy 触发重新读取
6 四层模式获取不到客户端 IP 后端日志全是 HAProxy IP 四层用 source + Proxy Protocol v2;七层用 option forwardfor
7 Keepalived 优先级计算错误 Master 恢复后不抢回 VIP weight -30 后优先级 = priority + weight = 70 < Backup 90 → Backup 继续持有,等 Master 脚本恢复后 100 > 90 才抢回
8 slowstart 导致新后端过载 新加入的服务器瞬间接收大量流量 slowstart 30s 让权重从 1 线性增长到设定值
9 maxconn 设置不当 HAProxy 内存溢出 OOM maxconn 全局值 ≤ ulimit -n;后端 maxconn ≤ 服务实际承载能力
10 VRRP 广播被防火墙拦截 Backup 一直无法感知 Master 宕机 放行 VRRP 协议:iptables -A INPUT -p 112 -j ACCEPT

总结

HAProxy + Keepalived 是 Linux 下负载均衡与高可用的黄金组合。核心要点:

  1. 四层用 HAProxy mode tcp,七层用 mode http,按场景选择
  2. Keepalived 的 track_script 是灵魂——HAProxy 挂了 VIP 必须漂移,不能只靠进程守护
  3. 动态后端管理(socket)是滚发布的利器——drain → 部署 → ready,零停机
  4. 双主模式最大化资源利用率,两台同时服务,DNS 轮询分流
  5. 脑裂是最大风险——务必配置交叉监控 + vrrp_strict + 心跳网络冗余

生产环境进阶:如果 HAProxy 单机性能不够,可在 HAProxy 前面再加一层 LVS(DR 模式),LVS 负责四层分发到多台 HAProxy,HAProxy 负责七层路由——这就是经典的 LVS + HAProxy + Nginx 三层架构。

分享:

相关文章

评论区