Перейти к содержанию

2. Настройка HAProxy

Синтаксис файлов, кавычки, переменные, условия, форматы времени и размера, адреса и примеры

2.1. Формат файла конфигурации

При настройке HAProxy используются 3 основных источника параметров:

  • аргументы из командной строки, которые всегда имеют приоритет
  • файл или файлы конфигурации, формат которых описан здесь
  • окружение запущенного процесса, если на переменные окружения явно ссылаются в конфигурации

Файл конфигурации имеет довольно простой иерархический формат, подчиняющийся нескольким основным правилам:

1. Файл конфигурации — упорядоченная последовательность инструкций.

2. Инструкция — одна непустая строка до первого незащищённого символа "#"
   (решётки).

3. Строка — последовательность токенов, или «слов», разделённых
   незащищёнными пробелами или табуляциями.

4. Первое слово или последовательность слов в строке — одно из ключевых
   слов или сочетаний ключевых слов, перечисленных в этом документе.

5. Все остальные слова — аргументы первого: одни являются известными
   ключевыми словами из этого документа, другие — значениями, ссылками
   на другие части конфигурации или выражениями.

6. Некоторые ключевые слова открывают секцию, внутри которой поддерживается
   только определённое подмножество ключевых слов.

7. Секция заканчивается в конце файла либо перед специальным ключевым
   словом, открывающим новую секцию.

Этого достаточно для создания простого, но надёжного генератора конфигурации, однако недостаточно для надёжного разбора произвольной конфигурации и обработки некоторых особых случаев.

Из этих правил следует несколько выводов. Правила 6 и 7 означают, что ключевые слова, определяющие новую секцию, допустимы везде и не могут иметь другого смысла в какой-либо отдельной секции. Такие ключевые слова всегда состоят из одного слова, а не из последовательности слов. Традиционно следующую за ними секцию называют так же. Например, «секция global» — это часть конфигурации после ключевого слова “global”. Такое обозначение часто используется в сообщениях об ошибках, чтобы указать место, требующее исправления.

Некоторые секции создают внутренний объект или область конфигурации, которую необходимо отличать от других. В этом случае дополнительное слово задаёт имя секции. Для некоторых типов секций имя обязательно. Например, “frontend foo” создаёт секцию типа “frontend” с именем “foo”. Обычно имя относится только к своему типу секций, и две секции разных типов могут иметь одинаковое имя, но это не рекомендуется, поскольку усложняет управление конфигурацией.

Прямое следствие правила 7 заключается в том, что при одновременном чтении нескольких файлов каждый из них должен начинаться с новой секции, а завершение каждого файла должно завершать соответствующую секцию. Файл не может содержать вложенные секции и не может завершать существующую секцию, чтобы начать новую.

Правило 1 указывает, что порядок имеет значение. Некоторые ключевые слова создают директивы, которые можно повторять, формируя последовательность правил для применения в заданном порядке. Например, “tcp-request” позволяет чередовать правила “accept” и “reject” с разными критериями. Поэтому средство обработки конфигурации при редактировании файла обязано сохранять порядок элементов внутри секции. Порядок самих секций обычно неважен, кроме секции global, которая должна находиться перед остальными, но при необходимости может повторяться. Кроме того, некоторым создаваемым объектам, например прокси, автоматически назначаются идентификаторы, которые меняются при перестановке секций. Эти идентификаторы встречаются, например, в статистике. В приведённой ниже конфигурации “foo” получит меньший идентификатор, чем “bar”. Если поменять секции местами, соотношение изменится:

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. Разбивая строки конфигурации на слова, анализатор учитывает кавычки и обратные косые черты, чтобы отличить разделитель от буквального символа внутри текущего слова. Затем символ экранирования и кавычки удаляются, а оставшееся слово используется без изменений, например как ключевое слово или аргумент.

Если в слове нужна обратная косая черта, её необходимо либо экранировать такой же чертой, то есть удвоить, либо заключить в одинарные кавычки.

Вне кавычек специальный символ экранируют обратной косой чертой (’\’), стоящей перед ним:

\    пробел, который следует отличать от разделителя
\#   решётка, которую следует отличать от начала комментария
\\   обратная косая черта
\'   одинарная кавычка, которую следует отличать от границы сильного цитирования
\"   двойная кавычка, которую следует отличать от границы слабого цитирования

Кроме того, несколько непечатаемых символов могут быть выведены с помощью их обычного представления на языке C:

\n   перевод строки (LF, символ \x0a или десятичный код ASCII 10)
\r   возврат каретки (CR, символ \x0d или десятичный код ASCII 13)
\t   табуляция (символ \x09 или десятичный код ASCII 9)
\xNN символ с шестнадцатеричным кодом ASCII NN (например, \x0a для LF).

Для слабого цитирования защищаемый символ или последовательность символов заключают в двойные кавычки (""). Это предотвращает интерпретацию следующих символов:

     пробел или табуляция как разделитель слов
'    одинарная кавычка как граница сильного цитирования
#    решётка как начало комментария

Слабое цитирование допускает подстановку переменных окружения, перед которыми стоит знак доллара (’$’); вне кавычек эти переменные не вычисляются. Если внутри двойных кавычек нужен сам знак доллара, его необходимо экранировать обратной косой чертой.

Для сильного цитирования защищаемый символ или последовательность символов заключают в одинарные кавычки (’’). Внутри них ничего не интерпретируется, поэтому они удобны для регулярных выражений.

Следующая таблица показывает, как вводить специальные символы в разных контекстах. Непечатаемые символы обозначены именами в угловых скобках. Некоторые символы можно представить только с экранированием, а внутри одинарных кавычек их представить невозможно, поэтому соответствующие ячейки пусты:

  Символ     |  Без кавычек  |  В двойных кавычках         |  В одинарных
  -----------+---------------+-----------------------------+-----------------
    <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”, принимающим 3 аргумента: регулярное выражение, строку замены и набор флагов:

# 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 _/        /
                    лишние символы    _________/

Кажется, достаточно заключить закрывающую скобку в кавычки. Однако это не сработает: как сказано выше, анализатор верхнего уровня обработает кавычки ещё до разбора этого слова:

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)]
                                        |--------|---------||-----|--|
                  преобразователь=" 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\\\')

При сомнениях начните с варианта без кавычек. Затем заключите в одинарные или двойные кавычки аргументы, которым нужна запятая или закрывающая скобка, и при наличии в строке знака доллара или обратной косой черты предусмотрите экранирование этих кавычек обратной косой чертой. Это похоже на двойное экранирование команды, передаваемой “eval” в оболочке Bourne. Разработчикам API, вероятно, удобнее заключать каждый аргумент в экранированные кавычки независимо от его содержимого. Для пользователей, вероятно, более читаема конфигурация с одинарными кавычками вокруг всего выражения и двойными вокруг каждого аргумента.

2.3. Переменные окружения

В конфигурации HAProxy поддерживаются переменные окружения. Они интерпретируются только внутри двойных кавычек, а их значения подставляются при разборе конфигурации. Перед именем переменной должен стоять знак доллара ("$"); само имя можно заключить в фигурные скобки ("{}"), как в оболочке Bourne. Имена могут содержать буквы, цифры и знак подчёркивания ("_"), но их не следует начинать с цифры. Если переменная содержит несколько значений, разделённых пробелами, их можно подставить как отдельные аргументы: для этого переменную заключают в фигурные скобки и добавляют суффикс ‘[*]’ перед закрывающей скобкой. Также можно задать значение по умолчанию для случая, когда переменная не определена: его добавляют после имени переменной через дефис ‘-’. Значение по умолчанию заменяет только отсутствующую переменную, а не переменную с пустым значением.

Пример:

bind "fd@${FD_APP1}"

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

user "$HAPROXY_USER"

Некоторые переменные определяются HAProxy, они могут использоваться в конфигурации. Эти переменные приведены в матрице ниже и классифицируются по четырём категориям:

  • доступна: переменную можно использовать в конфигурации — подставить её значение либо использовать её в условных блоках или предикатах, чтобы включать или отключать фрагменты конфигурации, как описано в разделе 2.4 «Условные блоки».

  • изменяема: переменную можно переопределить или удалить в конфигурации с помощью ключевых слов “setenv”/“unsetenv”.

  • выводится: переменная присутствует в выводе команды CLI “show env”, описанной в разделе 9.3 «Команды сокетов Unix» руководства по управлению.

Также есть две подкатегории — главный и рабочий процессы, обозначенные в таблице ниже соответственно ‘M’ и ‘W’. Они показывают различия между процессами при запуске HAProxy в режиме master-worker.

  • главный процесс: переменная задана и доступна в главном процессе. Она отображается в выводе “show env” интерфейса CLI главного процесса и может использоваться в условных блоках или директивах для включения специальных настроек этого процесса (см. примеры в разделе 2.4 «Условные блоки»).

  • рабочий процесс: переменная задана и доступна в рабочем процессе. Она отображается в выводе “show env” интерфейса CLI рабочего процесса или “@1 show env” интерфейса CLI главного процесса. От неё также могут зависеть параметры рабочего процесса (см. примеры в разделе 2.4 «Условные блоки»).

В автономном режиме, без параметра “-W” и ключевого слова “master-worker”, процесс ведёт себя как рабочий, но переменные “HAPROXY_MASTER_CLI” и “HAPROXY_MWORKER” не определены.

Некоторые переменные помечены как недоступные и неизменяемые:

  • HAPROXY_CFGFILES
  • HAPROXY_MWORKER
  • HAPROXY_CLI
  • HAPROXY_MASTER_CLI
  • HAPROXY_LOCALPEER

При разборе конфигурации их значения не определены: они устанавливаются позднее, на этапе инициализации. Поэтому рекомендуется не использовать эти переменные в условных блоках и не ссылаться на них в директивах “setenv”/“resetenv”/“unsetenv” секции global.

Таблица ниже содержит краткое описание состояния каждой переменной для различных режимов работы:

  +---------------------------+---------+------------+-----------+
  |        Переменная         |Доступна | Изменяема  | Выводится |
  |                           +---------+------------+-----------+
  |                           |  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, но содержит формат журнала HTTPS, описанный в разделе 8.2.4 «Формат журнала HTTPS».

  • HAPROXY_TCP_LOG_FMT: аналогична HAPROXY_HTTP_LOG_FMT, но содержит формат журнала TCP, описанный в разделе 8.2.2 «Формат журнала TCP».

  • HAPROXY_TCP_CLF_LOG_FMT: аналогична HAPROXY_HTTP_CLF_LOG_FMT, но содержит формат журнала TCP CLF, описанный в разделе 8.2.2 «Формат журнала TCP».

  • 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: В режиме master-worker, эта переменная устанавливается в 1.

  • HAPROXY_CLI: настроенные адреса слушателей сокета статистики каждого процесса, эти адреса разделяются точками с запятой.

  • HAPROXY_MASTER_CLI: В режиме master-worker, адреса слушателей главного CLI, разделённые точками с запятой.

  • HAPROXY_STARTUP_VERSION: содержит версию, использованную при запуске. В режиме master-worker это версия, с которой запущен главный процесс, даже после обновления исполняемого файла и перезагрузки конфигурации.

  • HAPROXY_BRANCH: содержит версию ветки HAProxy (например, “2.8”). В ней не содержится полная версия. Может быть полезна при миграции, если ресурсы (например, карты или сертификаты) находятся в пути, содержащем номер ветки.

