Nginxサーバーとロケーションブロック選択アルゴリズムを理解する

提供:Dev Guides
移動先:案内検索

序章

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が通常の非[ X170X] root ユーザー)。 これにより、これらのブロックはポート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アドレスとポートに基づいて、リクエストに最も具体的に一致するサーバーブロックのリストを収集しようとします。 つまり、0.0.0.0をIPアドレスとして機能的に使用している(任意のインターフェイスと一致する)ブロックは、特定のIPアドレスをリストする一致するブロックがある場合は選択されません。 いずれの場合も、ポートは正確に一致している必要があります。
  • 最も具体的な一致が1つしかない場合は、そのサーバーブロックがリクエストの処理に使用されます。 同じレベルの特異性が一致するサーバーブロックが複数ある場合、Nginxは各サーバーブロックのserver_nameディレクティブの評価を開始します。

Nginxは、listenディレクティブで同じレベルの特異性に一致するサーバーブロックを区別する必要がある場合にのみ、server_nameディレクティブを評価することを理解することが重要です。 たとえば、example.com192.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は最初に、リクエスト正確にHostヘッダーの値と一致するserver_nameを持つサーバーブロックを見つけようとします。 これが見つかった場合、関連するブロックがリクエストの処理に使用されます。 完全に一致するものが複数見つかった場合は、最初のが使用されます。
  • 完全に一致するものが見つからない場合、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とそれに適合する開始ワイルドカードがあるかどうかを確認します。 ワイルドカードで始まる最長の一致が、要求を満たすために選択されます。

この例では、リクエストにwww.example.orgHostヘッダーがある場合、2番目のサーバーブロックが選択されます。

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

    . . .

}

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

    . . .

}

server {
    listen 80;
    server_name *.org;

    . . .

}

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

たとえば、リクエストのHostヘッダーが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ディレクティブの一致の試行に進みます。 リクエストに応答するために、最初の一致する正規表現が選択されます。

たとえば、リクエストのHostヘッダーが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がロケーションブロックを照合しようとする方法に影響します。 以下の修飾子により、関連するロケーションブロックは次のように解釈されます。

  • (なし):修飾子が存在しない場合、場所はプレフィックスの一致として解釈されます。 これは、指定された場所がリクエスト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に一致するfirst正規表現の場所が、リクエストを処理するためにすぐに選択されます。
  • リクエストURIに一致する正規表現の場所が見つからない場合は、以前に保存されたプレフィックスの場所がリクエストを処理するために選択されます。

デフォルトでは、Nginxはプレフィックス一致よりも正規表現一致を提供することを理解することが重要です。 ただし、最初にプレフィックス位置を評価し、管理者が=および^~修飾子を使用して位置を指定することにより、この傾向を無効にすることができます。

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

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

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

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

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

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

  • 索引
  • try_files
  • リライト
  • error_page

これらについて簡単に説明します。

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

この例では、最初の場所は/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が適用するコンテキストを追跡することができます。