Nginxサーバーとロケーションブロックの選択アルゴリズムについて

前書き

Nginxは、世界で最も人気のあるWebサーバーの1つです。 多くの同時クライアント接続で高負荷を正常に処理でき、Webサーバー、メールサーバー、またはリバースプロキシサーバーとして簡単に機能できます。

このガイドでは、Nginxがクライアントリクエストを処理する方法を決定する舞台裏の詳細について説明します。 これらのアイデアを理解することで、サーバーおよびロケーションブロックの設計から当て推量を取り除くことができ、リクエストの処理が予測不能になりやすくなります。

Nginxブロックの構成

Nginxは、さまざまなコンテンツを提供するための構成を論理的にブロックに分割します。ブロックは階層構造になっています。 クライアントの要求が行われるたびに、Nginxは要求を処理するために使用する構成ブロックを決定するプロセスを開始します。 この決定プロセスは、このガイドで説明する内容です。

これから説明する主なブロックは、* server ブロックと location *ブロックです。

サーバーブロックは、定義済みの種類のリクエストを処理するために使用される仮想サーバーを定義するNginxの構成のサブセットです。 管理者は多くの場合、複数のサーバーブロックを構成し、要求されたドメイン名、ポート、およびIPアドレスに基づいて、どのブロックがどの接続を処理するかを決定します。

ロケーションブロックはサーバーブロック内に存在し、Nginxが親サーバーのさまざまなリソースとURIに対する要求を処理する方法を定義するために使用されます。 URIスペースは、管理者がこれらのブロックを使用して好きな方法で細分化できます。 これは非常に柔軟なモデルです。

Nginxがリクエストを処理するサーバーブロックを決定する方法

Nginxを使用すると、管理者は個別の仮想Webサーバーインスタンスとして機能する複数のサーバーブロックを定義できるため、これらのサーバーブロックのいずれを使用して要求を満たすかを決定する手順が必要です。

これは、最適な一致を見つけるために使用される定義済みのチェックシステムを通じて行われます。 このプロセス中にNginxが関係するメインのサーバーブロックディレクティブは、 `+ listen `ディレクティブと ` server_name +`ディレクティブです。

一致する可能性のあるものを見つけるための「listen」ディレクティブの解析

まず、NginxはリクエストのIPアドレスとポートを調べます。 これを各サーバーの `+ listen +`ディレクティブと照合して、リクエストを解決できるサーバーブロックのリストを作成します。

+ listen +`ディレクティブは通常、サーバーブロックが応答するIPアドレスとポートを定義します。 デフォルトでは、 `+ listen +`ディレクティブを含まないサーバーブロックには、 `+ 0.0.0.0:80 +(または、Nginxが通常の方法で実行されている場合は +0.0.0.0:8080 + 、非rootユーザー)。 これにより、これらのブロックはポート80上の任意のインターフェイスの要求に応答できますが、このデフォルト値はサーバー選択プロセス内であまり重要ではありません。

`+ listen +`ディレクティブは次のように設定できます:

  • IPアドレス/ポートのコンボ。

  • デフォルトのポート80でリッスンする唯一のIPアドレス。

  • そのポート上のすべてのインターフェースをリッスンする唯一のポート。

  • Unixソケットへのパス。

通常、最後のオプションは、異なるサーバー間でリクエストを渡す場合にのみ意味を持ちます。

どのサーバーブロックにリクエストを送信するかを決定しようとすると、Nginxは最初に次のルールを使用して `+ listen +`ディレクティブの特異性に基づいて決定しようとします:

  • Nginxは、各ブロックがIPアドレスとポートによって評価されるように、欠損値をデフォルト値に置き換えることにより、すべての「不完全な」 `+ listen +`ディレクティブを変換します。 これらの翻訳の例を次に示します。

  • `+ listen +`ディレクティブのないブロックは、値 `+0.0.0.0:80 +`を使用します。

  • ポートのないIPアドレス「111.111.111.111」に設定されたブロックは「111.111.111.111:80」になります

  • IPアドレスなしでポート「8888」に設定されたブロックは「0.0.0.0:8888」になります

  • Nginxは、IPアドレスとポートに最も具体的に基づいて、リクエストに最も一致するサーバーブロックのリストを収集しようとします。 これは、機能的にIPアドレスとして「0.0.0.0」を使用している(すべてのインターフェイスに一致する)ブロックは、特定のIPアドレスをリストする一致するブロックがある場合は選択されないことを意味します。 いずれにしても、ポートは正確に一致する必要があります。

  • 最も具体的な一致が1つしかない場合、そのサーバーブロックを使用して要求を処理します。 同じレベルの特異性が一致するサーバーブロックが複数ある場合、Nginxは各サーバーブロックの `+ server_name +`ディレクティブの評価を開始します。

Nginxは、 `+ listen `ディレクティブの同じレベルの特異性に一致するサーバーブロックを区別する必要がある場合にのみ ` server_name `ディレクティブを評価することを理解することが重要です。 たとえば、「 example.com 」が「+192.168.1.10」のポート「80」でホストされている場合、「+ example.com 」のリクエストは、この例の最初のブロックによって常に処理されますが、 2番目のブロックの ` server_name +`ディレクティブ。

server {
   listen 192.168.1.10;

   . . .

}

server {
   listen 80;
   server_name example.com;

   . . .

}

複数のサーバーブロックが同じ特異性で一致する場合、次のステップは `+ server_name +`ディレクティブをチェックすることです。

「server_name」ディレクティブを解析して一致を選択する

次に、同様に特定の `+ listen +`ディレクティブを持つリクエストをさらに評価するために、Nginxはリクエストの「Host」ヘッダーをチェックします。 この値は、クライアントが実際に到達しようとしていたドメインまたはIPアドレスを保持します。

