NGINXとNGINX Plusは、非常に強力なHTTP、TCP、UDPロードバランサーです。大量のリクエストが突発的に発生した場合のプロキシ処理や、多数の同時接続の維持を極めて効率的に行えます。しかし、こうした特性により、NGINXとNGINX Plusでは特にエフェメラルポートの枯渇が発生しやすくなります。これは、新しいローカルソケットの確立用に割り当てられたポート番号をOSが使い果たし、新しい接続を作成できなくなる状態です。(エフェメラルポートの枯渇は両製品に該当しますが、簡潔にするため、以降の本ブログではNGINX Plusのみを取り上げます。)
このブログでは、TCP接続の構成要素と、接続が確立される前にその内容がどのように決定されるかを確認します。次に、NGINX Plusがエフェメラルポートの枯渇による影響を受けているかどうかを判断する方法を紹介します。最後に、Linuxカーネルの調整とNGINX Plusディレクティブの両方を使用して、こうした制約に対処する方法について説明します。
ネットワークソケットの概要
TCP経由で接続が確立されると、ローカルホストとリモートホストの両方にソケットが作成されます。これらのソケットが接続されてソケットペアが形成されます。このソケットペアは、ローカルのIPアドレスとポート、およびリモートのIPアドレスとポートで構成される一意の4タプルで表されます。
リモートIPアドレスとポートは接続のサーバー側に属し、クライアントが接続を開始する前に特定しておく必要があります。ほとんどの場合、接続に使用するローカルIPアドレスはクライアントによって自動的に選択されますが、接続を確立するソフトウェアによって選択されることもあります。最後に、ローカルポートは、オペレーティングシステムが使用可能としている所定の範囲からランダムに選択されます。このポートは接続中に限りクライアントに関連付けられるため、エフェメラルポートと呼ばれます。接続が終了すると、エフェメラルポートは再利用できるようになります。
エフェメラルポートの枯渇を特定する
はじめに述べたように、NGINX Plusはその性質上、エフェメラルポートの枯渇と、それに伴う問題の影響を受けます。NGINX Plusがアップストリームサーバーにリクエストをプロキシする場合、前述のソケット作成プロセスではクライアントとして動作します。デフォルトでは、プロキシ対象リクエストのソケットを、NGINX Plusが稼働するホスト上のローカルIPアドレスと、使用可能なエフェメラルポートに自動的にバインドします。接続レートが高く、既存のオープンソケットが閉じられるよりも速く、新たに確立されたソケットが待機状態に移行すると、最終的に使用可能なポートが枯渇し、新しいソケットを作成できなくなります。その結果、オペレーティングシステムとNGINX Plusの両方でエラーが発生します。以下は、NGINX Plusのエラーログに記録されるエラーの例です。
2016/03/18 09:08:37 [crit] 1888#1888: *13 connect() to 10.2.2.77:8081 failed (99: Cannot assign requested address) while connecting to upstream, client: 10.2.2.42, server: , request: "GET / HTTP/1.1", upstream: "http://10.2.2.77:8081/", host: "10.2.2.77"ポート枯渇が発生すると、アップストリームサーバーではなくNGINX Plusに起因する500エラーも急増します。以下は、NGINX Plusのアクセスログのエントリ例です。
10.2.2.42 - - [18/Mar/2016:09:14:20 -0700] "GET / HTTP/1.1" 500 192 "-" "curl/7.35.0"NGINX PlusサーバーでTIME-WAIT状態のソケット数を確認するには、Linuxシェルで次のssコマンドを実行します。この例では、TIME-WAIT状態で開いているソケットが143個あることがわかります。この例では、wcコマンドでコマンド出力の行数を取得しています。
# ss -a | grep TIME-WAIT | wc -l143利用可能なエフェメラルポート数を増やすためのカーネルチューニング
エフェメラルポートの枯渇を軽減する方法の1つは、Linuxカーネルの「net.ipv4.ip_local_port_range」設定を使用することです。デフォルトの範囲は通常、32768~61000です。
エフェメラルポートが不足している場合は、デフォルトの範囲を1024~65000に変更することで、使用可能なエフェメラルポート数を実質的に2倍に増やせます。カーネル設定の変更について詳しくは、F5のブログ記事「パフォーマンス向上のためのNGINXチューニング」をご覧ください。
NGINX Plusでのキープアライブ接続の有効化
エフェメラルポートの枯渇を軽減するもう1つの方法は、NGINX Plusとアップストリームサーバー間でキープアライブ接続を有効にすることです。HTTPの最も単純な実装では、クライアントが新しい接続を開いてリクエストを送信し、レスポンスを読み取った後、関連するリソースを解放するために接続を閉じます。

keepalive接続は、クライアントがレスポンスを読み取った後も開いたまま維持されるため、後続のリクエストで再利用できます。

