高可用性のためのNginx負荷分散戦略

Nginxの負荷分散を使用してWebアプリケーションの高可用性を実現する方法を学びます。このガイドでは、ラウンドロビン、重み付きラウンドロビン、最小接続数、IPハッシュなどの重要なNginx負荷分散戦略を解説します。実用的な設定例を紹介し、ヘルスチェックメカニズムを理解し、さまざまなトラフィック負荷の下でアプリケーションのアクセス性とパフォーマンスを維持するためのベストプラクティスを実装します。

高可用性のためのNginx負荷分散戦略

Nginxの負荷分散は、通常、最初の痛みを伴う限界に直面した後に導入されます。つまり、1つのアプリケーションサーバーが過負荷になったり、メンテナンスが必要になったり、サイト全体をダウンさせるような障害が発生した場合です。Nginxを複数のバックエンドの前に配置することで、リクエストを分散し、サーバーをドレインし、通常の障害を乗り越える余裕が生まれます。

それ自体が魔法のように高可用性を実現するわけではありません。オープンソースのNginxは、接続障害が発生した後にバックエンドへのトラフィックを停止できますが、チェックアウトページ、API依存関係、データベース接続が正常かどうかを深く理解するわけではありません。適切なセットアップは、Nginxのアップストリーム設定、適切なタイムアウト、アプリケーションレベルのヘルスエンドポイント、外部監視、および不良バックエンドを迅速に削除できるデプロイプロセスを組み合わせます。

負荷分散の理解

負荷分散の核心は、クライアントリクエストをサーバーのプールにインテリジェントに振り向けることです。単一のサーバーがすべてのトラフィックを処理する代わりに、複数のサーバーが連携して動作します。これにより、いくつかの重要な利点が得られます。

  • 高可用性: 1つのサーバーが検出可能な方法で障害を起こした場合でも、他のサーバーがリクエストを処理し続けることができます。
  • スケーラビリティ: トラフィックが増加するにつれて、プールにサーバーを追加して負荷を処理できます。
  • パフォーマンス: トラフィックを分散することで、単一のサーバーが過負荷になるのを防ぎ、応答時間を短縮できます。
  • 信頼性: 単一障害点を排除することで、アプリケーションの堅牢性が向上します。

Nginxは、負荷分散設定においてリバースプロキシとして機能します。受信したクライアントリクエストを受け取り、設定されたアルゴリズムに基づいて利用可能なバックエンドサーバーの1つに転送します。また、バックエンドサーバーから応答を受け取り、クライアントに送り返すため、エンドユーザーからはプロセスが透過的に見えます。

Nginx負荷分散ディレクティブ

Nginxは、設定ファイル(通常はnginx.confまたはそこからインクルードされるファイル)内で特定のディレクティブを使用して、アップストリームサーバーグループとその負荷分散動作を定義します。

upstreamブロック

upstreamブロックは、Nginxがトラフィックを分散するサーバーのグループを定義するために使用されます。このブロックは通常、httpコンテキスト内に配置されます。

http {
    upstream my_backend_servers {
        # サーバー設定はここに記述
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://my_backend_servers;
        }
    }
}

upstreamブロック内では、serverディレクティブを使用してバックエンドサーバーをリストし、IPアドレスまたはホスト名とポートを指定します。

upstream my_backend_servers {
    server backend1.example.com;
    server backend2.example.com;
    server 192.168.1.100:8080;
}

proxy_passディレクティブ

locationブロック内で使用されるproxy_passディレクティブは、定義したupstreamグループを指します。Nginxは、設定された負荷分散アルゴリズムを使用して、各リクエストに対してこのグループからサーバーを選択します。

Nginx負荷分散アルゴリズム

Nginxは、それぞれ独自のトラフィック分散アプローチを持ついくつかの負荷分散アルゴリズムをサポートしています。デフォルトのアルゴリズムはラウンドロビンです。

1. ラウンドロビン(デフォルト)

ラウンドロビンでは、Nginxはupstreamグループ内の各サーバーに順番にリクエストを分散します。時間の経過とともに、各サーバーは均等な負荷を受け取ります。これはシンプルで、同一のサーバーに効果的であり、最も一般的に使用される方法です。

設定:

