Nginx ロードバランシング実践:upstream設定とヘルスチェック
深夜2時、携帯が異常な振動。監視ダッシュボードを開くと、backend1のステータスバーが真っ赤—単一アプリサーバーがダウンした。
その年のダブル11セール、バックエンドサーバーは2台しかなかった。Nginx設定は upstream backend { server backend1; server backend2; } と書いてあり、対称的に見えた。しかしbackend1が70%のトラフィックを担っていた。設定ファイルで先頭に配置されていたからだ。Nginxはデフォルトラウンドロビンを使用—ウェイト設定なし、ヘルスチェックもなし。
backend1がクラッシュした瞬間、ユーザーの注文要求はまだこの死んだサーバーに送られていた。Nginxはダウンを知らず、そこに要求を転送し続けた。ユーザーが見たもの?500エラーページ。運用が手動でbackend1をupstreamから削除した時、15分が経過していた。
この事故後、不足していた知識を補った:Nginx upstreamはサーバーアドレスをリストするだけではない。ウェイト分配、ヘルスチェック、フェイルオーバー—これらが本番環境で本当に必要なものだ。この記事は踏んだ穴と後に学んだ設定方法をまとめる。オープンソースNginxでアクティブヘルスチェックを実装する方法も含む(NGINX Plus購入費不要)。
1. upstream基本設定:単一サーバーからクラスタへ
upstreamブロックの核心機能は単純:複数サーバーを1つの論理グループにまとめ、Nginxに要求転送先を知らせる。しかしパラメータは多くの人が想像するより豊富だ。
upstream backend {
zone backend 64k;
server backend1.example.com weight=3 max_fails=2 fail_timeout=30s;
server backend2.example.com;
server backup1.example.com backup;
}
行ごとに説明:
zone backend 64k:共有メモリ領域。Nginxのworkerプロセス間でバックエンドサーバー状態情報(誰が生きてる、誰がダウン)を共有する必要がある。64kは初期値—サーバー数が多い場合は増量可能。この行がないと、workerは各自状態を管理し、問題が発生する。
weight=3:ウェイト。backend1のウェイトは3、backend2はデフォルト1。これは4要求中、3がbackend1、1がbackend2に行くことを意味。バックエンドサーバー構成が不均一なシナリオに適用—例えばbackend1は8コア16GB、backend2は4コア8GB。
max_fails=2:失敗回数閾値。fail_timeout時間ウィンドウ内で、このサーバーへの要求が2回失敗すると、Nginxは利用不可とマーク。デフォルトは1回—センシティブすぎる。一回のネットワークジッターでトリガーされ、適切ではない。本番環境では2または3推奨。
fail_timeout=30s:二重意味。第一、失敗カウント時間ウィンドウは30秒;第二、サーバーが利用不可マーク後、Nginxは30秒後に再接続試行。デフォルト10秒は、スロースタートサービスには不足かもしれない。
backup:バックアップサーバー。全主サーバーが利用不可時のみ、backupサーバーが要求を受信。低構成サーバーを最後の砦として使用に適用。
downパラメータもあり、サーバーを手動オフラインマーク、保守時常用:
server backend3.example.com down; # 一時オフライン保守
実際使用時、多くの人がzone設定を無視するのを見た。結果:workerプロセスが各自状態を維持。あるworkerがサーバーダウンを発見しても、他workerはそこに要求を送り続ける。zone追加後、状態同期問題が解決。
2. 5つのロードバランシング戦略:どれを使う?
デフォルトround-robinラウンドロビン戦略で十分?状況次第。
デフォルトラウンドロビンで数年問題なく運用したプロジェクトを見た。しかしWebSocket長接続、カートセッション、キャッシュパンクチャーシナリオに遭遇すると、デフォルト戦略が適切でないことを発見。以下はまとめた選択ガイド:
| シナリオ | 推奨戦略 | 理由 |
|---|---|---|
| ステートレスAPI | round-robin | 均等分配、特別処理不要 |
| WebSocketサービス | least_conn | 接続数動的監視、一台過負荷回避 |
| ECカート | ip_hash | 同ユーザー要求は同サーバーへ |
| キャッシュプロキシ | hash key=$uri | キャッシュ無効化・パンクチャー減少 |
| テスト環境 | random | 高速検証、設定単純 |
round-robin(デフォルトラウンドロビン)
設定なしはラウンドロビン。要求は順序通り各サーバーに送信:
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
ステートレスサービスに適用。各要求は独立、前回要求状態に依存しない。大多数REST APIに適用。
least_conn(最小接続)
現在接続数最少サーバーに優先送信:
upstream websocket_app {
least_conn;
server ws1.example.com:8080;
server ws2.example.com:8080;
}
WebSocketサービスの典型的シナリオ。1ユーザーが1長接続を確立、接続数は波動大。ラウンドロビン使用だと、あるサーバーに大量長接続が蓄積し、新要求もそこに送り続けられる。least_connは接続数をリアルタイム監視、負荷最低サーバーに新要求を送信。
ip_hash(IPハッシュ)
クライアントIPアドレスからハッシュ値計算—同IP要求は常に同サーバーへ:
upstream shopping_cart {
ip_hash;
server cart1.example.com;
server cart2.example.com;
}
セッション整合性必要シナリオに適用。ECカート例—ユーザーがcart1で商品追加、次要求がcart2にラウンドロビンされると、カートデータが見つからない(分散セッションストレージ使用以外)。ip_hashで解決。
しかしip_hashには制限:あるサーバーがダウンすると、元々そのサーバーにhashされたユーザーは再割り当て。この部分ユーザーのセッションは失われる。なのでip_hashはセッションデータが致命的でないシナリオ、またはセッション共有ストレージとの併用に適用。
hash(整合性ハッシュ)
カスタムハッシュキー、整合性ハッシュアルゴリズム対応:
upstream cache_proxy {
hash $uri consistent;
server cache1.example.com;
server cache2.example.com;
}
キャッシュプロキシシナリオ首选。$uriは要求パスをハッシュキーとして使用。consistentパラメータで整合性ハッシュ有効化—サーバー増減時、一部キーのみ再マップ、全洗い替えなし。キャッシュヒット率は大幅低下しない。
random
単純ランダム分配:
upstream test_backend {
random;
server test1.example.com;
server test2.example.com;
}
テスト環境で十分。本番環境は推奨しない—制御力不足。
正直、私の経験:大多数Webアプリはround-robinまたはleast_connで十分。ip_hashとhashは特定シナリオの解決策—“高度に見える”ために強制使用しない。
3. パッシブヘルスチェック:max_failsとfail_timeout
“パッシブ”意味:Nginxはバックエンドサーバー健康状態を主动探査しない、実際要求成否観察で判断。隣人に”あなた大丈夫?“と尋ねない—犬の散歩、パッケージ受取を見て、日常行為から間接判断するように。
max_failsとfail_timeoutパラメータはこの”観察機構”を設定:
upstream backend {
server backend1.example.com max_fails=3 fail_timeout=30s;
server backend2.example.com max_fails=3 fail_timeout=30s;
}
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}
proxy_next_upstreamは”失敗”条件を指定。errorは接続エラー、timeoutはタイムアウト、http_500からhttp_504は各HTTPエラーステータスコード。これら発生時、Nginxは要求を次サーバーに転送、現在サーバーに1回失敗記録。
30秒内3回失敗、サーバーは利用不可マーク。次30秒、Nginxはそこに要求送信停止。30秒後、Nginxは1回試行—成功ならサーバー回復;失敗なら、さらに30秒待機。
この機構の問題:障害応答遅い。少なくとも3回リアルユーザー要求失敗が必要。この3ユーザーは既にエラー応答受信—体験被害。
さらに極端なケース:サーバー起動直後、まだ初期化中、健康状態不安定。max_fails=1設定は起動期間の1回失敗でサーバーを利用不可マークし、常に除外される可能性。
私の提案:
- max_failsは2または3に設定、偶発ネットワークジッター許容
- fail_timeoutは30秒以上に設定、サーバー回復機会を与える
- proxy_next_upstreamに完全エラー種別リスト設定、失敗ケース漏れ回避
パッシブチェックの利点:設定単純、オープンソースNginx直接対応。欠点:ユーザー要求トリガー依存、ユーザー体験が先に被害。より高速障害検出、主动バックエンド探査が必要なら、アクティブヘルスチェック。
4. アクティブヘルスチェック:NGINX Plusとオープンソース代替案
アクティブヘルスチェックのロジック:Nginxは定期にバックエンドサーバーに探査要求送信(例GET /health)、応答状態でサーバー健康判断。ユーザー要求失敗待機不要—Nginx自身で障害サーバー発見、事前削除。
公式ソリューションはNGINX Plus(商用)、年費$3,675/インスタンス。10インスタンスで年$36,750。正直、この価格は多く企業には高い。
オープンソースソリューション:nginx_upstream_check_module、Taobao技術チーム開発。Nginx再コンパイルでモジュール追加必要、しかし機能は相当完全:
| 特徴 | NGINX Plus | nginx_upstream_check_module |
|---|---|---|
| 価格 | $3,675/年 | オープンソース無料 |
| HTTPチェック | 対応 | 対応 |
| TCPチェック | 対応 | 対応 |
| MySQLチェック | 未対応 | 対応 |
| FastCGIチェック | 未対応 | 対応 |
| ステータスページ | 対応 | 対応(check_status) |
オープンソースモジュールはMySQLとFastCGIチェック対応—NGINX Plusにない機能。バックエンドがPHP-FPMまたはMySQLなら、このモジュールが適切。
nginx_upstream_check_module設定例
upstream backend {
server backend1.example.com:8080;
server backend2.example.com:8080;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
location / {
proxy_pass http://backend;
}
location /upstream_status {
check_status json;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
パラメータ説明:
- interval=3000:3秒毎に探査要求送信
- rise=2:連続2回成功後、サーバー健康マーク(起動直後サーバーは不安定可能性、連続成功確認必要)
- fall=5:連続5回失敗後、サーバー利用不可マーク(偶発タイムアウト許容)
- timeout=1000:探査要求タイムアウト1秒
- type=http:HTTPプロトコル探査使用(tcp、ssl_hello、mysql、ajp、fastcgiも対応)
check_http_sendは探査要求内容定義。ここでは単純GET /health要求送信。バックエンドで/healthエンドポイント実装必要、200または3xxステータスコード返却。
check_http_expect_aliveは”健康”と判定するステータスコード指定。http_2xxとhttp_3xxは200-299と300-399ステータスコードが成功と判定。
check_statusはステータス監視ページ提供。JSON形式出力、PrometheusやZabbix連携に便利。allow/denyはアクセス制御—このページは随意公開不可。
モジュールインストール方法
nginx_upstream_check_moduleはコンパイルインストール必要。概略手順:
# モジュールソースダウンロード
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
# Nginxソースダウンロード
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
# パッチ適用(Nginxバージョン選択)
cd nginx-1.24.0
patch -p1 < ../nginx_upstream_check_module/check_1.20.1+.patch
# コンパイル
./configure --add-module=../nginx_upstream_check_module
make && make install
Docker展開の場合、モジュール含むイメージを自作、またはコミュニティ既存イメージを探す。
5. 本番環境実践:セキュリティと監視
本番環境でのヘルスチェック設定にはいくつか陥りやすい穴がある。3原則をまとめた:
セキュリティ設定:3要点
1. check_statusページにアクセス制御必須
ステータスページはバックエンドサーバーリストと健康状態を公開。外部アクセスされると、攻撃者は内部トポロジー情報を見られ、現在どのサーバーが利用不可を知られる—攻撃の好機だ。
location /upstream_status {
check_status json;
allow 127.0.0.1; # ローカルアクセス
allow 10.0.0.0/8; # 内部IP
deny all; # 他拒否
}
またはより厳格—特定監視サーバーIPのみ許可。
2. 専用ヘルスチェックポート使用
ヘルスチェック要求は高頻度(3-5秒毎)。ビジネスポート直接探査だと、バックエンドログに大量/health要求記録。ログファイル膨張、性能影響。
バックエンドサービスで2ポート監視推奨:ビジネスポート(例8080)とヘルスチェックポート(例8888)。ヘルスチェックポートは単純ステータスコードのみ返却、ビジネスロジック処理なし、ログ書込なし。
check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;
port=8888で専用探査ポート指定。
3. ヘルスチェックエンドポイントは機密情報返さない
/healthエンドポイントはステータスコードのみ返却必要。バージョン番号、設定情報、メモリ使用等内部データ返却不可。攻撃者はこの情報で脆弱性位置特定。
# バックエンド実装例
@app.route('/health')
def health():
return '', 200 # ステータスコードのみ返却
監視連携:JSON出力
check_statusは多形式対応。JSON形式は監視システム連携に適切:
curl http://127.0.0.1/upstream_status
出力例:
{
"servers": {
"total": 3,
"generation": 12,
"server": [
{"index": 0, "name": "10.0.0.1:8080", "status": "up", "rise": 5, "fall": 0, "type": "http"},
{"index": 1, "name": "10.0.0.2:8080", "status": "up", "rise": 3, "fall": 0, "type": "http"},
{"index": 2, "name": "10.0.0.3:8080", "status": "down", "rise": 0, "fall": 5, "type": "http"}
]
}
}
generationは設定変更カウンタ。upstream設定変更・reload毎にgeneration値増加。監視スクリプトはこの値比較で設定有効確認。
パラメータ調整提案
intervalは3000ms以上
探査頻度過高はバックエンドに圧力。3-5秒間隔で十分—障害検出遅延は秒級、ユーザー体験影響なし。
riseとfall閾値バランス
- rise値過小(例1)、サーバー起動時初期化未完了で不健康誤判定、快速回復、反復振動
- fall値過小(例1)、1回ネットワークジッターで削除トリガー、過度センシティブ
私の経験値:rise=2、fall=3またはfall=5。瞬時障害許容、持続障害確認後削除。
完全本番設定
upstream web_app {
zone web_app 64k;
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080;
server 10.0.0.3:8080 backup;
check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;
check_http_send "GET /health HTTP/1.1\r\nHost: app.example.com\r\n\r\n";
check_http_expect_alive http_2xx;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://web_app;
proxy_set_header Host $host;
proxy_next_upstream error timeout http_502 http_503 http_504;
}
location /upstream_status {
check_status json;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
この設定:
- zone共有メモリでworker状態同期保証
- アクティブヘルスチェックは5秒毎専用ポート探査
- ステータスページは内部ネットワークアクセスのみ許可
- proxy_next_upstreamで障害サーバー上要求を健全バックエンド転送保証
結論
その年ダブル11事故に戻る。後にupstreamにzone共有メモリ追加、max_failsとfail_timeout設定、さらにnginx_upstream_check_moduleコンパイルインストールでアクティブヘルスチェック実施。サーバークラッシュ時、Nginxは5秒内で発見・削除—ユーザーは基本エラー応答受信しない。
ロードバランシング戦略選択、一言で:ステートレスサービスはround-robinまたはleast_conn、ステートフルサービスはip_hashまたはhash。ヘルスチェック選択:本番環境必須、オープンソースソリューションはnginx_upstream_check_module、check_statusページにアクセス制御忘れない。
Nginxがまだデフォルトラウンドロビン、ヘルスチェック未設定なら、パッシブチェック(max_fails + fail_timeout)から開始推奨。最小変更、即時効果。安定性検証後、アクティブヘルスチェックアップグレード考慮。テスト環境で先試行、設定問題なし確認後本番展開。
FAQ
Nginx upstreamのzone設定は何をする?
パッシブとアクティブヘルスチェックの違いは?
nginx_upstream_check_moduleとNGINX Plusどちらが良い?
ロードバランシング戦略round-robinとleast_connどう選ぶ?
ヘルスチェックriseとfallパラメータどう設定?
check_statusページにアクセス制御必須な理由?
7 min read · 公開日: 2026年4月27日 · 更新日: 2026年4月29日
関連記事
Nginx リバースプロキシ完全ガイド:upstream、バッファ、タイムアウト
Nginx リバースプロキシ完全ガイド:upstream、バッファ、タイムアウト
Nginx パフォーマンスチューニング:gzip、キャッシュ、接続プール設定
Nginx パフォーマンスチューニング:gzip、キャッシュ、接続プール設定
Vitest 単体テスト実践:TDD ワークフローとカバレッジレポート


コメント
GitHubアカウントでログインしてコメントできます