NGINX Plusからアップストリームサーバーへのキープアライブ接続を有効にするには、keepaliveディレクティブを使用します。このディレクティブでは、各ワーカープロセスのキャッシュに保持する、アップストリームサーバーへのアイドル状態のキープアライブ接続の最大数を定義します。この数を超えると、最も長く使用されていない接続が閉じられます。キープアライブを使用しない場合、オーバーヘッドが増加し、接続とエフェメラルポートの両方を効率的に使用できません。
次のサンプル設定では、backendという名前のupstreamブロックで定義されたサーバーに対して、少なくとも128個のkeepalive接続を維持するようNGINX Plusに指定します。
upstream backend { server 10.0.0.100:1234;
server 10.0.0.101:1234;
keepalive 128;
}アップストリームサーバーへのキープアライブ接続を有効にする場合は、proxy_http_versionディレクティブを使用してNGINX PlusにHTTPバージョン1.1を使用するよう指定するとともに、proxy_set_headerディレクティブを使用してConnectionという名前のヘッダーをすべて削除する必要があります。どちらのディレクティブも、http、server、またはlocation設定ブロックに配置できます。
proxy_http_version 1.1; proxy_set_header Connection "";定義済みのローカルIPアドレスリストへの接続の動的バインド
カーネルを最適化し、キープアライブ接続を有効にすると、エフェメラルポートの可用性と使用状況をより細かく制御できます。ただし、エフェメラルポートの過剰使用を抑えるには、これらの変更だけでは不十分な場合があります。その場合は、NGINX Plusのディレクティブとパラメーターを使用して、一定割合の接続を静的なローカルIPアドレスにバインドできます。これにより、使用可能なエフェメラルポート数は、実質的に構成で定義したIPアドレス数に応じて増加します。これを実現するには、proxy_bindディレクティブとsplit_clientsディレクティブを使用します。
以下の設定例では、proxy_bindディレクティブが、locationブロック内でリクエストごとに$split_ip変数の値に基づいてローカルIPアドレスを設定します。この変数には、ハッシュ関数を使用して値を決定するsplit_clientsブロックが生成した値を動的に設定します。
The first parameter to the split_clientsディレクティブの第1パラメーターは文字列("$remote_addr$remote_port")で、リクエストごとにMurmurHash2関数を使用してハッシュ化されます。第2パラメーター($split_ip)は、第1パラメーターのハッシュに基づいて動的に設定する変数です。
波括弧内のステートメントは、ハッシュテーブルを「バケット」に分割します。各バケットには、ハッシュの一部が指定された割合で格納されます。ここではテーブルを同じサイズの10個のバケットに分割していますが、バケットは任意の数で作成でき、すべてを同じサイズにする必要はありません。(ハッシュ数が指定された割合で均等に分割できない場合があるため、最後のバケットの割合は特定の数値ではなく、常にアスタリスク[*]で表します。)
ハッシュ値の範囲は0~4294967295であるため、この例では各バケットに約429496700個の値(全体の10%)が含まれます。第1バケットには0~429496700、第2バケットには429496701~858993400の値が含まれ、以降も同様です。$split_ip変数には、$remote_addr$remote_port文字列のハッシュが含まれるバケットに関連付けられたIPアドレスが設定されます。具体例として、ハッシュ値150000000は第4バケットに該当するため、この場合、$split_ip変数には10.0.0.213が動的に設定されます。
http { upstream backend {
server 10.0.0.100:1234;
server 10.0.0.101:1234;
}
server {
# ...
location / {
# ...
proxy_pass http://backend;
proxy_bind $split_ip;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
split_clients "$remote_addr$remote_port" $split_ip { 10% 10.0.0.210; 10% 10.0.0.211; 10% 10.0.0.212; 10% 10.0.0.213; 10% 10.0.0.214; 10% 10.0.0.215; 10% 10.0.0.216; 10% 10.0.0.217; 10% 10.0.0.218; * 10.0.0.219; }
}まとめ
エフェメラルポート数に関するLinuxカーネル設定を調整すると、ポートの可用性と使用効率が向上します。NGINX Plusからアップストリームサーバーへのキープアライブ接続を有効にすると、エフェメラルポートをより効率的に使用できます。最後に、NGINX Plusの設定でsplit_clientsディレクティブとproxy_bindディレクティブを使用すると、定義済みのローカルIPアドレスのリストに送信接続を動的にバインドできるため、NGINX Plusで使用可能なエフェメラルポート数が大幅に増加します。
NGINX Plusをお試しいただくには、今すぐ30日間の無料トライアルを開始するか、ユースケースについてご相談ください。
著者について

関連するブログ記事
チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。
AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。
JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。
F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。
NGINX PlusによるAWS Auto Scalingグループの負荷分散
新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります