CentOS Stream 9でHAProxy Layer 7ロードバランサーを構築:インストール、設定、バックエンドヘルスチェック

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

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マシン
  • バックエンド1192.168.1.21 — ApacheまたはNginxが稼働中
  • バックエンド2192.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が自動的に残りのサーバーへトラフィックを切り替える。ユーザーは何が起きたかさえ気づかない。

Share: