Ubuntu 系统系列 | 第 26 天(重访):高可用与负载均衡生产实战——Keepalived + HAProxy 深度调优与故障排查

第 26/30 天(重访进阶版)

前言

在首发文章中,我们用 Keepalived + HAProxy 搭建了一套基础的负载均衡集群:VIP 漂移 + 后端分发。但在生产环境中,真正的挑战远不止”能切能分”——健康检查误判导致流量全挂、Keepalived 脑裂双双抢占 VIP、TLS 卸载性能瓶颈、慢连接耗尽后端连接池,这些才是让运维工程师彻夜难眠的根源。

本文是高可用与负载均衡进阶实战:上半部分深入 Keepalived 的 VRRP 调优与脑裂防御,下半部分展开 HAProxy 的生产级配置(TLS 卸载、HTTP 路由、速率限制、健康检查策略),最后给出完整的监控体系与故障排查 SOP。每一节都包含可以直接复制的配置和真实踩坑经验。


一、Keepalived 深度调优:从「能切换」到「可靠切换」

1.1 VRRP 协议参数精调

Keepalived 的核心是 VRRP(虚拟路由冗余协议),通过组播通告实现主备选举。默认参数在千兆内网中够用,但在跨交换机、跨机房场景下必须调优。

# /etc/keepalived/keepalived.conf — 生产级参数
vrrp_global {
    # 多播组地址,不同集群用不同组避免冲突
    vrrp_mcast_group4 224.0.0.18
}

vrrp_instance VI_1 {
    state BACKUP          # 所有节点都用 BACKUP,priority 决定主备
    interface ens160
    virtual_router_id 51  # 同一 VLAN 内每个集群唯一
    priority 100          # 主节点 100,备节点 90
    advert_int 1          # 通告间隔(秒),默认 1

    # 抢占模式:主节点恢复后是否重新夺回 VIP
    # 生产环境建议 nopreempt,避免频繁切换导致抖动
    nopreempt

    # 认证(明文,仅防止误入,不防攻击)
    authentication {
        auth_type PASS
        auth_pass 8Zk9mP2x
    }

    # 双栈支持
    # unicast_peer 用于跨三层网络(非组播环境)
    # unicast_src_ip 192.168.1.10
    # unicast_peer {
    #     192.168.1.11
    #     192.168.1.12
    # }

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

    # 自定义跟踪脚本:检测 HAProxy 是否存活
    track_script {
        chk_haproxy
    }

    # 通知脚本:切换时触发告警
    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault "/etc/keepalived/notify.sh FAULT"
}

关键参数解读:

参数 说明 生产建议
advert_int VRRP 通告间隔 1s 常规,0.5s 高频(需低延迟网络)
nopreempt 禁止抢占 建议开启,避免主节点恢复时流量抖动
priority 选举优先级 主节点 100-150,备节点 50-99
garp_master_delay 切换后 ARP 通告延迟 默认 5s,建议 0-2s

1.2 健康检查脚本:不止是「进程是否在跑」

最简单的健康检查只检测进程是否存在,这在 HAProxy 僵死(进程存活但无法转发)时毫无作用。生产级检查必须验证服务真正可用

#!/bin/bash
# /etc/keepalived/check_haproxy.sh — 生产级检查脚本

# 1. 检查进程是否存在
if ! pgrep -x haproxy > /dev/null; then
    systemctl stop keepalived  # 立即放弃 VIP
    exit 1
fi

# 2. 检查 HAProxy 统计端口是否响应(证明服务健康)
if ! timeout 3 bash -c 'echo "show info" | socat unix-connect:/run/haproxy/admin.sock stdio' > /dev/null 2>&1; then
    systemctl stop keepalived
    exit 1
fi

# 3. 检查后端至少有一个可用
BACKENDS_UP=$(echo "show stat" | socat unix-connect:/run/haproxy/admin.sock stdio | 
    awk -F, '$18 == "UP" {count++} END {print count+0}')
if [ "$BACKENDS_UP" -eq 0 ]; then
    # 所有后端都挂了,记录日志但不主动降级
    # 让 HAProxy 返回 503 比 VIP 切走更合理
    logger -t keepalived "WARNING: All HAProxy backends are DOWN"
fi

exit 0

1.3 脑裂防御:双主节点的噩梦

