過度のアクセスに備える(その2)!!Nginxのlimit_reqの設定と検証

Nginxのモジュールngx_http_limit_req_moduleの使い方と、動作検証の結果をまとめました。このモジュールを使うと、単位時間あたりにリクエストできる数に制限をかけることができます。

例えば、同一IPから秒間10アクセス以上発生していた場合に、遅延を発生させることなどができます。また、遅延を発生させずに、即座に503エラーを返すこともできます。

※同じような過度のアクセスへの対策である、limit_connに関してはこちらのページにまとめてあります。

ngx_http_limit_req_moduleの設定

まずはhttpディレクティブに、limit_req_zoneなどの設定をします。

http {
...
    limit_req_zone $binary_remote_addr zone=limit_req_by_ip:10m rate=1r/s;
    limit_req_log_level error;
    limit_req_status 503;
...
}

上記設定では、limit_req_zoneで、同一IPからのアクセスが秒間1回以上あったら制限をかけるゾーンを定義しています。

続けて、locationディレクティブなどにlimit_reqを設定します。

server {
    location / {
        limit_req zone=limit_req_by_ip burst=10;
        ...
    }
}

上記設定ではlocation「/」に対して、limit_req_by_ipで設定した秒間1アクセスを超えたら遅延を発生させるように設定しています。また、burst=10で、遅延が発生した場合の最大リクエスト数を10としています。もし最大リクエスト数を超えたら503エラーが発生します。

また、nodelayを指定すると、制限に達したら即座に503エラーを返すようになります。

server {
    location / {
        limit_req zone=limit_req_by_ip burst=10 nodelay;
        ...
    }
}

ngx_http_limit_req_moduleの動作確認

abコマンドを使って、limit_reqの挙動を確かめてみます。使用するabのコマンドは全て「リクエスト回数100、同時リクエスト数10」とします。

# 使用するabコマンド
$ ab -n 100 -c 10 http://test.remotestance.com/

秒間1アクセスで制限

まずは、秒間1アクセスで制限がかかるように設定します。その上で、burstの数値やnodelayの指定などを変化させたときの挙動を調べてみます。

limit_req_zone $binary_remote_addr zone=limit_req_by_ip:10m rate=1r/s;

「burst=10」の場合

まずはburstを10に設定して試してみます。abコマンドの同時接続数は10なので、遅延は発生するけれどもエラーは返ってこないことが予想されます。

limit_req zone=limit_req_by_ip burst=10;
結果

abコマンドの結果を見るとエラーは返ってきていません。

Time taken for tests:   99.140 seconds
Complete requests:      100
Failed requests:        0

ただし、error.logを見ると遅延が発生していることがわかります。

2015/05/31 xx:xx:xx [warn] 4117#0: *21938 delaying request, excess: 9.892, by zone "limit_req_by_ip", client: xxx.xxxx.xxx.xxx, server: remotestance.com, request: "GET / HTTP/1.0", host: "test.remotestance.com"

「burst=9」の場合

今度はburst=9でabコマンドを実行してみます。abコマンドの同時接続数が10なので、遅延が発生して、かつエラーが返ってくることが予想されます。

limit_req zone=limit_req_by_ip burst=9;
結果

abの結果を見ると、やはりエラーが発生していました。

Time taken for tests:   33.131 seconds
Complete requests:      100
Failed requests:        66
Non-2xx responses:      66

また、エラーログには遅延が発生したこと(warn)と、制限に達したこと(error)が記録されていました。

2015/05/31 xx:xx:xx [warn] 4214#0: *22031 delaying request, excess: 8.930, by zone "limit_req_by_ip", client: xxx.xxx.xxx.xxx, server: remotestance.com, request: "GET / HTTP/1.0", host: "test.remotestance.com"
2015/05/31 xx:xx:xx [error] 4214#0: *22032 limiting requests, excess: 9.681 by zone "limit_req_by_ip", client: xxx.xxx.xxx.xxx, server: remotestance.com, request: "GET / HTTP/1.0", host: "test.remotestance.com"

「nodelay」を指定した場合

次はnodelayを指定してみます。この場合は遅延が発生せずに、即座にエラーとなることが予想されます。

limit_req zone=limit_req_by_ip burst=10 nodelay;
結果

abの結果を見るとエラーが返ってきていることがわかります。また、遅延が発生していないので、abの実行時間が短いこともわかります。(burst=10のテストが約100秒かかったのに対して、このテストでは約1.5秒で終了しています。)

Time taken for tests:   1.442 seconds
Complete requests:      100
Failed requests:        88
Non-2xx responses:      88

error.logを見ると遅延は発生しておらず、制限に達したことだけが記録されています。

2015/05/31 xx:xx:xx [error] 4293#0: *22135 limiting requests, excess: 10.612 by zone "limit_req_by_ip", client: xxx.xxx.xxx.xxx, server: remotestance.com, request: "GET / HTTP/1.0", host: "test.remotestance.com"

秒間999999999アクセスで制限

最後は秒間999999999アクセスで制限がかかるようにしてみます。このときは、制限がかからず、エラーも返ってこないことが予想されます。

limit_req_zone $binary_remote_addr zone=limit_req_by_ip:10m rate=999999999r/s;

結果

abの結果をみるとエラーが返ってきていないことがわかります。また、遅延していないので、abの実行時間も短いこともわかります。(burst=10のテストが約100秒かかったのに対して、このテストでは約3.5秒で終了しています。)

Time taken for tests:   3.359 seconds
Complete requests:      100
Failed requests:        0

また、error.logには何も記録されていないことがわかります。

$ ls -l error.log
-rw-r--r-- 1 root root 0  5月 31 xx:xx error.log

この記事が役に立った場合、シェアしていただけると励みになります!!