ネットワーク

NGINX での流量制御について

みなさん、バースト的なトラフィックに対して、どのような対処をしていますか?
NGINX (Plus) でも簡単な流量制御を行うことが可能ですので、その方法を紹介します。

こんにちは。 narai です。

サービスを公開していると、様々な要因により一時的なトラフィック増が起こることがあると思います。そういった状況に備えて、流量制御を検討される方も多いと思います。 専用装置を導入してもよいですが、NGINX でもトラフィックの流量制御を行うことが可能です。 今回は、NGINX で流量制御を行う方法を紹介します。

目次


1.NGINX での流量制御

2.リクエストレート制限

3.レート制限の設定

4.まとめ

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_zonelimit_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 を使って手軽に流量制御できることも覚えていただければと思います。

この記事に関連する製品・サービス