Nginx の管理と内部構造

最終更新: 2022-12-19 02:06:42 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

Nginx は、イベント駆動型の HTTP、ストリーム、メール向けプロキシ兼サーバーである。 設定、プロセス権限、アップストリームのタイムアウトは本番コードとして扱う。 リロード前に変更を検証し、正常動作が確認済みの設定を保持する。

イベント駆動 I/O #

Nginx はノンブロッキングイベントループを中心に構築されている。マスタープロセスが 設定を読み込んでワーカーを起動し、各ワーカーは epoll、kqueue、またはプラットフォーム 上の同等の準備完了通知バックエンドで待機する。ワーカーは通信相手ごとの接続レコードを 保持し、ソケットが処理可能になったときに限り各リクエストの処理を進める。

したがって、リクエストごとにスレッドを割り当てなくても、1 つのイベントループで多数の 接続を処理できる。このモデルから、ログ記録とバッファリングの選択が重要な理由も分かる。 アップストリーム、クライアント、ディスク書き込みが個別に進行する間も、ワーカーは応答性を 維持する必要があるためである。

分離された設定 #

ローカルビルドをテストするときは、プレフィックスと設定を明示してインスタンスを実行する。 ログや一時ファイルなどの相対パスは、プレフィックスを基準に解決される。

sh
nginx -p "$PWD" -c nginx.conf
nginx -t -p "$PWD" -c nginx.conf

非特権のローカルプロセスでは、非特権ポートのリスナーを使い、pid、ログ、 各リクエストボディまたはアップストリーム用の一時パスを、書き込み可能な場所に設定する。 daemon off; と master_process off; はフォアグラウンドでのデバッグを簡単にするが、 本番環境向けの設定ではない。

include ディレクティブは、プレフィックスに対する相対パスまたは絶対パスで指定した設定を テキストとして取り込む。nginx -t で include の依存グラフ全体を検証し、nginx -T で 展開後の設定を確認する。リロード時、マスターは新しい設定を解析して検証し、成功すれば 新しいワーカーを起動して、古いワーカーには進行中の処理を完了してから終了するよう求める。 解析またはリソース取得に失敗した場合、古い設定のままトラフィックの処理が続く。

仮想サーバーと CORS #

Nginx はリスナーと Host ヘッダーから HTTP 仮想サーバーを選択する。同じリスナー内では、 完全一致の server_name、最長の先頭ワイルドカード、最長の末尾ワイルドカード、 最初に一致する正規表現、という順で優先される。server_name .example.com は apex ドメインと *.example.com の省略表記である。*.example.com だけでは apex ドメインに一致しない。

サーバーを選択した後、Nginx はプレフィックス規則と正規表現規則から location を探す。 完全一致の location は即座に採用される。それ以外では最長のプレフィックスを記憶し、 対象となる正規表現を順番に評価する。^~ 修飾子を指定すると、そのプレフィックスが 一致した後の正規表現チェックを行わない。内部 rewrite によって location 検索が 再度始まることがあるため、プロキシの動作が目に見えるリクエストと異なる場合は、 最終的な URI を追跡する。

資格情報を許可する場合、CORS では許可するオリジンを明示しなければならない。 * と Access-Control-Allow-Credentials: true は併用できない。オリジンを反映するのは 許可リストとの照合後だけにし、Vary: Origin を追加する。共有キャッシュが、資格情報を 伴うレスポンスを別のオリジンに再利用してはならないためである。

nginx
map $http_origin $cors_origin {
    default "";
    "~^https://(app|admin)\.example\.com$" $http_origin;
}

server {
    location /api/ {
        if ($cors_origin != "") {
            add_header Access-Control-Allow-Origin $cors_origin always;
            add_header Access-Control-Allow-Credentials true always;
            add_header Vary Origin always;
        }
        proxy_pass http://api_upstream;
    }
}

プリフライト OPTIONS リクエストも同じ許可リストで処理し、許可するメソッドとヘッダーを 明示する。信頼できない Host ヘッダーを、認可や URL 生成に利用するアップストリームへ 転送してはならない。必要に応じて、Host を想定するアップストリーム名に設定する。

