HAProxy を使ってみたメモ

CentOS 7 の yum でさくっとインストールできるバージョンで試しています。

  • haproxy-1.5.14-3.el7.x86_64

その他に、ログとかスティッキーセッションとかについても書いてます。

あと、いまのところ使う予定がなかったので mode tcp は試していません。

WEB で統計レポートを表示

プロキシのセクション(defaults/listen/frontend/backend)で stats enable を指定すれば、http://localhost/haproxy?stats のような URL で HAProxy の統計レポートを表示できる。

frontend main *:80
    stats enable
    :

URL は stats uri で変更できる。下記の例だと http://localhost/admin?stats となる。

frontend main *:80
    stats enable
    stats uri /admin?stats
    :

stats http-requestacl を組み合わせればソース IP アドレスで制限できる。

frontend main *:80
    acl is_private src 192.168.33.0/24
    stats enable
    stats uri /admin?stats
    stats http-request allow if is_private
    :

stats admin で条件を指定すると、その条件に該当するときだけ管理レベルの操作が可能になる。通常は統計レポート画面はリードオンリーな操作しかできないが、この設定が有効だとバックエンドサーバのメンテナンスモードへの切り替えや、ヘルスチェックの有効/無効、などの操作が可能になる。

frontend main *:80
    acl is_private src 192.168.33.0/24
    stats enable
    stats uri /admin?stats
    stats http-request allow if is_private
    stats admin if LOCALHOST
    :

↑で指定した LOCALHOST は組み込みの acl で、自分で定義した acl も指定できる。

stats auth で統計レポートの画面に認証を設けることもできる。

frontend main *:80
    acl is_private src 192.168.33.0/24
    stats enable
    stats uri /admin?stats
    stats http-request allow if is_private
    stats admin if TRUE
    stats auth admin:AdMiN123
    :

CLI で統計レポートを表示

グローバルセクションで stats socket を指定すると Unix ドメインソケット経由で CLI で統計レポートを得ることができる。

global
    stats socket /var/lib/haproxy/stats level admin
    :

例えば、次のように nc とかで統計情報レポートを得ることができる。

echo "show stat -1 4 -1" | nc -U /var/lib/haproxy/stats | grep -v -E '^#|^$' | cut -d, -f2,18

level で指定している admin はソケット経由で操作可能なレベルを表していて、admin ならすべての操作が可能。

また、次のように指定すれば TCP でも接続できる。

global
    stats socket ipv4@127.0.0.1:9999 level admin
    :

monitor-uri / monitor fail

monitor failmonitor-uri を使うと、特定の URL へのリクエストを、条件によって 200 または 503 を返すようにすることができる。

frontend main *:80
    acl is_dead nbsrv(app) lt 2
    monitor fail if is_dead
    monitor-uri /haproxy?alive
    :

この例だと /haproxy?alive へリクエストしたときに app バックエンドの生きているサーバが 2 台以上あるなら 200 を、2 台未満なら 503 を返す。

URL にクエリストリングを含めているのには特に深い意味は無い、クエリストリング無しで /alive とかでも構わない。

monitor-net

monitor-net を使うと、特定のアドレス帯からのリクエストは即座に 200 を応答するようになる。

frontend main *:80
    monitor-net 192.168.33.0/24
    :

この例だと 192.168.33.0/24 からのリクエストは直ちに 200 を応答する。

バックエンドのサーバが全滅しても 200 を応答するけど・・どういう用途に使うものなの? これ。

fullconn / maxconn / minconn

backendfullconnservermaxconnminconn の関係。

http://permalink.gmane.org/gmane.comp.web.haproxy/5378

上記によると、例えば次のような設定だと、

backend dynamic
    fullconn 10000
    server srv1 dyn1:80 minconn 100 maxconn 1000
    server srv2 dyn2:80 minconn 100 maxconn 1000
    :

低負荷時には最大接続数の制限は minconn の 100 で、バックエンド全体に対する同時接続数が増えてくると最大接続数の制限も増加する。その増加は線形で、バックエンド全体に対する同時接続数が fullconn の 10000 になったときに maxconn の 1000 になるように増加する。ただし maxconn を超えることはない。

ニュースサイトとかでトラフィックパターンに大きな変動を伴うサイトでは静的な maxconn だとちょうど良い値を設定することができない(低負荷時は小さい値にしたいし、高負荷時は大きい値にしたい)。

fullconnminconn を使えばサイトの負荷に応じて動的に同時接続数の制限を変動することができる。

・・らしいんだけど maxconn だけじゃダメな理由が良くわからない。

maxconn の設定箇所ごとの意味

globalmaxconn はプロセスに適用されるもので、frontend の接続の合計。

default/frontend/listenmaxconnfrontend ごとに適用される。

frontend がもたらすものよりも大きい値を servermaxconn に設定した場合、キューが使われることがなくなるだけで、とくに問題ではない。

HTTP のモードの設定

下記を参考に・・・

https://blog.cloudpack.jp/2014/07/28/ha-proxy-translation-document-http-transaction/ https://blog.cloudpack.jp/2014/07/28/ha-proxy-translation-document-option-httpclose/ https://blog.cloudpack.jp/2014/08/08/tips-haproxy-option-httpclose-and-http-server-close-with-tcpdump/

http-tunnelhttpclose は非奨励のようなので、下記のいずれかを設定する。

  • option http-keep-alive
    • サーバ側とクライアント側の両方で keep-alive する
  • option http-server-close
    • 応答の終わりでサーバ側はクローズするがクライアント側は keep-alive する
  • option forceclose
    • 応答の終わりでクライアントとサーバの両方をクローズする

おおむね、次のようにしておくと良い。

  • option http-keep-alive
    • 静的コンテンツのサーバ
  • option http-server-close
    • アプリケーションサーバ

タイムアウト関係の設定

  • timeout connect
    • HAProxy からバックエンドサーバへの接続のタイムアウト時間
    • クライアントからの接続のプロキシ時とヘルスチェックでの両方で適用される
  • timeout client
    • クライアントから HAProxy への接続で無応答のタイムアウト時間
    • つまり、HAProxy がクライアントから ACK やデータ送信を期待したときに、どれだけ待つかの時間
    • なので、リクエスト〜レスポンスのトータル時間とは異なる
  • timeout server
    • HAProxy からバックエンドサーバへの接続で無応答のタイムアウト時間
    • timeout client のバックエンドサーバの版
    • なので、リクエスト〜レスポンスのトータル時間とは異なる
  • timeout http-request
    • 完全なリクエスト〜レスポンスのタムアウト時間
    • クライアントから最初のバイトを受信してからクライアントに最後のバイトを送信するまでの時間
    • クライアントからのリクエストの開始からリクエストヘッダを受けきるまでのタムアウト時間
    • リクエストボディやレスポンスにはこのタイムアウト時間は適用されない
  • timeout http-keep-alive
    • いわゆる keep-alive のタイムアウト時間
    • クライアントに最後のバイトを送信してから次のリクエストの最初のバイトを受信するまでの時間
  • timeout check
    • ヘルスチェックで接続が確立された後の追加のタイムアウト時間
    • ヘルスチェックでは接続のタイムアウト時間として timeout connectinter の小さい方が使用される
    • 接続が確立されると read のタイムアウト時間として timeout check が追加される
  • timeout queue
    • キューに入った接続を解放するまでのタイムアウト時間
    • HAProxy への接続が servermaxconn に達すると接続はキューに入って server の接続の空きを待つ
    • タイムアウト時間が経過するとクライアントには 503 が応答される

option redispatch

スティッキークッキーが有効な場合、振り分けられるバックエンドのサーバが死ぬと他のサーバが生きていても HAProxy が当該サーバをダウンと判断するまでクライアントはサービスにアクセスすることができなくなる。

下記のオプションを指定すると、そのようなケースでも別のサーバへの振り分けが行われる。

defaults
    option redispatch
    :

また、retries も指定されている場合、リトライの最後の施行だけ別のサーバへ振り分けられる。

defaults
    option redispatch
    retries 3
    :

option httplog

HTTP のアクセスログを記録する。

次のように指定すると CLF という Apache のアクセスログでお馴染みの標準的な形式のログになる。

defaults
    option httplog clf
    :

option dontlognull

いわゆる NULL スキャンのログを記録しないオプション、、、というわけではなくて、TCP で 80 番ポートを開いて直ぐ閉じるような、内容が HTTP ではないアクセスもログをログに記録しないようにするオプション。

nmap でいうところの -sT オプションのスキャンがログに残るかどうか、ということ(-sN はどっちにしろ残らない)。

例えば、死活監視でポートが開いているかを監視するために bash で < /dev/tcp/127.0.0.1/80 みたいなことをやっていると、通常はこのアクセスまでログに記録されてしまうが、option dontlognull を指定しておけば、このようなアクセスはログには残らなくなる。

インターネットのような制御されていない環境(uncontrolled environments)では指定しないことをおすすめする。

forwardfor

次のように指定すると X-Forwarded-ForX-Original-To ヘッダがバックエンドサーバへのリクエストに追加される。

defaults
    option forwardfor
    option originalto
    :

既に X-Forwarded-ForX-Original-To が付与されていた場合(HAProxy へのリクエストの時点でこれらのヘッダが付いていた場合)、カンマ区切りで追記される。

次のように except を追加すると、そのネットワークからのリクエストでは X-Forwarded-ForX-Original-To を追加しなくなる。

defaults
    option forwardfor except 127.0.0.0/8
    option originalto except 127.0.0.0/8
    :

例えば、HAProxy へのアクセスが、クライアントから直接と、既知のリバースプロキシ経由との 2 つが混在するような場合に、except でリバースプロキシを指定すれば、既知のアドレスが X-Forwarded-For に追加されないようにすることができる。

