Nginx HTTP ロードバランサーを運用するときの注意点

December 18, 2016

最近のおすすめは、青森県のお米「晴天の霹靂」です。 この記事は nginx アドベントカレンダー 2016 の 18 日目の記事です。

はじめに

Nginx を HTTP ロードバランサーとして利用するケースがどんどん増えてきています。きちんと設定しておかないとリクエストがロストする、バッチがコケる、異常なサーバがぶら下がったままになるといった問題が生じます。そこで、今回は運用するときに知っておきたい・理解しておきたいポイントをまとめてみました。

nginx のバージョンは 1.11.6 です。前提とする環境は以下の通りです。

  • HTTP ロードバランサーは Nginx
  • バックエンドサーバは 2 台以上
  • ロードバランサーとバックエンドサーバとの通信は HTTP プロトコルを利用する

タイムアウト

ロードバランサーとバックエンドサーバ間の通信において、タイムアウト値を設定するディレクティブは主に3つがあります。いずれもデフォルト値は60秒です。ロードバランサーからバックエンドサーバへの接続は以下の順序で行なわれ、それぞれについて括弧内のディレクティブが対応します。

  1. ロードバランサーとバックエンドサーバ間の接続を確立(proxy_connect_timeout
  2. ロードバランサーからバックエンドサーバへリクエストを送信(proxy_send_timeout
  3. ロードバランサーはバックエンドサーバのレスポンスを受信(proxy_read_timeout
  • proxy_connection_timeout
    • L4のタイムアウト値
    • ロードバランサーとバックエンドサーバ間の通信経路が安定している場合は、大きな問題になることはないと思います
  • proxy_send_timeout
    • HTTP リクエスト送信時のタイムアウト値
    • GET リクエストであれば数ミリ秒で終了しますが、大きなファイルの POST など、巨大なリクエスト送信が想定される場合は調整が必要です
  • proxy_read_timeout
    • バックエンド(アプリケーション)サーバの応答時間の上限
    • この時間に到達すると強制的に接続が切断されるため、バックエンドサーバのアプリケーションが長時間処理を行う場合には長めに設定します

なお、これらの設定値に内包関係はないので、リクエストあたりの最大応答時間を考える場合はこれらをすべて加算する必要があります。

異常サーバの判定

デフォルトでは接続エラーまたはタイムアウトが発生した場合は次のバックエンドサーバにリクエストが再送されます。注意が必要なのは、デフォルトではL7のエラーをクライアントにそのまま返し、振り分け先として残り続ける点です。

500番台エラーが発生した場合に、即座に切り離して別のバックエンドサーバへリクエストを再送するには、以下のように proxy_next_upstream ディレクティブを用いて対象となるエラーコードを明示的に指定する必要があります。

proxy_next_upstream error timeout http_500 | http_502 | http_503 | http_504;

異常サーバの切り離しと復帰

proxy_next_upstream で定義した異常な応答を行ったサーバが切り離され、再度復帰するまでの流れは以下の通りです。ドキュメントを読んでもよくわからなかったので検証して確認しました。

  1. 異常なレスポンスを返す
  2. 1 から fail_timeout 経過する間に、 max_fail 回だけ異常なレスポンスを返した場合に切り離し発生
  3. 切り離し後、fail_timeout だけ待機
  4. 再度、サーバへリクエストを1つ振り分け、正常であれば即座に復帰(max_fail に依存しない)。異常であれば即座に 3 に戻る。

このように、max_fail は切り離しのみ、fail_timeout は切り離しと復帰の両方に影響します。なお、デフォルト値は、fail_timeout: 10smax_fail: 1 です。これらのパラメータは以下のようにバックエンドサーバ1台ずつ設定します。

server 192.168.2.1 max_fail=3 fail_timeout=5s;
server 192.168.2.2 max_fail=3 fail_timeout=5s;
server 192.168.2.3 max_fail=3 fail_timeout=5s;

nginx の標準のヘルスチェックはアクセスをトリガーとした受動的なヘルスチェックなので、fail_timeout の時間が経過し、その後アクセスが来たときに正常なレスポンスを返すことが復帰の条件になります。

参考: NGINX LOAD BALANCING – HTTP LOAD BALANCER

リクエスト再送の仕様

異常なレスポンスを受け取った場合は、次のバックエンドサーバにリクエストを再送します。ただし、以下の冪等ではない HTTP メソッドについては再送せず、クライアントにエラーレスポンスを返します。厳密には、ロードバランサーからバックエンドサーバに対してリクエストを送信する前にエラーが発生した場合のみ、次のサーバへ再送します。この仕組みによって二重リクエスト問題を回避します。

POST, LOCK, PATCH

上記のメソッドについても再送したい(二重リクエストになってもいいからリクエストを落としたくないとか)場合は、non_idempotent を指定します。

proxy_next_upstream error timeout non_idempotent;

GET, DELETE, PUT は冪等なメソッドなので再送されます。そのため、これらのメソッドは利用しているけど冪等性が保証されないようなつらいシステムでは、深刻な問題が発生する可能性があります。任意のメソッドを再送しないように設定することはできないので、これらを無効化する場合は以下のようにすべてのメソッドについてリクエスト再送を無効化します。

proxy_next_upstream off;

まとめ

nginx を利用した HTTP ロードバランサーを運用する際に気をつけるべき点を紹介しました。ロードバランサーの設定はバックエンドの処理に合わせた設定が必要なので、各アプリケーションの仕様を理解しておくことももちろん大切です。この記事よって悲しい事件が1つでもなくなることを祈っています。

以下はこの記事の簡単なまとめです。

  • proxy_read_timeout のデフォルトは60秒なので長時間処理するアプリケーションは要注意
  • バックエンドサーバのL7エラーが発生しても、デフォルトではエラー内容がそのまま返り、切り離されもしない
  • バックエンドサーバ異常時、冪等なメソッドは再送される。冪等でない HTTP メソッドは再送されない
    • 冪等である更新系メソッド DELETE, PUT が冪等ではないシステムだと二重リクエスト問題が発生するから要注意
  • 異常サーバの切り離しは max_failfail_time、復帰は fail_time で調整する

参考 URL


Profile picture

Written by Narimichi Takamura (@nari_ex) who works at Topotal as CEO. He love engineering and fighting game.