第 9-2 章 Linux haproxy 服务进阶配置

作者: Brinnatt 分类: ARM64 Linux 进阶架构 发布时间: 2022-01-21 16:11

haproxy 基础篇中我们强调过,默认配置虽然可以直接使用,但是真正的生产环境中,想要实现高性能的代理服务,必须把握好众多参数,这就要求对 haproxy 的配置文件有更进一步的精深理解。下面我们会对 haproxy 的配置文件进行详解。

配置文件的重点在于前端(frontend)和后端(backend)的定制。全局选项(global)的配置将默认提供的参数稍微修改下即可。

可以使用 haproxy 命令行检查配置文件语法是否正确:

haproxy -f /etc/haproxy/phaproxy.cfg -c

或者使用 sysv 脚本的 check 参数:

service haproxy check

9.1、配置文件说明

HAProxy 在启动之前会解析配置文件,有 3 处配置信息来源:

  1. 最优先处理来自 haproxy 命令行给出的参数。
  2. "global" 配置段的参数,设定为全局参数。
  3. 代理配置段,包括 defaultslistenfrontendbackend 段。

另外 haproxy 配置文件引入了引号和转义符:反斜线表示转义符;单引号表示强引用;双引号表示弱引用。如果字符串内需要输入空格,则空格需要进行转义或者通过引号包围,不转义时在配置文件中表示分隔符。

如:

\    标记一个空白字符以区分它的本义和用作分隔符时的空白符
\#   to mark a hash and differentiate it from a comment
\\   to use a backslash
\'   to use a single quote and differentiate it from strong quoting
\"   to use a double quote and differentiate it from weak quoting

下面几种情况是等价的:

log-format %{+Q}o\ %t\ %s\ %{-Q}r
log-format "%{+Q}o %t %s %{-Q}r"
log-format '%{+Q}o %t %s %{-Q}r'
log-format "%{+Q}o %t"' %s %{-Q}r'
log-format "%{+Q}o %t"' %s'\ %{-Q}r

在配置文件中,一些包含了数值的参数表示时间,如 timeout。这些值默认以毫秒为单位,但也可以使用其它的时间单位后缀。

  • us:微秒(microseconds),即 1/1000000 秒;
  • ms:毫秒(milliseconds),即 1/1000 秒;
  • s:秒(seconds);
  • m:分钟(minutes);
  • h:小时(hours);
  • d:天(days);

9.2、简单配置示例

以下是一个简单的配置文件,该配置文件代理模式为 http,frontend 定义的是监听在前端所有网卡的 80 端口上,此文件中只定义了一个后端服务器组 backend,该 backend 只包含一台监听在 127.0.0.1:8000 的服务器。在 haproxy 的术语中,frontend 表示的是监听套接字,用于等待客户端的连接。

global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http-in
    bind *:80
    default_backend web_servers

backend web_servers
    server server1 127.0.0.1:8000 maxconn 32

如果使用 listen 配置方式替换 backend 和 frontend,则更简单,以下是等价配置:

global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

listen http-in
    bind *:80
    server server1 127.0.0.1:8000 maxconn 32

9.3、全局配置参数

全局配置参数设定 haproxy 进程运行环境,一般和操作系统指定的值有关,配置正确后一般都不会去修改。全局配置参数一般都有对应的命令行选项。

  1. 进程管理及安全相关的参数

    haproxy 是单进程、事件驱动、非阻塞模型的调度器。虽然是单进程,但官方强烈建议不要设置为多进程,因为单进程可以处理很多个代理连接请求且性能极好(官方手册说 30W 个代理实例都能良好运行),设置为多进程反而有一些限制。

    • chroot :修改 haproxy 工作目录至指定目录,可提升 haproxy 安全级别,但要确保必须为空且任何用户均不能有写权限;
    • daemon:让 haproxy 以守护进程的方式工作于后台,等同于命令行的 "-D" 选项,当然,也可以在命令行中以 "-db" 选项将其禁用;(建议设置项)
    • uid/user:以指定的 UID 或用户名身份运行 haproxy 进程;
    • gid/group:以指定的 GID 或组名运行 haproxy,建议使用专用于运行 haproxy 的 GID,以免因权限问题带来风险;
    • log:定义全局的 syslog 服务器,接收 haproxy 启动和停止的日志。最多可以定义两个;
      • log <address> <facility> [max level [min level]]
    • log-send-hostname [string]:在日志的最前面记录本机主机名或 string。远程发送到日志服务器时可由此知道是 haproxy 主机发送的。
    • pidfile:等同于命令行的 "-p" 选项。使用服务脚本启动 haproxy 时建议不要设置该项,以保证脚本能正确获取 pid 文件。
    • nbproc :指定启动的 haproxy 进程个数,只能用于守护进程模式的 haproxy;默认只启动一个进程,一般只在单进程仅能打开少数文件描述符的场景中才使用多进程模式;(官方强烈建议不要设置该选项)
    • ulimit-n:设定每进程能够打开的最大文件描述符数量,默认 haproxy 会自动进行计算,因此不推荐修改此选项;(不建议设置项)
    • stats:和多进程 haproxy 有关,由于不建议使用多进程,所以也不建议设置此项。但建议设置为 "stats socket" 将套接字和本地文件进行绑定,如 "stats socket /var/lib/haproxy/stats"。
    • node:定义当前节点的名称,用于 HA 场景中多 haproxy 进程使用相同 IP 地址时分辨哪个 node 正处于使用状态;
  2. 性能调整相关的参数

    • maxconn :设定每 haproxy 进程所接受的最大并发连接数,当达到此限定连接数后将不再接受新的连接。该参数特指和客户端的连接数,不包括和服务端的连接。等同于命令行选项 "-n";"ulimit -n" 就是根据此值进行自动调整的;
    • maxpipes :haproxy 在使用 splice() 在内核中零复制时,是使用 pipe 传递进行报文粘接重组的,此选项用于设定每进程所允许使用的最大 pipe 个数;每个 pipe 会打开两个文件描述符,因此 "ulimit -n" 自动计算时会按需调大此值;默认值为 maxconn/4。调小时会影响一定的性能;
    • noepoll:在 Linux 系统上禁用 epoll 机制;(不建议设置此项)
    • nokqueue:在 BSD 系统上禁用 kqueue 机制;
    • nopoll:禁用 poll 机制;
    • nosplice:禁止在 Linux 套接字上使用内核 tcp 重组,这会导致更多的 recv/send 系统调用;(在内核版本 2.6.28 之后极度不建议设置此项)
    • spread-checks <0..50, in percent>:在 haproxy 后端有着众多服务器的场景中,在精确的时间间隔后统一对众服务器进行健康状况检查可能会带来意外问题;此选项用于在其检查的时间间隔长度上增加或减小一定的随机时长;默认为 0,官方建议设置为 2 到 5 之间。(建议设置项)
    • tune.bufsize :设定 buffer 的大小,同样的内存条件下,较小的值可以让 haproxy 有能力接受更多的并发连接,较大的值可以让某些应用程序使用较大的 cookie 信息;默认为 16384,可在编译时修改,不过强烈建议使用默认值;(不建议设置项)
    • tune.chksize :设定检查缓冲区的大小,单位为字节;更大的值有助于在较大的页面中完成基于字符串或正则 pattern 的文本查找,但也会占用更多的系统资源;(不建议设置项)
    • tune.maxaccept :设定 haproxy 进程内核调度运行时一次性可以接受的连接的个数,较大的值可以带来较大的吞吐率,默认在单进程模式下为 100,多进程模式下为 8,设定为 -1 可以禁止此限制;(不建议设置项)
    • tune.maxpollevents :设定一次 io 复用时系统调用可以处理的事件最大数,默认值取决于 OS;其值小于 200 时可节约带宽,但会略微增大网络延迟,而大于 200 时会降低延迟,但会稍稍增加网络带宽的占用量;(不建议设置项)
    • tune.maxrewrite :设定为首部重写或追加而预留的缓冲空间,建议使用 1024 左右的大小;在需要使用更大的空间时,haproxy 会自动增加其值;(不建议设置项)
    • tune.rcvbuf.client :设定两端的 recv_buff 大小(haproxy 和客户端建立 tcp,和后端服务器建立 tcp,共两端,因此有两个 recv_buff 和两个 send_buff)。单位为字节;(强烈推荐使用默认值)
    • tune.rcvbuf.server :(强烈推荐使用默认值)
    • tune.sndbuf.client:设定两端的 send_buff 大小(强烈推荐使用默认值)
    • tune.sndbuf.server:(强烈推荐使用默认值)

