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が通常のroot以外のユーザーによって実行されている場合は0.0.0.0:8080)のリッスンパラメーターが与えられます。 これにより、これらのブロックはポート80上の任意のインターフェイスの要求に応答できますが、このデフォルト値はサーバー選択プロセス内であまり重要ではありません。

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

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

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

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

  • Unixソケットへのパス。

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

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

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

    • 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アドレスをリストする一致するブロックがある場合、IPアドレスとして機能的に0.0.0.0を使用している(任意のインターフェイスに一致する)ブロックは選択されないことを意味します。 いずれにしても、ポートは正確に一致する必要があります。

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

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

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の1つが使用されます。

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

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

  • 末尾のワイルドカードを使用して一致するものが見つからない場合、Nginxは正規表現(名前の前に~で示される)を使用してserver_nameを定義するサーバーブロックを評価します。 「Host」ヘッダーに一致する正規表現を持つfirstserver_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 optional_modifier location_match {

    . . .

}

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

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

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

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

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

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

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

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

location /site {

    . . .

}

正確なリクエストURIの一致を示すために、このブロックは常に/page1のようなリクエストURIに応答するために使用されます。 notは、/page1/index.htmlの要求URIに応答するために使用されます。 このブロックが選択され、インデックスページを使用してリクエストが満たされると、リクエストの実際のハンドラーになる別の場所に内部リダイレクトが行われることに注意してください。

location = /page1 {

    . . .

}

大文字と小文字を区別する正規表現として解釈する必要がある場所の例として、このブロックを使用して/tortoise.jpgの要求を処理できますが、/FLOWER.PNGの場合はnotを処理できます。

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

    . . .

}

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

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

    . . .

}

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

location ^~ /costumes {

    . . .

}

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

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

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

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

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

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

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

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

    • 最長一致プレフィックス位置does not^~修飾子を使用する場合、検索のフォーカスをシフトできるように、一致はNginxによって当面保存されます。

  • 一致する最長のプレフィックスの場所が決定されて保存されると、Nginxは正規表現の場所(大文字と小文字を区別するものと区別しないものの両方)の評価に進みます。 一致する最長のプレフィックス位置である正規表現の場所withinがある場合、Nginxはそれらを正規表現の場所のリストの一番上に移動して確認します。 次に、Nginxは、正規表現の場所に対して順次照合を試みます。 リクエストURIに一致するfirst正規表現の場所は、リクエストを処理するためにすぐに選択されます。

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

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

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

最後に、正規表現がwithinに一致すると、Nginxが正規表現の場所を評価するときに最長のプレフィックス一致が「行をジャンプ」することを理解することが重要です。 これらは、他の正規表現の一致が考慮される前に、順番に評価されます。 非常に役立つNginx開発者であるMaximDouninが、選択アルゴリズムのこの部分をthis postで説明しています。

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

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

これは予測可能な方法でロケーションブロックを設計できる一般的なルールですが、選択したロケーション内の特定のディレクティブによって新しいロケーション検索がトリガーされる場合があることを認識することが重要です。 「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つの方法ですが、ほとんどの構成ではおそらく役に立ちません。 ほとんどの場合、ディレクトリの完全一致は、リクエストの書き換えなどの場合に役立ちます(新しい場所の検索も行われます)。

処理場所を再評価できるもう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ディレクティブです。 lastパラメーターをrewriteディレクティブとともに使用する場合、またはパラメーターをまったく使用しない場合、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が適用するコンテキストをトレースできます。

Related