upstream my_backend_servers {
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

長所:

  • 実装と理解が簡単。
  • サーバーの容量が類似している場合、負荷を均等に分散します。

短所:

  • サーバーの負荷や応答時間を考慮しません。遅いサーバーがリクエストを受け取り続ける可能性があります。

2. 重み付きラウンドロビン

重み付きラウンドロビンでは、各サーバーに重みを割り当てることができます。重みが大きいサーバーは、比例してより多くのトラフィックを受け取ります。これは、異なる容量(例えば、より強力なハードウェア)を持つサーバーがある場合に便利です。

設定:

upstream my_backend_servers {
    server backend1.example.com weight=3;
    server backend2.example.com weight=1;
}

この例では、backend1.example.combackend2.example.comの3倍のリクエストを受け取ります。

長所:

  • サーバー容量に基づいた負荷分散が可能。

短所:

  • リアルタイムのサーバー負荷を考慮しません。

3. 最小接続数

最小接続数アルゴリズムは、アクティブな接続が最も少ないサーバーにリクエストを振り向けます。この方法は、各サーバーの現在の負荷を考慮するため、より動的です。

設定:

最小接続数を有効にするには、upstreamブロックにleast_connパラメータを追加するだけです。

upstream my_backend_servers {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

長所:

  • 現在のサーバー負荷を考慮して、よりインテリジェントに負荷を分散します。
  • 接続時間が変動するアプリケーションに適しています。

短所:

  • 接続数が急激に変動する場合、管理がやや複雑になる可能性があります。

4. IPハッシュ

IPハッシュでは、NginxはクライアントのIPアドレスのハッシュに基づいて、どのサーバーがリクエストを処理するかを決定します。これにより、同じクライアントIPアドレスからのリクエストが常に同じバックエンドサーバーに送信されることが保証されます。これは、共有セッションストレージを使用せずにセッション永続性(スティッキーセッション)に依存するアプリケーションにとって重要です。

設定:

upstreamブロックにip_hashパラメータを追加します。

upstream my_backend_servers {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
}

長所:

  • すぐに使えるセッション永続性を提供します。

短所:

  • 多くのクライアントが単一のIPアドレスを共有している場合(例:NATゲートウェイの背後)、負荷分散が不均等になる可能性があります。
  • サーバーが障害を起こした場合、そのサーバーにハッシュされたすべてのクライアントは、サーバーが復旧するかハッシュが再計算されるまで影響を受けます(ただし、Nginxは再ルーティングを試みます)。

5. 汎用ハッシュ

IPハッシュと同様に、汎用ハッシュではハッシュのキーを指定できます。このキーは、$request_id$cookie_jsessionidなどの変数、または変数の組み合わせにすることができます。これにより、セッション永続性や特定のリクエスト属性に基づくルーティングにおいて、より柔軟性が得られます。

設定:

upstream my_backend_servers {
    hash $remote_addr consistent;
    server backend1.example.com;
    server backend2.example.com;
}

hashconsistentを使用すると、一貫性ハッシュが実装され、サーバーのセットが変更されたときのキーの再分散が最小限に抑えられます。

長所:

  • カスタムルーティングロジックに非常に柔軟。
  • サーバー変更時の安定性を高めるために一貫性ハッシュをサポート。

短所:

  • ハッシュキーの慎重な選択が必要。

ヘルスチェックとサーバーステータス

有用な高可用性を実現するには、Nginxは障害が発生しているバックエンドを回避する必要があります。オープンソースバージョンは主に受動的にこれを行います。実際のトラフィックをプロキシしている間に失敗した試行を検出します。これは、ダウンしたホスト、接続拒否、および一部のタイムアウトケースに役立ちます。これは、ユーザーがサービスにヒットする前に数秒ごとに/healthzを呼び出すアクティブなヘルスチェックと同じではありません。

max_failsfail_timeout

これらのパラメータは、upstreamブロック内のserverディレクティブに追加され、Nginxが障害が発生したサーバーをどのように扱うかを制御します。

  • max_fails: 指定されたfail_timeout期間内にサーバーとの通信に失敗した試行回数。max_fails回の失敗後、サーバーは利用不可としてマークされます。
  • fail_timeout: サーバーが利用不可と見なされる期間。この期間が経過すると、Nginxは再度ステータスを確認しようとします。

設定:

upstream my_backend_servers {
    server backend1.example.com max_fails=3 fail_timeout=30s;
    server backend2.example.com max_fails=3 fail_timeout=30s;
}

この例では、backend1.example.comが障害ウィンドウ中に3回の失敗した試行があった場合、Nginxは一時的にそのサーバーを回避します。タイムアウト後、Nginxは再度試行する可能性があります。失敗は、追加のツールやNginx Plusの機能を使用していない限り、カスタムアプリケーションのヘルス応答ではなく、接続/プロキシ試行に基づいています。

backupパラメータ

backupパラメータは、サーバーをバックアップとして指定します。このサーバーは、upstreamグループ内の他のすべてのアクティブなサーバーが利用不可の場合にのみトラフィックを受け取ります。

設定:

upstream my_backend_servers {
    server backend1.example.com;
    server backend2.example.com;
    server backup.example.com backup;
}

backend1backend2がダウンしている場合、backup.example.comが引き継ぎます。

Nginx Plusのヘルスチェック

商用バージョンのNginx Plusには、組み込みのアクティブなヘルスチェックが含まれています。バックエンドに定期的にリクエストを送信し、応答を評価し、ユーザートラフィックがルーティングされる前に異常なサーバーを削除できます。オープンソースのNginxを使用している場合でも、堅牢なシステムを構築することは可能ですが、通常は外部監視、サービスディスカバリ、またはアップストリームターゲットを編集/削除する自動化と組み合わせる必要があります。

実用的な設定例

これらの概念を一般的なシナリオで実践してみましょう。

シナリオ1: シンプルなラウンドロビン負荷分散

2つの同一のWebサーバーにトラフィックを分散します。

設定:

http {
    upstream web_servers {
        server 10.0.0.10;
        server 10.0.0.11;
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://web_servers;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

説明:

  • upstream web_servers: web_serversという名前のグループを定義します。
  • server 10.0.0.10;server 10.0.0.11;: バックエンドサーバーを指定します。
  • proxy_pass http://web_servers;: トラフィックをweb_serversアップストリームグループに振り向けます。
  • proxy_set_header: これらのディレクティブは、元のクライアント情報をバックエンドサーバーに渡すために重要であり、ロギングやアプリケーションロジックで必要になることがよくあります。

シナリオ2: セッション永続性を備えた負荷分散(IPハッシュ)

ユーザーが同じバックエンドサーバーに接続し続けるようにします。これは、セッションデータをローカルに保存するアプリケーションに役立ちます。

トレードオフを理解している場合にのみ使用してください。多くのユーザーが同じオフィスのNAT、モバイルキャリアゲートウェイ、または企業プロキシを経由する場合、IPハッシュは1つのバックエンドに過剰なトラフィックを送信する可能性があります。共有セッションストレージ、署名付きステートレスクッキー、またはアプリケーションレベルのセッションレプリケーションは、クライアントIPのスティッキネスに依存するよりもクリーンな場合が多いです。

設定:

http {
    upstream app_servers {
        ip_hash;
        server 192.168.1.50:8000;
        server 192.168.1.51:8000;
    }

    server {
        listen 80;
        server_name api.yourdomain.com;

        location / {
            proxy_pass http://app_servers;
            # ... その他のproxy_set_headerディレクティブ ...
        }
    }
}

シナリオ3: フェイルオーバーを備えた重み付き負荷分散

より強力なサーバーにより多くのトラフィックを振り向け、バックアップを準備します。

設定:

http {
    upstream balanced_app {
        server app_server_1.local weight=5;
        server app_server_2.local weight=2;
        server app_server_3.local backup;
    }

    server {
        listen 80;
        server_name staging.yourdomain.com;

        location / {
            proxy_pass http://balanced_app;
            # ... その他のproxy_set_headerディレクティブ ...
        }
    }
}

ここでは、app_server_1.localがトラフィックの5/7を受け取り、app_server_2.localが2/7を受け取り、app_server_3.localは他の2つが利用不可の場合にのみリクエストを処理します。

ベストプラクティスとヒント

  • proxy_set_headerを使用する: HostX-Real-IPX-Forwarded-ForX-Forwarded-Protoなどのヘッダーを常に設定して、バックエンドアプリケーションが元のクライアントの詳細を認識できるようにします。
  • Nginxを最新の状態に保つ: セキュリティとパフォーマンスの向上のために、安定した最新バージョンのNginxを実行していることを確認してください。
  • バックエンドサーバーを監視する: Nginxの内部ヘルスチェックに加えて、外部監視ツールを実装します。Nginxはサーバーに到達できるかどうかしか認識できず、サーバー上のアプリケーションが正しく機能しているかどうかを必ずしも認識できるわけではありません。
  • Nginx Plusを検討する: ミッションクリティカルなアプリケーションには、Nginx Plusがアクティブヘルスチェック、セッション永続性、ライブアクティビティ監視などの高度な機能を提供し、管理を簡素化し、復元力を向上させることができます。
  • DNS負荷分散: リージョン間または複数のNginxエントリポイント間でのトラフィック分散にはDNSが役立ちますが、DNSフェイルオーバーはリゾルバーの動作とTTLに依存します。即時フェイルオーバーとして扱わないでください。
  • SSL終端: 多くの場合、ロードバランサー(Nginx)でSSLを終端して、バックエンドサーバーからSSL処理をオフロードできます。

実用的な出発点

2つまたは3つの同一のアプリサーバーの場合、プレーンなラウンドロビン、控えめなプロキシタイムアウト、明確なアップストリームロギングから始めてください。max_failsfail_timeoutを追加し、1つのバックエンドを停止したときに何が起こるかをテストします。実際のインシデントを待ってNginxの動作を学ばないでください。

リクエストの処理時間が大きく異なる場合は、least_connを試してください。1つのサーバーが他のサーバーより大きい場合は、重みを使用します。アプリケーションがセッション状態をローカルに保存する場合は、可能であればセッション設計を修正してください。ip_hashは、実用的な橋渡しが必要な場合にのみ使用してください。

最適なNginx負荷分散戦略は、アプリケーションの障害の仕方に一致するものです。停止したVM、遅いバックエンド、壊れたリリース、データベースの停止はすべて、プロキシの観点からは異なって見えます。アルゴリズムを設定し、セットアップを高可用性と呼ぶ前に、小さなテストで障害動作を証明してください。