Nginx流量制御(Rate Limit)の活用方法

こちらは、Nginxアドベントカレンダーの12月10日分の記事です。

Nginxがネイティブで提供しているlimit_reqモジュールは、
対象サイトに対する過度なリクエストを制御する機能を提供します。

http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

使ってみた系の記事は多々あるので、
もう一歩踏み込んでユースケースに沿った実装について言及します。

## 流量制御のトレンド

従来、流量制御はサイトダウンの保護を目的として導入されるケースが多く、
LBアプライアンスの機能やapacheのMaxConnectionsなどによる制御が中心でした。

ただし、"プレミア商品のWeb限定販売"や"Web限定特別セール"など、
小売の中心がWebへと移ってきている昨今、サイトダウンを目的とした攻撃の制御だけでは不十分です。

上記に加え、転売による利ざやの確保などを目的とした、
「一見すると正常ユーザに見える不正なアクセス」をいかに防ぐか、
これが非常に重要になってきています。


## ユースケース
### ①バックエンド転送の総量を制限したい

まずはこれが基本的なケースです。

```
　：
    limit_req_zone key_all zone=z_all:50m rate=300r/s;
    limit_req_zone key_sales zone=z_sales:50m rate=100r/s;
　：
    location / {
        location /sales/ {
            limit_req zone=z_all  burst=2000;
            limit_req zone=z_sales  burst=2000;
            proxy_pass   http://192.168.0.110;
        }
        limit_req zone=z_all  burst=2000;
        proxy_pass   http://192.168.0.110;
    }

　：
```

`limit_req_zone` : 流量制御設定の定義
`limit_req` : 定義した流量制御設定の利用

がイメージに近いと思います。

また、上記のような設定で、バックエンドサーバ全体に対する流量を300rpsに制御しつつ、
`/sales/` へのリクエストを１００rpsに制御することができます。

### ②ユーザごとに任意の流量で制限したい 

なるべく、全てのユーザに対して均等な機会を提供したいのは、
多くのサイト運営者がお考えになることと思います。

**\\\それ、Nginxでできます！//**

`limit_req_zone` ディレクティブは下記のようなフォーマットです。

```
limit_req_zone <key> zone=<name>:<size> rate=<rate> [sync];
```

第二引数の`key`ごとに、`rate`の値が管理されることになるため、
ユーザごとに一意となる`key`を設定すれば、ユーザごとの流量制御を実現できます。

ユーザのIPアドレスごとに制御したい場合、下記のような設定となります。
１ユーザあたり5r/sで制御する設定となります。

```
　：
    limit_req_zone key_all zone=z_all:50m rate=300r/s;
    limit_req_zone key_sales zone=z_sales:50m rate=100r/s;
    limit_req_zone $binary_remote_addr zone=z_peruser:10m rate=1r/s;

：
    location / {
        location /sales/ {
            limit_req zone=z_all  burst=2000;
            limit_req zone=z_sales  burst=2000;
            limit_req zone=z_peruser  burst=5;
            proxy_pass   http://192.168.0.110;
        }
        limit_req zone=z_all  burst=2000;
        limit_req zone=z_peruser  burst=5;
        proxy_pass   http://192.168.0.110;
    }

　：
```

`key`部に`$binary_remote_addr`と設定することで、ユーザのIPアドレスの値を元に制御しています。
※ここでは言及しませんが、remote_addrがELBのIPアドレスなどにならないように注意が必要です。
当然、`$cookie_jsessionid`と設定することで、Cookieの値を元に制御することも可能です。

### ③特定のユーザ・拠点からのアクセスは流量制御しない

ユーザごとに流量制御したいけど、自社オフィスからのアクセスの場合流量制御したくないケースなどが該当します。

http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
>Requests with an empty key value are not accounted.

マニュアルに上記のような記述があり、
`<key>`の値が空の場合は、流量制御が適用されない、と書かれているわけです。

つまり、何らかの条件に基づいて`<key>`を空にするような実装を施せばよいので、
下記のような実装によって実現可能です。

```
　：
    geo $limit {
         default 1;
         10.0.0.0/8 0;
         192.168.0.0/24 0;
         x.x.x.x/32 0;
    }

    map $limit $limit_req_key {
        0 "";
        1 $binary_remote_addr;
    }

    limit_req_zone key_all zone=z_all:50m rate=300r/s;
    limit_req_zone key_sales zone=z_sales:50m rate=100r/s;
    limit_req_zone $limit_req_key zone=z_peruser:10m rate=1r/s;

：
    location / {
        location /sales/ {
            limit_req zone=z_all  burst=2000;
            limit_req zone=z_sales  burst=2000;
            limit_req zone=z_peruser  burst=5;
            proxy_pass   http://192.168.0.110;
        }
        limit_req zone=z_all  burst=2000;
        limit_req zone=z_peruser  burst=5;
        proxy_pass   http://192.168.0.110;
    }

　：
```

IPアドレスによって`$limit`に 0 or 1 を設定し、
`$limit`が 1 の場合は`$limit_req_key`にIPアドレスを設定しています。
これにより、ユーザごとのアクセス制御と流量制御のホワイトリストを実現しています。

### ④クエリストリングのキーによって流量制御したい

ビジネスロジックやURLを変更できないが、リクエストの種別を判定して流量制御したいケースもあると思います。
そちらについては、以前ブログで紹介したことがあるので、手前味噌ですが下記をご覧ください。
https://iryond.hatenablog.com/entry/2016/09/16/010856

## 最後に

Nginxを使った流量制御パターンをいくつか紹介しました。
他にも、`conn_limit` モジュールを活用したコネクション数での流量制御パターンや、
アクセス頻度(10reqまではサクサク、それ以降は1分に１回など)に応じた流量制御なども
Nginx単体で実現可能なので、次回以降紹介したいと思います。

Nginx本家のブログでも活用方法に関する紹介があるので、下記などもご覧ください。

本家版：https://www.nginx.com/blog/rate-limiting-nginx/
日本語翻訳版：https://iryond.hatenablog.com/entry/2017/07/08/203025

誤記や訂正依頼、「こんなことできないの？」などありましたらコメントいただければ幸いです。