Nginxは、まだ選択候補である各サーバーブロック内の `+ server_name +`ディレクティブを調べることにより、見つかった値に最適な一致を見つけようとします。 Nginxは、次の式を使用してこれらを評価します。

  • Nginxは最初に、リクエスト_exactly_の「Host」ヘッダーの値と一致する「+ server_name +」を持つサーバーブロックを見つけようとします。 これが見つかった場合、関連するブロックを使用してリクエストを処理します。 複数の完全一致が見つかった場合、* first *が使用されます。

  • 完全に一致するものが見つからない場合、Nginxは、先頭のワイルドカード(構成内の名前の先頭にある「+ * 」で示される)を使用して一致する「 server_name +」を持つサーバーブロックを見つけようとします。 見つかった場合、そのブロックを使用してリクエストを処理します。 複数の一致が見つかった場合、*最長*一致がリクエストの処理に使用されます。

  • 先頭のワイルドカードを使用して一致するものが見つからない場合、Nginxは、末尾のワイルドカードを使用して一致する(+ server_name + を持つサーバーブロックを探します(構成内でサーバー名が + * + `で終わることで示されます)。 見つかった場合、そのブロックを使用してリクエストを処理します。 複数の一致が見つかった場合、*最長*一致がリクエストの処理に使用されます。

  • 末尾のワイルドカードを使用して一致が見つからない場合、Nginxは正規表現(名前の前に「〜」で示される)を使用して、「+ server_name 」を定義するサーバーブロックを評価します。 「Host」ヘッダーに一致する正規表現を持つ* first * ` server_name +`が、リクエストの処理に使用されます。

  • 一致する正規表現が見つからない場合、NginxはそのIPアドレスとポートのデフォルトサーバーブロックを選択します。

各IPアドレス/ポートコンボには、上記の方法では一連のアクションを決定できない場合に使用されるデフォルトのサーバーブロックがあります。 IPアドレス/ポートコンボの場合、これは設定の最初のブロックか、 `+ listen `ディレクティブの一部として ` default_server `オプションを含むブロックのいずれかです(最初に見つかったアルゴリズムをオーバーライドします)。 各IPアドレス/ポートの組み合わせごとに、1つの ` default_server +`宣言のみが可能です。

「Host」ヘッダー値と正確に一致する「+ server_name +」が定義されている場合、そのサーバーブロックがリクエストを処理するために選択されます。

この例では、リクエストの「Host」ヘッダーが「host1.example.com」に設定されている場合、2番目のサーバーが選択されます。

server {
   listen 80;
   server_name *.example.com;

   . . .

}

server {
   listen 80;
   server_name host1.example.com;

   . . .

}

完全に一致するものが見つからない場合、Nginxは、開始ワイルドカードが適合する「+ server_name +」があるかどうかを確認します。 ワイルドカードで始まる最長一致が要求を満たすために選択されます。

この例では、リクエストに「http://www.example.org [www.example.org]」という「Host」ヘッダーがある場合、2番目のサーバーブロックが選択されます。

server {
   listen 80;
   server_name www.example.*;

   . . .

}

server {
   listen 80;
   server_name *.example.org;

   . . .

}

server {
   listen 80;
   server_name *.org;

   . . .

}

開始ワイルドカードと一致するものが見つからない場合、Nginxは、式の最後にワイルドカードを使用して一致が存在するかどうかを確認します。 この時点で、ワイルドカードで終わる最長一致が選択され、リクエストが処理されます。

たとえば、リクエストの「Host」ヘッダーが「http://www.example.com [www.example.com]」に設定されている場合、3番目のサーバーブロックが選択されます。

server {
   listen 80;
   server_name host1.example.com;

   . . .

}

server {
   listen 80;
   server_name example.com;

   . . .

}

server {
   listen 80;
   server_name www.example.*;

   . . .

}

ワイルドカードの一致が見つからない場合、Nginxは正規表現を使用する `+ server_name +`ディレクティブの一致の試行に進みます。 _first_一致する正規表現が選択され、リクエストに応答します。

たとえば、リクエストの「Host」ヘッダーが「http://www.example.com [www.example.com]」に設定されている場合、リクエストを満たすために2番目のサーバーブロックが選択されます。

server {
   listen 80;
   server_name example.com;

   . . .

}

server {
   listen 80;
   server_name ~^(www|host1).*\.example\.com$;

   . . .

}

server {
   listen 80;
   server_name ~^(subdomain|set|www|host1).*\.example\.com$;

   . . .

}

上記の手順のいずれも要求を満たすことができない場合、要求は一致するIPアドレスとポートの_default_サーバーに渡されます。

一致するロケーションブロック

Nginxがリクエストを処理するサーバーブロックを選択するために使用するプロセスと同様に、Nginxにはリクエストの処理に使用するサーバー内のロケーションブロックを決定するための確立されたアルゴリズムもあります。

ロケーションブロックの構文

Nginxがリクエストを処理するためにどのロケーションブロックを使用するかを決定する方法を説明する前に、ロケーションブロックの定義に表示される構文のいくつかを見ていきましょう。 ロケーションブロックはサーバーブロック(または他のロケーションブロック)内に存在し、リクエストURI(ドメイン名またはIPアドレス/ポートの後に来るリクエストの一部)の処理方法を決定するために使用されます。

ロケーションブロックは通常、次の形式を取ります。

location   {

   . . .

}

上記の `++`は、NginxがリクエストURIをチェックする対象を定義します。 上記の例の修飾子の有無は、Nginxがロケーションブロックとの一致を試みる方法に影響します。 以下の修飾子により、関連するロケーションブロックは次のように解釈されます。

  • (なし):修飾子が存在しない場合、位置は_prefix_一致として解釈されます。 これは、指定された場所がリクエストURIの先頭と照合され、一致を判断することを意味します。

  • * + = + *:等号が使用されている場合、リクエストURIが指定された場所と完全に一致する場合、このブロックは一致と見なされます。

  • * *:チルダ修飾子が存在する場合、この場所は大文字と小文字を区別する正規表現の一致として解釈されます。

  • * +〜* + *:チルダおよびアスタリスク修飾子が使用される場合、ロケーションブロックは大文字と小文字を区別しない正規表現の一致として解釈されます。

  • * + ^〜+ *:カラットとチルダ修飾子が存在し、このブロックが最適な非正規表現一致として選択されている場合、正規表現一致は行われません。

ロケーションブロックの構文を示す例

プレフィックス一致の例として、次のロケーションブロックを選択して、「+ / site 」、「 / site / page1 / index.html 」、または「 / site / index.html +」のようなリクエストURIに応答することができます。 :

location /site {

   . . .

}

正確なリクエストURIマッチングのデモンストレーションのために、このブロックは常に「+ / page1 」のようなリクエストURIに応答するために使用されます。 ` / page1 / index.html +`リクエストURIへの応答には使用されません。 このブロックが選択され、インデックスページを使用してリクエストが満たされると、リクエストの実際のハンドラーになる別の場所に内部リダイレクトが行われることに注意してください。

location = /page1 {

   . . .

}

大文字と小文字を区別する正規表現として解釈する必要がある場所の例として、このブロックは、 `+ / tortoise.jpg `のリクエストを処理するために使用できますが、 ` / FLOWER.PNG +`の*ではありません*:

location ~ \.(jpe?g|png|gif|ico)$ {

   . . .

}

上記と同様の大文字と小文字を区別しないマッチングを可能にするブロックを以下に示します。 ここでは、このブロックで「+ / tortoise.jpg 」と「 / FLOWER.PNG +」の両方を処理できます。

location ~* \.(jpe?g|png|gif|ico)$ {

   . . .

}

最後に、このブロックは、最適な非正規表現一致であると判断された場合、正規表現一致の発生を防ぎます。 `+ / costumes / ninja.html`のリクエストを処理できます。

location ^~ /costumes {

   . . .

}

ご覧のように、修飾子はロケーションブロックの解釈方法を示します。 ただし、これは、リクエストの送信先のロケーションブロックを決定するためにNginxが使用するアルゴリズムを示しています。 次にそのことについて説明します。

Nginxがリクエストの処理に使用する場所を選択する方法

Nginxは、サーバーブロックを選択する方法と同様の方法で要求を処理するために使用される場所を選択します。 特定の要求に最適なロケーションブロックを決定するプロセスを実行します。 このプロセスを理解することは、Nginxを確実かつ正確に設定できるようにするための重要な要件です。

上記で説明した場所の宣言の種類を念頭に置いて、NginxはリクエストURIを各場所と比較することで、可能な場所のコンテキストを評価します。 次のアルゴリズムを使用してこれを行います。

  • Nginxは、すべてのプレフィックスベースの場所の一致(正規表現を含まないすべての場所の種類)をチェックすることから始まります。 完全なリクエストURIに対して各場所をチェックします。

  • まず、Nginxは完全一致を探します。 `+ = +`修飾子を使用するロケーションブロックがリクエストURIと完全に一致することが判明した場合、このロケーションブロックはすぐにリクエストを処理するために選択されます。

  • ( `+ = +`修飾子を使用した)正確なロケーションブロックの一致が見つからない場合、Nginxは非正確なプレフィックスの評価に進みます。 指定されたリクエストURIに一致する最長のプレフィックスの場所を検出し、次のように評価します。

  • 一致する最長のプレフィックスの場所に「+ ^〜+」修飾子がある場合、Nginxはすぐに検索を終了し、この場所を選択してリクエストを処理します。

  • 最も長い一致するプレフィックスの場所が「+ ^〜+」修飾子を使用しない場合、検索のフォーカスを移動できるように、現在Nginxによって一致が保存されます。

  • 一致する最長のプレフィックスの場所が決定されて保存されると、Nginxは正規表現の場所(大文字と小文字を区別するものと区別しないものの両方)の評価に進みます。 一致する最長のプレフィックスの場所に正規表現の場所がある場合、Nginxはそれらを正規表現の場所のリストの先頭に移動してチェックします。 次に、Nginxは、正規表現の場所に対して順次照合を試みます。 要求URIに一致する*最初の*正規表現の場所がすぐに選択され、要求を処理します。

  • リクエストURIに一致する正規表現の場所が見つからない場合、以前に保存されたプレフィックスの場所が選択され、リクエストを処理します。

デフォルトでは、Nginxはプレフィックス一致に優先して正規表現一致を提供することを理解することが重要です。 ただし、最初にプレフィックスの位置を_評価_し、 `+ = `および ` ^〜+`修飾子を使用して位置を指定することにより、管理者がこの傾向をオーバーライドできるようにします。

プレフィックスの場所は通常、最も長く最も具体的な一致に基づいて選択されますが、最初に一致した場所が見つかると、正規表現の評価が停止されることに注意することも重要です。 これは、構成内の位置決めが正規表現の場所に大きな意味を持つことを意味します。

最後に、正規表現は_within_に一致することを理解することが重要です。Nginxが正規表現の位置を評価するとき、最長のプレフィックス一致は「行をジャンプ」します。 これらは、他の正規表現の一致が考慮される前に、順番に評価されます。 信じられないほど役立つNginx開発者であるMaxim Douninがhttps://www.ruby-forum.com/topic/4422812#1136698 [この投稿]で選択アルゴリズムのこの部分を説明しています。

ロケーションブロックの評価はいつ他のロケーションにジャンプしますか?

一般的に、リクエストを処理するためにロケーションブロックが選択されると、リクエストはそれ以降、そのコンテキスト内で完全に処理されます。 選択された場所と継承されたディレクティブのみが、兄弟の場所ブロックからの干渉なしに、リクエストの処理方法を決定します。

これは予測可能な方法でロケーションブロックを設計できる一般的なルールですが、選択したロケーション内の特定のディレクティブによって新しいロケーション検索がトリガーされる場合があることを認識することが重要です。 「1つのロケーションブロックのみ」ルールの例外は、リクエストが実際に処理される方法に影響を与える可能性があり、ロケーションブロックを設計するときの期待と一致しない場合があります。

このタイプの内部リダイレクトにつながる可能性のあるディレクティブは次のとおりです。

  • インデックス

  • * try_files *

  • リライト

  • * error_page *

これらを簡単に見ていきましょう。

`+ index +`ディレクティブは、リクエストの処理に使用された場合、常に内部リダイレクトにつながります。 正確な位置の一致は、アルゴリズムの実行をすぐに終了することで選択プロセスを高速化するためによく使用されます。 ただし、_directory_である場所と完全に一致させると、実際の処理のためにリクエストが別の場所にリダイレクトされる可能性が高くなります。

この例では、最初の場所は `+ / exact `のリクエストURIと一致しますが、リクエストを処理するために、ブロックによって継承された ` index +`ディレクティブは2番目のブロックへの内部リダイレクトを開始します。

index index.html;

location = /exact {

   . . .

}

location / {

   . . .

}

上記の場合、最初のブロックにとどまるために実行が本当に必要な場合、ディレクトリへの要求を満たすための別の方法を考え出す必要があります。 たとえば、そのブロックに無効な + index`を設定し、 + autoindex`をオンにできます。

location = /exact {
   index nothing_will_match;
   autoindex on;
}

location  / {

   . . .

}

これは、「+ index +」によるコンテキストの切り替えを防ぐ1つの方法ですが、おそらくほとんどの構成では役に立たないでしょう。 ほとんどの場合、ディレクトリの完全一致は、リクエストの書き換えなどの場合に役立ちます(新しい場所の検索も行われます)。

処理場所が再評価される別のインスタンスは、 `+ try_files +`ディレクティブを使用する場合です。 このディレクティブは、ファイルまたはディレクトリの名前付きセットの存在をチェックするようにNginxに指示します。 最後のパラメーターは、Nginxが内部リダイレクトを行うURIです。

次の構成を検討してください。

root /var/www/main;
location / {
   try_files $uri $uri.html $uri/ /fallback/index.html;
}

location /fallback {
   root /var/www/another;
}

上記の例では、リクエストが `+ / blahblah `に対して行われた場合、最初の場所が最初にリクエストを取得します。 ` / var / www / main `ディレクトリで ` blahblah `というファイルを見つけようとします。 見つからない場合は、 ` blahblah.html `と呼ばれるファイルを検索してフォローアップします。 次に、 ` / var / www / main `ディレクトリ内に ` blahblah / `というディレクトリがあるかどうかを確認します。 これらすべての試みに失敗すると、 ` / fallback / index.html`にリダイレクトされます。 これにより、2番目のロケーションブロックによってキャッチされる別のロケーション検索がトリガーされます。 これにより、ファイル「+ / var / www / another / fallback / index.html」が提供されます。

ロケーションブロックのパスオフを引き起こす可能性がある別のディレクティブは、 `+ rewrite `ディレクティブです。 ` rewrite `ディレクティブで ` last +`パラメーターを使用する場合、またはパラメーターをまったく使用しない場合、Nginxは書き換えの結果に基づいて新しい一致する場所を検索します。

たとえば、最後の例を修正して書き換えを含めると、リクエストが `+ try_files +`ディレクティブに依存せずに2番目の場所に直接渡されることがあることがわかります。

root /var/www/main;
location / {
   rewrite ^/rewriteme/(.*)$ /$1 last;
   try_files $uri $uri.html $uri/ /fallback/index.html;
}

location /fallback {
   root /var/www/another;
}

上記の例では、 `+ / rewriteme / hello `のリクエストは最初に最初のロケーションブロックによって処理されます。 ` / hello `に書き換えられ、場所が検索されます。 この場合、最初の場所に再び一致し、通常どおり ` try_files `によって処理され、何も見つからない場合は ` / fallback / index.html `にキックバックされます( ` try_files +`内部リダイレクトを使用して、上で議論した)。

ただし、 `+ / rewriteme / fallback / hello `に対してリクエストが行われた場合、最初のブロックが再び一致します。 書き換えが再び適用され、今回は ` / fallback / hello +`になります。 その後、リクエストは2番目のロケーションブロックから提供されます。

関連する状況は、 `+ 301 `または ` 302 `ステータスコードを送信するときに ` return `ディレクティブで発生します。 この場合の違いは、外部から見えるリダイレクトという形で完全に新しいリクエストが発生することです。 これと同じ状況は、 ` redirect `または ` permanent `フラグを使用するときに ` rewrite +`ディレクティブで発生する可能性があります。 ただし、外部から見えるリダイレクトは常に新しいリクエストになるため、これらのロケーション検索は予期しないものではありません。

`+ error_page `ディレクティブは、 ` try_files `によって作成されたものと同様の内部リダイレクトを引き起こす可能性があります。 このディレクティブは、特定のステータスコードが検出されたときに何が起こるかを定義するために使用されます。 ` try_files +`が設定されている場合、このディレクティブはリクエストのライフサイクル全体を処理するため、おそらく実行されません。

この例を考えてください。

root /var/www/main;

location / {
   error_page 404 /another/whoops.html;
}

location /another {
   root /var/www;
}

すべてのリクエスト( `+ / another `で始まるものを除く)は最初のブロックで処理され、 ` / var / www / main `からのファイルを処理します。 ただし、ファイルが見つからない場合(404ステータス)、 ` / another / whoops.html `への内部リダイレクトが発生し、最終的に2番目のブロックに到達する新しい場所検索が行われます。 このファイルは、 ` / var / www / another / whoops.html`から提供されます。

ご覧のとおり、Nginxが新しいロケーション検索をトリガーする状況を理解することは、リクエストを行うときに見られる動作を予測するのに役立ちます。

結論

Nginxがクライアントリクエストを処理する方法を理解すると、管理者としての仕事がはるかに簡単になります。 Nginxが各クライアントリクエストに基づいて選択するサーバーブロックを知ることができます。 また、リクエストURIに基づいてロケーションブロックがどのように選択されるかを伝えることもできます。 全体として、Nginxがさまざまなブロックを選択する方法を知ることで、各リクエストを処理するためにNginxが適用するコンテキストをトレースできます。

前の投稿:オブジェクトストレージと ブロックストレージサービス
次の投稿:macOSでRubyをインストールしてローカルプログラミング環境をセットアップする方法