因此,抛去不建议设置的项后,global 段的设置大致如下:这也是 yum 安装 haproxy 时默认提供的配置:

global
    daemon
    log         127.0.0.1 local2
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    stats socket /var/lib/haproxy/stats

注意上面配置了使用 local2 记录 log,因此还需去 rsyslogd 的配置文件中添加该设备以及记录的日志位置。如下:

cat <<eof>>/etc/rsyslog.conf
  local2.*     /var/log/haproxy.log
eof

9.4、proxy 配置段和常用配置选项

proxy 配置部分是 haproxy 最重要的配置部分,包含下面四种配置段:

  1. defaults []: 设置 frontend/backend/listen 配置段的默认值。
  2. frontend: 配置监听客户端连接的套接字。
  3. backend: 配置 haproxy 所代理的后端服务器组。
  4. listen: 定义一个完整的前端和后端代理,但后端可以不定义。所以有时候等价于 frontend + backend。它常用于绑定前后端 1 对 1 的情况。

所有代理的名称只能使用大写字母、小写字母、数字、-(中线)、_(下划线)、.(点号) 和 :(冒号)。此外,ACL 名称会区分字母大小写。

目前,有两种主流的代理模式:tcp 代理(即所谓的 4 层代理)和 http 代理(即所谓的 7 层代理)。

  • 在 4 层代理模式下,haproxy 简单的在两端进行双向转发。
  • 在 7 层代理模式下,haproxy 会对协议进行分析,可以根据协议来允许、阻塞、切换、增加、修改和移除 request 或 response 中的属性内容。

9.4.1、http 事务模型相关设置

  1. (no) option http-keep-alive

    启用或禁用客户端和服务端到 haproxy 之间的长连接。haproxy 将处理所有请求和响应报文,请求完后 haproxy 两端的连接都处于空闲状态。由于和后端保持了连接,可以以最快的方式重用会话。

  2. (no) option http-server-close

    启用或禁用在 haproxy 处理完第一次响应之后关闭 haproxy 到服务端之间长连接的功能,但客户端的长连接还保持,后续的每次请求都重新建立和后端的连接,每次响应后都关闭和后端的连接。

    启用该选项时,haproxy 将会在转发给后端 server 的 request 数据包中添加一个 "Connection:Close" 标记,后端 Server 看到此标记就会在响应后关闭 tcp 连接。

  3. (no) option http-pretend-keepalive

    有些服务器会无视带有 "Connection:Close" 标记的请求,从而 http-server-close 的后端发送响应后不会关闭 tcp 连接。设置该选项时,haproxy 在收到响应后会主动关闭和后端的连接。

    不建议设置该选项,因为绝大多数服务器都能正常工作并且有很好的调整能力。

一般来说,对于高速局域网络来说,如果后端响应的速度非常快(比如后端是静态服务器响应小文件、后端是静态缓存服务器),这时建立 tcp 连接的代价就比较大,维持空闲连接的优势会非常明显。

如果后端是动态应用程序,响应给 haproxy 的速度相对较慢,维持空闲连接的代价非常大,完全可以先释放长连接以腾出资源,在需要连接的时候再建立新 tcp 连接。因此:

  1. 后端是静态内容缓存服务器时,或者就是静态服务器时,首选使用 http-keep-alive 模式;
  2. 后端是动态应用程序服务器时,首选使用 http-server-close 模式

