OpenResty FAQ | 特権プロセス権限、実行フェーズなど

API7.ai

November 11, 2022

OpenResty (NGINX + Lua)

この記事には、よくある質問が6つ含まれています:

1. 特権プロセスの権限

Q: 特権プロセスとは何ですか?非特権ユーザーがどのようにしてroot権限を取得できるのですか?特権プロセスの使用シナリオをいくつか紹介できますか?

A: 特権プロセスの権限は、masterプロセスの権限と同じです。非特権ユーザーとしてOpenRestyを起動すると、masterプロセスはそのユーザーの権限を継承するため、「特権プロセス」は現在何の権限も持っていません。

通常のユーザーがプロセスを起動した場合、root権限がないことは簡単に理解できます。

特権プロセスの使用シナリオについては、一般的に高い権限を必要とするタスク、例えばログのクリーンアップやOpenRestyの再起動などに使用します。ただし、セキュリティリスクがあるため、ワーカープロセスのタスクを特権プロセスで実行しないように注意してください。

ある開発者は、すべてのtimerタスクを特権プロセスで実行しています。なぜ彼はこれを行うのでしょうか?特権プロセスは1つしかないため、この方法でtimerが繰り返し起動されないようにしています。

この開発者は、worker.idを使用せずに目標を達成したため「賢い」と言えます。しかし、忘れないでください。timerタスクがクライアントの入力に依存している場合、非常に危険です。

2. フェーズとデバッグ

Q: ngx.say('hello')を実行した後、OpenRestyは現在のフェーズの残りのロジックを実行した後、クライアントに直接応答しますか?つまり、後のフェーズを実行しないということですか?

A: そうではありません。その実行フェーズを見てみましょう:

image

まずcontentフェーズでngx.sayをテストし、その後logまたはbody filterフェーズでngx.logを使用してログを出力できます。

以前の記事では、OpenRestyでのコードデバッグについて具体的に言及していなかったため、開発者が混乱するかもしれません。

OpenRestyには、ブレークポイントのような高度なデバッグ機能はありません(有料のプラグインはありますが、私は使用したことがありません)。ngx.sayngx.logを使用して出力を確認するしかありません。これは、私が知るすべての開発者、OpenRestyの作者や貢献者も行っている方法です。したがって、堅牢なテストケースとデバッグログを保証として用意する必要があります。

3. ngx.exitの実践

Q: 以前の記事で、OpenRestyのHTTPステータスコードには特別な定数ngx.OKがあると説明されていました。ngx.exit(ngx.OK)を実行すると、リクエストは現在のフェーズを終了し、次のフェーズに進みますが、クライアントに直接返されるわけではありません。

ngx.OKはHTTPステータスコードと見なすべきではなく、その値は0です。私の理解は以下の通りです:

  • ngx.exit(ngx.OK)ngx.exit(ngx.ERROR)、またはngx.exit(ngx.DECLINED)を実行すると、リクエストは現在のフェーズを終了し、次のフェーズに進みます。
  • ngx.exit(ngx.HTTP_*)ngx.HTTP_*のさまざまなHTTPステータスコードをパラメータとして取る場合、クライアントに直接応答します。

私の理解は正しいでしょうか?

A: 最初の質問について、ngx.okはHTTPステータスコードではなく、OpenRestyの定数で、その値は0です。

2番目の質問については、ngx.exitの公式ドキュメントが正確な答えとなります:

  1. status >= 200(つまり、ngx.HTTP_OK以上)の場合、現在のリクエストの実行を中断し、ステータスコードをnginxに返します。

  2. status == 0(つまり、ngx.OK)の場合、現在のフェーズハンドラ(またはcontent_by_lua*ディレクティブが使用されている場合はコンテンツハンドラ)を終了し、現在のリクエストの後続のフェーズ(もしあれば)を実行し続けます。

ただし、ドキュメントにはngx.exit(ngx.ERROR)ngx.exit(ngx.DECLINED)の処理方法については言及されていません。以下のようにテストできます:

location /lua {
    rewrite_by_lua "ngx.exit(ngx.ERROR)";
    echo hello;
}

このlocationにアクセスすると、HTTPレスポンスコードが空で、レスポンスボディも空であり、次のフェーズには進まないことがわかります。

OpenRestyの学習を深めていくと、ドキュメントやテストケースでは答えられない疑問に必ずぶつかります。その時は、自分のテストケースを構築してアイデアを検証する必要があります。手動で行うか、test::nginxで構築されたテストケースセットにテストを追加できます。

4. 変数と競合状態

Q: 前述のように、ngx.var変数のスコープはnginx Clua-nginx-moduleモジュールの間にあります。

  1. これがよく理解できません。リクエストの観点から見ると、ワーカープロセス内の単一のリクエストを意味するのでしょうか?

  2. 私の理解では、モジュール内で変数を操作する場合、2つの操作の間にブロッキング操作があると、競合状態が存在する可能性があります。したがって、2つの操作の間にブロッキング操作がなく、CPU時間が尽きたときに現在のプロセスが準備キューに入る場合、競合状態が存在する可能性はありますか?

A: これらの質問を見ていきましょう。

まず、ngx.var変数について、あなたの理解は正しいです。ngx.varのライフサイクルはリクエストと同じで、リクエストが終了すると消えます。しかし、その利点は、CモジュールとLuaコードの間でデータを渡すことができる点です。これは他のいくつかの方法では不可能です。

次に、2つの操作の間にyield操作がある場合、競合状態が存在する可能性があります。ブロッキング操作がある場合、競合状態はありません。つまり、NGINXのイベントループに主導権を渡さない限り、競合状態は発生しません。

5. shared dictはロックを必要としない

Q: 複数のワーカーが同時にデータを保存する場合、ロックを追加する必要がありますか?

例えば:

resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
local lock= ngx.xxxx.lock
lock.lock()
 dogs:set("Jim", 8)
lock.unlock()
 local v = dogs:get("Jim")
 ngx.say(v)
 '

A: ここではロックを追加する必要はありません。なぜなら、getset操作に関係なく、shared dictの操作はアトミックだからです。OpenRestyはすでにこのようなロック的な処理を考慮しています。

6. OpenRestyでの時間操作

Q: ngx.now()を使用して時間を取得する場合、それはresume関数の復元フェーズで発生しますか?

A: NGINXはパフォーマンスを最優先に設計されており、時間をキャッシュしています。ngx.nowのソースコードでこれを確認できます:

static int
ngx_http_lua_ngx_now(lua_State *L)
{
    ngx_time_t              *tp;

    tp = ngx_timeofday();

    lua_pushnumber(L, (lua_Number) (tp->sec + tp->msec / 1000.0L));

    return 1;
}

ご覧の通り、現在の時刻を取得するngx.now()関数の背後には、NGINXのngx_timeofday関数があります。ngx_timeofday関数はマクロ定義です:

#define ngx_timeofday()      (ngx_time_t *) ngx_cached_time

ここで、ngx_cached_timeの値はngx_time_update関数でのみ更新されます。

したがって、問題は「ngx_time_update関数がいつ呼び出されるか?」になります。NGINXのソースコードを追跡すると、ngx_time_updateの呼び出しはイベントループで発生することがわかります。これで問題は解決します。

まとめ

これらの質問を通じて、オープンソースプロジェクトの利点は、手がかりをたどり、ソースコードで答えを見つけることができることです。これにより、事件を解決するような感覚を得ることができます。

最後に、コミュニケーションとQ&Aを通じて、学んだことを得るものに変えるお手伝いができれば幸いです。この記事を転送していただき、一緒にコミュニケーションと改善を行いましょう。