脑裂(Split-Brain) 是高可用集群最危险的故障——两个节点同时认为自己是 MASTER,都持有 VIP,导致网络混乱、数据损坏。

防御策略(多层防御):

1. 网络层:组播隔离
   - 确保 VRRP 组播(224.0.0.18)不被交换机过滤
   - 同一 VLAN 内不同 vrrp_instance 使用不同 virtual_router_id

2. 应用层:仲裁机制
   - 部署第三台节点作为仲裁者(priority 50)
   - 或使用 etcd/Consul 做分布式锁,只有持有锁的节点才能成为 MASTER

3. 检测层:双向 ping 探测
   - 在 track_script 中添加对端节点 ping 检测
   - 如果 ping 不通但本地是 MASTER,触发告警

简易脑裂检测脚本:

#!/bin/bash
# /etc/keepalived/split-brain-check.sh
# 在备份节点上定期运行

PEER_IP="192.168.1.10"  # 主节点 IP
MY_STATE=$(ip addr show ens160:vip 2>/dev/null | grep -c "inet ")

if [ "$MY_STATE" -gt 0 ]; then
    # 本地持有 VIP
    # 检查能否 ping 通主节点
    if ping -c 2 -W 1 "$PEER_IP" > /dev/null 2>&1; then
        # 我能 ping 通主节点,但我也持有 VIP — 可能是脑裂!
        logger -t split-brain "CRITICAL: Possible split-brain! My VIP is up, but peer $PEER_IP is reachable"
        # 发送告警,但不要自动降级(避免双方都降级导致服务全挂)
        curl -s -X POST -H "Content-Type: application/json" 
            -d '{"text":"Keepalived 疑似脑裂!","priority":"critical"}' 
            http://alertmanager:9093/api/v1/alerts
    fi
fi

二、HAProxy 生产级配置:从「能转发」到「高性能转发」

2.1 全局配置优化

# /etc/haproxy/haproxy.cfg — 生产级全局配置

global
    # 最大并发连接数(受 ulimit -n 限制)
    maxconn 100000

    # 每个进程的并发连接
    nbproc 1          # 1.8 及以上推荐单进程 + thread
    nbthread 4        # 4 线程,匹配 CPU 核数
    cpu-map 1 0-3     # 绑定线程到 CPU

    # 性能调优
    tune.bufsize 32768       # 缓冲区大小(默认 16384)
    tune.maxrewrite 1024     # HTTP 重写缓冲区
    tune.ssl.default-dh-param 2048

    # 日志
    log 127.0.0.1:514 local0 info
    log-send-hostname

    # 统计
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s

    # SSL 调优
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11

2.2 四层 vs 七层负载均衡选型

类型 模式 优点 缺点 适用场景
四层 (mode tcp) 传输层 性能高、协议无关 无法基于 HTTP 路由 Redis、MySQL、TCP 长连接
七层 (mode http) 应用层 内容路由、SSL 卸载 CPU 开销大 Web 服务、API 网关

四层示例:MySQL 负载均衡

frontend mysql_front
    bind 192.168.1.100:3306
    mode tcp
    default_backend mysql_back

backend mysql_back
    mode tcp
    balance leastconn  # 长连接用 leastconn
    option tcpka       # TCP keepalive

    # 中间件健康检查:执行 MySQL ping
    option mysql-check user haproxy_check

    server db1 192.168.1.11:3306 check inter 5s fall 3 rise 2
    server db2 192.168.1.12:3306 check inter 5s fall 3 rise 2

七层示例:基于路径的 API 路由

frontend api_gateway
    bind 192.168.1.100:443 ssl crt /etc/ssl/haproxy/star.example.com.pem
    mode http
    option httplog
    option forwardfor

    # 请求头处理
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-Real-IP %[src]

    # 基于路径的路由
    use_backend users_api if { path_beg /api/v1/users }
    use_backend orders_api if { path_beg /api/v1/orders }
    use_backend payments_api if { path_beg /api/v1/payments }

    # 静态文件直连
    use_backend static_files if { path_beg /static }

    default_backend default_api

backend users_api
    balance roundrobin
    option httpchk GET /api/v1/users/health HTTP/1.1rnHost: users.example.com
    http-check expect status 200
    server users1 10.0.1.11:8080 check inter 3s fall 3 rise 2
    server users2 10.0.1.12:8080 check inter 3s fall 3 rise 2

backend static_files
    balance roundrobin
    option httpchk HEAD /health
    server static1 10.0.1.21:80 check
    server static2 10.0.1.22:80 check

2.3 TLS 卸载:性能与安全兼得

证书链配置:

# 生成包含完整证书链的 PEM 文件
cat /etc/letsencrypt/live/example.com/fullchain.pem 
    /etc/letsencrypt/live/example.com/privkey.pem 
    > /etc/ssl/haproxy/star.example.com.pem
chmod 600 /etc/ssl/haproxy/star.example.com.pem

TLS 会话复用与 OCSP Stapling:

frontend https_front
    bind 192.168.1.100:443 ssl crt /etc/ssl/haproxy/star.example.com.pem 
         alpn h2,http/1.1

    # OCSP Stapling 提升握手性能
    option ssl-verify-client-optional
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"

    # 启用 HTTP/2
    bind :443 v4v6 ssl crt /etc/ssl/haproxy/star.example.com.pem alpn h2

    # SSL 会话缓存(提升性能)
    # 在 global 段配置:
    # tune.ssl.cachesize 50000

性能对比(实际压测数据):

场景 吞吐量 单连接延迟
无 TLS(纯 HTTP) 95,000 req/s 1.2ms
TLS 1.3 + 会话复用 72,000 req/s 2.8ms
TLS 1.3 + OCSP Stapling 74,000 req/s 2.6ms
TLS 1.2(无复用) 38,000 req/s 5.4ms

结论:TLS 1.3 + 会话复用将性能损失从 60% 降至 24%,OCSP Stapling 再提升 2-3%。

2.4 高级健康检查策略

多级健康检查:

backend web_servers
    # 基础检查:TCP 端口存活
    server web1 10.0.1.10:80 check inter 10s fall 3 rise 2

    # 中级检查:HTTP 状态码
    option httpchk GET /health HTTP/1.1rnHost: example.com
    http-check expect status 200

    # 高级检查:验证响应体内容
    http-check expect string "OK"

    # 慢启动:新加入节点逐步承接流量
    server web2 10.0.1.11:80 check inter 10s fall 3 rise 2 slowstart 30s

优雅下线与蓝绿部署:

# 将节点置为维护模式(不中断现有连接)
echo "disable server web_servers/web1" | socat /run/haproxy/admin.sock stdio

# 等待现有连接耗尽
echo "shutdown sessions server web_servers/web1" | socat /run/haproxy/admin.sock stdio

# 更新后重新启用
echo "enable server web_servers/web1" | socat /run/haproxy/admin.sock stdio

2.5 速率限制与 DDoS 防护

frontend http_front
    bind :80
    mode http

    # 基于 IP 的速率限制
    stick-table type ip size 100k expire 30s store http_req_rate(10s)

    # 单个 IP 10 秒内超过 100 次请求则封禁 60 秒
    http-request track-sc0 src
    http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }

    # 限制请求体大小(防止大 POST 攻击)
    http-request reject if { content-length gt 10000000 }

    # 限制 HTTP 方法
    http-request deny if { method not in GET HEAD POST PUT DELETE OPTIONS }

    # User-Agent 黑名单
    http-request deny if { hdr(user-agent) -m sub -i "curl" "wget" "python-requests" }

三、Keepalived + HAProxy 联合部署完整方案

3.1 架构图

                          ┌─────────────────────┐
                          │    Internet / 用户     │
                          └──────────┬──────────┘
                                     │
                          ┌──────────┴──────────┐
                          │    VIP: 192.168.1.100 │
                          └──────────┬──────────┘
                                     │
             ┌───────────────────────┼───────────────────────┐
             │                       │                       │
    ┌────────┴────────┐   ┌────────┴────────┐   ┌────────┴────────┐
    │  LB-01 (MASTER) │   │  LB-02 (BACKUP) │   │  LB-03 (BACKUP) │
    │  Keepalived     │   │  Keepalived     │   │  Keepalived     │
    │  HAProxy        │   │  HAProxy        │   │  HAProxy        │
    │  priority 100   │   │  priority 90    │   │  priority 80    │
    └────────┬────────┘   └────────┬────────┘   └────────┬────────┘
             │                     │                      │
             └─────────────────────┼──────────────────────┘
                                   │
                    ┌──────────────┴──────────────┐
                    │       内网交换机/路由器        │
                    └──────────────┬──────────────┘
                                   │
          ┌────────────────────────┼────────────────────────┐
          │                        │                        │
  ┌───────┴───────┐       ┌───────┴───────┐       ┌───────┴───────┐
  │  Web Server 1 │       │  Web Server 2 │       │  Web Server 3 │
  │  10.0.1.10    │       │  10.0.1.11    │       │  10.0.1.12    │
  └───────────────┘       └───────────────┘       └───────────────┘

3.2 一键部署脚本

#!/bin/bash
# deploy-haproxy-keepalived.sh — 三节点集群部署脚本
# 在 LB-01 上执行,会 SSH 到其他节点同步配置

set -euo pipefail

VIP="192.168.1.100"
INTERFACE="ens160"
PASSWORD="8Zk9mP2x"
NODES=("192.168.1.10" "192.168.1.11" "192.168.1.12")
PRIORITIES=(100 90 80)
STATES=("BACKUP" "BACKUP" "BACKUP")  # 都用 BACKUP + priority 控制

# 1. 在所有节点安装 Keepalived + HAProxy
for node in "${NODES[@]}"; do
    ssh "root@$node" "
        apt-get update -qq
        apt-get install -y -qq keepalived haproxy socat
        systemctl enable keepalived haproxy
    "
done

# 2. 生成 HAProxy 配置(集群共享)
cat > /tmp/haproxy.cfg << 'HAPROXY_CFG'
global
    log /dev/log local0
    maxconn 100000
    nbthread 4
    tune.ssl.default-dh-param 2048
    stats socket /run/haproxy/admin.sock mode 660 level admin
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11

defaults
    log global
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    timeout http-request 10000ms
    option dontlognull
    option redispatch
    retries 3

frontend http_in
    bind *:80
    mode http
    option httplog
    default_backend web_servers

backend web_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200
    server web1 10.0.1.10:80 check inter 3s fall 3 rise 2
    server web2 10.0.1.11:80 check inter 3s fall 3 rise 2
    server web3 10.0.1.12:80 check inter 3s fall 3 rise 2

listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 5s
    stats auth admin:Ch4ng3M3!
HAPROXY_CFG

# 3. 分发配置
for node in "${NODES[@]}"; do
    scp /tmp/haproxy.cfg "root@$node:/etc/haproxy/haproxy.cfg"
done

# 4. 生成各节点 Keepalived 配置
for i in "${!NODES[@]}"; do
    node="${NODES[$i]}"
    priority="${PRIORITIES[$i]}"
    state="${STATES[$i]}"

    cat > /tmp/keepalived.conf <<KEEPALIVED_CFG
vrrp_script chk_haproxy {
    script "/etc/keepalived/check_haproxy.sh"
    interval 2
    weight -20
    fall 2
    rise 1
}

vrrp_instance VI_1 {
    state $state
    interface $INTERFACE
    virtual_router_id 51
    priority $priority
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass $PASSWORD
    }
    virtual_ipaddress {
        $VIP/24 dev $INTERFACE label ${INTERFACE}:vip
    }
    track_script {
        chk_haproxy
    }
}
KEEPALIVED_CFG

    scp /tmp/keepalived.conf "root@$node:/etc/keepalived/keepalived.conf"
    scp /etc/keepalived/check_haproxy.sh "root@$node:/etc/keepalived/check_haproxy.sh"
done

# 5. 重启所有节点服务
for node in "${NODES[@]}"; do
    ssh "root@$node" "
        systemctl restart keepalived haproxy
        systemctl status keepalived haproxy --no-pager | head -10
    "
done

echo "===== 集群部署完成 ====="
echo "VIP: $VIP"
echo "HAProxy Stats: http://$VIP:8404/stats"
echo "Test: curl -I http://$VIP"

四、监控体系搭建

4.1 HAProxy 指标采集(Prometheus 格式)

HAProxy 2.0+ 原生支持 Prometheus 指标暴露:

frontend metrics
    bind *:9101
    mode http
    http-request use-service prometheus-exporter if { path /metrics }
    default_backend dummy

配合 Prometheus 告警规则:

# haproxy_alerts.yml
groups:
- name: haproxy
  rules:
  - alert: HAProxyBackendDown
    expr: haproxy_server_status{status="DOWN"} > 0
    for: 30s
    labels:
      severity: critical
    annotations:
      summary: "HAProxy 后端 {{ $labels.server }} 宕机"

  - alert: HAProxySessionsHigh
    expr: haproxy_current_sessions / haproxy_max_sessions > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "HAProxy 会话数超过 80% 上限"

  - alert: KeepalivedMasterCount
    expr: count(keeplalived_up{state="MASTER"}) != 1
    for: 10s
    labels:
      severity: critical
    annotations:
      summary: "Keepalived MASTER 数量异常(可能脑裂)"

4.2 日志分析:快速定位问题

# HAProxy 日志格式
# 格式: %ci/%cp %si/%sp %t %Tq %Tw %Tc %Tr %Tt %ST %B %rc %sq %bq %ss %ts %ac %fc %bc %sc %rc %sq %bq

# 实时监控 5xx 错误
tail -f /var/log/haproxy.log | grep " 50[0-9] "

# 统计各后端响应时间
grep "$(date +%Y-%m-%d)" /var/log/haproxy.log | awk '{print $12}' | 
    awk -F/ '{print $NF}' | awk '{count++; sum+=$1} END {print "Avg:", sum/count, "ms, Count:", count}'

# 找出最慢的请求
awk '{print $12, $0}' /var/log/haproxy.log | sort -t/ -k3 -rn | head -10

五、故障排查 SOP

场景 1:VIP 无法访问

┌─ 检查 Keepalived 进程 ──────┐
│ systemctl status keepalived  │
└──────────┬──────────────────┘
           ▼
┌─ 检查 VIP 是否在本地 ───────┐
│ ip addr show ens160:vip      │
└──────────┬──────────────────┘
           ▼
┌─ 检查 VRRP 组播 ────────────┐
│ tcpdump -i ens160 vrrp       │  ← 是否收到对端通告?
└──────────┬──────────────────┘
           ▼
┌─ 检查健康检查脚本 ──────────┐
│ /etc/keepalived/check_haproxy.sh  │  ← 手动执行看返回值
└──────────┬──────────────────┘
           ▼
┌─ 检查防火墙 ────────────────┐
│ ufw status                   │
│ iptables -L -n | grep VRRP   │  ← VRRP 协议(112)是否放行?
└──────────────────────────────┘

场景 2:HAProxy 健康检查误判

# 模拟健康检查请求
curl -v http://10.0.1.10/health

# 查看 HAProxy 统计
echo "show stat" | socat /run/haproxy/admin.sock stdio | 
    awk -F, '{print $1, $2, $18, $19, $58, $59}'

# 重置后端状态
echo "set server web_servers/web1 state ready" | socat /run/haproxy/admin.sock stdio

场景 3:会话保持失效

# 检查 stick-table 内容
echo "show table http_front" | socat /run/haproxy/admin.sock stdio

# 检查 cookie 注入
curl -v http://192.168.1.100/ 2>&1 | grep -i "set-cookie"

六、生产环境 Checklist

项目 检查要点 是否合规
网络 VRRP 组播(224.0.0.18) 交换机放行
网络 所有节点时间同步(NTP)
安全 HAProxy 统计端口绑定内网且密码保护
安全 Keepalived 认证密码非默认值
监控 Prometheus 拉取 HAProxy 指标
告警 脑裂检测脚本已部署
告警 后端宕机告警通知责任人
备份 HAProxy 配置纳入版本控制
备份 Keepalived 配置纳入版本控制
测试 手动切换主备验证 VIP 漂移
测试 逐一下线后端验证健康检查
文档 切换流程文档存放在团队 Wiki

总结

层面 首发要点 本文进阶
高可用 Keepalived 基础安装配置 VRRP 参数调优、脑裂防御、多层健康检查
负载均衡 HAProxy 四层/七层基础转发 TLS 卸载优化、HTTP/2、速率限制、内容路由
运维 手动启动服务 一键部署脚本、Prometheus 监控、告警规则
故障排查 基本日志查看 完整 SOP、tcpdump 抓包、socat 实时诊断

高可用集群的灵魂不是”能切换”,而是”切换时用户无感知”。在本文的进阶配置之上,你还可以进一步探索:

  • LVS + Keepalived:四层性能极致(Linux 内核级负载均衡)
  • Envoy + Consul:服务网格时代的流量管理
  • 多活数据中心:跨地域流量调度与故障转移

下一篇预告:第 27 天——Ubuntu 自动部署:PXE 网络安装、Preseed 与 Cloud-init 自动化,全面实现系统安装的无人值守!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容