默认情况下,如果客户端请求根据调度算法被调度到另一台后端服务器时,http-keep-alive 模式下和后端服务器的空闲连接会立即断开,并重新和被调度选中的后端服务器建立连接。

可以使用 "prefer-last-server" 选项,使得 haproxy 先查看当前保持的空闲连接是否可用,如果可用,则继续使用该空闲连接,但是这样会影响调度性能。

frontend 和 backend 都可以设置这些模式选项,如果它们交叉设置了,最终何种模式会生效?例如,frontend 设置了 http-keep-alive,而 bakcend 设置了 http-server-close 时,取何种模式?计算方式采用如下矩阵:keepalive 优先级是最弱的,forceclose 是优先级是最高的。

The effective mode that will be applied to a connection passing through a
frontend and a backend can be determined by both proxy modes according to the
following matrix, but in short, the modes are symmetric, keep-alive is the
weakest option and close is the strongest.

                   Backend mode

                | KAL | SCL | CLO
            ----+-----+-----+----
            KAL | KAL | SCL | CLO
            ----+-----+-----+----
   mode     SCL | SCL | SCL | CLO
            ----+-----+-----+----
            CLO | CLO | CLO | CLO

9.4.2、balance

# 可用于 default、listen、backend 配置段。
balance <algorithm> [ <arguments> ]
balance url_param <param> [check_post]

可用于以下配置段:

defaults frontend listen backend
yes
yes
no
cross
yes
yes
yes
check

Examples:

balance roundrobin
balance url_param userid
balance url_param session_id check_post 64
balance hdr(User-Agent)
balance hdr(host)
balance hdr(Host) use_domain_only

指定代理时负载均衡算法(algorithm),支持的算法有:

  1. roundrobin(默认):根据权重进行轮询,在服务器的处理时间保持均匀分布时,这是最平衡、最公平的算法。此算法是动态的,表示权重可以在 haproxy 运行时调整后端服务器的权重并生效;
  2. static-rr:基于权重进行轮询,与 roundrobin 类似,但是为静态方法,在 haproxy 运行时调整其服务器权重不会生效;
  3. leastconn:新的连接请求被派发至具有最少连接数目的后端服务器;在有着较长时间会话的场景中推荐使用此算法,如 LDAP、SQL 等,其并不太适用于较短会话的应用层协议,如 HTTP;此算法是动态的,可以在运行时调整其权重;
  4. source:将请求的源地址进行 hash 运算,使得同一个客户端 IP 的请求始终被派发至某特定的服务器;但当服务器权重总数发生变化时,如某服务器宕机或添加了新的服务器,许多客户端的请求可能会被派发至与此前请求不同的服务器;类似于 nginx 的 ip_hash,可用于负载均衡无 cookie 功能基于 TCP 协议时。默认为静态;
  5. uri:对 URI 的左半部分("?" 标记之前的部分)进行 hash 运算,并除以服务器的总权重来计算派发至某匹配服务器;这可以使得对同一个 URI 的请求总是被派发至某特定的服务器,除非服务器的权重总数发生了变化;此算法常用于代理缓存以提高缓存的命中率;但此算法仅应用于提供 http 服务的后端服务器;默认为静态算法;缺点是后端 server 宕机会造成严重抖动,可以通过 hash-type 设置 hash 算法为一致性哈希解决。
  6. url_param:一般用于将同一用户 ID 转发至同一服务器的情况。在使用了 basic 认证时,url 中的 param 一般都会使用 user=XXX。使用该算法会对该参数进行 hash 运算,然后除以总权重以决定分配到哪台后端 server。
  7. hdr(name):基于指定的请求首部名称进行调度。首部中指定名称相同的调度至同一服务器。一般使用 "hdr(host)" 根据请求首部中的 host 即目标主机来进行 hash 运算。使用 use_domain_only 选项可以基于域名来哈希,使得访问 www.brinnatt.com 和 web.brinnatt.com 的请求都调度至同一服务器。
  8. rdp-cookie(name):基于远程桌面 RDP 协议设置 RDP cookie,不区分字母大小写,把相同用户或者相同 session ID 调度到相同服务器。如果没有 cookie,使用 roundrobin 调度。

roundrobin 和 static-rr 是有区别的,roundrobin 是动态慢轮询,不用重启服务即可调整其权重,而 static-rr 必须重启服务修改的权重才生效。

  • 例如原有 2 台后端 server,新添加一台后,roundrobin 会从此时开始慢慢的将请求轮询至此新服务器,而 static-rr 由于需要重启,所以重启前新 server 不会被调度到,重启后新 server 和旧 server 平均调度。
  • 一般来说,考虑加权轮询的时候,roundrobin 要比 static-rr 好。

一般可纳入考虑的算法有 roundrobin/static-rr/leastconn/uri,其中 leastconn 算法用于代理 ldap、mysql 等长时间会话连接的情况,uri 算法用于代理后端为缓存服务器的情况。

  • 用于调度 MySQL 服务器,使用何种算法?
    • 答:leastconn
  • 用于调度静态服务器组,使用何种算法?
    • 答:roundrobin
  • 调度动态应用程序服务器组,使用何种算法?
    • 答:通常客户端需要和后端应用程序服务器保持联系,一般会使用 cookie 或者 session 来实现,但如果特殊情况下无法通过它们实现,则可以使用 source 作为最后 "亲和性" 手段。注意,使用 source 算法时,后端服务器数量一改变,就会导致大量的会话断开。
  • 调度缓存服务器,使用何种算法?
    • 答:uri,且设置 hash-type 为一致性哈希算法。

9.4.3、hash-type

# 指定一种 method 映射 hashes 到 servers
hash-type <method> <function> <modifier>

可用在以下配置段:

defaults frontend listen backend
yes
yes
no
cross
yes
yes
yes
check
<method> is the method used to select a server from the hash computed by the <function> :
    map-based   hash 表是一个包含所有活动服务器的静态阵列。两个重点,一个是服务器正在运行时,修改权重无效;
                二是一旦有服务器 down 或 up,会出现大面积会话保持失效,连接重新调度。

    consistent  hash 表是一个由各服务器填充而成的树状结构,即一致性哈希算法;基于hash键在hash树中查找相应的服务器时,最近的服务器将被选中。
                此方法是动态的,支持在运行时修改服务器权重,因此兼容慢速启动的特性。
                添加一个新的服务器时,仅会对一小部分请求产生影响,因此,适用于后端服务器为cache的场景。
                不过,此算法不甚平滑,派发至各服务器的请求未必能达到理想的均衡效果,因此,可能需要不时的调整服务器的权重以获得更好的均衡性。

<function> is the hash function to be used :
    sdbm        这个最初是为 sdbm(a public-domain reimplementation of ndbm) 创建数据库的库函数,后来发现做散列运算也挺合适
    djb2        djb2 通过研究表明在某些工作负载情况下,可以提供比 sdbm 更好的散列运算。
    wt6         wt6 为 haproxy 设计出来,在过去用来测试其它的函数。
    crc32       crc32 是在以太网中最通用的 CRC32 实现,比如 gzip, PNG等。其散列算法比其它的要慢一些,但是从散列结果和不可预测上来说,显得更加优秀。

<modifier> indicates an optional method applied after hashing the key :
    avalanche   这个指令表示上面的哈希函数的结果不应该以原始形式使用,必须首先应用一个 4 字节的完整雪崩哈希;
                此步骤的目的是混合前一个散列的结果位,以避免在输入包含一些有限值或服务器数量是散列某个组件的倍
                数时(SDBM为64,DJB2为33)产生任何不希望的影响。
                主要目的就是让结果更难预测。
The default hash type is "map-based" and is recommended for most usages. The
default function is "sdbm", the selection of a function should be based on
the range of the values being hashed.

默认的 hash 类型是 map-bashed,大多数情况建议使用这个 hash 类型。默认的函数为 sdbm,函数的选择应该基于散列值的范围。

9.4.4、bind

bind [<address>]:<port_range> [, ...] [param*]
bind /<path> [, ...] [param*]

可用在以下配置段:

defaults frontend listen backend
no
yes
yes
cross
yes
yes
no
check

Examples:

listen http_proxy
    bind :80,:443
    bind 10.0.0.1:10080,10.0.0.1:10443
    bind /var/run/ssl-frontend.sock user root mode 600 accept-proxy

listen http_https_proxy
    bind :80
    bind :443 ssl crt /etc/haproxy/site.pem

listen http_https_proxy_explicit
    bind ipv6@:80
    bind ipv4@public_ssl:443 ssl crt /etc/haproxy/site.pem
    bind unix@ssl-frontend.sock user root mode 600 accept-proxy

listen external_bind_app1
    bind "fd@${FD_APP1}"

9.4.5、mode

mode { tcp|http }

可用在以下配置段:

defaults frontend listen backend
yes
yes
yes
cross
yes
yes
yes
check

Examples:

defaults http_instances
    mode http

指定 haproxy 实例运行模式。可为 tcp、http。tcp 为 4 层代理模式,不会对协议进行任何分析,只是单纯地转发数据包,如 HTTPS/MYSQL 等,http 为 7 层代理模式。如果所有配置区段都没有设置 mode,则默认为 tcp 模式。

9.4.6、log

log global
log <address> [len <length>] [format <format>] [sample <ranges>:<sample_size>]
    <facility> [<level> [<minlevel>]]
no log

可用在以下配置段:

defaults frontend listen backend
yes
yes
yes
cross
yes
yes
yes
check

Examples:

log global
log stdout format short daemon              # send log to systemd
log stdout format raw daemon                # send everything to stdout
log stderr format raw daemon notice         # send important events to stderr
log 127.0.0.1:514 local0 notice             # only send important events
log tcp@127.0.0.1:514 local0 notice notice  # same but limit output
                                            # level and send in tcp

log "${LOCAL_SYSLOG}:514" local0 notice       # send to local server

如果使用 log global,则表示从全局继承日志设置。另外,如果全局已经定义过两个 log 了,此处除引用 global 外还自定义了一个 log,则此自定义的 log 失效,因为只支持两个日志设置。

9.4.7、capture

# 仅能用于"frontend"和"listen"区段
capture request header <name> len <length>
capture response header <name> len <length>

捕获并记录最近一次出现的指定请求首部或响应首部。请求首部是从客户端发起的请求首部,响应首部是从后端 server 响应并在 haproxy 准备发送给客户端前捕获的。捕获的首部值使用大括号 括起来后会添加进日志中。如果需要捕获多个首部值,它们将以指定的秩序出现在日志文件中,并以竖线 "|" 作为分隔符。不存在的首部记录为空字符串。

最常需要捕获的请求首部包括:在虚拟主机环境中使用的 "Host"、上传请求首部中的 "Content-length"、快速区别真实用户和网络机器人的 "User-agent",以及代理环境中记录请求真实来源的 "X-Forward-For"。

一般需要捕获的响应首部为:记录还有多少内容需要接收的 "Content-length"、跟踪重定向路径的 "Location"。

  • <name>:要捕获的首部的名称,此名称不区分字符大小写,但建议与它们出现在首部中的格式相同,比如大写首字母。需要注意的是,记录在日志中的是首部对应的值,而非首部名称。
  • <length>:指定记录首部值时所记录的精确长度,超出的部分将会被忽略。

可以捕获的请求首部的个数没有限制,但每个捕获最多只能记录 64 个字符。为了保证同一个 frontend 中日志格式的统一性,首部捕获仅能在 frontend 中定义。

Examples:

capture request header Host len 15
capture request header X-Forwarded-For len 15
capture request header Referer len 15
capture response header Content-length len 9
capture response header Location len 15

9.4.8、maxconn

maxconn <conns>

可用于以下配置段:

defaults frontend listen backend
yes
yes
yes
cross
yes
yes
no
check

