NGINX での流量制御について
みなさん、バースト的なトラフィックに対して、どのような対処をしていますか?
NGINX (Plus) でも簡単な流量制御を行うことが可能ですので、その方法を紹介します。
こんにちは。 narai です。
サービスを公開していると、様々な要因により一時的なトラフィック増が起こることがあると思います。そういった状況に備えて、流量制御を検討される方も多いと思います。 専用装置を導入してもよいですが、NGINX でもトラフィックの流量制御を行うことが可能です。 今回は、NGINX で流量制御を行う方法を紹介します。
目次
NGINX での流量制御
NGINX では以下の方法で流量を制御することが可能です。
- リクエストのレートを制限する
- コネクション数を制限する
- 帯域幅を制限する
今回は、「リクエストのレートを制限する」方法について紹介します。
リクエストレート制限
レート制限は「一定期間の間にどのくらいのリクエストを許容するか」という管理方法となり、NGINX にてレート制限を行う場合、rate limit
を使用します。
なお、NGINX のレート制御には Leaky Bucket アルゴリズムが使用されています。 これは、以下のように穴の開いたバケツにたとえられ、Input に関係なく OutPut は常に一定であり、指定したレート以上のトラフィックは流さない方法となります。
そのため、Input Rate ≦ Output Rate であれば、何も起こりませんが、Input Rate > Output Rate となると、超過した分をバッファし、バッファ容量が一杯になると、新規リクエストを受け付けなくなります。
※ 出典: https://learn.microsoft.com/ja-jp/windows/win32/medfound/the-leaky-bucket-buffer-model
書式
NGINX にてレート制限をする場合、limit_req_zone
と limit_req
ディレクティブを使用します。 limit_req_zone
ディレクティブではレート制限のパラメーターを定義し、limit_req
にて、レート制限を有効にします。 なお、レート制限を超過したリクエストに対しては、503 Service Temporarily Unavailable
をレスポンスします。
limit_req_zone <key> zone=<zone名>:<サイズ> rate=<制限レート>;
key : レート制御を行う通信を識別する条件を指定します。
zone名 : レート制御の情報を保持する共有メモリの名称とサイズを指定します。
rate : 最大リクエストレートを指定します。 レートは秒間あたりのリクエスト数として r/s
を指定します。
limit_req zone <zone名> [burst=<xx>] [nodeley | deley=<xx>];
burst : 制限レートを超過した際のバッファ量を指定します。
nodeley|deley : バッファしたリクエストを即座に転送するかどうか
レート制限の設定
それでは、実際の設定で動作を見てみましょう。 「/ 配下へのリクエストはクライアント IP 毎に秒間 100 リクエストに制限される」ようにするために、以下の設定を行います。
limit_req_zone $binary_remote_addr zone=req:1M rate=100r/s;
# <key> に $binary_remote_addr を指定しているので、クライアントIP毎にレート制御を行う
# rate=100r/s なので、秒間 100 リクエストのレート制御
server {
listen 80 ;
location / {
proxy_pass http://pool1;
limit_req zone=req;
}
}
この設定に対して、apache bench を使用してリクエストを投げてみましょう。
■ 同時 5コネクション、100 リクエストを投げてみる
# ab -c 5 -n 100 localhost/app1/index.html
Time taken for tests: 0.016 seconds
Complete requests: 100
Failed requests: 98 <---!!!
あれ? 秒間 100 リクエストなのに、2 リクエストしか成功していませんね。 これは、NGINX はミリ秒単位でリクエストを追跡しているため、10r/s
と指定した場合、 「1秒間に 100リクエスト」ではなく、「10 ミリ秒ごとに 1 リクエスト」を許容する動作となります。
そのため、同一設定で 5 秒間で 50000 リクエストを発生させた場合、以下のように 500 程度に制限されます。
■ 5秒間で 50000 リクエストを投げてみる
# ab -c 5 -n 100 -t 10 localhost/app1/index.html
Time taken for tests: 5.048 seconds
Complete requests: 50000
Failed requests: 49495
burst オプション
制限レートを越えたリクエストについて、即座に 503 Server Unavailable
を返すのではなく、 リクエストをバッファさせることもできます。 その場合、burst
オプションを使用して、レートを超えたリクエストをいくつバッファするかを設定します。
それでは、実際の動作を見てみましょう。 burst の動きがよくわかるよう、1秒間に 1 リクエストを許容し、2 リクエストをバッファする設定とし、curl でリクエストを投げてみます。
limit_req_zone $binary_remote_addr zone=req:1M rate=1r/s;
server {
listen 80 ;
location / {
proxy_pass http://pool1;
limit_req zone=req burst=2;
}
}
今度はクライアントにてターミナルを複数展開し、同時に 4 リクエストを投げてみます。
#curl http://localhost/app1/index.html -I
■ ターミナル1
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:28 GMT <--!!! すぐにレスポンスが返ってくる
■ ターミナル2
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:29 GMT <--!!! 1s 遅れてレスポンスが返ってくる
■ ターミナル3
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:30 GMT <--!!! 2s 遅れてレスポンスが返ってくる
■ ターミナル4
HTTP/1.1 503 Service Temporarily Unavailable <--!!! burst を越えたので 503 が返ってくる
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:28 GMT
このように、burst で指定したリクエストを越えた場合は、503 Service Temporarily Unavailable
が返ってきます。 また、バッファされたリクエストについては、rate
で設定した間隔で転送されるため、レスポンスが遅延します。
deley オプション
burst オプションを使用することでリクエストをバッファできるようになりましたが、リクエストが転送されるまでにタイムラグが出てしまいます。 こういった状況に対応するために、deley=|nodeley オプションがあります。
nodeley はバッファしたリクエストを即座にサーバーに転送します。 deley= は指定リクエスト分を即座にサーバーに転送します。
それでは、実際の動作を見てみましょう。 この設定では、1秒間に 1 リクエストを許容し、2 リクエストをバッファしたうえで、バッファしたリクエストを即座にサーバーに転送します。
limit_req_zone $binary_remote_addr zone=req:1M rate=1r/s;
server {
listen 80 ;
location / {
proxy_pass http://pool1;
limit_req zone=req burst=2 nodeley;
}
}
クライアントにてターミナルを複数展開し、同時に 4 リクエストを投げてみます。
#curl http://localhost/app1/index.html -I
■ ターミナル1
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:50 GMT <--!!! すぐにレスポンスが返ってくる
■ ターミナル2
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:50 GMT <--!!! すぐにレスポンスが返ってくる
■ ターミナル3
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:50 GMT <--!!! すぐにレスポンスが返ってくる
■ ターミナル4
HTTP/1.1 503 Service Temporarily Unavailable <--!!! burst を越えたので 503 が返ってくる
Server: nginx/1.21.6
Date: Wed, 29 Mar 2023 08:17:28 GMT
このとおり、バッファを含めた 3 リクエストに対するレスポンスがすぐに返ってきました。 しかしながら、バッファを越えたリクエストに対しては、503 が返って来ます。
なお、nodeley はリクエストの転送を即座に行うだけで、バッファからのリクエスト削除は、rate
で指定したレートで行われます。この設定であれば、1秒後に1リクエスト分、2秒後に2リクエスト分のバッファが確保されます。
これらのことから、NGINX にて レート制限を行う場合は、burst , nodeley(deley=xx) オプションを併用することが推奨されています。
まとめ
今回は、NGINX でのレート制限の機能を見てきました。 rate limit
はここで紹介した内容以外に、dry-run
オプションを使用することで実際の通信は制御せず、limit に達したことのみをログ出力することもできます。そのため、「適切な閾値がわからず、limit を有効にしたとたんにユーザビリティが低下した」といったことを回避することもできます。
サービスを公開していると、バースト的なトラフィックは避けては通れない問題となります。 auto scale 等で対処することも大切ですが、NGINX を使って手軽に流量制御できることも覚えていただければと思います。