1台のサーバーでは足りなくなったとき
CentOS 8がEOLを迎えたとき、5台のサーバーを1週間でRocky Linuxに移行しなければならなかった。それが、なぜロードバランサーが必要なのかを初めて本当に理解した瞬間だった。それまでは「高スペックのサーバーさえあれば十分」と思っていたが、完全に間違いだった。ハードウェアがいくら優れていても、数時間でトラフィックが5倍になったときや、30秒のダウンタイムも許されないデプロイが必要なときには何の役にも立たない。
HAProxy(High Availability Proxy)は、筆者が何度も選んできたツールだ。軽量で安定しており、無料で使える。NginxやApacheでもロードバランシングはできるが、それは本来の用途ではない。HAProxyは最初からその目的のために設計されているため、ミドルレンジのハードウェアで数万リクエスト/秒を処理してもCPUに余裕がある。
Layer 7とは、HAProxyがHTTPのコンテンツを理解できることを意味する。URLパス、ヘッダー、Cookieなどに基づいてトラフィックをルーティングする。IPとポートしか見ないLayer 4とは異なり、Layer 7では/api/*をサーバーAに、/static/*をサーバーBに振り分けたり、ユーザーのCookieによるスティッキーセッションも実現できる。実用的な例を挙げると、ReactフロントエンドとREST APIをそれぞれ別のプールに振り分け、外部からは同一のパブリックIPで提供できる。
CentOS Stream 9へのHAProxyインストール
ラボ構成
- ロードバランサー:
192.168.1.10— HAProxyをインストールするCentOS Stream 9マシン - バックエンド1:
192.168.1.21— ApacheまたはNginxが稼働中 - バックエンド2:
192.168.1.22— ApacheまたはNginxが稼働中
パッケージのインストール
CentOS Stream 9のAppStreamリポジトリにはHAProxyが含まれており、dnfで直接インストールできる:
sudo dnf install -y haproxy
haproxy -v
haproxy -vでバージョンを確認しよう。AppStreamには通常HAProxy 2.4.xが含まれており、ほとんどの本番環境のワークロードには十分な機能を持っている。
設定前のSELinux対応
ここを見落としたあとで「なぜバックエンドに接続できないのか」と悩む人が多い箇所だ。SELinuxはデフォルトでHAProxyが任意のポートに接続するのをブロックする。次のbooleanを有効にする必要がある:
# HAProxyがバックエンドへ接続するのを許可する
sudo setsebool -P haproxy_connect_any 1
# 有効になっているか確認する
getsebool haproxy_connect_any
バックエンドが8080などの非標準ポートを使用している場合は、SELinuxポリシーに追加で定義する必要がある:
sudo semanage port -a -t http_port_t -p tcp 8080
firewalldでポートを開放する
クライアント向けにポート80を、Statsモニタリングページ向けにポート8404を開放する。一度設定してリロードするだけでよい:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-port=8404/tcp
sudo firewall-cmd --reload
# 設定を確認する
sudo firewall-cmd --list-all
HAProxy Layer 7の設定
設定ファイルの作成
元のファイルをバックアップしてから新しい設定を作成する:
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
sudo nano /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats
#---------------------------------------------------------------------
# すべてのfrontend/backendに適用されるデフォルト設定
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
#---------------------------------------------------------------------
# Stats page — http://192.168.1.10:8404/stats
#---------------------------------------------------------------------
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:StrongPassword123
stats admin if TRUE
#---------------------------------------------------------------------
# メインfrontend — クライアントからのリクエストを受け付ける
#---------------------------------------------------------------------
frontend http_front
bind *:80
default_backend web_servers
# Layer 7ルーティング:/api/* を専用バックエンドへ転送
acl is_api path_beg /api/
use_backend api_servers if is_api
#---------------------------------------------------------------------
# Backend web servers
#---------------------------------------------------------------------
backend web_servers
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.21:80 check inter 5s fall 3 rise 2
server web2 192.168.1.22:80 check inter 5s fall 3 rise 2
#---------------------------------------------------------------------
# Backend API servers
#---------------------------------------------------------------------
backend api_servers
balance leastconn
option httpchk GET /api/health
http-check expect status 200
server api1 192.168.1.21:8080 check inter 5s fall 3 rise 2
server api2 192.168.1.22:8080 check inter 5s fall 3 rise 2
重要なパラメータの説明
- balance roundrobin:リクエストを均等に順番に分散する。処理に時間がかかるAPIには、接続数が最も少ないサーバーへ転送する
leastconnが適している。 - option httpchk GET /health:HAProxyが
/healthにHTTP GETリクエストを送信して、バックエンドが稼働中かどうかを確認する。 - inter 5s:5秒ごとにヘルスチェックを実行する。
- fall 3:3回連続で失敗して初めてDOWNとマークされる。
- rise 2:2回連続で成功して初めてUPに戻される。
- option forwardfor:バックエンドがクライアントの実際のIPを把握できるよう、
X-Forwarded-Forヘッダーを追加する。
バックエンドで/healthエンドポイントを作成する
バックエンドがHAProxyのヘルスチェックに応答するには200 OKを返す必要がある。Apacheの場合、最も手軽な方法は次のとおりだ:
# 各バックエンドサーバーで実行する
echo "OK" | sudo tee /var/www/html/health
HAProxyの起動
# 設定ファイルを検証する
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# 起動して自動起動を有効にする
sudo systemctl start haproxy
sudo systemctl enable haproxy
# ステータスを確認する
sudo systemctl status haproxy
テストとモニタリング
Statsページへのアクセス
ブラウザでhttp://192.168.1.10:8404/statsにアクセスし、admin / StrongPassword123でログインする。日常的なモニタリングには十分なダッシュボードで、以下がリアルタイムで表示される:
- 各バックエンドのステータス(緑 = UP、赤 = DOWN)
- リクエスト数、転送バイト数、現在のセッション数
- 平均レスポンスタイムとエラー率
curlでロードバランシングをテストする
推測するより、10リクエストを連続してcurlで送り、HAProxyがどのように分散するかを直接確認しよう:
for i in {1..10}; do
curl -s http://192.168.1.10/ -o /dev/null -w "%{http_code} from: %{url_effective}\n"
done
各バックエンドがレスポンスヘッダーに異なるサーバー名を返している場合、分散の様子がより明確に確認できる:
for i in {1..6}; do
curl -si http://192.168.1.10/ | grep -i 'x-served-by\|server:'
done
ソケット経由でバックエンドのステータスを確認する
ブラウザを開かずに各サーバーの詳細を確認したい場合:
echo "show servers state" | sudo socat stdio /var/lib/haproxy/stats
リアルタイムでログを確認する
テスト中は別のターミナルを開いて並行して実行しよう。HAProxyのログは非常に詳細だ:
sudo journalctl -u haproxy -f
ログの各行には、クライアントIP、リクエストパス、レスポンスコード、処理時間、そしてどのバックエンドがリクエストを処理したかが記録される。
フェイルオーバーのテスト
最も現実的なテストは、バックエンドを1台停止して、HAProxyがどう反応するかを見ることだ:
# バックエンドサーバー192.168.1.21で実行する
sudo systemctl stop httpd
# ロードバランサーでログを監視する
sudo journalctl -u haproxy -f
15秒以内(3回のチェック × 5秒間隔)でHAProxyはweb1をDOWNとマークし、すべてのトラフィックをweb2に切り替える。Statsページではweb1が赤色で表示される。httpdを再起動すると、10秒後(2回のチェック成功)にweb1は自動的にUPに戻る。
実際の経験から学んだこと
CentOS 8の緊急移行作業から学んだことが一つある。ヘルスチェックエンドポイントのロジックを必ず確認すること。ウェブサーバーが起動しているかどうかだけを確認するのでは不十分だ。以前、HAProxyはweb1をUP(緑色)と表示していたのに、実際にはデータベース接続が切れてアプリケーションがエラーになっていたことがあった。ウェブサーバーが/healthに対して200を返し続けていたというだけだ。それ以来、/healthエンドポイントにロジックを追加している。データベースのping確認、キャッシュ接続の確認など、何かが失敗したら500を返すようにして、HAProxyがそのサーバーを避けられるようにしている。
まとめ
これだけで、CentOS Stream 9上に本番対応のLayer 7ロードバランサーを構築できる。SELinuxを有効にした状態で、firewalldも適切に設定し、何も無効化せずに済む。HAProxyはバックエンドの障害を自動検出してフェイルオーバーし、Statsページでリアルタイムにモニタリングでき、Layer 7のACLでURLパスに基づくトラフィックルーティングも実現できる。
さらに発展させたい場合は、Let’s EncryptによるSSL/TLSターミネーションをHAProxyに追加したり、ステートフルなアプリ向けにスティッキーセッションを設定したりできる。今この時点でも、バックエンドの1台に障害が発生すれば、HAProxyが自動的に残りのサーバーへトラフィックを切り替える。ユーザーは何が起きたかさえ気づかない。