设定一个前端的最大并发连接数。对于大型站点来说,可以尽可能提高此值以便让 haproxy 管理连接队列,从而避免无法应答用户请求。

当然,此最大值不能超出 "global" 段中的定义。此外,haproxy 会为每个连接维持两个缓冲,每个缓冲的大小为 16KB,再加上其它的数据,每个连接将大约占用 33KB 的 RAM 空间。这意味着经过适当优化后,有着 1GB 的可用 RAM 空间时将能维护 20000-25000 并发连接。

如果为指定了一个过大值,极端场景下,其最终占据的空间可能会超出当前主机的可用内存,这可能会带来意想不到的结果;因此,将其设定为一个可接受值方为明智决定。默认为 2000。

9.4.9、use_backend

# Switch to a specific backend if/unless an ACL-based condition is matched.
use_backend <backend> [{if | unless} <condition>]

定义当满足或不满足什么条件时使用哪个 backend。条件判断是可选的,并且 condition 是基于 acl 的条件。

可用于以下配置段:

defaults frontend listen backend
no
yes
yes
cross
yes
yes
no
check

9.4.10、default_backend

# Specify the backend to use when no "use_backend" rule has been matched.
default_backend <backend>

在没有匹配的 "use_backend" 规则时为实例指定默认后端。在 "frontend" 和 "backend" 之间进行内容交换时,通常使用 "use-backend" 定义匹配规则;而没有被规则匹配到的请求将由此参数指定的后端接收。

可用于以下配置段:

defaults frontend listen backend
yes
yes
yes
cross
yes
yes
no
check

Arguments:

<backend> is the name of the backend to use.

Examples:

use_backend     dynamic  if  url_dyn
use_backend     static   if  url_css url_img extension_img
default_backend dynamic
  • 已有两组 backend,名称分别为 dynamic 和 static,当不匹配 use_backend 时将默认使用 dynamic 作为转发后端。

9.4.11、server 和 default-server

server

# Declare a server in a backend
server <name> <address>[:[port]] [param*]

可用于以下配置段:

defaults frontend listen backend
no
yes
no
cross
yes
yes
yes
check

Examples:

server first  10.1.1.1:1080 cookie first  check inter 1000
server second 10.1.1.2:1080 cookie second check inter 1000
server transp ipv4@
server backup "${SRV_BACKUP}:1080" backup
server www1_dc1 "${LAN_DC1}.101:80"
server www1_dc2 "${LAN_DC2}.101:80"

default-server

# Change default options for a server in a backend
default-server [param*]

可用于以下配置段:

defaults frontend listen backend
yes
yes
no
cross
yes
yes
yes
check

Examples:

default-server inter 1000 weight 13

上面两个指令的简单使用方法如上所述,下面对他们的参数进行重要说明:

  • 格式说明:

    • <name>:为此服务器指定的内部名称,将出现在日志及警告信息中;
    • <address>:此服务器的 IPv4 地址,也支持使用可解析的主机名;
    • [:port]:haproxy 将请求转发至后端服务器的哪个端口,为可选项;未设定时,将使用客户端请求时的同一端口;
    • [param*]:为此服务器设定的一系列参数;可用的参数非常多,下面是几个常用的参数;
  • 服务器或默认服务器参数:

    • backup:设定为备用服务器,当其它所有后端 server 均不可用时将启用此 server;

    • disabled:禁用此后端服务器。

    • check:启动对此 server 执行健康状态检查,但需要配合定义在 backend 的具体检查方法(如 httpcheck,mysql-check)才会进行指定的检查方式,不指定检查方法时将默认以 tcp 方式检查。check 可以借助于额外的参数完成更精细的设定,如:

    • inter <delay>:设定健康状态检查的时间间隔,单位为毫秒,默认为 2000;

    • rise <count>:设定 server 从离线状态重新上线需要成功检查的次数;不指定默认为 2,一般可设置为 1。

    • fall <count>:确认 server 从正常状态转换为不可用状态需要检查的次数;默认为 3。

    • cookie <value>:为指定 server 设定 cookie 值,指定的值将在请求入站时被检查,第一次为此值挑选的 server 将在后续的请求中被选中,其目的在于实现基于客户端 cookie 的持久连接;

    • maxconn <maxconn>:指定此后端服务器接受的最大并发连接数(不同于全局设置的 maxconn,全局的 maxconn 是面向客户端的);如果发往此服务器的连接数高于指定的值,将被放于请求队列以等待其它连接释放;

    • maxqueue <maxqueue>:设定请求队列的最大长度;

    • observe <mode>:通过观察服务器的通信状况来判定其健康状态,默认禁用,支持的类型有 "layer4" 和 "layer7",layer4 表示检查 tcp 连接是否正常,layer7 仅用于 http 代理场景,通过后端 server 发送的 response 来判断,例如可以判断状态码,响应报文头部是否无法解析等;

    • redir <prefix>:启用重定向功能,将发往此服务器的 GET 和 HEAD 请求均以 302 状态码响应,意味着不会将请求转发至后端服务器;在 prefix 后面不能使用 /,且不能使用相对地址;例如:server srv1 192.168.1.1:80 redir http://image1.mydomain.com check

    • weight <weight>:权重,默认为 1,最大值为 256,0 表示不参与负载均衡,即认为下线了不进行调度;

关于 maxconn 和 maxqueue,这两个值都是此后端服务器的值。它们的大小和全局定义的 maxconn 是有一定大小比较关系的。

  • 如果没有定义 maxqueue,则全局 maxconn 应该小于或等于后端所有服务器的 maxconn 之和,如果定义了 maxqueue,则需要小于或等于后端所有服务器的 maxconn 和 maxqueue 之和。
  • 否则 haproxy 接收进来的请求超过后端服务器的压力极限,可能压垮后端。

9.4.12、option httpchk

# Enables HTTP protocol to check on the servers health
option httpchk
option httpchk <uri>
option httpchk <method> <uri>
option httpchk <method> <uri> <version>