他にも、HAProxy のホスト上から http://localhost/ などとアクセスすると X-Forwarded-For127.0.0.1 になり、バックエンドサーバでこの値をアクセスログに記録している場合、まるで意味のないログになってしまう。そこで、except 127.0.0.0/8 を指定すれば X-Forwarded-For が記録されなくなるので、Web サーバで X-Forwarded-For がなければ通常のリモートアドレスにフォールバックするようになっていれば、アクセスログには Web サーバから見た HAProxy のアドレスが記録されるようになる・・・という使い方もできるかもしれない。

option logasap

デフォルトではログはレスポンスの完了時に記録されるが(じゃないと総転送量とかがわからない)、次のように設定するとレスポンスヘッダが送信された時点でアクセスログが記録されるようになる。

defaults
    option logasap
    :

ただ、その場合、ログに記録される転送量や転送時間は不完全なものになる。実際に見比べた感じ、このオプションがなければ 688 のような具体的な値が記録されるのに対して、このオプションを指定していると +445 のように記録される。

option log-health-checks

デフォルトではヘルスチェックのログは risefall で指定された回数のリクエストの結果を元にステータスが変化したときにだけ記録されるが、次の設定をすると、ステータスが変化する前のヘルスチェックの結果もログに記録される。

defaults
    option log-health-checks
    :

例えば、ヘルスチェックの設定が次のようになっているとき、

defaults
    default-server inter 2s rise 2 fall 3
    :

ステータスが OK の状態でヘルスチェックに失敗したとき、上記のオプションが指定されていなければ 3 回失敗して初めてログに記録されるが、上記のオプションが指定されている場合は最初の 1 回目からログに記録され、3 回ログに記録された後にステータスが変わった旨のログが記録される。

agent-check

バックエンドサーバの Up/Down や重みをサーバの特定のポートに TCP でアクセスして返ってきた値に応じて行なう。

次のように設定すると、サーバの 1234 ポートに定期的にアクセスする。

backend app
    balance roundrobin

    default-server inter 2s rise 2 fall 3 agent-port 1234 agent-inter 3s

    server ap01 192.168.33.21:80 check agent-check
    server ap02 192.168.33.22:80 check agent-check

    option httpchk GET /ok.html
    http-check expect status 200

そのポートが 80% のような値を返すと、振り分けの重みが 80% になる。

他にも、次のような値を返すことができる。

  • サーバの Up/Down の状態
    • up
    • down または failed または stopped
  • サーバの管理状態
    • ready
    • maint
    • drain
  • 重み
    • 80% など

ポートが返さなかった値は変更されない。つまり 80% とだけ返した場合は重みだけが変更されて、Up/Down やメンテナンスなどの状態は変更されない。

また、ポートが応答がなかった場合でも Down になるわけではないので(ステータスが更新されないだけ)、option httpchk と併用はしなければならない。

READY/MAINT/DRAIN

agent-check の説明ででてきた READY/MAINT/DRAIN はバックエンドサーバの管理状態を意味するもので、それぞれ次のような意味。

  • READY
    • MAINT や DRAIN が解除された通常の状態
  • DRAIN
    • 新しい接続を受け入れない
  • MAINT
    • 新しい接続を受け入れず、かつ、死活監視も停止する

動的なサーバの取り外し

稼働中の HAProxy から一時的に特定のバックエンドサーバへの振り分けを停止する方法。

WEB での統計レポートの管理モードを有効にして、特定のサーバを DRAIN や MAINT に切り替えることで実現できる。

もしくは、CLI での統計レポートの管理モードが有効になっていれば、次のようなコマンドで切り替えることができる。

# MAINT にする
echo "disable server app/ap01" | nc -U /var/lib/haproxy/stats

# MAINT から元に戻す
echo "enable server app/ap01" | nc -U /var/lib/haproxy/stats
# DRAIN
echo "set weight app/ap01 0%" | nc -U /var/lib/haproxy/stats

# DRAIN から元に戻す
echo "set weight app/ap01 100%" | nc -U /var/lib/haproxy/stats

サービスダウンのログ

バックエンドサーバが全滅すると次のようなログが発生する。

Server app/ap02 is DOWN, reason: Layer7 wrong status, code: 404, info: "HTTP status check returned code <3C>404<3E>", check duration: 0ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
backend app has no server available!

1個目のログは alert だけど2個目のログは emerg で発生する。

emerg のログは rsyslog の設定に依らず(というか rsyslog を停止しても)コンソールにブロードキャストされる。

Broadcast message from systemd-journald@ha01 (Tue 2016-04-19 14:04:43 JST):

haproxy[7758]: backend app has no server available!

見ての通り systemd-journald が出しているもので、/etc/systemd/journald.confMaxLevelWall のデフォルトが emerg だからなのだと思われる。

参考