跳转到主要内容

2. 配置 HAProxy

文件语法、引号、变量、条件、时间与大小格式、地址及示例

2.1. 配置文件格式

HAProxy 的配置过程涉及三个主要参数来源:

  • 命令行参数,始终具有最高优先级
  • 配置文件,其格式在此处描述
  • 运行进程的环境,若显式引用了某些环境变量

配置文件采用一种相对简单的分层格式,遵循若干基本规则:

1. a configuration file is an ordered sequence of statements

2. a statement is a single non-empty line before any unprotected "#" (hash)

3. a line is a series of tokens or "words" delimited by unprotected spaces or
   tab characters

4. the first word or sequence of words of a line is one of the keywords or
   keyword sequences listed in this document

5. all other words are all arguments of the first one, some being well-known
   keywords listed in this document, others being values, references to other
   parts of the configuration, or expressions

6. certain keywords delimit a section inside which only a subset of keywords
   are supported

7. a section ends at the end of a file or on a special keyword starting a new
   section

编写一个简单但可靠的配置生成器只需了解以上内容,但这些信息不足以可靠地解析任意配置,也无法确定如何处理某些特殊情况。

首先,上述规则带来若干后果。规则 6 和 7 意味着,用于定义新段的关键词在任何位置均有效,且在特定段中不可具有不同含义。这些关键词始终为单个单词(而非单词序列),传统上,其后跟随的段以相同名称进行标识。例如,提及“global 段”时,即指 “global” 关键字之后的配置段。此用法在错误消息中广泛使用,以帮助定位需处理的部分。

若干段会创建一个内部对象或配置空间,需与其他对象或空间区分开。此时,这些段将额外使用一个词来指定该特定段的名称。部分段的名称为必填项。例如,“frontend foo”将创建一个类型为 “frontend”、名称为 “foo” 的新段。通常,名称仅在其所属段内具有唯一性,不同类型段可使用相同名称,但不建议如此,因为这会增加配置管理的复杂性。

规则 7 的直接后果是,当同时读取多个文件时,每个文件都必须以新的段开始,且每个文件的结尾将结束一个段。文件中不能包含子段,也不能结束一个现有段并开始一个新的段。

规则 1 提到顺序至关重要。确实,某些关键字会创建可重复的指令,从而形成按特定顺序应用的规则序列。例如,“tcp-request” 可用于根据不同的条件交替使用 “accept” 和 “reject” 规则。因此,配置文件处理器在编辑文件时必须始终保留段的顺序。段的顺序通常无关紧要,但全局段必须置于其他段之前,必要时可重复出现。此外,某些自动标识符可能被自动分配给部分创建的对象(例如代理),通过重新排序段,其标识符将发生变化。这些标识符会在统计信息中出现。因此,以下配置将使 “foo” 的 ID 号小于其 “bar” 对应项的 ID 号。若两个段顺序互换,该 ID 号将随之对调:

listen foo
    bind:80

listen bar
    bind:81

另一个重要点是,根据上述规则 2 和 3,紧跟在未受保护的 “#” 字符之后的空行、空格、制表符以及注释均不属于配置内容,仅用作分隔符。这意味着以下配置在语法上完全等价:

    global#this is the global section
daemon#daemonize
    frontend         foo
mode             http   # or tcp

和:

global
    daemon

# this is the public web frontend
frontend foo
    mode http

通常的做法是,仅将启动新段的关键词左对齐,其余关键词则缩进(即在前面添加制表符或若干空格),以便立即看出它们属于同一段(如上文第二个示例所示)。在新段之前添加注释,有助于读者判断该段是否为所需内容。在段末留空行,也有助于在编辑时直观识别段的结束位置。

制表符在缩进时非常方便,但复制粘贴时表现不佳。若改用空格,建议避免使用过多空格(2 到 4 个),以免在不支持自动缩进的编辑器中编辑时造成负担。

在早期,人们常将参数按固定制表位分割,因为大多数关键字最多只需两个参数。随着现代版本引入复杂表达式,这种做法已不再适用,且不建议继续使用。

2.2. 引用与转义

在现代配置中,某些参数需要使用此前被视为纯分隔符的字符。为实现此目的,HAProxy 支持通过在待转义字符前添加反斜杠(’\’)进行字符转义,使用双引号("")对文本片段进行弱引号包围,以及使用单引号(’’)对文本片段进行强引号包围。

这与多种编程语言中的处理方式非常相似,也与 Bourne shell 中常见的做法极为接近。其原理如下:当配置解析器将行拆分为单词时,会同时处理引号和反斜杠,以判断某个字符是分隔符,还是当前单词中该字符的原始表示。随后,转义字符被移除,引号被去除,剩余部分直接作为关键字或参数使用。

如果在单词中需要使用反斜杠,必须通过自身转义(即双反斜杠)或使用强引号包围。

转义引号外的特殊字符需在该字符前添加反斜杠(’\’):

\    to mark a space and differentiate it from a delimiter
\#   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

此外,部分不可打印字符可使用其常见的 C 语言表示法输出:

\n   to insert a line feed (LF, character \x0a or ASCII 10 decimal)
\r   to insert a carriage return (CR, character \x0d or ASCII 13 decimal)
\t   to insert a tab (character \x09 or ASCII 9 decimal)
\xNN to insert character having ASCII code hex NN (e.g \x0a for LF).

弱引用通过在字符或字符序列周围加上双引号("")来实现。弱引用可防止以下内容的解释:

     space or tab as a word separator
'    single quote as a strong quoting delimiter
#    hash as a comment start

弱引号允许通过在环境变量前加上美元符号($)来解释这些变量(变量仅在引号内求值)。若需在双引号内使用美元符号,必须使用反斜杠进行转义。

强引用通过在字符或字符序列周围使用单引号(’’)来实现,以保护其不被解释。在单引号内,任何内容均不会被解析,这是引用正则表达式最高效的方式。

因此,以下是不同上下文中特殊字符输入方式的对照矩阵(不可打印字符以尖括号内的名称代替)。请注意,部分仅能通过转义方式表示的字符在单引号内无法表示,故该处为空:

  Character  |  Unquoted     |  Weakly quoted              |  Strongly quoted
  -----------+---------------+-----------------------------+-----------------
    <TAB>    |  \<TAB>, \x09 |  "<TAB>", "\<TAB>", "\x09"  |  '<TAB>'
  -----------+---------------+-----------------------------+-----------------
    <LF>     |  \n, \x0a     |  "\n", "\x0a"               |
  -----------+---------------+-----------------------------+-----------------
    <CR>     |  \r, \x0d     |  "\r", "\x0d"               |
  -----------+---------------+-----------------------------+-----------------
    <SPC>    |  \<SPC>, \x20 |  "<SPC>", "\<SPC>", "\x20"  |  '<SPC>'
  -----------+---------------+-----------------------------+-----------------
    "        |  \", \x22     |  "\"", "\x22"               |  '"'
  -----------+---------------+-----------------------------+-----------------
    #        |  \#, \x23     |  "#", "\#", "\x23"          |  '#'
  -----------+---------------+-----------------------------+-----------------
    $        |  $, \$, \x24  |  "\$", "\x24"               |  '$'
  -----------+---------------+-----------------------------+-----------------
    '        |  \', \x27     |  "'", "\'", "\x27"          |
  -----------+---------------+-----------------------------+-----------------
    \        |  \\, \x5c     |  "\\", "\x5c"               |  '\'
  -----------+---------------+-----------------------------+-----------------

示例:

# those are all strictly equivalent:
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

在特定情况下,可能需要进行第二层引号或转义操作。某些关键字的参数位于括号内,有时由逗号分隔。这些参数通常为整数或预定义词,但当参数为任意字符串时,可能需要进行额外的转义处理,以明确区分属于参数本身的字符与用于分隔参数的字符。一个常见的情况是 “regsub” 转换器。该转换器的参数为正则表达式,若在正则表达式内部需要使用右括号,则该右括号必须使用自身的引号进行包裹。

关键字参数解析器在引号处理方面与顶层解析器完全相同,但不会处理 \#、\$ 和 \xNN 的转义序列。但并非总是显而易见的是,内部使用的分隔符必须先进行转义或加引号,以确保它们不会在顶层被解析。

本例使用 “regsub” 转换器,该转换器接受三个参数:一个正则表达式、一个替换字符串以及一组标志。

# replace all occurrences of "foo" with "blah" in the path:
http-request set-path %[path,regsub(foo,blah,g)]

此处无需特殊引号。但若现在我们想将 “foo” 或 “bar” 替换为 “blah”,则需要使用正则表达式 “(foo|bar)"。无法编写:

http-request set-path %[path,regsub((foo|bar),blah,g)]

因为我们希望字符串按如下方式截断:

    http-request set-path %[path,regsub((foo|bar),blah,g)]
                                       |---------|----|-|
                                 arg1 _/         /    /
                                 arg2 __________/    /
                                 arg3 ______________/

