本文へ移動

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セクションだけは他のセクションより前に置く必要があります。必要ならglobalセクションを繰り返すことはできます。また、作成するオブジェクト(プロキシなど)の一部には識別子が自動的に割り当てられるため、セクションの順序を変えると識別子も変わります。これらは統計などに現れます。したがって、次の設定では"foo"に"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シェルでよく見る動作に非常に近いものです。原則は次のとおりです。設定パーサーは行を語に分割しながら、引用符とバックスラッシュも考慮し、文字が区切りなのか、現在の語の中の文字そのものなのかを判断します。その後、エスケープ文字と引用符を除去し、残った語を、たとえばキーワードや引数としてそのまま使います。

語の中でバックスラッシュが必要な場合は、バックスラッシュ自身でエスケープする(二重のバックスラッシュにする)か、強い引用で囲まなければなりません。

引用符の外では、特殊文字の前にバックスラッシュ(’\’)を置いてエスケープします。

\    空白を表し、区切りと区別します。
\#   ハッシュを表し、コメントと区別します。
\\   バックスラッシュを使います。
\'   単一引用符を使い、強い引用と区別します。
\"   二重引用符を使い、弱い引用と区別します。

また、一部の表示できない文字は、通常のC言語表現で出力できます。

\n   改行を挿入します(LF、文字\x0a、またはASCIIの10進数10)。
\r   復帰を挿入します(CR、文字\x0d、またはASCIIの10進数13)。
\t   タブを挿入します(文字\x09、またはASCIIの10進数9)。
\xNN ASCIIコードが16進数NNの文字を挿入します(たとえばLFは\x0a)。

弱い引用では、保護する文字または文字列を二重引用符("")で囲みます。弱い引用は、次の解釈を防ぎます。

     空白またはタブを、語の区切りとして解釈すること。
'    単一引用符を、強い引用の区切りとして解釈すること。
#    ハッシュを、コメントの開始として解釈すること。

弱い引用の中では、ドル記号(’$’)を前に付けると環境変数を解釈できます(引用符の外では評価しません)。二重引用符の中でドル文字そのものが必要な場合は、バックスラッシュでエスケープしなければなりません。

強い引用では、保護する文字または文字列を単一引用符(’’)で囲みます。単一引用符の中では何も解釈しないため、正規表現を引用する効率的な方法です。

以上をまとめると、各文脈で特殊文字を入力する方法は次の表になります(表示できない文字は、山括弧で囲んだ名前で示します)。エスケープでしか表現できない一部の文字は、単一引用符の中では表現できないため、その欄は空です。

  文字       |  引用なし     |  弱い引用                   |  強い引用
  -----------+---------------+-----------------------------+-----------------
    <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のエスケープは処理しません。ただし、内部で使う区切りを最上位で解決しないよう、まずエスケープまたは引用する必要があることは、必ずしも明らかではありません。

正規表現、置換文字列、フラグ集合という3つの引数を取る"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)]
                                       |---------|----|-|
                                引数1 _/         /    /
                                引数2 __________/    /
                                引数3 ______________/

ところが、実際に渡されるのは、開き丸括弧と閉じ丸括弧の間の文字列と、その後の不要な文字列です。

    http-request set-path %[path,regsub((foo|bar),blah,g)]
                                       |--------|--------|
                       引数1=(foo|bar _/        /
                    末尾の不要な文字  _________/

明らかな解決策は閉じ丸括弧を引用することに見えますが、それだけでは動作しません。前述のとおり、この語を処理する前に、最上位のパーサーが引用符を処理して解決してしまうためです。

http-request set-path %[path,regsub("(foo|bar)",blah,g)]
------------ -------- ----------------------------------
   語1         語2      語3  =%[path,regsub((foo|bar),blah,g)]

したがって、二段階目の引数パーサーにとっては何も変わりません。切り詰められた正規表現が唯一の引数となり、文字列末尾には不要な文字が残ります。引用符をエスケープすると、そのまま二段階目に渡せます。

    http-request set-path %[path,regsub(\"(foo|bar)\",blah,g)]
    ------------ -------- ------------------------------------
       語1         語2      語3  =%[path,regsub("(foo|bar)",blah,g)]
                                                |---------||----|-|
                               引数1=(foo|bar) _/          /    /
                                   引数2=blah  ___________/    /
                                       引数3=g _______________/

別の方法は、文字列全体の外側に単一引用符、内側に二重引用符を使うことです(二重引用符が再び除去されないようにします)。

    http-request set-path '%[path,regsub("(foo|bar)",blah,g)]'
    ------------ --------  ----------------------------------
       語1         語2      語3  =%[path,regsub("(foo|bar)",blah,g)]
                                                |---------||----|-|
                               引数1=(foo|bar) _/          /    /
                                         引数2 ___________/    /
                                         引数3 _______________/

ただし、この場合、上位の文字列に埋め込まれた区切りは単なる文字となり、区切りではなくなることに注意が必要です。特に、カンマの前後の空白やタブは文字列の一部になります。次の例には複数の誤りがあります。

    http-request set-path '%[path, regsub("(foo|bar)", blah, g)]'
    ------------ --------  --------------------------------------
       語1         語2      語3  =%[path, regsub("(foo|bar)", blah, g)]
                                        |--------|---------||-----|--|
                    コンバーター=" regsub" _/        /         /   /
                                   引数1=(foo|bar) _/         /   /
                                    引数2=" blah" ___________/   /
                                       引数3=" g" ______________/

カンマの前後に空白を置くだけで、その空白がフィールド自体に含まれます。そのため、コンバーター名が空白で始まる” regsub"になり、見つからずエラーになります。さらに分かりにくい点として、置換文字列” blah"は出力に空白を挿入します。式の中に不要な空白を入れないことが、よい目安です。

正規表現を使う場合、式にドル文字(’$’)が現れたり、置換文字列にバックスラッシュ(’\’)を使ったりすることがあります。その場合、二重引用符の中でも処理されるため、単一引用符を推奨します(または二重にエスケープします)。例:

    http-request set-path '%[path,regsub("^/(here)(/|$)","my/\1",g)]'
    ------------ --------  -----------------------------------------
       語1         語2      語3  =%[path,regsub("^/(here)(/|$)","my/\1",g)]
                                                |-------------| |-----||-|
                             引数1=(here)(/|$) _/               /      /
                                   引数2=my/\1 ________________/      /
                                         引数3 ______________________/

単一引用符の中ではバックスラッシュはエスケープ文字ではなく、上記の語全体は、すでに単一引用符でその処理から保護されていることを忘れないでください。逆に、式全体を二重引用符で囲むと、ドル文字とバックスラッシュが最上位で解決され、二段階目の引数内容が壊れます。

残念ながら、強い引用の中では単一引用符をエスケープできないため、引数に単一引用符を含めるには、二重にエスケープまたは引用する必要があります。いくつかの方法があります。

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シェルで"eval"に渡すコマンドを二重にエスケープする方法とよく似ています。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"キーワードで、再定義または解除できます。

  • 表示:管理ガイドのセクション9.3 「Unixソケットコマンド」で説明するCLIの"show env"出力に表示されます。

さらに、下表でそれぞれ’M’と’W’で示す"master"と"worker"の二つの下位カテゴリーがあります。HAProxyをマスターワーカーモードで起動した場合の、二つのプロセスの違いを示します。

  • master:マスタープロセスで変数が設定され、アクセスできます。したがって、マスター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

これらの値は設定解析時には未定義で、その後の初期化で設定します。したがって、条件ブロックで使わず、globalセクションの"setenv”/“resetenv”/“unsetenv"キーワードで参照しないことを推奨します。

各動作モードにおける変数の状態を次の表にまとめます。

  +---------------------------+---------+------------+-----------+
  |          変数             |使用可能 | 変更可能   |  表示     |
  |                           +---------+------------+-----------+
  |                           |  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:セクション8.2.3 「HTTPログ形式」で定義するデフォルトのHTTPログ形式の値を保持します。元の定義全体をコピーせずに、デフォルトのログ形式を上書きできます。

  • HAPROXY_HTTP_CLF_LOG_FMT:セクション8.2.3 「HTTPログ形式」で定義するデフォルトのHTTP CLFログ形式の値を保持します。元の定義全体をコピーせずに、デフォルトのログ形式を上書きできます。

例:

# 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ログ形式」で定義する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:現在解析中の設定ファイルの行番号。一から始まります。

  • .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の起動」を参照してください。

条件は空文字列(偽を返します)、または次の要素を任意に組み合わせた式です。

  • 整数のゼロ(‘0’)。常に"false"を返します。
  • ゼロでない整数(‘1’など)。常に"true"を返します。
  • 述語。任意で、丸括弧内に引数を続けます。
  • 丸括弧’(‘と’)‘で囲んだ条件。
  • 上記の空でない要素の前に置く感嘆符(’!’)。その真偽を反転します。
  • 論理AND(’&&’)で結合した式。いずれかが偽を返すまで、左から右へ評価します。
  • 論理OR(’||’)で結合した式。いずれかが真を返すまで、右から左へ評価します。

設定言語の他の部分と同じ行トークナイザーと引数パーサーを使います。引用されていない一つ以上の連続した空白やタブで語を分割し、評価の前に、単一の空白を区切りとして再結合します。利用者が行全体を引用しなくて済むようにするためです。ただし、これはカンマや丸括弧の前後の空白も確実に値の一部になることを意味し、必ずしも期待どおりではありません。たとえば、次の式は、

.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. 引用とエスケープ」で説明したとおり、式の中に不要な空白を入れないことが、よい目安です。

他の言語と同様、AND演算子はOR演算子より優先されるため、“A && B || C && D"は”(A && B) || (C && D)“として評価します。

現在サポートする述語は次のとおりです。

  • awslc_api_atleast(<ver>):現在のawslc API番号が<ver>以上なら真、それ以外は偽を返します。例:awslc_api_atleast(35)

  • awslc_api_before(<ver>):現在のawslc API番号が<ver>より厳密に古ければ真、それ以外は偽を返します。例:awslc_api_before(26)

  • defined(<name>):環境変数<name>が存在すれば、内容に関係なく真を返します。

  • feature(<name>):“haproxy -vv"が報告する機能一覧に、<name>が存在すると示されていれば(’+‘の後に<name>が現れれば)、真を返します。

  • openssl_version_atleast(<ver>):現在のopensslバージョンが<ver>以上なら真、それ以外は偽を返します。LibreSSL、AWS-LC、WolfSSLなどのライブラリも、疑似OpenSSLバージョンを提供します。例:

ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)
  • openssl_version_before(<ver>):現在のopensslバージョンが<ver>より厳密に古ければ真、それ以外は偽を返します。LibreSSL、AWS-LC、WolfSSLなどのライブラリも、疑似OpenSSLバージョンを提供します。例:openssl_version_before(3.5.0)

  • ssllib_name_startswith(<name>):HAProxyをリンクしたSSLライブラリ名が<name>で始まれば、真を返します。例:ssllib_name_startswith(wolfSSL)

  • streq(<str1>,<str2>):二つの文字列が等しい場合にのみ、真を返します。

  • strneq(<str1>,<str2>):二つの文字列が異なる場合にのみ、真を返します。

  • strstr(<str1>,<str2>):最初の文字列の中に二番目の文字列が見つかった場合にのみ、真を返します。

  • version_atleast(<ver>):現在のhaproxyバージョンが<ver>以上なら真、それ以外は偽を返します。バージョン構文は"haproxy -v"の表示と同じで、省略された要素はゼロとみなします。

  • version_before(<ver>):現在のhaproxyバージョンが<ver>より厳密に古ければ真、それ以外は偽を返します。バージョン構文は"haproxy -v"の表示と同じで、省略された要素はゼロとみなします。

  • enabled(<opt>):実行時にオプション<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の設定では、サンプル取得関数、コンバーター、ログ形式文字列、TCP/HTTPアクションで変数を使えます。プロセス全体の生存期間を通じてグローバルにアクセスできる、プロセス単位の変数を定義できます。それより寿命の短い変数もあります。変数はシェルスクリプトの変数と同様、メモリ領域に付けた記号名です。変数のサイズに制限はなく、動的に割り当てます。したがって、特に大量に使う場合は注意が必要です。ただし、globalパラメーター"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"変数へアクセスできるのは、親ストリームのレスポンス処理中だけです。

親ストリームを参照するスコープは、親を定義した時点から使えます。通常、親ストリームはありませんが、ある場合は明示的に記載します。現在は、親ストリームのスコープに定義した変数の値を取得することしかできず、設定や解除はできません。通常、子ストリームは特定の時点で親のために処理を行い、その操作が完了するまで親の進行を止めます。つまり、親はリクエスト処理やレスポンス処理の途中で停止する場合があります。そのため、子ストリームから利用できないスコープもあります。たとえば、子ストリームがリクエストを解析している場合、親はレスポンスを処理しておらず、“res"スコープの変数がないので、子は"pres"スコープに変数を見つけられません。

変数の内容はサンプル取得式を評価した結果で、その式の出力型を引き継ぎます。変数の型は用途と互換性がなければならないため、使用時にはこの点が重要です。たとえば、文字列を保持する変数を"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>です。fdはバインド済みでなければなりませんが、すでにリッスンしていても、していなくても構いません。

‘ip@<address>[:port1[-port2]]’:続く<address>は、構文に応じてIPv4またはIPv6アドレスとみなします。そのアドレスを使う文により、ポートまたはポート範囲の指定が任意または必須になります。

‘ipv4@<address>[:port1[-port2]]’:続く<address>は、常にIPv4アドレスとみなします。そのアドレスを使う文により、ポートまたはポート範囲の指定が任意または必須になります。

‘ipv6@<address>[:port1[-port2]]’:続く<address>は、常にIPv6アドレスとみなします。そのアドレスを使う文により、ポートまたはポート範囲の指定が任意または必須になります。

‘sockpair@<n>’:続くアドレスは、接続済みunixソケットまたはsocketpairのファイルディスクリプターです。接続時、開始側は接続したソケットのペアを作り、その一方をFD経由で相手へ渡します。リスナーはunixソケットからFDを受け取るのを待ち、accept()が返したFDと同じように使います。慎重に使用してください。

           不具合:macOSのsendmsg(2)実装の問題により、このプロトコルはmacOS上では信頼できないことが知られています。接続を正しく受け入れられない場合があります。

‘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+ipv6@‘の別名とみなします。

‘mptcp@<address>[:port1[-port2]]’:続く<address>は構文に応じてIPv4またはIPv6アドレスとみなしますが、MPTCPプロトコルで、ソケット型と転送方式を"stream"に強制します。そのアドレスを使う文により、ポートまたはポート範囲の指定が任意または必須になります。

‘mptcp4@<address>[:port1[-port2]]’:続く<address>は常にIPv4アドレスとみなしますが、MPTCPプロトコルで、ソケット型と転送方式を"stream"に強制します。そのアドレスを使う文により、ポートまたはポート範囲の指定が任意または必須になります。

‘mptcp6@<address>[:port1[-port2]]’:続く<address>は常にIPv6アドレスとみなしますが、MPTCPプロトコルで、ソケット型と転送方式を"stream"に強制します。そのアドレスを使う文により、ポートまたはポート範囲の指定が任意または必須になります。

‘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>’:続く文字列はunixソケットの<path>とみなしますが、転送方式を"datagram"に強制します。‘dgram+unix@‘の別名とみなします。

‘uxst@<path>’:続く文字列はunixソケットの<path>とみなしますが、転送方式を"stream"に強制します。‘stream+unix@‘の別名とみなします。

将来のバージョンでは、“datagram"型ソケット上にストリーム転送を提供するQUICなどのプロトコルを指定するため、他の接頭辞を使う可能性があります。

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