プロキシされる GET リクエスト #

HTTP は GET を安全なメソッドとして定義するが、アプリケーションの実装次第では 高コストになる。GET リクエストのコンテンツには一般に定義された意味がないため、 非公開の契約で動作が定義かつ保護されていない限り、アップストリームは GET ボディに 依存すべきではない。 Nginx は、proxy_next_upstream に従ってアップストリーム障害後にプロキシ対象の リクエストを再試行できる。したがって、再試行によってアップストリームアプリケーションが 同じ GET を複数回実行する可能性がある。GET ハンドラーには副作用を持たせず、 再試行ポリシーを明示し、処理を生成する操作には冪等性キーまたは別のメソッドを使う。

アップストリームのレスポンスタイムアウトは、アップストリームが何も実行しなかったことを 意味しない。プロキシとアプリケーションの境界でリクエスト ID を記録し、その ID を使って、 プロキシによる再試行、クライアントによる再試行、重複メッセージを区別する。

デフォルトでは、プロキシ処理はリクエストのメソッドとボディを保持しつつ、 Nginx が制御するヘッダーで新しいアップストリームリクエストを構築する。 authority と転送メタデータを明示的に設定する。

nginx
location /api/ {
    proxy_set_header Host api.internal.example;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://api_upstream/;
}

ここでは proxy_pass に URI が含まれるため、転送前に /api/ と一致する正規化済み部分が / に置換される。proxy_pass に URI がなければ、Nginx は文書化された rewrite 規則に 従ってリクエスト URI を転送する。末尾のスラッシュを変更するときは、この違いを確認する。 アップストリームの契約側でも元のリクエストボディを意図的に処理または破棄しない限り、 変更を伴うリクエストを proxy_method で GET に変換してはならない。

ソースアーキテクチャ #

Nginx は、設定を読み込んでワーカーを管理するマスタープロセスを起動する。ワーカーは epoll や kqueue などのイベント機構を使い、ngx_connection_t で接続を保持し、 ハンドラーフェーズを通じてリクエスト処理を進める。モジュールは core、event、HTTP、 stream、mail のいずれかの種類を宣言し、ディレクティブ、設定の作成関数とマージ関数、 実行時ハンドラーを登録する。

各モジュールはグローバルな ngx_module_t ディスクリプターを公開する。モジュールの種類は、 どの設定構造体と初期化フックを組み合わせるかを Nginx に伝える。コアソースファイルと 追加モジュールはビルド時に組み込まれるが、一部のモジュールは動的にビルドして後から 読み込める。

HTTP リクエスト処理は rewrite、access、content、log などのフェーズで構成される。 リクエスト行とヘッダーを解析した後、リクエストはこれらのフェーズを進むため、 モジュールはリクエスト経路全体を所有せずに 1 つの段階へフックできる。

ログ機構の内部 #

ngx_log_error_core() は中核となるログ関数である。ngx_log_error() や ngx_log_debug() などの上位マクロはこの関数をラップし、設定されたログレベルに従う。 デバッグログは、ビルドと実行時設定の両方で許可されている場合に限りコンパイルされ、 有効になる。

error_log ディレクティブはエラーの出力先を制御する。アクセスログは独立しており、 意図的にエラーログと同じチャネルとして扱われない。デバッグビルドでは、debug ログレベルにより、設定解析、location 検索、フェーズハンドラー、アップストリーム処理を 通じてリクエストを実用的に追跡できる。

固定したバージョンのソースリリースから --with-debug を指定してデバッグバイナリを ビルドし、専用のプレフィックスで実行して、debug レベルのエラーログを確認する。 ソースコードを読む際は、設定解析とサイクル初期化から始める。その後、受け付けた接続を イベントループ、HTTP リクエスト行の解析、location 設定、フェーズハンドラー、 アップストリーム処理の順に追跡する。

ACdlmiiennitstfrlaotwosrswiogrnkaelrsc,onrfeilgoaduwpmosartskrteeerarmsworker

参考資料 #

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)