但实际传递的是括号内开头与结尾之间的字符串,随后是垃圾数据:

    http-request set-path %[path,regsub((foo|bar),blah,g)]
                                       |--------|--------|
                        arg1=(foo|bar _/        /
                    trailing garbage  _________/

显然,此处的解决方案似乎是需要对右括号进行转义,但仅这样做无效,因为如上所述,引号由顶层解析器处理,会在处理该单词之前被解析:

http-request set-path %[path,regsub("(foo|bar)",blah,g)]
------------ -------- ----------------------------------
   word1       word2    word3=%[path,regsub((foo|bar),blah,g)]

因此,我们未对第二层的参数解析器进行任何修改,它仍然将截断的正则表达式视为唯一参数,并将字符串末尾的垃圾数据视为无效输入。通过转义引号,这些引号将原样传递至第二层:

    http-request set-path %[path,regsub(\"(foo|bar)\",blah,g)]
    ------------ -------- ------------------------------------
       word1       word2    word3=%[path,regsub("(foo|bar)",blah,g)]
                                                |---------||----|-|
                                arg1=(foo|bar) _/          /    /
                                    arg2=blah  ___________/    /
                                        arg3=g _______________/

另一种方法是使用单引号包围整个字符串,而在字符串内部使用双引号(以避免双引号被再次去除):

    http-request set-path '%[path,regsub("(foo|bar)",blah,g)]'
    ------------ --------  ----------------------------------
       word1       word2    word3=%[path,regsub("(foo|bar)",blah,g)]
                                                |---------||----|-|
                                arg1=(foo|bar) _/          /    /
                                          arg2 ___________/    /
                                          arg3 _______________/

但在这种情况下,需要注意的是,嵌入到高层级字符串中的分隔符仍为纯字符,不再作为分隔符使用。这尤其意味着,逗号周围的空格和制表符属于字符串的一部分。以下示例在多个方面均存在错误:

    http-request set-path '%[path, regsub("(foo|bar)", blah, g)]'
    ------------ --------  --------------------------------------
       word1       word2    word3=%[path, regsub("(foo|bar)", blah, g)]
                                        |--------|---------||-----|--|
                       converter=" regsub" _/        /         /   /
                                    arg1=(foo|bar) _/         /   /
                                     arg2=" blah" ___________/   /
                                        arg3=" g" ______________/

仅因在逗号周围添加空格,导致空格成为字段本身的一部分,因此转换器 " regsub”(以空格开头)将无法被找到,从而触发错误;更微妙的是,替换字符串 " blah" 会在输出中插入一个空格。一个良好的经验是,切勿在表达式中插入不必要的空格。

使用正则表达式时,可能出现美元符号($)出现在表达式中,或在替换字符串中使用反斜杠(\)的情况。此时这些字符也会在双引号内被处理,因此建议使用单引号(或双重转义)。示例:

    http-request set-path '%[path,regsub("^/(here)(/|$)","my/\1",g)]'
    ------------ --------  -----------------------------------------
       word1       word2    word3=%[path,regsub("^/(here)(/|$)","my/\1",g)]
                                                |-------------| |-----||-|
                              arg1=(here)(/|$) _/               /      /
                                    arg2=my/\1 ________________/      /
                                          arg3 ______________________/

请注意,单引号内的反斜杠不是转义字符,且上述整个单词已通过单引号完全保护,避免了反斜杠的解析。反之,若整个表达式使用双引号包围,则单个美元符号和反斜杠将在顶层被解析,导致第二层参数内容被破坏。

由于单引号无法在强引用内部进行转义,若需在参数中包含单引号,必须对它们进行双重转义或双重引用。有几种实现方式:

http-request set-var(txn.foo) str("\\'foo\\'")
http-request set-var(txn.foo) str(\"\'foo\'\")
http-request set-var(txn.foo) str(\\\'foo\\\')

如有疑问,建议在任何位置均不使用引号,仅在需要逗号或右括号的参数周围添加单引号或双引号,并在字符串包含美元符号或反斜杠时考虑使用反斜杠进行转义。再次强调,这与在 Bourne shell 中对传递给 “eval” 的命令进行双重转义时的做法非常相似。对于 API 开发者而言,最稳妥的做法是无论参数内容如何,均对每个参数使用转义引号。用户可能会发现,将整个表达式用单引号包围,每个参数用双引号包围,可提供更具可读性的配置。

2.3. 环境变量

HAProxy 的配置支持环境变量。这些变量仅在双引号内被解析。变量在配置解析期间展开。变量名必须以美元符号($)开头,可选地用花括号({})括起,与 Bourne shell 中的做法类似。变量名可包含字母数字字符或下划线("_"),但不应以数字开头。若变量包含多个以空格分隔的值,可通过将变量用花括号括起并在闭合花括号前添加后缀 ‘[*]’,将其展开为独立参数。也可在变量名后紧接一个连字符 ‘-’ 并指定默认值,以在变量未设置时使用该值。请注意,此默认值仅替换未定义的变量,而非空值。

示例:

bind "fd@${FD_APP1}"

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

user "$HAPROXY_USER"

HAProxy 定义了一些变量,可在配置文件中使用。这些变量列于下方矩阵中,并按四类进行分类:

  • usable:该变量可在配置中访问,既可直接解析使用,也可用于条件块或谓词中,以启用或禁用某些配置片段,具体说明参见 第 2.4 节 “条件块”。

  • 可修改:该变量可通过 “setenv”/“unsetenv” 关键字在配置中重新定义或取消设置。

  • listed:该变量会显示在 CLI 的 “show env” 命令输出中,详见管理指南 第 9.3 节 “Unix 套接字命令”。

此外,还存在两个子类别 “master” 和 “worker”,分别在下表中标记为 ‘M’ 和 ‘W’,展示了 HAProxy 以主从模式启动时两个进程之间的差异。

  • master:该变量在主进程(master process)中被设置并可访问。因此,它将出现在主进程 CLI 的 “show env” 输出中,并可用于条件块或指令中,以为主进程启用某些特殊设置(参见 第 2.4 节 “条件块”中的示例)。

  • worker:该变量在工作进程内设置并可访问。它将出现在工作进程 CLI 的 “show env” 命令(或主进程 CLI 的 “@1 show env” 命令)中,也可用于条件控制工作进程的某些参数(参见 第 2.4 节 “条件块” 中的示例)。

在独立模式下(未使用 “-W” 选项或 “master-worker” 关键字),进程的行为类似于工作进程,但变量 “HAPROXY_MASTER_CLI” 和 “HAPROXY_MWORKER” 未被定义。

部分变量标记为不可用且不可修改:

  • HAPROXY_CFGFILES
  • HAPROXY_MWORKER
  • HAPROXY_CLI
  • HAPROXY_MASTER_CLI
  • HAPROXY_LOCALPEER

在配置解析期间,这些变量的值未定义,它们将在初始化阶段被设置。因此,建议不要在条件块中使用这些变量,也不要在全局段的 “setenv”/“resetenv”/“unsetenv” 关键字中引用它们。

下表总结了各变量在不同工作模式下的状态:

  +---------------------------+---------+------------+-----------+
  |          variable         | usable  | modifiable |  listed   |
  |                           +---------+------------+-----------+
  |                           |  M | W  |   M  |  W  |  M  |  W  |
  +---------------------------+----+----+------+-----+-----+-----+
  | HAPROXY_STARTUP_VERSION   |  X | X  |      |     |  X  |  X  |
  | HAPROXY_BRANCH            |  X | X  |      |     |  X  |  X  |
  | HAPROXY_CFGFILES          |    |    |      |     |  X  |  X  |
  | HAPROXY_MWORKER           |    |    |      |     |  X  |  X  |
  | HAPROXY_CLI               |    |    |      |     |     |  X  |
  | HAPROXY_MASTER_CLI        |    |    |      |     |  X  |     |
  | HAPROXY_LOCALPEER         |    | X  |      |     |     |  X  |
  | HAPROXY_HTTP_LOG_FMT      |    | X  |      |  X  |     |     |
  | HAPROXY_HTTP_CLF_LOG_FMT  |    | X  |      |  X  |     |     |
  | HAPROXY_HTTPS_LOG_FMT     |    | X  |      |  X  |     |     |
  | HAPROXY_TCP_LOG_FMT       |    | X  |      |  X  |     |     |
  | HAPROXY_TCP_CLF_LOG_FMT   |    | X  |      |  X  |     |     |
  | HAPROXY_KEYLOG_FC_LOG_FMT |    | X  |      |  X  |     |     |
  | HAPROXY_KEYLOG_BC_LOG_FMT |    | X  |      |  X  |     |     |
  +---------------------------+----+----+------+-----+-----+-----+

相关变量如下:

  • HAPROXY_LOCALPEER:在包含本地对等节点名称的进程启动时定义。(参见管理指南中的 “-L”。)

  • HAPROXY_CFGFILES:HAProxy 加载的配置文件列表,各文件间以分号分隔。在指定目录的情况下可能有用。

  • HAPROXY_HTTP_LOG_FMT:包含默认 HTTP 日志格式的值,定义于 第 8.2.3 节 “HTTP 日志格式”。可用来覆盖默认日志格式,而无需复制完整的原始定义。

  • HAPROXY_HTTP_CLF_LOG_FMT:包含默认 HTTP CLF 日志格式的值,定义于 第 8.2.3 节 “HTTP 日志格式”。可用来覆盖默认日志格式,而无需复制完整的原始定义。

示例:

# Add the rule that gave the final verdict to the log
log-format "${HAPROXY_TCP_LOG_FMT} lr=%[last_rule_file]:%[last_rule_line]"
  • HAPROXY_HTTPS_LOG_FMT:与 HAPROXY_HTTP_LOG_FMT 类似,但适用于 第 8.2.4 节 中定义的 HTTPS 日志格式。

  • HAPROXY_TCP_LOG_FMT:与 HAPROXY_HTTP_LOG_FMT 类似,但适用于 第 8.2.2 节 “TCP 日志格式” 中定义的 TCP 日志格式。

  • HAPROXY_TCP_CLF_LOG_FMT:与 HAPROXY_HTTP_CLF_LOG_FMT 类似,但适用于 第 8.2.2 节 “TCP 日志格式” 中定义的 TCP CLF 日志格式。

  • HAPROXY_KEYLOG_FC_LOG_FMT:指定前端(客户端面向)TLS 连接的密钥日志格式,密钥条目以换行符分隔,因此可能与syslog 服务器不兼容。“tune.ssl.keylog on” 必须启用。

  • HAPROXY_KEYLOG_BC_LOG_FMT:与 HAPROXY_KEYLOG_FC_LOG_FMT 类似,但用于后端(服务器面向)的 TLS 连接。密钥条目以换行符分隔,可能与syslog 服务器不兼容。“tune.ssl.keylog on” 必须启用。

  • HAPROXY_MWORKER:在主进程/工作进程模式下,此变量被设置为 1。

  • HAPROXY_CLI:配置每个进程的统计信息套接字的监听器地址,地址之间以分号分隔。

  • HAPROXY_MASTER_CLI:在主进程/工作进程模式下,主进程 CLI 监听器地址,以分号分隔。

  • HAPROXY_STARTUP_VERSION:包含用于启动的版本信息。在主进程/工作进程模式下,即使更新了二进制文件并重载,该值仍为启动主进程时所用的版本。

  • HAPROXY_BRANCH:包含 HAProxy 分支版本(例如 “2.8”)。不包含完整版本号。在迁移场景下,若资源(如映射或证书)路径中包含分支号,则该信息可能有用。

此外,部分伪变量会内部解析,可作为普通变量使用。 伪变量始终以英文句点(’.’)开头,且仅此类变量允许使用句点。 当前支持的伪变量列表如下:

  • .FILE: 当前正在解析的配置文件的名称。

  • .LINE:当前正在解析的配置文件的行号,从 1 开始。

  • .SECTION:当前正在解析的段的名称,或若该段无名称则为其类型(例如 “global”),或在首个段之前为空字符串。

这些变量在被解析的位置进行解析。例如,若在 defaults 段中某个 “log-format” 指令里使用了 “.LINE” 变量,其行号将在解析和编译 “log-format” 指令之前被解析,因此后续代理将重用该行号。

通过这种方式,可以将信息输出至变量、日志、错误状态、健康检查、头字段值,甚至使用行号来命名某些配置对象(例如服务器),以帮助定位规则。

2.4. 条件块

有时,能够有条件地启用或禁用配置中的任意部分会非常方便,例如启用或禁用 SSL 或加密套件,无需修改配置即可启用或禁用某些预生产环境监听器,或调整配置语法以在迁移期间支持 HAProxy 的两个不同版本。HAProxy 提供了一组可嵌套的类似预处理器的指令,可用于集成或忽略某些文本块。这些指令必须单独占一行,且作用于其后的行。其中两个指令支持表达式,其余指令仅用于切换到备用块或结束当前层级。以下 4 个指令用于构成条件块:

  • .if <condition>
  • .elif <condition>
  • .else
  • .endif

“.if” 指令嵌套新层级,".elif" 保持在同一层级,".else" 同样如此,而 “.endif” 用于关闭层级。每个 “.if” 必须由对应的 “.endif” 结束。".elif" 仅可在 “.if” 或 “.elif” 之后放置,且 “.elif” 的链式连接数量无限制。每个 “.if” 中只能存在一个 “.else”,且其必须位于 “.if” 或区块中最后一个 “.elif” 之后。

注释可置于行尾,使用 ‘#’ 后跟注释内容,该部分将被忽略。指令的解析方式与其他配置指令相同,因此可在条件中使用环境变量。

也可以在启动时通过 -cc 参数评估条件。参见管理文档中的“3. 启动 HAProxy”节。

条件可以是空字符串(此时返回 false),或由以下任意组合构成的表达式:

  • 整数零(‘0’),始终返回 “false”
  • 非零整数(例如 ‘1’),始终返回 “true”
  • 一个可选的谓词,后接括号内的参数
  • 位于一对括号 ‘(’ 和 ‘)’ 之间的条件
  • 在上述任意非空元素前加上感叹号(’!’),用于对其状态取反
  • 使用逻辑与(’&&’)组合的表达式,从左到右依次求值,直到其中一个返回 false
  • 使用逻辑或(’||’)组合的表达式,从右到左依次求值,直到其中一个返回 true

与配置语言其余部分使用的行分词器和参数解析器相同。 单词在连续的一个或多个未加引号的空格或制表符处进行分割,并在评估前使用单个空格重新组合以分隔它们,从而避免用户必须对整行进行加引号。 但这同时也意味着,逗号或括号周围的空格肯定是值的一部分,这并不总是符合预期。例如,下面的表达式:

.if defined( HAPROXY_MWORKER )

将测试变量 " HAPROXY_MWORKER “(含空格)是否存在,以及此条:

.if streq("$ENABLE_SSL",     1)

将环境变量 “ENABLE_SSL” 与值 " 1”(前导单个空格)进行比较。 原因是该行首先被拆分为单词,格式如下:

   .if streq("$ENABLE_SSL",     1)
  |---|--------------------|   |--|
    1           2               3

然后应用弱引用,解析环境变量 “$ENABLE_SSL”(例如,假设 ENABLE_SSL=0),最后通过在各单词之间插入一个空格,将单词重新组合为一个字符串:

   .if streq(0, 1)
  |---|-------|--|
    1     2     3

且仅在此时,它才会被解析为单一表达式。插入在逗号与"1"之间的空格仍属于参数值的一部分,导致该参数为“ 1”:

   .if streq(0, 1)
  |---|-----|-|--|
    \    \    \  \_ argument2: " 1"
     \    \    \___ argument1: "0"
      \    \_______ function: "streq"
       \___________ directive: ".if"

可见,即使 ENABLE_SSL 等于 “1”,也不会匹配 " 1",因为字符串会因多一个空格而不同。

请注意:如第“2.2. 引号与转义”段所述,一个良好的经验是切勿在表达式中插入不必要的空格。

请注意,与其他语言类似,AND 运算符的优先级高于 OR 运算符,因此表达式“A && B || C && D”等价于“(A && B) || (C && D)”。

当前支持的谓词列表如下:

  • awslc_api_atleast(<ver>):若当前 awslc API 版本号不低于 <ver>,则返回 true,否则返回 false。示例:awslc_api_atleast(35)

  • awslc_api_before(<ver>):若当前 awslc API 版本号严格早于 <ver>,则返回 true,否则返回 false。示例:awslc_api_before(26)

  • defined(<name>) : 当环境变量 <name> 存在时返回 true,无论其内容为何

  • feature(<name>) : 当 “haproxy -vv” 报告的特性列表中包含 <name> 时返回 true(即在 ‘+’ 后出现 <name>)

  • openssl_version_atleast(<ver>):若当前 OpenSSL 版本不低于 <ver>,则返回 true,否则返回 false。LibreSSL、AWS-LC 和 WolfSSL 等库也提供伪 OpenSSL 版本。示例:

ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)
  • openssl_version_before(<ver>):若当前 OpenSSL 版本严格早于 <ver>,则返回 true,否则返回 false。LibreSSL、AWS-LC 和 WolfSSL 等库也提供伪 OpenSSL 版本。示例:openssl_version_before(3.5.0)

  • ssllib_name_startswith(<name>) :若 HAProxy 链接的 SSL 库名称以 <name> 开头,则返回 true。示例:ssllib_name_startswith(wolfSSL)

  • streq(<str1>,<str2>) : 仅当两个字符串相等时返回 true

  • strneq(<str1>,<str2>):仅当两个字符串不相等时返回 true

  • strstr(<str1>,<str2>):仅当第二个字符串在第一个字符串中找到时返回 true。

  • version_atleast(<ver>):若当前 HAProxy 版本不低于 <ver>,则返回 true,否则返回 false。版本语法与 “HAProxy -v” 所显示的相同,缺失的组件视为零。

  • version_before(<ver>):若当前 HAProxy 版本严格早于 <ver>,则返回 true,否则返回 false。版本语法与 “HAProxy -v” 所显示的相同,缺失的组件视为零。

  • enabled(<opt>) : 在运行时若选项 <opt> 已启用则返回 true。仅支持部分选项:

POLL, EPOLL, KQUEUE, EVPORTS, SPLICE,
GETADDRINFO, REUSEPORT, FAST-FORWARD,
SERVER-SSL-VERIFY-NONE

示例:

# 1. HAPROXY_MWORKER variable is set automatically by HAProxy in master and
# in worker process environments (see HAProxy variables matrix from
# 2.3. Environment variables). Its presence enables an additional listener.

global
  master-worker

.if defined(HAPROXY_MWORKER) listen mwcli_px bind:1111 … .endif

# 2. HAPROXY_BRANCH is set automatically by HAProxy in master and in worker
# process environments (see HAProxy variables matrix from 2.3. Environment
# variables). We check HAPROXY_BRANCH value and conditionally enable
# mworker-max-reloads parameter.

global
  master-worker

.if streq("$HAPROXY_BRANCH", 3.1) mworker-max-reloads 5 .endif

# 3. Some arbitrary environment variables are set by user in the global
# section. If HAProxy is started in master-worker mode, they are presented in
# master and in worker process environments. We check values of these
# variables and conditionally enable ports 80 and 443. Environment variables
# checks can be mixed with features and version checks.

global
  setenv WITH_SSL yes
  unsetenv SSL_ONLY

.if strneq("$SSL_ONLY", yes) bind:80 .endif

若 $WITH_SSL 的值为 yes 且 OpenSSL 功能可用,则绑定端口 443 并启用 SSL,指定证书文件为 …。

若启用 OpenSSL 功能且 “$WITH_SSL",yes) || streq("$SSL_ONLY” 的值为 yes,则绑定端口 443 并启用 SSL,使用 crt 参数指定证书…

.如果版本不低于 2.4-dev19,则启用内存分析功能 .结束条件

.if !feature(OpenSSL) .alert “SSL 支持为必需项” .endif

此外还提供了四个指令用于报告某些状态:

  • .diag “message” : 仅在诊断模式下(-dD)输出此消息
  • .notice “message” : 以 NOTICE 级别输出此消息
  • .warning “message” : 以 WARNING 级别输出此消息
  • .alert “message” : 以 ALERT 级别输出此消息

警告级别发出的消息在 “zero-warning” 启用时可能导致进程无法启动。警报级别发出的消息将始终导致致命错误。这些消息可用于检测某些不适当的状态,并向用户提供建议。

示例:

.if "${A}"
  .if "${B}"
     .notice "A=1, B=1"
  .elif "${C}"
     .notice "A=1, B=0, C=1"
  .elif "${D}"
     .warning "A=1, B=0, C=0, D=1"
  .else
     .alert "A=1, B=0, C=0, D=0"
  .endif
.else
     .notice "A=0"
.endif

.diag "WTA/2021-05-07: replace 'redirect' with 'return' after switch to 2.4"
      http-request redirect location /goaway if ABUSE

2.5. 时间格式

某些参数涉及表示时间的值,例如超时。这些值通常以毫秒为单位(除非另有明确说明),但也可以通过在数值后附加单位来表示其他单位。这一点非常重要,因为这不会在每个关键字中重复说明。支持的单位包括:

  • us:微秒。1 微秒 = 1/1000000 秒
  • ms:毫秒。1 毫秒 = 1/1000 秒。这是默认值。
  • s:秒。1s = 1000ms
  • m:分钟。1m = 60s = 60000ms
  • h:小时。1h = 60m = 3600s = 3600000ms
  • d:天。1d = 24h = 1440m = 86400s = 86400000ms

2.6. 大小格式

某些参数涉及表示大小的数值,例如带宽限制。这些数值通常以字节为单位(除非另有明确说明),但也可以通过在数值后附加单位来表示其他单位。这一点非常重要,因为这不会在每个关键字中重复说明。支持的单位不区分大小写:

  • k:千字节。1 千字节 = 1024 字节
  • m:兆字节。1 兆字节 = 1048576 字节
  • g:吉字节。1 吉字节 = 1073741824 字节

时间格式和大小格式均需使用整数,不支持小数表示。

2.7. 映射和 ACL 的名称格式

可以为映射或 ACL 使用模式列表。模式列表通过名称进行标识,可在配置中的多个位置使用。模式列表根据名称格式分为三类:

  • 基于常规文件的模式列表:这是默认情况。文件名(绝对或相对路径)用作名称。文件必须存在,否则将触发错误。但文件可以为空。也可指定 “file@” 前缀,但该前缀不参与标识列表的名称。无论是否包含前缀,文件名均引用同一组模式列表。

  • 基于可选文件的模式列表:文件名必须以“opt@”前缀开头。文件是否存在为可选。若文件存在,则加载其内容;若文件不存在,不报告错误。前缀不参与标识列表的名称。这意味着,对于给定的文件名,可选文件与普通文件引用的是同一组模式列表。

  • 基于虚拟文件的模式列表:名称仅作为标识符使用,不指向任何文件。必须使用 “virt@” 前缀,该前缀属于名称的一部分,因此不能与其他类型列表混合使用。

虚拟文件在模式完全动态管理且启动及重载时均无预设模式的情况下非常有用。在相同条件下,也可使用可选文件。但可通过外部脚本(例如基于 “show map” CLI 命令)将模式转储到文件中。借此方式,可在重载时保留模式。

请注意:即使可能性极低,这意味着无法加载任何以 “file@"、“opt@” 或 “virt@” 开头的普通文件,除非在文件名前显式添加 “./"(例如 “file@./virt@map”)。

2.8. 变量

在 HAProxy 配置中,变量可用于样本提取函数、转换器、日志格式字符串或 TCP/HTTP 动作中。进程级变量可被定义,其作用域覆盖整个进程生命周期,全局可访问。部分变量的生命周期较短。变量类似于 shell 脚本中的变量,是内存块的符号名称。变量大小无限制,且动态分配内存。因此必须谨慎使用,尤其在高频率使用场景下。然而,可通过设置 “tune.vars” 全局参数来限制变量所使用的最大内存总量。

变量必须使用格式 “<scope>.<name>” 进行指定。<scope> 为一个单词,用于表示变量的生命周期。<name> 部分在作用域内,仅可包含字符 ‘a-z’、‘A-Z’、‘0-9’ 和 ‘_’。该名称在当前作用域内唯一,但不同作用域中可使用相同名称,分别指向不同的变量。支持的作用域包括:

  • proc:用于定义在整个进程生命周期内始终存在且全局可访问的变量。“proc” 变量可通过 CLI 使用 “get var” 和 “set var” 命令进行操作。也可通过 “global” 段中的 “set-var” 和 “set-var-fmt” 指令进行设置。

  • sess:用于表示在整个会话生命周期内已知的变量。“sess” 变量仅属于特定会话,对外不可见,且不会与其他会话共享。

  • txn:用于在事务整个生命周期内已知的变量。“txn” 变量仅属于流,对外不可见,且不与其他流共享。

  • req:用于在特定流的请求处理过程中已知的变量。“req” 变量从流创建时起可见,直至首次尝试连接服务器为止。它们仅对流私有,外部不可见,且不与其他流共享。“req” 与 “res” 变量之间不存在任何重叠。

  • res : 用于在特定流的响应处理期间已知的变量。“res” 变量从首次服务器连接尝试起即可访问,直至流销毁为止。它们仅对流私有,外部不可见,且不与其他流共享。“req” 与 “res” 变量之间不存在任何重叠。

  • check: 用于在健康检查执行期间已知的变量。“check” 变量仅对健康检查私有,外部不可见,且不会与其他健康检查共享。可使用专用的 “tcp-check” 或 “http-check” 指令进行设置。

根据上下文,可使用额外的作用域来引用当前流的父级:

  • psess:与 “sess” 相同,但使用父流的会话(如果存在)。

  • ptxn:与 “txn” 相同,但使用父流的事务(若存在)。

  • preq : 与 “req” 相同,但使用父流(如有)。“preq” 变量仅在父流的请求处理期间可访问。

  • pres:与 “res” 相同,但使用父流(如果存在)。“pres” 变量仅在父流的响应处理期间可访问。

引用父流的范围从其定义之时起即可使用。大多数情况下,不存在父流。但若适用,将明确说明。目前,仅可获取父流作用域中定义的变量值,无法设置或取消设置此类变量。通常,子流会在特定时刻为父流执行某些处理,并阻止父流在该操作完成前继续推进。这意味着父流可能在请求处理或响应处理过程中被中断。因此,某些作用域从子流中不可用。例如,若请求需由子流执行分析,则该子流在 “pres” 作用域中将找不到任何变量,因为父流当前并未处理响应,因而其 “res” 作用域中也无任何变量。

变量的内容是样本提取表达式求值的结果,并继承该表达式输出的类型。在使用变量时,其类型必须与使用方式兼容。例如,用于“add()”转换器的字符串类型变量必须可转换为有效整数才能成功。当变量与静态值进行比较时,这一点尤为重要,必须使用正确的匹配方法。

2.9. 地址格式

多个语句,如 “bind, “server”, “nameserver” 和 “log”",需要指定地址。

该地址可以是主机名、IPv4 地址、IPv6 地址或 ‘’。’’ 等同于特殊地址 “0.0.0.0”,在 “bind” 或 “dgram-bind” 的情况下,可用于监听系统的所有 IPv4 地址。IPv6 的等效地址为 ‘::’。

根据语句类型,IP 地址后跟端口或端口范围。在 ‘bind’ 语句中为必选项,在 ‘server’ 语句中为可选项。

该地址也可以以斜杠 ‘/’ 开头。它被视为 “unix” 协议族,且必须包含斜杠 ‘/’ 及其后的路径字符。

默认套接字类型或传输方法 “datagram” 或 “stream” 取决于配置语句中显示的地址。实际上,‘bind’ 和 ‘server’ 默认将使用 “stream” 套接字类型,而 ’log’、’nameserver’ 或 ‘dgram-bind’ 将使用 “datagram”。

可选地,可使用前缀强制指定地址族和/或套接字类型及传输方法。

2.9.1. 地址族前缀

‘abns@<name>’ 后接 <name> 是一个抽象命名空间(仅限 Linux)。

‘abnsz@<name>’ 后跟 <name> 是一个以空字符结尾的抽象命名空间(仅限 Linux)。

‘fd@<n>’ 后跟的地址是一个由父进程继承的文件描述符 <n>。该文件描述符必须已绑定,且可能已处于监听状态,也可能尚未开始监听。

‘ip@<address>[:port1[-port2]]’ 在 <address> 后被视为 IPv4 或 IPv6 地址,具体取决于语法。根据使用该地址的语句,可能需要指定端口或端口范围,也可能必须指定。

‘ipv4@<address>[:port1[-port2]]’ 在 <address> 之后始终被视为 IPv4 地址。根据使用该地址的语句,可能需要指定端口或端口范围,也可能必须指定。

‘ipv6@<address>[:port1[-port2]]’ 在 <address> 之后始终被视为 IPv6 地址。根据使用此地址的语句,可能需要指定端口或端口范围,也可能必须指定。

‘sockpair@<n>’ 后跟的地址是已连接的 Unix 套接字或 socketpair 的文件描述符。在建立连接时,发起方创建一对已连接的套接字,并将其中一个文件描述符通过文件描述符传递给另一端。监听器等待从 Unix 套接字接收该文件描述符,并将其当作 accept() 返回的文件描述符使用。应谨慎使用。

           Bugs: This protocol is known to be unreliable on macOS because
           of an issue in the macOS sendmsg(2) implementation. The
           connection might not be accepted correctly.

‘unix@<path>’ 后跟的字符串被视为 Unix 套接字 <path>。此前缀可用于声明以斜杠 ‘/’ 以外字符开头的 Unix 套接字路径。

2.9.2. 套接字类型前缀

先前的“地址族前缀”也可附加以强制指定套接字类型和传输方法。默认值取决于使用该地址的语句,但在某些情况下用户可强制其采用其他类型。以 “log” 语句为例,其默认为通过 UDP 传输的 syslog,但可强制改为通过 TCP 传输的 syslog。

这些前缀专为内部用途设计,用户应改用下一节“2.9.3 协议前缀”中提供的别名。然而,在某些情况下,这些前缀也可能较为方便,例如与通过文件描述符编号识别的继承套接字配合使用时,此时地址族为 “fd”,且必须声明套接字类型。

如果用户需要使用这些前缀以实现预期行为,但无法通过协议前缀进行相同配置,则应向维护者报告此问题。

‘stream+<family>@<address>’ 强制指定套接字类型和传输方式为 “stream”

‘dgram+<family>@<address>’ 强制将套接字类型和传输方法设为 “datagram”。

‘quic+<family>@<address>’ 强制将套接字类型设为 “datagram”,传输方式设为 “stream”。

2.9.3. 协议前缀

‘quic4@<address>[:port1[-port2]]’ 中的 <address> 始终被视为 IPv4 地址,但套接字类型强制为 “datagram”,传输方式强制为 “stream”。根据使用该地址的语句,可能需要或必须指定 UDP 端口或端口范围。其效果等同于 “quic+ipv4@"。

‘quic6@<address>[:port1[-port2]]’ 中的 <address> 始终被视为 IPv6 地址,但套接字类型强制为 “datagram”,传输方式强制为 “stream”。根据使用该地址的语句,可能需要或必须指定 UDP 端口或端口范围。其效果等同于 “quic+ipv6@"。

’tcp@<address>[:port1[-port2]]’ 中的 <address> 被视为 IPv4 或 IPv6 地址,具体取决于语法,但套接字类型和传输方式强制为 “stream”。根据使用该地址的语句,可能需要或必须指定端口或端口范围。该地址被视为 ‘stream+ip@’ 的别名。

’tcp4@<address>[:port1[-port2]]’ 中的 <address> 始终被视为 IPv4 地址,但套接字类型和传输方式强制为 “stream”。根据使用此地址的语句,可能需要或必须指定端口或端口范围。该地址被视为 ‘stream+ipv4@’ 的别名。

’tcp6@<address>[:port1[-port2]]’ 中的 <address> 始终被视为 IPv6 地址,但套接字类型和传输方式强制为 “stream”。根据使用该地址的语句,可能需要或必须指定端口或端口范围。该地址被视为 ‘stream+ipv4@’ 的别名。

‘mptcp@<address>[:port1[-port2]]’ 依 <address> 的语法被视作 IPv4 或 IPv6 地址,但套接字类型和传输方式强制为 “stream”,使用 MPTCP 协议。根据使用该地址的语句,端口或端口范围可选或必须指定。

‘mptcp4@<address>[:port1[-port2]]’ 依附于 <address> 时始终被视为 IPv4 地址,但套接字类型和传输方式强制为 “stream”,使用 MPTCP 协议。根据使用此地址的语句,可能需要或必须指定端口或端口范围。

‘mptcp6@<address>[:port1[-port2]]’ 依附于 <address> 时始终被视为 IPv6 地址,但套接字类型和传输方式强制为 “stream”,使用 MPTCP 协议。根据使用该地址的语句,可能需要或必须指定端口或端口范围。

‘udp@<address>[:port1[-port2]]’ 中的 <address> 被视为 IPv4 或 IPv6 地址,具体取决于语法,但套接字类型和传输方式强制为 “datagram”。根据使用该地址的语句,可能需要指定端口或端口范围,也可能必须指定。该地址被视为 ‘dgram+ip@’ 的别名。

‘udp4@<address>[:port1[-port2]]’ 中,<address> 后始终被视为 IPv4 地址,但套接字类型和传输方式强制为 “datagram”。根据使用此地址的语句,可能需要或必须指定端口或端口范围。该地址被视为 ‘dgram+ipv4@’ 的别名。

‘udp6@<address>[:port1[-port2]]’ 中,<address> 后始终被视为 IPv6 地址,但套接字类型和传输方式强制为 “datagram”。根据使用该地址的语句,可能需要或必须指定端口或端口范围。该地址被视为 ‘dgram+ipv4@’ 的别名。

‘uxdg@<path>’ 后跟的字符串被视为 Unix 套接字 <path>,但传输方式被强制为 “datagram”。这被视为 ‘dgram+unix@’ 的别名。

‘uxst@<path>’ 后跟的字符串被视为 Unix 套接字 <path>,但传输方式被强制设为 “stream”。这被视为 ‘stream+unix@’ 的别名。

在后续版本中,可使用其他前缀来指定 QUIC 等协议,该协议基于类型为 “datagram” 的套接字提供流传输。

2.10. 示例

# Simple configuration for an HTTP proxy listening on port 80 on all
    # interfaces and forwarding requests to a single backend "servers" with a
    # single server "server1" listening on 127.0.0.1:8000
    global
        daemon
        maxconn 256

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

    frontend http-in
        bind *:80
        default_backend servers

    backend servers
        server server1 127.0.0.1:8000 maxconn 32


    # The same configuration defined with a single listen block. Shorter but
    # less expressive, especially in HTTP mode.
    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

假设 HAProxy 已在 $PATH 中,可在 shell 中执行以下测试:

$ sudo haproxy -f configuration.conf -c