Кроме того, некоторые псевдопеременные вычисляются внутри HAProxy и могут использоваться как обычные переменные. Их имена всегда начинаются с точки (’.’); только в именах псевдопеременных разрешена точка. Текущий список псевдопеременных:

  • .FILE: имя разбираемого в данный момент файла конфигурации.

  • .LINE: номер разбираемой в данный момент строки файла конфигурации, начиная с единицы.

  • .SECTION: имя разбираемой в данный момент секции, её тип, если имя отсутствует (например, “global”), либо пустая строка до первой секции.

Значения этих переменных вычисляются в месте их разбора. Например, если переменная “.LINE” используется в директиве “log-format” секции defaults, номер строки вычисляется до разбора и компиляции этой директивы “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» в руководстве по управлению.

Условия могут быть либо пустой строкой (в этом случае возвращается ложь), либо выражением, состоящим из любой комбинации:

  • целое число ноль (‘0’), которое всегда возвращает “false”
  • ненулевое целое число, например ‘1’, которое всегда возвращает “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)
  |---|-----|-|--|
    \    \    \  \_ аргумент2: " 1"
     \    \    \___ аргумент1: "0"
      \    \_______ функция: "streq"
       \___________ директива: ".if"

Здесь видно, что даже если ENABLE_SSL был равен “1”, это не совпадало с " 1", поскольку строка отличалась одним пробелом.

Примечание: как объяснено в секции “2.2. Цитирование и экранирование”, хорошим правилом является никогда не вставлять ненужные пробелы внутри выражений.

Как и в других языках, оператор И имеет приоритет над ИЛИ, поэтому “A && B || C && D” вычисляется как “(A && B) || (C && D)”.

Список поддерживаемых на данный момент предикатов следующий:

  • awslc_api_atleast(<ver>): возвращает true, если текущее значение awslc API не старше <ver>, иначе false. Пример: awslc_api_atleast(35)

  • awslc_api_before(<ver>): возвращает true, если текущий номер awslc API строго старше <ver> иначе false. Пример: awslc_api_before(26)

  • defined(<name>) : возвращает true, если переменная среды <name> существует, независимо от её содержимого

  • feature(<name>) : возвращает true, если возможность <name> отмечена как доступная в списке, выводимом командой “haproxy -vv”, то есть <name> стоит после знака ‘+’.

  • openssl_version_atleast(<ver>): возвращает true, если текущая версия OpenSSL не старше <ver>, в противном случае — false. Библиотеки, такие как LibreSSL, AWS-LC и WolfSSL, также предоставляют псевдо-версию OpenSSL. Пример:

ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)
  • openssl_version_before(<ver>): возвращает true, если текущая версия OpenSSL строго старше <ver>, иначе false. Библиотеки, такие как LibreSSL, AWS-LC и WolfSSL, также предоставляют псевдоверсию OpenSSL. Пример: openssl_version_before(3.5.0)

  • ssllib_name_startswith(<name>) : возвращает true, если имя библиотеки SSL, с которой скомпонован HAProxy, начинается с <name>. Пример: ssllib_name_startswith(wolfSSL)

  • streq(<str1>,<str2>) : возвращает true только в случае равенства двух строк

  • strneq(<str1>,<str2>): возвращает true только если две строки различаются

  • strstr(<str1>,<str2>): возвращает true только если вторая строка содержится в первой.

  • version_atleast(<ver>): возвращает true, если текущая версия HAProxy не старше <ver>, в противном случае — false. Синтаксис версии совпадает с тем, что показано командой “haproxy -v”, отсутствующие компоненты считаются равными нулю.

  • version_before(<ver>): возвращает true, если текущая версия HAProxy строго старше <ver> в противном случае — false. Синтаксис версии соответствует тому, что показывает “haproxy -v” и отсутствующие компоненты считаются нулевыми.

  • enabled(<opt>) : возвращает true, если параметр <opt> включён во время работы. Поддерживается только часть параметров:

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

