個人的に、Nginx で「これは危険だ」と思っている設定があって、Nginx でなにかあるたびにその設定をつい疑ってしまいます。その設定について他の人に話すたびに、いちいち資料を集めるのが面倒になってきたので、今回はその設定項目についての情報をまとめておきます。
まだ理解に自信がない部分があるので、新しい情報が入ってきたら、この記事を適宜修正します。
リバースプロクシ設定の基本
Nginx をリバースプロクシとして使う時には、ngx_http_upstream_module でサーバのグループを定義します。そして、サーバ名やロケーション(パス)に対して、送信先のグループを指定します。
以下はマニュアルにある例です。その Nginx サーバへのすべてのアクセスを、backend グループに指定されたいずれかのサーバに送信します。
upstream backend { server backend1.example.com weight=5; server backend2.example.com:8080; server unix:/tmp/backend3; server backup1.example.com:8080 backup; server backup2.example.com:8080 backup; } server { location / { proxy_pass http://backend; } }
この送信に関わる設定は、proxy_pass を含む ngx_http_proxy_module の方にあります。このモジュールの設定のなかで、(僕が個人的に)よくつまづくのが proxy_next_upstream から始まる設定です。
proxy_next_upstream から始まる設定
これらは、upstream(リクエストの送信先)からエラーが返されたり、リクエストがタイムアウトした場合の動作に関する設定です。
- proxy_next_upstream
- 失敗したリクエストを他のサーバに再送する条件(複数指定可)
- proxy_next_upstream_timeout
- Nginx 側でリクエストがタイムアウトしたと判断するまでの時間
- proxy_next_upstream で timeout が指定された場合のみ、この設定が使われる
- 時間の単位は Configuration file measurement units を参照
- proxy_next_upstream_tries
- proxy_next_upstream の条件に合致したリクエストを、最大で何台のサーバに送信するか
- マニュアルには明示されていないが、この送信回数は最初の1台を含む
- 1が設定されたら、最初のサーバ1台にしかリクエストを送信しない
- 3が設定されたら、最初のサーバ1台への送信と、それ以外の2台への再送を行う
これらの設定が明示的に指定されなかった場合のデフォルト値と、その意味は以下の通りです。
- Default:
proxy_next_upstream error timeout;
- 何らかのエラーが発生した場合、または Nginx 側でリクエストがタイムアウトした場合に、リクエストを再送する
- ここで言う「エラー」とは、(転送先)サーバへの接続時、リクエストの転送時、またはレスポンスヘッダの読み込み時に発生するエラーのこと
- 4xx 応答、5xx 応答は、ここで言う「エラー」には含まれない
- Default:
proxy_next_upstream_timeout 0;
- Nginx 側でのタイムアウトは起こらない(0 は無制限を表す)
- Default:
proxy_next_upstream_tries 0;
- upstream ディレクトリで定義されたすべてのサーバに対して順番に、エラーが発生したリクエストを再送する(0 は無制限を表す)
不適切な設定が問題になるケース
proxy_next_upstream_tries を指定せずに使っていると、バックエンドのサーバへの接続で何らかのエラーが発生したら、最悪の場合、そのリクエストはすべてのサーバに対して送信 されます。
例えば、以下のような状況になると、無駄なリクエストが Nginx で大量に増幅されて、システム全体の負荷が急増します。
proxy_next_upstream 設定を何も指定していない状態で、
→ アプリケーションサーバが何かのバグで不正なレスポンスを返すようになる
→ そのバグを踏むリクエストが来る
→ そのリクエストがアプリケーションサーバの台数だけ複製される(サーバ10台なら10倍になる)
→ システム全体の負荷が急増
また、proxy_next_upstream_timeout だけ設定していると、こういうこともあり得ます。
proxy_next_upstream_timeout が2秒に設定されていて、proxy_next_upstream_tries は未指定の状態で、
→ 処理時間が2秒を超える重いリクエストが来る
→ その重いリクエストがアプリケーションサーバの台数だけ複製される(サーバ10台なら10倍になる)
→ システム全体の負荷が上がって、普段は2秒未満のリクエストも2秒以上かかるようになる
→ それらのリクエストも10倍に複製される
→ システム全体の負荷が急増
あるべき設定
個人的に考える、あるべき設定は以下の通りです。
proxy_next_upstream_tries は必ず0以外に設定する
この値がデフォルト値の0(無制限)でさえなければ、上記のような問題は起こらないので、まずこれを設定します。
1にすれば再送は起こりませんが、アプリケーションサーバを再起動するような場合にいちいちエラーが出てしまいます。再起動の場合のみを考えるなら、この値が大きすぎても意味はありません。そのため、proxy_next_upstream_tries は2〜3でいいと思います。
proxy_next_upstream_timeout はアプリケーションサーバ側の応答時間より長くする
proxy_next_upstream_timeout がアプリケーションサーバ側の応答時間よりも短いと、せっかくアプリケーションサーバがレスポンスを返しても Nginx で破棄されてしまいます。これではサーバの計算資源の無駄遣いです。
そのため、アプリケーションサーバの応答時間を事前に見積もって、それより長い時間を proxy_next_upstream_timeout に指定しましょう。これは、タイムアウト設計をきちんとしましょう、そして時間がかかる処理(データベース接続)があるならアプリケーション内にきちんとタイムアウト処理を入れましょう、という話ですね。
応答時間の見積もりが難しいなら、proxy_next_upstream_timeout はデフォルト(タイムアウトなし)のままでもいいと思います。
サービスによっては再送処理をオフにする
API 提供時など、クライアント側で再送処理をしてくれるなら、proxy_next_upstream off;
を設定し、再送処理をオフにするという手もあります。