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にリクエストが届く前に自動的にブロックするようになった。

