
HAProxy ACL规则实战:动态路由与流量分发
一、概述
1.1 背景介绍
ACL(Access Control List)是HAProxy最强大的功能之一,不仅仅是访问控制,更是实现复杂流量调度的核心武器。在微服务架构和多租户场景下,我们经常需要根据请求的各种特征(域名、路径、Header、Cookie、IP等)把流量路由到不同的后端。
我接触HAProxy ACL是在2018年,当时需要做一个API网关,要实现:不同域名走不同后端、同一域名不同路径走不同服务、还要支持灰度发布。当时觉得用Nginx的location和if语句太费劲,朋友推荐了HAProxy的ACL,用了之后就再也离不开了。
HAProxy ACL的表达能力非常强,正则匹配、IP段匹配、请求头匹配、Cookie匹配都支持,而且可以组合使用。配合use_backend指令,可以实现非常灵活的路由策略。
1.2 技术特点
HAProxy ACL的几个核心特点:
声明式规则:ACL是声明式的,先定义条件,再使用条件。代码可读性比if-else好得多。
丰富的匹配方式:支持字符串精确匹配、前缀匹配、后缀匹配、正则匹配、IP段匹配等。
灵活的取值方法:可以从请求的任何部分取值——URL、Header、Cookie、Query参数、POST数据等。
条件组合:支持AND、OR、NOT组合多个ACL条件。
运行时更新:通过Runtime API可以动态更新ACL规则,不需要reload配置。
1.3 适用场景
hdr(host) -i api.company.com | ||
path_beg /api/v1/users | ||
hdr(X-Gray) -i true | ||
cook(user_group) -i group_a | ||
hdr(X-Version) -i v2 | ||
src 192.168.0.0/16 | ||
src_http_req_rate() gt 100 | ||
nbsrv(backend) lt 1 |
1.4 环境要求
二、详细步骤
2.1 准备工作
2.1.1 ACL基础语法
HAProxy ACL的基本语法:
acl <名称> <条件> [标志] [匹配值]
条件(Fetch):从请求中提取数据标志(Flag):修改匹配行为匹配值(Pattern):要匹配的值
常用条件(Fetch):
# 请求相关
path # URL路径(不含查询参数)
path_beg # 路径前缀
path_end # 路径后缀
path_sub # 路径包含
path_reg # 路径正则
url # 完整URL
url_param # URL参数
# Header相关
hdr(name) # 指定请求头
hdr_beg(name) # 请求头前缀
hdr_end(name) # 请求头后缀
hdr_sub(name) # 请求头包含
hdr_reg(name) # 请求头正则
# Cookie相关
cook(name) # 指定Cookie值
cook_sub(name) # Cookie包含
# 来源相关
src # 客户端IP
src_port # 客户端端口
# 后端相关
nbsrv(backend) # 后端可用服务器数量
# SSL相关
ssl_fc # 是否SSL连接
ssl_fc_sni # SNI主机名
# 方法相关
method # HTTP方法(GET/POST等)
# 其他
always_true # 永真
always_false # 永假
常用标志(Flag):
-i # 忽略大小写
-f # 从文件读取匹配值
-m # 指定匹配方法
# -m str 字符串匹配
# -m beg 前缀匹配
# -m end 后缀匹配
# -m sub 包含匹配
# -m reg 正则匹配
# -m found 存在即匹配
# -m len 长度匹配
2.1.2 ACL使用方式
定义好ACL后,可以在以下指令中使用:
# 路由选择
use_backend <backend> if <acl>
use_backend <backend> unless <acl>
default_backend <backend>
# 请求处理
http-request deny if <acl>
http-request allow if <acl>
http-request redirect location <url> if <acl>
http-request set-header <name> <value> if <acl>
http-request add-header <name> <value> if <acl>
http-request del-header <name> if <acl>
http-request replace-path <regex> <replacement> if <acl>
# 响应处理
http-response set-header <name> <value> if <acl>
# 连接处理
tcp-request connection reject if <acl>
tcp-request content reject if <acl>
2.2 核心配置
2.2.1 多域名路由
这是最常见的场景——不同域名路由到不同后端:
# /etc/haproxy/haproxy.cfg
# 多域名路由配置
global
log /dev/log local0
user haproxy
group haproxy
daemon
maxconn 50000
stats socket /run/haproxy/admin.sock mode 660 level admin
defaults
mode http
log global
timeout connect 5s
timeout client 30s
timeout server 30s
option httplog
option forwardfor
# 前端:统一接收所有请求
frontend http_front
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
# 定义域名ACL
acl host_api hdr(host) -i api.company.com
acl host_web hdr(host) -i www.company.com
acl host_admin hdr(host) -i admin.company.com
acl host_static hdr(host) -i static.company.com cdn.company.com
# 支持带端口的Host头
acl host_api_port hdr_beg(host) -i api.company.com:
acl host_web_port hdr_beg(host) -i www.company.com:
# 路由规则
use_backend api_servers if host_api or host_api_port
use_backend web_servers if host_web or host_web_port
use_backend admin_servers if host_admin
use_backend static_servers if host_static
# 默认后端
default_backend web_servers
# API后端
backend api_servers
balance roundrobin
option httpchk GET /health
http-check expect status 200
server api1 192.168.1.11:8080 check
server api2 192.168.1.12:8080 check
# Web后端
backend web_servers
balance roundrobin
option httpchk GET /health
server web1 192.168.1.21:8080 check
server web2 192.168.1.22:8080 check
# Admin后端
backend admin_servers
balance roundrobin
# Admin只允许内网访问
acl internal_network src 192.168.0.0/16 10.0.0.0/8
http-request deny unless internal_network
server admin1 192.168.1.31:8080 check
# 静态资源后端
backend static_servers
balance roundrobin
# 静态资源启用缓存
http-response set-header Cache-Control "public, max-age=86400"
server static1 192.168.1.41:80 check
server static2 192.168.1.42:80 check
2.2.2 路径路由
根据URL路径路由到不同后端,微服务网关必备:
# 路径路由配置
frontend http_front
bind *:80
# 路径ACL - 注意优先级:更具体的规则放前面
acl path_api_users path_beg /api/v1/users
acl path_api_orders path_beg /api/v1/orders
acl path_api_products path_beg /api/v1/products
acl path_api path_beg /api/
acl path_static path_beg /static/ /assets/ /images/
acl path_ws path_beg /ws/ /websocket/
acl path_health path -i /health /healthz /ready
# 静态文件扩展名
acl is_static_file path_end .js .css .png .jpg .gif .ico .woff .woff2
# WebSocket升级
acl is_websocket hdr(Upgrade) -i websocket
# 路由规则
use_backend ws_servers if path_ws or is_websocket
use_backend user_service if path_api_users
use_backend order_service if path_api_orders
use_backend product_service if path_api_products
use_backend api_gateway if path_api
use_backend static_servers if path_static or is_static_file
# 健康检查直接返回
http-request return status 200 content-type "text/plain" string "OK"if path_health
default_backend web_servers
# WebSocket后端
backend ws_servers
balance source# 同一客户端保持连接到同一后端
option http-server-close
# WebSocket需要更长的超时
timeout server 1h
timeout tunnel 1h
server ws1 192.168.1.51:8080 check
server ws2 192.168.1.52:8080 check
# 用户服务
backend user_service
balance roundrobin
server user1 192.168.1.61:8080 check
server user2 192.168.1.62:8080 check
# 订单服务
backend order_service
balance roundrobin
server order1 192.168.1.71:8080 check
server order2 192.168.1.72:8080 check
# 商品服务
backend product_service
balance roundrobin
server product1 192.168.1.81:8080 check
server product2 192.168.1.82:8080 check
2.2.3 Header路由
根据请求头路由,常用于灰度发布、版本控制:
# Header路由配置
frontend http_front
bind *:80
# 灰度标记ACL
acl gray_header hdr(X-Gray-Release) -i true
acl gray_cookie cook(gray) -i 1
acl gray_user hdr_sub(X-User-ID) gray_
# 版本控制ACL
acl version_v2 hdr(X-API-Version) -i v2 2.0
acl version_v3 hdr(X-API-Version) -i v3 3.0
# 客户端类型ACL
acl is_mobile hdr_sub(User-Agent) -i mobile android iphone
acl is_bot hdr_sub(User-Agent) -i bot spider crawler
# 内部请求ACL(来自其他服务)
acl is_internal hdr(X-Internal-Request) -i true
# 优先级用户ACL
acl is_vip hdr(X-User-Level) -i vip premium
# 路由规则
# 灰度用户走灰度环境
use_backend gray_servers if gray_header or gray_cookie or gray_user
# 不同API版本走不同后端
use_backend api_v2 if version_v2
use_backend api_v3 if version_v3
# 移动端专属后端(可能有特殊优化)
use_backend mobile_servers if is_mobile
# VIP用户走高优先级后端
use_backend vip_servers if is_vip
# 爬虫限制
http-request deny if is_bot
default_backend api_v1
# 灰度后端
backend gray_servers
balance roundrobin
server gray1 192.168.10.11:8080 check
server gray2 192.168.10.12:8080 check
# API V2后端
backend api_v2
balance roundrobin
server api_v2_1 192.168.11.11:8080 check
server api_v2_2 192.168.11.12:8080 check
# API V3后端
backend api_v3
balance roundrobin
server api_v3_1 192.168.12.11:8080 check
server api_v3_2 192.168.12.12:8080 check
# VIP专属后端
backend vip_servers
balance roundrobin
# 更多资源,更高权重
server vip1 192.168.20.11:8080 check weight 100
server vip2 192.168.20.12:8080 check weight 100
2.2.4 流量分发(灰度/A/B测试)
按比例分发流量,实现灰度发布和A/B测试:
# 流量分发配置
frontend http_front
bind *:80
# 方法1:使用随机数实现按比例分流
# rand(100) 生成0-99的随机数
acl is_canary rand(100) lt 10 # 10%流量
# 方法2:基于请求头的Hash分流
# 使用用户ID的hash值确保同一用户始终走同一版本
acl canary_by_hash req.hdr(X-User-ID),crc32,mod(100) lt 10
# 方法3:基于Cookie分流(手动设置灰度标记)
acl canary_by_cookie cook(canary_group) -i true
# 方法4:基于IP段分流(内部测试)
acl canary_by_ip src 192.168.100.0/24
# 组合条件:任一条件满足即走灰度
use_backend canary_servers if canary_by_cookie
use_backend canary_servers if canary_by_ip
use_backend canary_servers if canary_by_hash
default_backend stable_servers
# 稳定版后端
backend stable_servers
balance roundrobin
option httpchk GET /health
server stable1 192.168.1.11:8080 check
server stable2 192.168.1.12:8080 check
server stable3 192.168.1.13:8080 check
# 灰度版后端
backend canary_servers
balance roundrobin
option httpchk GET /health
# 灰度环境资源较少
server canary1 192.168.2.11:8080 check
server canary2 192.168.2.12:8080 check
动态调整灰度比例(不需要reload):
#!/bin/bash
# adjust_canary_ratio.sh
# 动态调整灰度比例
SOCKET="/run/haproxy/admin.sock"
# 获取当前配置
get_current_ratio() {
echo"show acl #0" | socat stdio $SOCKET
}
# 设置灰度比例(需要使用map方式)
# 这种方式需要在配置中使用map文件
set_canary_ratio() {
local ratio=$1
echo"set map /etc/haproxy/canary_ratio.map default $ratio" | socat stdio $SOCKET
}
# 实际生产中更推荐使用server权重调整
# 通过调整各后端的权重来实现流量比例控制
adjust_by_weight() {
local canary_percent=$1
local stable_percent=$((100 - canary_percent))
echo"设置灰度比例: ${canary_percent}%"
# 调整稳定版后端权重
echo"set server stable_servers/stable1 weight $stable_percent" | socat stdio $SOCKET
echo"set server stable_servers/stable2 weight $stable_percent" | socat stdio $SOCKET
# 调整灰度版后端权重
echo"set server canary_servers/canary1 weight $canary_percent" | socat stdio $SOCKET
echo"set server canary_servers/canary2 weight $canary_percent" | socat stdio $SOCKET
}
# 使用示例
case"$1"in
get)
get_current_ratio
;;
set)
adjust_by_weight $2
;;
*)
echo"用法: $0 {get|set <percent>}"
echo"示例: $0 set 20 # 设置20%灰度流量"
;;
esac
更灵活的基于map的灰度配置:
# /etc/haproxy/haproxy.cfg
# 基于map文件的灰度配置
frontend http_front
bind *:80
# 使用map文件存储灰度配置
# map文件格式:key value
# 可以运行时更新
# 根据用户ID查map决定路由
# 如果在map中找到用户ID,值为canary则走灰度
http-request set-var(req.user_id) hdr(X-User-ID)
http-request set-var(req.canary) var(req.user_id),map(/etc/haproxy/maps/user_canary.map,stable)
acl is_canary var(req.canary) -m str canary
use_backend canary_servers if is_canary
default_backend stable_servers
Map文件:
# /etc/haproxy/maps/user_canary.map
# 格式:用户ID 路由目标
# 可以运行时用 set map 命令更新
user_001 canary
user_002 canary
user_003 canary
# ... 更多灰度用户
运行时更新map:
# 添加灰度用户
echo "add map /etc/haproxy/maps/user_canary.map user_004 canary" | \
socat stdio /run/haproxy/admin.sock
# 移除灰度用户
echo "del map /etc/haproxy/maps/user_canary.map user_001" | \
socat stdio /run/haproxy/admin.sock
# 查看map内容
echo "show map /etc/haproxy/maps/user_canary.map" | \
socat stdio /run/haproxy/admin.sock
2.3 启动和验证
2.3.1 配置验证
#!/bin/bash
# verify_acl_config.sh
# ACL配置验证脚本
CONFIG_FILE="/etc/haproxy/haproxy.cfg"
SOCKET="/run/haproxy/admin.sock"
echo"=== HAProxy ACL配置验证 ==="
# 1. 语法检查
echo"[1] 检查配置语法..."
if haproxy -c -f $CONFIG_FILE; then
echo" 配置语法正确"
else
echo" 配置语法错误!"
exit 1
fi
# 2. 检查ACL定义
echo""
echo"[2] 已定义的ACL:"
grep -E "^\s*acl\s+"$CONFIG_FILE | awk '{print " " $2 " -> " $3}'
# 3. 检查路由规则
echo""
echo"[3] 路由规则:"
grep -E "^\s*(use_backend|default_backend)"$CONFIG_FILE | \
sed 's/^[ \t]*/ /'
# 4. 检查运行状态
echo""
echo"[4] 运行时ACL状态:"
if [ -S "$SOCKET" ]; then
echo"show acl" | socat stdio $SOCKET | head -20
else
echo" HAProxy socket不可用,跳过运行时检查"
fi
# 5. 检查map文件
echo""
echo"[5] Map文件检查:"
for map_file in /etc/haproxy/maps/*.map; do
if [ -f "$map_file" ]; then
entries=$(wc -l < "$map_file")
echo" $map_file: $entries 条记录"
fi
done
2.3.2 路由测试
#!/bin/bash
# test_acl_routing.sh
# ACL路由规则测试脚本
BASE_URL="http://127.0.0.1"
echo"=== ACL路由测试 ==="
# 测试函数
test_route() {
local description=$1
local curl_args=$2
local expected=$3
echo -n "测试: $description ... "
response=$(curl -s -o /dev/null -w "%{http_code}"$curl_args"$BASE_URL/" 2>/dev/null)
if [ "$response" == "$expected" ]; then
echo"PASS (HTTP $response)"
else
echo"FAIL (期望 $expected, 实际 $response)"
fi
}
# 测试域名路由
echo""
echo"--- 域名路由测试 ---"
test_route "api.company.com""-H 'Host: api.company.com'""200"
test_route "www.company.com""-H 'Host: www.company.com'""200"
test_route "unknown.company.com""-H 'Host: unknown.company.com'""200"# 走默认后端
# 测试路径路由
echo""
echo"--- 路径路由测试 ---"
test_route "/api/v1/users""-H 'Host: api.company.com' -X GET""200"
test_route "/api/v1/orders""-H 'Host: api.company.com' -X GET""200"
test_route "/static/logo.png""-H 'Host: www.company.com'""200"
# 测试Header路由
echo""
echo"--- Header路由测试 ---"
test_route "灰度标记""-H 'Host: api.company.com' -H 'X-Gray-Release: true'""200"
test_route "API版本V2""-H 'Host: api.company.com' -H 'X-API-Version: v2'""200"
test_route "VIP用户""-H 'Host: api.company.com' -H 'X-User-Level: vip'""200"
# 测试IP限制
echo""
echo"--- IP限制测试 ---"
# 这个测试需要从不同IP发起,这里只是示例
# test_route "内网访问Admin" "-H 'Host: admin.company.com'" "200"
# 详细测试(查看实际路由到哪个后端)
echo""
echo"--- 详细路由信息 ---"
echo"测试请求详情:"
curl -v -s -o /dev/null \
-H "Host: api.company.com" \
-H "X-Gray-Release: true" \
"$BASE_URL/api/v1/users" 2>&1 | grep -E "< |> " | head -20
三、示例代码和配置
3.1 完整配置示例
3.1.1 企业级API网关配置
这是一个完整的企业级API网关配置,包含了常用的ACL场景:
# /etc/haproxy/haproxy.cfg
# 企业级API网关配置 - HAProxy 3.0
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log /dev/log local0 info
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# 性能优化
maxconn 100000
nbthread 4
# SSL优化
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
tune.ssl.default-dh-param 2048
#---------------------------------------------------------------------
# Default settings
#---------------------------------------------------------------------
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 30s
timeout connect 5s
timeout client 30s
timeout server 30s
timeout http-keep-alive 10s
timeout check 5s
# 错误页面
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
#---------------------------------------------------------------------
# Stats frontend
#---------------------------------------------------------------------
frontend stats
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
stats admin if TRUE
stats show-legends
#---------------------------------------------------------------------
# Main HTTP/HTTPS frontend
#---------------------------------------------------------------------
frontend http_front
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/company.pem alpn h2,http/1.1
# ===== 通用ACL定义 =====
# 请求方法
acl is_get method GET
acl is_post method POST
acl is_options method OPTIONS
# SSL检测
acl is_https ssl_fc
# ===== 安全相关ACL =====
# 内网IP
acl internal_network src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# 黑名单IP(从文件读取)
acl blocked_ips src -f /etc/haproxy/blacklist.ips
# 恶意User-Agent
acl bad_bots hdr_sub(User-Agent) -i -f /etc/haproxy/bad_bots.txt
# SQL注入检测(基础)
acl sql_injection url_sub -i select union insert drop delete update
# XSS检测(基础)
acl xss_attack url_sub -i <script javascript:
# ===== 域名路由ACL =====
acl host_api hdr(host) -i api.company.com
acl host_web hdr(host) -i www.company.com company.com
acl host_admin hdr(host) -i admin.company.com
acl host_ws hdr(host) -i ws.company.com
acl host_static hdr(host) -i static.company.com cdn.company.com
# ===== 路径路由ACL =====
# API版本路由
acl path_api_v1 path_beg /api/v1/
acl path_api_v2 path_beg /api/v2/
acl path_api_v3 path_beg /api/v3/
# 微服务路由
acl path_user_service path_beg /api/v1/users /api/v2/users
acl path_order_service path_beg /api/v1/orders /api/v2/orders
acl path_product_service path_beg /api/v1/products /api/v2/products
acl path_payment_service path_beg /api/v1/payments
acl path_notification_service path_beg /api/v1/notifications
# 特殊路径
acl path_health path -i /health /healthz /ready /live
acl path_metrics path -i /metrics /prometheus
acl path_swagger path_beg /swagger /api-docs
# 静态资源
acl path_static path_beg /static/ /assets/ /public/
acl is_static_file path_end .js .css .png .jpg .jpeg .gif .ico .svg .woff .woff2 .ttf .eot
# WebSocket
acl path_websocket path_beg /ws/ /websocket/ /socket.io/
acl is_websocket hdr(Upgrade) -i websocket
# ===== 灰度发布ACL =====
# Header标记
acl canary_header hdr(X-Canary) -i true
acl canary_header_v2 hdr(X-API-Version) -i canary beta
# Cookie标记
acl canary_cookie cook(canary) -i true 1
# 用户ID Hash(10%灰度)
acl canary_by_hash req.hdr(X-User-ID),crc32,mod(100) lt 10
# 特定用户ID(从map读取)
http-request set-var(req.user_id) hdr(X-User-ID)
acl canary_user var(req.user_id),map(/etc/haproxy/maps/canary_users.map,stable) -m str canary
# ===== 流量控制ACL =====
# 请求频率限制
acl rate_limit_exceeded src_http_req_rate(rate_limit_table) gt 100
# VIP用户(不限速)
acl is_vip hdr(X-User-Level) -i vip premium enterprise
# ===== 安全处理 =====
# 拒绝黑名单IP
http-request deny deny_status 403 if blocked_ips
# 拒绝恶意爬虫
http-request deny deny_status 403 if bad_bots
# 拒绝SQL注入
http-request deny deny_status 400 if sql_injection
# 拒绝XSS攻击
http-request deny deny_status 400 if xss_attack
# HTTP重定向到HTTPS(非内网)
http-request redirect scheme https code 301 unless is_https or internal_network
# ===== 请求头处理 =====
# 添加请求ID
http-request set-header X-Request-ID %[uuid()]
# 添加真实IP
http-request set-header X-Real-IP %[src]
# 删除敏感头
http-request del-header X-Forwarded-For
http-request add-header X-Forwarded-For %[src]
# 添加灰度标记(用于追踪)
http-request set-header X-Canary-Flag trueif canary_header or canary_cookie or canary_user
# ===== CORS处理 =====
# OPTIONS预检请求直接返回
http-request return status 204 \
hdr Access-Control-Allow-Origin "*" \
hdr Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" \
hdr Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-User-ID" \
hdr Access-Control-Max-Age "86400" \
if is_options
# ===== 路由规则(按优先级排列) =====
# 健康检查(直接返回)
http-request return status 200 content-type "application/json" \
string "{\"status\":\"ok\",\"timestamp\":\"%[date]\"}"if path_health
# 指标接口(仅内网)
http-request deny deny_status 403 if path_metrics !internal_network
use_backend metrics_servers if path_metrics
# WebSocket
use_backend ws_servers if is_websocket or path_websocket
# 静态资源
use_backend static_servers if host_static or path_static or is_static_file
# Admin后台(仅内网)
http-request deny deny_status 403 if host_admin !internal_network
use_backend admin_servers if host_admin
# 灰度流量
use_backend canary_user_service if path_user_service canary_header
use_backend canary_user_service if path_user_service canary_cookie
use_backend canary_user_service if path_user_service canary_user
# 微服务路由(正常流量)
use_backend user_service if path_user_service
use_backend order_service if path_order_service
use_backend product_service if path_product_service
use_backend payment_service if path_payment_service
use_backend notification_service if path_notification_service
# API版本路由
use_backend api_v3 if path_api_v3
use_backend api_v2 if path_api_v2
use_backend api_v1 if path_api_v1
# 域名路由
use_backend api_gateway if host_api
use_backend web_servers if host_web
# 默认后端
default_backend web_servers
#---------------------------------------------------------------------
# Backend definitions
#---------------------------------------------------------------------
# 流量限制表
backend rate_limit_table
stick-table type ip size 100k expire 30s store http_req_rate(10s)
# 用户服务
backend user_service
balance roundrobin
option httpchk GET /health HTTP/1.1
http-check send hdr Host localhost
http-check expect status 200
default-server inter 3s fall 3 rise 2
server user1 192.168.10.11:8080 check
server user2 192.168.10.12:8080 check
server user3 192.168.10.13:8080 check
# 用户服务(灰度)
backend canary_user_service
balance roundrobin
option httpchk GET /health HTTP/1.1
http-check send hdr Host localhost
http-check expect status 200
default-server inter 3s fall 3 rise 2
server canary_user1 192.168.20.11:8080 check
server canary_user2 192.168.20.12:8080 check
# 订单服务
backend order_service
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server order1 192.168.10.21:8080 check
server order2 192.168.10.22:8080 check
server order3 192.168.10.23:8080 check
# 商品服务
backend product_service
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server product1 192.168.10.31:8080 check
server product2 192.168.10.32:8080 check
# 支付服务(更严格的健康检查)
backend payment_service
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 2s fall 2 rise 3
server payment1 192.168.10.41:8080 check
server payment2 192.168.10.42:8080 check
# 通知服务
backend notification_service
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server notification1 192.168.10.51:8080 check
server notification2 192.168.10.52:8080 check
# API网关
backend api_gateway
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server gateway1 192.168.10.61:8080 check
server gateway2 192.168.10.62:8080 check
# API V1
backend api_v1
balance roundrobin
server api_v1_1 192.168.11.11:8080 check
server api_v1_2 192.168.11.12:8080 check
# API V2
backend api_v2
balance roundrobin
server api_v2_1 192.168.12.11:8080 check
server api_v2_2 192.168.12.12:8080 check
# API V3
backend api_v3
balance roundrobin
server api_v3_1 192.168.13.11:8080 check
server api_v3_2 192.168.13.12:8080 check
# WebSocket服务
backend ws_servers
balance source
option http-server-close
timeout server 1h
timeout tunnel 1h
server ws1 192.168.10.71:8080 check
server ws2 192.168.10.72:8080 check
# 静态资源服务
backend static_servers
balance roundrobin
http-response set-header Cache-Control "public, max-age=31536000"
server static1 192.168.10.81:80 check
server static2 192.168.10.82:80 check
# Web服务
backend web_servers
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server web1 192.168.10.91:8080 check
server web2 192.168.10.92:8080 check
server web3 192.168.10.93:8080 check
# Admin服务
backend admin_servers
balance roundrobin
option httpchk GET /health HTTP/1.1
server admin1 192.168.10.101:8080 check
server admin2 192.168.10.102:8080 check
# 监控指标服务
backend metrics_servers
balance roundrobin
server metrics1 192.168.10.111:9090 check
3.2 实际应用案例
3.2.1 案例一:多租户SaaS平台路由
某SaaS平台需要支持:
每个租户有独立的子域名 大客户有独立的后端集群 支持租户级别的灰度发布
# /etc/haproxy/haproxy.cfg
# 多租户SaaS平台配置
frontend http_front
bind *:443 ssl crt /etc/haproxy/certs/
# 从子域名提取租户ID
# tenant1.saas.company.com -> tenant1
http-request set-var(req.tenant_id) hdr(host),regsub(\.saas\.company\.com$,,)
# 从map查询租户路由信息
# map文件格式:tenant_id backend_name
http-request set-var(req.tenant_backend) var(req.tenant_id),map(/etc/haproxy/maps/tenant_routing.map,shared_pool)
# 租户级别灰度
http-request set-var(req.tenant_canary) var(req.tenant_id),map(/etc/haproxy/maps/tenant_canary.map,stable)
acl tenant_is_canary var(req.tenant_canary) -m str canary
# 大客户专属后端
acl tenant_enterprise var(req.tenant_backend) -m str enterprise_pool_1 enterprise_pool_2
# 路由规则
use_backend enterprise_pool_1 if { var(req.tenant_backend) -m str enterprise_pool_1 }
use_backend enterprise_pool_2 if { var(req.tenant_backend) -m str enterprise_pool_2 }
use_backend canary_servers if tenant_is_canary
default_backend shared_pool
# 共享资源池
backend shared_pool
balance roundrobin
server shared1 192.168.1.11:8080 check
server shared2 192.168.1.12:8080 check
server shared3 192.168.1.13:8080 check
server shared4 192.168.1.14:8080 check
# 大客户专属池1
backend enterprise_pool_1
balance roundrobin
server ent1_1 192.168.2.11:8080 check
server ent1_2 192.168.2.12:8080 check
# 大客户专属池2
backend enterprise_pool_2
balance roundrobin
server ent2_1 192.168.3.11:8080 check
server ent2_2 192.168.3.12:8080 check
# 灰度测试池
backend canary_servers
balance roundrobin
server canary1 192.168.10.11:8080 check
server canary2 192.168.10.12:8080 check
租户路由map文件:
# /etc/haproxy/maps/tenant_routing.map
# 租户ID -> 后端池
# 大客户使用专属池
bigcorp enterprise_pool_1
megacorp enterprise_pool_1
hugecompany enterprise_pool_2
# 其他租户使用共享池(默认值已在配置中设定)
# tenant1 shared_pool # 可以省略
租户灰度map文件:
# /etc/haproxy/maps/tenant_canary.map
# 租户ID -> 灰度状态
# 参与灰度测试的租户
testcorp canary
betacorp canary
democorp canary
动态管理租户路由:
#!/bin/bash
# manage_tenant_routing.sh
# 租户路由管理脚本
SOCKET="/run/haproxy/admin.sock"
ROUTING_MAP="/etc/haproxy/maps/tenant_routing.map"
CANARY_MAP="/etc/haproxy/maps/tenant_canary.map"
# 添加大客户专属后端
add_enterprise_tenant() {
local tenant_id=$1
local pool=$2
echo"add map $ROUTING_MAP $tenant_id $pool" | socat stdio $SOCKET
echo"已添加租户 $tenant_id 到 $pool"
}
# 设置租户灰度
set_tenant_canary() {
local tenant_id=$1
local status=$2# canary 或 stable
echo"set map $CANARY_MAP $tenant_id $status" | socat stdio $SOCKET
echo"已设置租户 $tenant_id 灰度状态为 $status"
}
# 查看租户路由
show_tenant_routing() {
echo"=== 租户路由配置 ==="
echo"show map $ROUTING_MAP" | socat stdio $SOCKET
echo""
echo"=== 租户灰度配置 ==="
echo"show map $CANARY_MAP" | socat stdio $SOCKET
}
# 使用示例
case"$1"in
add)
add_enterprise_tenant "$2""$3"
;;
canary)
set_tenant_canary "$2""$3"
;;
show)
show_tenant_routing
;;
*)
echo"用法:"
echo" $0 add <tenant_id> <pool_name> # 添加大客户"
echo" $0 canary <tenant_id> canary|stable # 设置灰度"
echo" $0 show # 查看配置"
;;
esac
3.2.2 案例二:蓝绿部署自动切换
实现蓝绿部署的自动化切换:
# /etc/haproxy/haproxy.cfg
# 蓝绿部署配置
frontend http_front
bind *:80
# 从map读取当前活跃环境
http-request set-var(req.active_env) str(default),map(/etc/haproxy/maps/active_env.map,blue)
# 强制指定环境(用于测试)
acl force_blue hdr(X-Env) -i blue
acl force_green hdr(X-Env) -i green
# 路由规则
use_backend blue_servers if force_blue
use_backend green_servers if force_green
use_backend blue_servers if { var(req.active_env) -m str blue }
use_backend green_servers if { var(req.active_env) -m str green }
default_backend blue_servers
# 蓝环境
backend blue_servers
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server blue1 192.168.1.11:8080 check
server blue2 192.168.1.12:8080 check
server blue3 192.168.1.13:8080 check
# 绿环境
backend green_servers
balance roundrobin
option httpchk GET /health HTTP/1.1
default-server inter 3s fall 3 rise 2
server green1 192.168.2.11:8080 check
server green2 192.168.2.12:8080 check
server green3 192.168.2.13:8080 check
蓝绿切换脚本:
#!/bin/bash
# blue_green_switch.sh
# 蓝绿部署切换脚本
SOCKET="/run/haproxy/admin.sock"
ENV_MAP="/etc/haproxy/maps/active_env.map"
get_current_env() {
# 从map获取当前环境
current=$(echo"show map $ENV_MAP" | socat stdio $SOCKET | grep "default" | awk '{print $2}')
if [ -z "$current" ]; then
current="blue"
fi
echo"$current"
}
check_backend_health() {
local backend=$1
local healthy_count=$(echo"show stat" | socat stdio $SOCKET | \
grep "$backend" | grep "UP" | wc -l)
echo"$healthy_count"
}
switch_env() {
local target_env=$1
echo"=== 蓝绿部署切换 ==="
echo"目标环境: $target_env"
# 检查目标环境健康状态
echo"检查 ${target_env}_servers 健康状态..."
healthy=$(check_backend_health "${target_env}_servers")
if [ "$healthy" -lt 2 ]; then
echo"错误:目标环境健康节点不足 ($healthy < 2),取消切换"
exit 1
fi
echo"健康节点: $healthy"
# 执行切换
echo"执行切换..."
echo"set map $ENV_MAP default $target_env" | socat stdio $SOCKET
# 验证切换结果
sleep 1
current=$(get_current_env)
if [ "$current" == "$target_env" ]; then
echo"切换成功!当前活跃环境: $current"
# 发送通知
curl -X POST "http://alert-api/webhook" \
-H "Content-Type: application/json" \
-d "{\"message\":\"蓝绿部署切换完成,当前环境: $target_env\"}" \
2>/dev/null
return 0
else
echo"切换失败!当前环境: $current"
return 1
fi
}
rollback() {
local current=$(get_current_env)
local target
if [ "$current" == "blue" ]; then
target="green"
else
target="blue"
fi
echo"回滚到 $target 环境..."
switch_env "$target"
}
status() {
echo"=== 蓝绿部署状态 ==="
echo"当前活跃环境: $(get_current_env)"
echo""
echo"蓝环境状态:"
echo"show stat" | socat stdio $SOCKET | grep "blue_servers" | \
awk -F',''{printf " %s: %s\n", $2, $18}'
echo""
echo"绿环境状态:"
echo"show stat" | socat stdio $SOCKET | grep "green_servers" | \
awk -F',''{printf " %s: %s\n", $2, $18}'
}
# 主程序
case"$1"in
switch)
if [ -z "$2" ]; then
echo"用法: $0 switch blue|green"
exit 1
fi
switch_env "$2"
;;
rollback)
rollback
;;
status)
status
;;
*)
echo"蓝绿部署管理工具"
echo""
echo"用法:"
echo" $0 switch blue|green # 切换到指定环境"
echo" $0 rollback # 回滚到另一个环境"
echo" $0 status # 查看当前状态"
;;
esac
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 ACL命名规范
好的命名让配置更易维护:
# 好的命名示例
acl host_api hdr(host) -i api.company.com # host_ 前缀表示域名
acl path_user_service path_beg /api/v1/users # path_ 前缀表示路径
acl is_websocket hdr(Upgrade) -i websocket # is_ 前缀表示布尔判断
acl has_auth_header hdr(Authorization) -m found # has_ 前缀表示存在性检查
acl rate_exceeded src_http_req_rate() gt 100 # 描述性名称
# 不好的命名示例
acl a1 hdr(host) -i api.company.com # 太简短,不知道是什么
acl check path_beg /api/ # 太宽泛
acl my_acl src 192.168.1.0/24 # my_ 前缀没有意义
4.1.2 ACL顺序优化
ACL匹配是按顺序的,把最常命中的放前面:
# 优化后的顺序
frontend http_front
bind *:80
# 1. 最简单的检查放前面(开销小)
acl is_options method OPTIONS
http-request return status 204 if is_options
# 2. 高频路径放前面
acl path_api path_beg /api/
acl path_static path_beg /static/
# 3. 复杂的正则放后面(开销大)
acl complex_pattern path_reg ^/api/v[0-9]+/users/[0-9]+$
# 4. 需要读取body的检查放最后(开销最大)
acl has_json_body req.body -m sub "json"
4.1.3 使用Map文件管理动态数据
大量动态数据不要写在配置文件里,用map文件管理:
# 配置文件中引用map
frontend http_front
# 黑名单IP - 从文件读取
acl blocked_ip src -f /etc/haproxy/blacklist.ips
# 用户路由 - 从map读取
http-request set-var(req.user_group) hdr(X-User-ID),map(/etc/haproxy/maps/user_groups.map,default)
# 优点:
# 1. 可以运行时更新,无需reload
# 2. 配置文件更简洁
# 3. 便于自动化管理
4.1.4 避免重复计算
相同的值只提取一次:
# 不好的写法 - 多次提取相同的值
acl is_api_v1 hdr(X-API-Version) -i v1
acl is_api_v2 hdr(X-API-Version) -i v2
acl is_api_v3 hdr(X-API-Version) -i v3
use_backend api_v1 if is_api_v1
use_backend api_v2 if is_api_v2
use_backend api_v3 if is_api_v3
# 好的写法 - 提取一次,多次使用
http-request set-var(req.api_version) hdr(X-API-Version)
use_backend api_v1 if { var(req.api_version) -m str v1 }
use_backend api_v2 if { var(req.api_version) -m str v2 }
use_backend api_v3 if { var(req.api_version) -m str v3 }
# 更好的写法 - 使用map
http-request set-var(req.api_version) hdr(X-API-Version)
http-request set-var(req.target_backend) var(req.api_version),map(/etc/haproxy/maps/api_version_routing.map,api_default)
use_backend %[var(req.target_backend)]
4.2 注意事项
4.2.1 常见ACL错误
# 错误1:忘记-i导致大小写敏感
acl host_api hdr(host) api.company.com # 不匹配 API.company.com
acl host_api hdr(host) -i api.company.com # 正确:忽略大小写
# 错误2:路径匹配没考虑结尾
acl path_api path_beg /api # 会匹配 /api 和 /api123
acl path_api path_beg /api/ # 正确:匹配 /api/ 开头
# 错误3:正则没转义
acl match_ip path_reg 192.168.1.1 # 错误:. 匹配任意字符
acl match_ip path_reg 192\.168\.1\.1 # 正确:转义.
# 错误4:Header名称大小写
acl has_auth hdr(x-auth-token) -m found # 可能不匹配 X-Auth-Token
acl has_auth hdr(X-Auth-Token) -m found # 推荐:使用标准大小写
# 错误5:逻辑组合错误
use_backend api if is_api !is_admin # 是AND关系
use_backend api if is_api or !is_admin # OR需要显式写
五、故障排查和监控
5.1 故障排查
5.1.1 ACL调试命令
#!/bin/bash
# debug_acl.sh
# ACL调试工具
SOCKET="/run/haproxy/admin.sock"
# 查看所有ACL定义
show_acls() {
echo"=== ACL定义列表 ==="
echo"show acl" | socat stdio $SOCKET
}
# 测试特定ACL
test_acl() {
local acl_id=$1
local test_value=$2
echo"测试ACL #$acl_id 匹配值: $test_value"
echo"get acl #$acl_id $test_value" | socat stdio $SOCKET
}
# 查看ACL内容
show_acl_content() {
local acl_id=$1
echo"show acl #$acl_id" | socat stdio $SOCKET
}
# 实时查看匹配情况
watch_acl_matches() {
echo"实时监控ACL匹配(查看HAProxy日志)..."
# 需要在配置中启用详细日志
# log global
# option httplog
tail -f /var/log/haproxy.log | grep -E "acl|backend"
}
case"$1"in
list)
show_acls
;;
test)
test_acl "$2""$3"
;;
show)
show_acl_content "$2"
;;
watch)
watch_acl_matches
;;
*)
echo"ACL调试工具"
echo"用法:"
echo" $0 list # 列出所有ACL"
echo" $0 test <id> <value> # 测试ACL匹配"
echo" $0 show <id> # 查看ACL内容"
echo" $0 watch # 实时监控匹配"
;;
esac
5.1.2 请求追踪
在请求中添加调试信息:
# 在frontend中添加调试header
frontend http_front
bind *:80
# 调试模式开关
acl debug_mode hdr(X-Debug) -i true
# 记录匹配的ACL到响应头(仅调试模式)
http-response set-header X-Matched-Host %[hdr(host)] if debug_mode
http-response set-header X-Matched-Path %[path] if debug_mode
http-response set-header X-Backend-Name %[be_name] if debug_mode
http-response set-header X-Server-Name %[srv_name] if debug_mode
测试命令:
# 发送带调试标记的请求
curl -v -H "X-Debug: true" -H "Host: api.company.com" http://localhost/api/v1/users
# 查看响应头中的调试信息
# X-Matched-Host: api.company.com
# X-Matched-Path: /api/v1/users
# X-Backend-Name: user_service
# X-Server-Name: user1
5.2 性能监控
5.2.1 ACL相关指标
# prometheus_acl_rules.yaml
# ACL相关的监控指标
groups:
-name:haproxy_acl_metrics
rules:
# 后端请求分布
-record:haproxy:backend_request_rate
expr:rate(haproxy_backend_http_requests_total[5m])
# 各后端请求比例
-record:haproxy:backend_request_ratio
expr:|
haproxy_backend_http_requests_total
/ ignoring(backend) group_left
sum(haproxy_backend_http_requests_total)
# 灰度流量比例告警
-alert:CanaryTrafficRatioAbnormal
expr:|
sum(rate(haproxy_backend_http_requests_total{backend="canary_servers"}[5m]))
/ sum(rate(haproxy_backend_http_requests_total{backend=~".*_servers"}[5m]))
> 0.2
for:5m
labels:
severity:warning
annotations:
summary:"灰度流量比例异常"
description:"灰度流量超过20%,当前: {{ $value | humanizePercentage }}"
5.3 备份与恢复
5.3.1 ACL配置备份
#!/bin/bash
# backup_acl_config.sh
# ACL配置备份脚本
BACKUP_DIR="/data/backups/haproxy_acl"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_PATH="$BACKUP_DIR/$DATE"
mkdir -p "$BACKUP_PATH"
# 备份主配置
cp /etc/haproxy/haproxy.cfg "$BACKUP_PATH/"
# 备份所有map文件
cp -r /etc/haproxy/maps "$BACKUP_PATH/"
# 备份黑白名单
cp /etc/haproxy/*.ips "$BACKUP_PATH/" 2>/dev/null
cp /etc/haproxy/*.txt "$BACKUP_PATH/" 2>/dev/null
# 导出运行时ACL状态
SOCKET="/run/haproxy/admin.sock"
if [ -S "$SOCKET" ]; then
echo"show acl" | socat stdio $SOCKET > "$BACKUP_PATH/runtime_acls.txt"
for map in /etc/haproxy/maps/*.map; do
mapname=$(basename "$map")
echo"show map $map" | socat stdio $SOCKET > "$BACKUP_PATH/runtime_${mapname}.txt"
done
fi
# 压缩
cd"$BACKUP_DIR"
tar -czf "${DATE}.tar.gz""$DATE"
rm -rf "$DATE"
echo"备份完成: ${BACKUP_DIR}/${DATE}.tar.gz"
# 清理旧备份(保留30天)
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
六、总结
6.1 技术要点回顾
ACL是HAProxy的核心功能:掌握ACL才能充分发挥HAProxy的能力。
合理使用Fetch和Flag:了解各种取值方法和匹配标志,选择最合适的组合。
善用Map文件:动态数据用map管理,支持运行时更新。
注意性能影响:复杂ACL会影响性能,要优化顺序和减少重复计算。
做好监控和调试:ACL匹配情况要可观测,方便排查问题。
6.2 进阶学习方向
Lua扩展:用Lua脚本实现更复杂的路由逻辑 SPOE:Stream Processing Offload Engine,把复杂逻辑卸载到外部服务 服务网格集成:HAProxy作为sidecar proxy的ACL配置 自动化配置:结合服务发现自动生成ACL配置
6.3 参考资料
HAProxy Configuration Manual HAProxy ACL Documentation HAProxy Runtime API
附录
A. 命令速查表
echo "show acl" | socat stdio /run/haproxy/admin.sock | |
echo "show acl #<id>" | socat stdio /run/haproxy/admin.sock | |
echo "add acl #<id> <value>" | socat stdio /run/haproxy/admin.sock | |
echo "del acl #<id> <value>" | socat stdio /run/haproxy/admin.sock | |
echo "clear acl #<id>" | socat stdio /run/haproxy/admin.sock | |
echo "show map <file>" | socat stdio /run/haproxy/admin.sock | |
echo "add map <file> <key> <value>" | socat stdio /run/haproxy/admin.sock | |
echo "set map <file> <key> <value>" | socat stdio /run/haproxy/admin.sock | |
echo "del map <file> <key>" | socat stdio /run/haproxy/admin.sock |
B. 配置参数详解
hdr(name) | hdr(Host) | |
path | path | |
path_beg | path_beg /api/ | |
path_end | path_end .html | |
path_reg | path_reg ^/api/v[0-9]+/ | |
url_param(name) | url_param(id) | |
cook(name) | cook(session_id) | |
src | src | |
method | method |
-i | hdr(Host) -i example.com | |
-f | src -f /etc/haproxy/whitelist.ips | |
-m str | path -m str /api/health | |
-m beg | path -m beg /api/ | |
-m end | path -m end .json | |
-m sub | path -m sub admin | |
-m reg | path -m reg ^/api/v[0-9]+/ | |
-m found | hdr(Authorization) -m found |
C. 术语表
(版权归原作者所有,侵删)
免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!

温馨提示:文章内容系作者个人观点,不代表Docker中文对观点赞同或支持。
版权声明:本文为转载文章,来源于 互联网 ,版权归原作者所有,欢迎分享本文,转载请保留出处!

发表评论