可用于以下配置段:

defaults frontend listen backend
yes
yes
no
cross
yes
yes
yes
check

此指令表示基于 http 协议来做健康状况检查,只有返回状态码为 2xx 或 3xx 的才认为是健康的,其余所有状态码都认为不健康。不设置该选项时,默认采用 tcp 做健康检查,只要能建立 tcp 就表示健康。

  • uri:检查的 uri 路径,默认为 "/"。接受带有查询参数的 uri
  • method:http 检查时使用的 METHOD。不指定时默认为 "OPTIONS" 方法,也建议采用此方法,因为该请求方法对服务器造成的资源损耗最小。
  • version:检查的 http 协议版本,默认为 http/1.0,但现在很多都采用 HTTP/1.1,因此此处检查版本需要修改为 HTTP/1.1,但对于该版本的 HTTP 协议来说,还强制要求指定 Host,中间使用 \r\n 隔离。

例如下面的配置,会将健康检查时的页面请求发送至后端 192.168.1.1 的 80 端口来确定该后端是正常的,但客户端的请求将转发至该后端的 443 端口。

backend https_relay
    mode tcp
    option httpchk
    option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www
    server apache1 192.168.1.1:443 check port 80

9.4.13、stats 相关

stats enable:启用基于程序编译时默认设置的统计报告,不能用于 "frontend" 区段。

只要没有另外的其它设定,默认就会使用如下的配置:

- stats uri   : /haproxy?stats
- stats realm : "HAProxy Statistics"
- stats auth  : no authentication
- stats scope : no restriction

尽管 "stats enable" 一条就能够启用统计报告,但还是建议设定其它所有的参数,以免依赖于默认设定而带来非预期后果。

例如:

backend public_www
    server websrv1 172.16.100.11:80
    stats enable
    stats hide-version
    stats scope   .
    stats uri     /haproxyadmin?stats
    stats realm   Haproxy\ Statistics
    stats auth    statsadmin:password
    stats auth    statsmaster:password
  • stats hide-version:启用统计报告并隐藏 HAProxy 版本报告,不能用于 "frontend" 区段。

  • stats realm:stats auth 身份认证时的提示信息。设置的提示信息中,如果有空白字符,则需要转义。仅在与 "stats auth" 配合使用时有意义。

  • stats auth:启用带认证的统计报告功能并授权一个用户帐号和对应的密码(明文)。也就是说,想要查看统计报告需要提供身份和密码。不能用于 "frontend" 区段。

  • stats admin:满足指定条件时启用统计报告页面的管理功能,它允许通过 web 接口启用或禁用后端服务器

stats admin { if | unless } <cond>

下面是两个案例,第一个限制了仅能在本机打开报告页面时启用管理功能,第二个定义了仅允许通过认证的用户使用管理功能。

backend stats_localhost
    stats enable
    stats admin if LOCALHOST

backend stats_auth
    stats enable
    stats auth  haproxyadmin:password
    stats admin if TRUE

9.4.14、option forwardfor

option forwardfor [ except <network> ] [ header <name> ] [ if-none ]
  • 允许在发往服务器的请求首部中插入 "X-Forwarded-For" 首部。
    • except <network>:可选参数,当指定时表示请求中的源地址能匹配此网络时禁用此功能。
    • header <name>:可选参数,自定义首部名,如 "X-Client" 来替代 "X-Forwarded-For"。有些独特的 web 服务器的确需要一个独特的首部。
    • if-none:仅在此首部不存在时才将其添加至请求报文首部中。

HAProxy 工作于反向代理模式,其发往服务器的请求中的客户端 IP 均为 HAProxy 主机的地址而非真正客户端的地址,这会使得服务器端的日志信息记录不了真正的请求来源,"X-Forwarded-For" 首部则可用于解决此问题。HAProxy 可以向每个发往服务器的请求上添加此首部,并以客户端 IP 为其 value。

frontend www
    mode http
    option forwardfor except 127.0.0.1

9.4.15、Error Page

9.4.15.1、errorfile

# Return a file contents instead of errors generated by HAProxy
errorfile <code> <file>
  • <code>:指 HTTP 状态码,haproxy 能够产生的状态码有 200、400、401、403、404、405、407、408、410、413、425、429、500、501、502、503 and 504。
  • <file>:指定用于响应的页面文件;

可用于以下配置段:

defaults frontend listen backend
yes
yes
yes
cross
yes
yes
yes
check

Examples:

errorfile 400 /etc/haproxy/errorfiles/400badreq.http
errorfile 408 /dev/null  # work around Chrome pre-connect bug
errorfile 403 /etc/haproxy/errorfiles/403forbid.http
errorfile 503 /etc/haproxy/errorfiles/503sorry.http

9.4.15.2、errorfiles

# Import, fully or partially, the error files defined in the <name> http-errors section.
errorfiles <name> [<code> ...]
  • <name>:定义 http-errors section 时取的名字。
  • <code>:HTTP 状态码列表。

可用于以下配置段:

defaults frontend listen backend
yes
yes
yes
cross
yes
yes
yes
check

Examples:

errorfiles generic
errorfiles site-1 403 404

9.4.15.3、errorloc 和 errorloc302

# 请求错误时,返回一个HTTP重定向至某URL的信息给客户端;可用于所有配置段中。
errorloc <code> <url>
errorloc302 <code> <url>
  • <code>:指定对 HTTP 的哪些状态码返回指定的页面;这里可用的状态码有 200、400、403、408、500、502、503 和 504;
  • <url>:Location 首部中指定的页面位置的具体路径,可以是在当前服务器上的页面的相对路径,也可以使用绝对路径;
  • 这两个关键字都会返回 302 状态吗,这将使得客户端使用同样的 HTTP 方法获取指定的 URL,对于非 GET 方法的场景(如 POST)来说会产生问题,因为返回客户端的 URL 是不允许使用 GET 以外的其它方法的。
    • 如果的确有这种问题,可以使用 errorloc303 来返回 303 状态码给客户端。

9.4.15.4、errorloc303

# 请求错误时,返回一个HTTP重定向至某URL的信息给客户端;可用于所有配置段中。
errorloc303 <code> <url>
  • <code>:指定对 HTTP 的哪些状态码返回指定的页面;这里可用的状态码有 400、403、408、500、502、503 和 504;
  • <url>:Location 首部中指定的页面位置的具体路径,可以是在当前服务器上的页面的相对路径,也可以使用绝对路径;需要注意的是,如果 URI 自身错误时产生某特定状态码信息的话,有可能会导致循环定向;

9.4.15.5、生产应用

在生产环境下,我们常用的做法是创建一个专门放置错误页面的目录 /etc/haproxy/errors,然后根据需要在里面定义以 .http 后缀结尾的文件,比如 /etc/haproxy/errors/404.http

HTTP/1.1 404 Not Found
Cache-Control: no-cache
Connection: close
Content-Type: text/html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>404 Not Found</title>
    </head>
    <body>
        <main>
            <h1>404 Not Found</h1>

            This is my custom 404 Not Found page!
        </main>
    </body>
</html>

然后打开 haproxy 配置文件 /etc/haproxy/haproxy.cfg,添加一个叫 http-errors 的配置段,该配置段有 errorfile 指令指向 xxx.http 的文件。注意,http-errors 配置段是 haproxy v2.2 版本后才开始支持,跟 frontendbackend 配置段是同一个级别。

http-errors myerrors
    errorfile 400 /etc/haproxy/errors/400.http    
    errorfile 401 /etc/haproxy/errors/401.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 404 /etc/haproxy/errors/404.http
  • 默认情况下,haproxy 只是在自己提供服务时遇到问题才返回对应的错误页面,比如:
    • haproxy 不能访问任何后端服务器,将会触发 503 Service Unavailable 错误。
    • haproxy 可以访问后端服务器,但是等待回复超时,将会触发 504 Gateway Timeout 错误。
  • haproxy 支持的状态码:200, 400, 401, 403, 404, 405, 407, 408, 410, 425, 429, 500, 502, 503, and 504.

一般情况下,默认的错误状态码匹配错误页面就够用户使用,如果想要自定义错误状态码匹配错误页面,需要检查服务器返回的状态码,然后给它一个自定义的匹配错误页面。比如,在 frontend 配置段,替代标准的 404 错误页面。

http-errors myerrors
    errorfile 404 /etc/haproxy/errors/404.http

frontend site1
    bind :80
    default_backend webservers
    errorfiles myerrors
    http-response return status 404 default-errorfiles if { status 404 }
  • http-response return 指令拦截状态为 404 的响应,并从 myerrors 部分返回一个定制的错误页面,该指令的 default-errorfiles 参数表示要参考 errorfiles 指定的 http-errors 段,从中提取自定义的错误页面。

然而,有些人通过同一个前端代理多个网站,然后根据接收到的 host header 将请求路由到不同的后端。在这种情况下,可以使用条件语句根据用户访问的网站动态选择不同的 http-errors 部分。比如:

http-errors site1
    errorfile 404 /etc/haproxy/errors/site1-404.http

http-errors site2
    errorfile 404 /etc/haproxy/errors/site2-404.http

frontend allsites
    bind :80
    default_backend site1-servers
    use_backend site2-servers if { req.hdr(host) site2.com }

    # Store host header in variable
    http-request set-var(txn.host) req.hdr(host)

    # Use site1 error page if site1.com
    http-response return status 404 errorfiles site1 if { status 404 } { var(txn.host) site1.com }

    # Use site2 error page if site2.com
    http-response return status 404 errorfiles site2 if { status 404 } { var(txn.host) site2.com }
  • 如果网站是 site1.com,则返回 site1 错误页面;如果网站是 site2.com 返回 site2 错误页面。这是通过检查传入 host header(它显示网站的名称),然后使用 if 语句执行与该名称匹配的 http-response return 行来实现的。
  • 注意,您需要通过使用 http-request set-var 指令将 host header 存储在一个变量中,否则 HAProxy 在响应阶段将无法访问它。HAProxy 能够通过相同的前端代理多个网站,然后根据条件逻辑选择合适的后端。使用这种技术,您可以将错误页面与请求的站点相匹配。

9.4.16、cookie 和 option redispatch

在 backend 服务器组启用 cookie 功能,以便实现 cookie 绑定。需要同时设置 server 指令中的 cookie 选项。

# 后端为静态服务器设置:
cookie NAME insert nocache

# PHP做后端时设置:
cookie SESSION_COOKIE insert indirect nocache

当客户端绑定 cookie 对应的后端服务器宕机后,应该为此客户端重新调度一个后端 server,否则将打不开页面。这时需要使用 option redispatch,表示当找不到 cookie 对应的服务器时分配新的服务器给客户端。

9.4.17、Timeout

时间单位默认都是毫秒。

timeout http-request

haproxy 等待客户端请求发送完整的超时时长。如果一开始发送了一部分,后续没有再发送,或者后续发送的一直是请求的某一部分,等达到超时时间将断开此连接。这可以防止 DoS 攻击。

timeout queue

当调度的后端服务器已经满负载了,即达到了该 backend 的最大并发连接数时,后续要调度到此 backend 的请求将进入队列等待后端服务器释放可用。该超时时间设置的就是某一请求在队列中的最大等待时长,当达到此时长后将被认为该请求永远无法到达服务端,haproxy 会丢弃该请求并向客户端返回 503 状态码。

timeout connect 和 retries

haproxy 要和后端服务器建立连接时等待超时时间。一般如果 haproxy 和后端服务器处于局域网中,建立连接是瞬间的,所以该值可以设置的小一些。

retries 表示和服务端建立连接失败时重试连接的次数。

注意:在健康检查时,将取 timeout connect 和 inter 的较小者作为检查时建立 tcp 连接的超时时间。

timeout client

客户端和 haproxy 之间非活动连接保持的最大时长,达到此时长 haproxy 将断开和此客户端的连接。非活动表示客户端没有请求报文发送给 haproxy。

timeout server

服务端和 haproxy 之间非活动连接保持的最大时长,达到此时长 haproxy 将断开和此服务器的连接。非活动表示服务端没有响应报文发送给 haproxy。

timeout http-keep-alive

等待出现 http 请求报文出现的最大时长,即和客户端保持长连接的时长。建议设置小一些,以尽快释放连接,例如设置为 2-3 秒钟。

如果此项未设置,则使用 timeout http-request 值,如果 timeout http-request 也没设置,则使用 timeout client 的值。

timeout check

在和服务端建立连接后,健康状况检查判断的超时时长(意思是,在建立 TCP 连接后,后端节点需要响应消息给 haproxy,响应的消息可能是:

  1. httpcheck 方式时,经过 hash 计算的 http 页面。
  2. tcp 检查方式时的回复消息。如果 haproxy 隔了一段时间,都没有接收完这些消息,就认为这次检查不健康。

因此 timeout check 是 read 回应消息的超时时长。而 min("timeout connect","inter") 则是建立 tcp 连接的超时时间,它们之和才是一次不健康检查的总时间)。

9.4.18、http 协议过滤:http-request

http-request {allow | auth [realm <realm>] | redirect <rule> | deny [deny_status <status>]} [{if | unless} <condition>]

做 7 层 http 协议过滤。当 http 协议相关项满足条件时执行一个 action,可以执行的 action 非常多,此处只列出了几项:

  • allow:表示接受该 http 请求。
  • auth:表示提示输入用户认证信息,指定 realm 时将给出提示信息。
  • redirect:重定向功能。
  • deny:表示拒绝该 http 请求。
acl nagios src 192.168.129.3
acl local_net src 192.168.0.0/16
acl auth_ok http_auth(L1)

http-request allow if nagios
http-request allow if local_net auth_ok
http-request auth realm Gimme if local_net auth_ok
http-request deny

9.4.19、tcp 请求和响应过滤

tcp-request content <action> [{if | unless} <condition>] 
tcp-response content <action> [{if | unless} <condition>]

做 4 层协议过滤。当满足条件时,haproxy 对 tcp 请求或响应报文执行某个 action。

对于 request 而言,只能用于 listen 和 frontend。常用的 action 是 accept 和 reject,表示满足条件时 haproxy 接受或拒绝该请求报文。

对于 response 而言,只能用于 listen 和 backend。常用的 action 是 accept、reject 和 close,前两者表示满足条件时接受或拒绝该响应报文,close 表示满足条件时立即关闭和服务端的连接。

9.5、ACL

9.5.1、ACL 语法

acl <aclname> <criterion> [flags] [operator] [<value>] ...

aclname:        指定acl的名称,在引用时区分大小写。可随意指定,且多个acl指令可以指定同一个aclname,这表示"或"的逻辑关系。
flags:          可选项,表示标识位。一般会用到的标识位只有"-i",表示不区分大小写。
operator:       可选项,某些操作符,有"eq"、"ge"、"gt"、"le"、"lt",表示数学上的等于、大于、小于。
<criterion>:  指定检查标准,即检查方法。见下文给出的常用4层标准和7层标准
value:          根据criterion的不同,值的类型不同。
  1. 4 层常用检查标准,官方手册:http://cbonte.github.io/haproxy-dconv/2.4/configuration.html#7.3.3

    • src <ip_addr>
    • src_port <PORT or PORT_ranges>
    • dst <ip_addr>
    • dst_port <PORT or PORT_ranges>

    其中 src、src_port、dst 和 dst_port 就是检查标准 creiterion,其后的值就是 value。

    acl accept_clients src 192.168.100.0/24
    acl reject_clients src 172.16.0.0/16
    tcp-request content accept if accept_clients
    tcp-request content reject if reject_clients
    tcp-request content reject   # 此项表明不匹配前两项的默认都拒绝
  2. 7 层常用检查标准,官方手册:https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#7.3.6

    • hdr(HEADER):检查首部字段的值是否为指定的值,如:

      • hdr(Connection) -i close 表示首部字段 Connection 的值是否为不区分大小写的 close。
      • hdr(Host) -i www.brinnatt.com 表示首部字段 Host 的值是否为 www.brinnatt.com,即请求的主机是否是指定的值。
    • hdr_reg(HEADER):检查首部字段是否匹配指定的模式,如:

      • hdr_reg(Host) -i .*\.brinnatt\.com
    • http_first_req:当正处理的请求是第一个请求时返回 true。

    • method:请求的方法为指定的方法时返回方法对应的数值,也就表示 true。例如 "method GET"。

      acl valid_method method GET
      http-request deny if ! valid_method
    • path:匹配 uri 的 path 部分,一般用来匹配精确的文件资源。例如 path -i /a.png

    • path_beg:匹配 path 的前缀部分。

    • path_end:匹配 path 的后缀部分。

    • path_reg:使用正则表达式来匹配 path。

    • url:对整个 url 进行匹配。

    • url_beg:对 url 的前缀进行匹配。

    还有很多很多检查方法,更多的查询官方手册,太多了。一般 4 层的检查标准和 7 层对路径 path 和首部 hdr 的标准就够了。

    多个条件使用 "AND"、"OR"、"!" 操作符表示逻辑与、逻辑或和取反,不写时默认的操作符是 "AND"。

9.5.2、ACL 实现动静分离示例

acl url_static path_beg /static /images /img /css /viedo /download  # 定义静态检查标准
acl url_static path_end .gif .png .jpg .css .js .bmp                # 定义静态检查标准
acl host_www   path /index.html                                     # 为主页专门定制acl
acl url_dynamic path_end .php .php5                                 # 定义动态检查标准
acl host_www    hdr_beg(Host) -i www.longshuai.com                  # 定位到主页
use_backend static  if url_static
use_backend dynamic if url_dynamic
use_backend www if host_www
标签云