LinuxでHAProxyを使ったロードバランサー設定ガイド

Network tutorial - IT technology blog
Network tutorial - IT technology blog

15分でできる:HAProxy基本設定

会社の唯一のnginxサーバーがピーク時にレスポンスタイムが8〜12秒まで跳ね上がり、CPUが95%に達してこれ以上垂直スケールできない状況になった。ハードウェアを増強する代わりに、バックエンドサーバーを2台追加してその前段にHAProxyを配置した。インストール開始からトラフィックが均等に分散されるまで、約15〜20分で完了した。

このセクションでは、同様の構成をゼロから構築する方法を説明する。

用意するトポロジー

Internet
    ↓
[HAProxy] 192.168.1.10:80
    ↓           ↓
[Backend1]  [Backend2]
192.168.1.11  192.168.1.12
    (nginx)      (nginx)

HAProxyのインストール

# Ubuntu/Debian
sudo apt update && sudo apt install -y haproxy

# CentOS/RHEL
sudo dnf install -y haproxy

# バージョン確認
haproxy -v

すぐに動かすための最小設定ファイル

/etc/haproxy/haproxy.cfg ファイルを編集する:

global
    log /dev/log local0
    maxconn 4096
    user haproxy
    group haproxy
    daemon

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5s
    timeout client  30s
    timeout server  30s

frontend web_frontend
    bind *:80
    default_backend web_servers

backend web_servers
    balance roundrobin
    server backend1 192.168.1.11:80 check
    server backend2 192.168.1.12:80 check
# 再起動前に設定を確認
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# サービスの再起動
sudo systemctl restart haproxy
sudo systemctl enable haproxy

ブラウザでHAProxyサーバーのIPにアクセスすると、リクエストがbackend1とbackend2に交互に振り分けられる。基本設定はこれで完了だ。

詳細解説:ロードバランシングアルゴリズム

すべてを知る必要はない。次の3つのアルゴリズムで90%のケースに対応できる。どれを選ぶかはサービスのリクエスト特性によって決まる:

roundrobin — デフォルト、ほとんどのケースに適している

リクエストが順番に均等に分散される:1→2→1→2。バックエンドサーバーのハードウェア構成が同じ場合に有効だ。

backend web_servers
    balance roundrobin
    server backend1 192.168.1.11:80 check weight 1
    server backend2 192.168.1.12:80 check weight 1

leastconn — 処理時間にばらつきがある場合に使用

新しいリクエストは常に最もアイドル状態のサーバー、つまりその時点でコネクション数が最も少ないサーバーに振り向けられる。ファイルアップロードエンドポイントと軽量JSONエンドポイントが同じプールで動いているAPIバックエンドに適している。

backend api_servers
    balance leastconn
    server api1 192.168.1.21:8080 check
    server api2 192.168.1.22:8080 check

source — クライアントIPによるスティッキーセッション

同じIPは常に同じバックエンドにルーティングされる。セッションを各サーバーにローカル保存していてRedisに移行していないアプリケーションで使用する。

backend legacy_app
    balance source
    hash-type consistent
    server app1 192.168.1.31:80 check
    server app2 192.168.1.32:80 check

高度なヘルスチェック

デフォルトでは、checkはTCP接続のみを確認する。実際のHTTPレスポンスを確認するには:

backend web_servers
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
    http-check expect status 200
    server backend1 192.168.1.11:80 check inter 5s rise 2 fall 3
    server backend2 192.168.1.12:80 check inter 5s rise 2 fall 3

各パラメータの意味:

  • inter 5s — 5秒ごとにチェック
  • rise 2 — サーバーをUPとみなすには連続2回の成功チェックが必要
  • fall 3 — 連続3回チェック失敗でサーバーをDOWNとマーク

各バックエンドに200 OKを返す/healthエンドポイントをデプロイしている。データベースやキャッシュに依存せず、そのプロセス自体のステータスのみを返す。この方法でHAProxyは実際に停止しているバックエンドと高負荷で処理中のバックエンドを区別できる。

応用:Statsページ、SSLターミネーション、ACL

Statsダッシュボードの有効化

HAProxyにはWebダッシュボードが内蔵されている。開くとすぐに各バックエンドのUP/DOWN状態、現在のコネクション数、エラーレート、平均レスポンスタイムが確認できる。追加インストールは不要だ:

frontend stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:strong_password
    stats hide-version

http://192.168.1.10:8404/statsにアクセスすると、すべてのバックエンドステータス、コネクション数、レスポンスタイムを確認できる。

SSLターミネーション — HAProxyでのHTTPS処理

HAProxyでHTTPSを受け取り、バックエンドにHTTPで転送する方法(バックエンドへのSSLインストールは不要):

# certとkeyを1つの.pemファイルにまとめる
cat /etc/ssl/certs/example.com.crt /etc/ssl/private/example.com.key \
    > /etc/haproxy/certs/example.com.pem
chmod 600 /etc/haproxy/certs/example.com.pem
frontend web_frontend
    bind *:80
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    # HTTP → HTTPSへリダイレクト
    http-request redirect scheme https unless { ssl_fc }
    # クライアントがHTTPSを使用していることをバックエンドに伝えるヘッダーを転送
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    default_backend web_servers

ACL — パスまたはドメインによるルーティング

ACLを使うとHAProxyがリクエストを分類して異なるバックエンドプールにルーティングできる。典型的な例:/api/はAPIサーバーへ、静的ファイルはCDNキャッシュへ、それ以外はWebサーバーへ:

frontend web_frontend
    bind *:80
    acl is_api path_beg /api/
    acl is_static path_end .jpg .png .css .js
    use_backend api_servers if is_api
    use_backend static_servers if is_static
    default_backend web_servers

backend api_servers
    balance leastconn
    server api1 192.168.1.21:8080 check

backend static_servers
    balance roundrobin
    server cdn1 192.168.1.31:80 check

backend web_servers
    balance roundrobin
    server web1 192.168.1.11:80 check
    server web2 192.168.1.12:80 check

本番環境からの実践的なヒント

ダウンタイムなしでの設定リロード

これがHAProxyで最も気に入っている点だ。既存のコネクションを切断せずに設定をリロードできる:

# 事前に設定を確認
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# グレースフルリロード(コネクションを切断しない)
sudo systemctl reload haproxy
# または
sudo kill -USR2 $(cat /var/run/haproxy.pid)

メンテナンス前のサーバードレイン

バックエンドを更新する前は、サーバーを直接シャットダウンせずに必ずトラフィックをドレインする。HAProxyランタイムソケット経由でweight 0を使用する:

# HAProxyランタイムAPIに接続
echo "set weight web_servers/backend1 0" | \
    sudo socat stdio /var/run/haproxy/admin.sock

# 状態を確認
echo "show servers state" | \
    sudo socat stdio /var/run/haproxy/admin.sock

# 更新完了後、weightを元に戻す
echo "set weight web_servers/backend1 100" | \
    sudo socat stdio /var/run/haproxy/admin.sock

globalセクションにこの行を追加するのを忘れずに。これがないとsocatが接続エラーを報告する:

stats socket /var/run/haproxy/admin.sock mode 660 level admin

デバッグのための詳細ログ

クライアントから502が返ってきてどのバックエンドが原因かわからない場合、ログフォーマットをこのように設定すれば特定のサーバーまでトレースできる:

frontend web_frontend
    bind *:80
    option httplog
    log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %tsc %ac/%fc/%bc/%sc/%rc %{+Q}r"
    default_backend web_servers
# リアルタイムでログを確認
sudo tail -f /var/log/haproxy.log | grep "502"

ログフォーマットの%b/%sフィールドは、そのリクエストを処理したバックエンドと具体的なサーバー名を示す。これのおかげでbackend1がPHP-FPMのワーカーが枯渇して502を返していることを発見できた。一方backend2は完全に正常な状態だった。

abuse防止のためのIP単位コネクション制限

frontend web_frontend
    bind *:80
    # IPごとに同時接続を100に制限
    stick-table type ip size 100k expire 30s store conn_cur
    tcp-request connection track-sc1 src
    tcp-request connection reject if { sc_conn_cur(1) gt 100 }
    default_backend web_servers

あるクローラースクリプトが誤って1つのIPから500の同時接続でシステムを叩いた後に、これを適用した。HAProxyがnginxにリクエストが届く前に自動的にブロックするようになった。

Share: