OpenResty中使用的NGINX知识
API7.ai
September 17, 2022
前回の記事を通じて、OpenRestyの一般的な知識を学びました。次のいくつかの記事では、OpenRestyの2つの基盤であるNGINXとLuaJITについて解説し、これらの基礎をマスターすることでOpenRestyをより深く理解できるようにします。
今日はNGINXから始めますが、ここではOpenRestyで使用される可能性のあるNGINXの基礎のみを紹介します。これはNGINXのごく一部に過ぎません。
設定に関して、OpenRestyの開発では以下の点に注意する必要があります。
nginx.conf
の設定をできるだけ少なくすること。if
、set
、rewrite
などの複数のディレクティブの組み合わせを避けること。- Luaコードで解決できる場合は、NGINXの設定、変数、モジュールを使用しないこと。
これらの方法により、可読性、保守性、拡張性が最大限に高まります。以下のNGINX設定は、設定をコードとして使用する典型的な悪い例です。
location ~ ^/mobile/(web/app.htm) {
set $type $1;
set $orig_args $args;
if ( $http_user_Agent ~ "(iPhone|iPad|Android)" ) {
rewrite ^/mobile/(.*) http://touch.foo.com/mobile/$1 last;
}
proxy_pass http://foo.com/$type?$orig_args;
}
これはOpenRestyで開発する際に避けるべきものです。
NGINXの設定
NGINXは設定ファイルを通じてその動作を制御します。この設定ファイルは、シンプルなDSLと考えることができます。NGINXはプロセス起動時に設定を読み取り、メモリにロードします。設定ファイルを変更した場合、NGINXを再起動またはリロードし、NGINXが再度設定ファイルを読み取るまで待つ必要があります。新しい設定が有効になるのはその時です。商用版のNGINXのみが、実行時にAPIの形でこの動的な機能の一部を提供しています。
以下の設定から始めましょう。これは非常にシンプルです。
worker_processes auto;
pid logs/nginx.pid;
error_log logs/error.log notice;
worker_rlimit_nofile 65535;
events {
worker_connections 16384;
}
http {
server {
listen 80;
listen 443 ssl;
location / {
proxy_pass https://foo.com;
}
}
}
stream {
server {
listen 53 udp;
}
}
しかし、これらのシンプルな設定でも、いくつかの基本的な概念が関わっています。
まず、各ディレクティブにはそのコンテキストがあります。これは、NGINX設定ファイル内でのそのスコープです。
最上位はmain
で、特定のビジネスに関係のないいくつかのディレクティブを含んでいます。例えば、worker_processes
、pid
、error_log
はすべてmain
コンテキストの一部です。また、コンテキスト間には階層関係があります。例えば、location
のコンテキストはserver
、server
のコンテキストはhttp
、http
のコンテキストはmain
です。
ディレクティブは誤ったコンテキストで実行することはできません。NGINXは起動時にnginx.conf
が合法かどうかをチェックします。例えば、listen 80
;をserver
コンテキストからmain
コンテキストに変更してNGINXサービスを起動すると、次のようなエラーが表示されます。
"listen" directive is not allowed here ......
次に、NGINXはHTTPリクエストやHTTPSトラフィックだけでなく、UDPやTCPトラフィックも処理できます。L7はHTTPに、L4はStreamにあります。OpenRestyでは、lua-nginx-module
とstream-lua-nginx-module
がそれぞれこれらに対応しています。
ここで注意すべき点は、OpenRestyがNGINXのすべての機能をサポートしているわけではなく、OpenRestyのバージョンを確認する必要があることです。OpenRestyのバージョンはNGINXと一致しており、識別が容易です。
上記のnginx.conf
で関わる設定ディレクティブは、NGINXのコアモジュールngx_core_module、ngx_http_core_module、ngx_stream_core_moduleにあります。具体的なドキュメントはリンクをクリックして確認できます。
MASTER-WORKERモード
設定ファイルを理解した後、NGINXのマルチプロセスモードを見てみましょう(以下の図を参照)。NGINXが起動すると、1つのMaster
プロセスと複数のWorker
プロセス(または1つのWorkerプロセス、設定による)が存在します。
まず、Master
プロセスはその名の通り「管理者」の役割を果たし、クライアントからのリクエストを処理しません。Worker
プロセスを管理し、管理者からのシグナルを受け取り、Worker
の状態を監視します。Worker
プロセスが異常終了した場合、Master
プロセスは新しいWorker
プロセスを再起動します。
Worker
プロセスは「実際に働く従業員」で、クライアントからのリクエストを処理します。これらはMaster
プロセスからフォークされ、互いに独立しています。このマルチプロセスモデルは、Apacheのマルチスレッドモデルよりもはるかに進んでおり、スレッド間のロックがなく、デバッグが容易です。プロセスがクラッシュして終了しても、通常は他のワーカーの作業に影響を与えません。
OpenRestyは、NGINXのMaster-Workerモデルに独自の特権エージェントを追加しています。このプロセスはどのポートもリッスンせず、NGINXのMaster
プロセスと同じ特権を持っているため、ローカルディスクファイルへの書き込み操作など、高い特権を必要とするタスクを実行できます。
特権プロセスがNGINXのバイナリホットアップグレードメカニズムと連携すると、OpenRestyは外部プログラムに依存せずに、実行中にバイナリ全体を自己アップグレードできます。
外部プログラムへの依存を減らし、OpenRestyプロセス内で問題を解決しようとすることで、デプロイが容易になり、運用コストが削減され、プログラムエラーの発生確率が低くなります。OpenRestyの特権プロセスとngx.pipe
は、この目的のために存在します。
実行フェーズ
実行フェーズもNGINXの重要な機能であり、OpenRestyの具体的な実装と密接に関連しています。NGINXには11の実行フェーズがあり、ngx_http_core_module.h
のソースコードで確認できます。
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_PRECONTENT_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
これらの11のフェーズの役割について詳しく知りたい場合は、NGINXのドキュメントを読むことをお勧めします。ここでは詳しく説明しません。
偶然にも、OpenRestyにもNGINXのフェーズに関連する11の*_by_lua
ディレクティブがあります(以下の図を参照、lua-nginx-module
のドキュメントより)。
init_by_lua
はMaster
プロセスが作成されたときにのみ実行され、init_worker_by_lua
は各Worker
プロセスが作成されたときにのみ実行されます。他の*_by_lua
コマンドはクライアントリクエストによってトリガーされ、繰り返し実行されます。
したがって、init_by_lua
フェーズでは、Luaモジュールや読み取り専用のデータを事前にロードし、OSのCOW(Copy On Write)機能を利用してメモリを節約できます。
ほとんどの操作はcontent_by_lua
内で行うことができますが、以下のように異なる機能に応じて分割することをお勧めします。
set_by_lua
: 変数の設定。rewrite_by_lua
: 転送、リダイレクトなど。access_by_lua
: アクセス、権限など。content_by_lua
: 返却コンテンツの生成。header_filter_by_lua
: レスポンスヘッダのフィルタ処理。body_filter_by_lua
: レスポンスボディのフィルタ処理。log_by_lua
: ロギング。
例を挙げて、このように分割する利点を示します。外部に多くの平文APIが提供されていると仮定し、カスタムの暗号化・復号化ロジックを追加する必要があるとします。すべてのAPIのコードを変更する必要があるでしょうか?
location /mixed {
content_by_lua '...';
}
もちろん、そうではありません。フェーズ機能を利用して、access
フェーズで復号化し、body filter
フェーズで暗号化することで、元のcontent
フェーズのコードに変更を加えることなく実現できます。
location /mixed {
access_by_lua '...';
content_by_lua '...';
body_filter_by_lua '...';
}
NGINXバイナリのホットアップグレード
最後に、NGINXバイナリのホットアップグレードについて簡単に説明します。NGINXの設定ファイルを変更した後、リロードして有効にする必要があることはご存知でしょう。しかし、NGINX自体をアップグレードする場合、実行中に行うことができます。これは本末転倒のように見えるかもしれませんが、NGINXが伝統的な静的ロードバランシング、リバースプロキシ、ファイルキャッシュから始まったことを考えると理解できます。
ホットアップグレードは、古いMaster
プロセスにUSR2
とWINCH
シグナルを送ることで行われます。前者は新しいMaster
プロセスを起動し、後者はWorker
プロセスを徐々にシャットダウンします。
これら2つのステップの後、新しいMaster
と新しいWorker
が起動します。この時点で、古いMaster
は終了しません。終了しない理由は簡単で、フォールバックが必要な場合、古いMaster
にHUP
シグナルを送ることができるからです。もちろん、フォールバックが必要ないと判断した場合は、古いMaster
にKILL
シグナルを送って終了させることができます。
これで、NGINXバイナリのホットアップグレードが完了します。
これについてより詳細な情報を知りたい場合は、公式ドキュメントをチェックして学習を続けてください。
まとめ
全体的に、OpenRestyで使用するのはNGINXの基礎であり、主に設定、マスター-ワーカープロセス、実行フェーズなどに関連しています。Luaコードで解決できるものは、できるだけコードで解決し、NGINXモジュールや設定を使用しないことが、OpenRestyを学ぶ際の考え方の変化です。
最後に、オープンな質問を残します。Nginxは公式にNJSをサポートしており、OpenRestyと同様にJSを使用してNGINXのロジックを制御できます。これについてどう思いますか?この記事を共有して意見を聞かせてください。