.if streq("$WITH_SSL",yes)
  .if feature(OPENSSL)
       bind :443 ssl crt ...
  .endif
.endif

.if feature(OPENSSL) && (streq("$WITH_SSL",yes) || streq("$SSL_ONLY",yes))
       bind :443 ssl crt ...
.endif

.if version_atleast(2.4-dev19)
    profiling.memory on
.endif

.if !feature(OPENSSL)
    .alert "SSL support is mandatory"
.endif

Четыре другие директивы предоставляются для отображения некоторых состояний:

  • .diag “message” : выводит сообщение только в режиме диагностики (-dD)
  • .notice “message” : выводит сообщение с уровнем NOTICE
  • .warning “message”: выводит сообщение с уровнем WARNING
  • .alert “message” : выводит сообщение с уровнем ALERT

Сообщения уровня WARNING могут помешать запуску процесса, если включён “zero-warning”. Сообщения уровня ALERT всегда приводят к неустранимой ошибке. Эти директивы позволяют выявлять неподходящие условия и давать пользователю рекомендации.

Пример:

.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@”. Он входит в идентификатор. Следовательно, такие списки не могут смешиваться с другими типами списков.

Виртуальные файлы полезны, когда шаблоны полностью управляются динамически и отсутствуют при запуске и перезагрузке конфигурации. В тех же условиях можно использовать необязательные файлы, но в них можно сохранять шаблоны, например внешним скриптом на основе команды CLI “show map”. Это позволяет сохранить шаблоны при перезагрузке конфигурации.

Примечание: хотя это маловероятный случай, обычный файл с именем, начинающимся с “file@”, “opt@” или “virt@”, нельзя загрузить без явного добавления “./” перед именем, например “file@./virt@map”.

2.8. Переменные

В конфигурации HAProxy переменные можно использовать в функциях извлечения образца, преобразователях, строках log-format и действиях TCP/HTTP. Можно определять переменные уровня процесса, доступные глобально на протяжении всей его работы. Другие переменные имеют меньший срок жизни. Как и в сценариях оболочки, переменная — это символическое имя области памяти. Размер переменных не ограничен, память для них выделяется динамически. Поэтому переменные необходимо использовать осторожно, особенно при интенсивном обращении к ним. Максимальный объём занимаемой ими памяти можно ограничить глобальными параметрами “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 или пары сокетов. При установке соединения инициатор создаёт пару соединённых сокетов и передаёт один из них другой стороне через этот дескриптор. Слушатель ждёт получения дескриптора через сокет Unix и использует его как дескриптор, возвращённый accept(). Следует использовать с осторожностью.

           Ошибки: известно, что этот протокол ненадёжен на macOS из-за
           ошибки в реализации sendmsg(2) в macOS. Соединение может быть
           принято некорректно.

‘unix@<path>’ — следующая строка рассматривается как путь <path> к сокету UNIX. Этот префикс удобен для пути к сокету UNIX, который не начинается с косой черты ‘/’.

2.9.2. Префиксы типов сокетов

Перед описанными выше префиксами семейства адресов можно добавить ещё один префикс, чтобы явно задать тип сокета и способ передачи. Значение по умолчанию зависит от директивы, использующей адрес, но в некоторых случаях его можно изменить. Например, директива “log” по умолчанию использует syslog поверх UDP, но её можно настроить на syslog поверх TCP.

Эти префиксы предназначены для внутреннего использования. Пользователям следует применять вместо них псевдонимы из следующего раздела «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+ipv6@’.

‘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+ipv6@’.

‘uxdg@<path>’ — следующая строка рассматривается как путь <path> к сокету unix, но способ передачи явно задаётся как “datagram”. Это псевдоним ‘dgram+unix@’.

‘uxst@<path>’ — следующая строка рассматривается как путь <path> к сокету unix, но способ передачи явно задаётся как “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, проверить эти конфигурации можно следующей командой оболочки:

$ sudo haproxy -f configuration.conf -c