Как настроить rate limit в Nginx
Здрасти мои хорошие!
Рассмотрим ситуацию — злодеи устроили нагрузочное тестирование и натравили на твой ламповый стартап какой-нибудь «яндекс-танк».
читать первым в телеграм читать первым в макс
Подключить DDosGuard или Кратор!
Эт я пеню, ценник на такие услуги — моё почтение, да и по функционалу там очень даже всё урезано. А cloudflare по регламенту компании запрещен. Замкнутый круг.
Ну так что делать-то?
В nginx есть параметр limit_req_zone, он-то нам и пригодится. Этот параметр ограничивает количество одновременных запросов с одного айпишника.
Поехали настраивать.
Создаем файл /etc/nginx/conf.d/assholes.conf:
geo $limited {
default 1;
192.168.1.1 0; # Этот IP не лимитируется
10.0.0.0/24 0; # 10.0.0.0/24 тоже без ограничений
}
map $limited $limit {
1 $binary_remote_addr;
0 "";
}
limit_req_zone $limit zone=bashdays:20m rate=200r/s;
limit_req_zone — ключ для отслеживания запросов.
$binary_remote_addr — системная переменная nginx, внутри хранит IP-адрес клиента.
zone=bashdays:20m — произвольное имя зоны, 20m это объём памяти в мегабайтах для хранения данных. В этой зоне хранятся данные о количестве запросов от каждого уникального клиента.
rate=200r/s — ограничение равное 200 запросов в секунду с одного IP-адреса клиента.
Для рейта есть такая табличка:
| Сценарий | rate | burst |
|---|---|---|
| API с высокой нагрузкой | 100r/s | 200 |
| Средний веб-сайт | 50r/s | 100 |
| Анти-DDoS защита | 10r/s | 20 |
| CDN или кеширующий прокси | 500r/s | 1000 |
Тестируем так:
ab -n 1000 -c 50 http://bashdays.ru/
wrk -t4 -c100 -d10s http://bashdays.ru/
И смотрим нет ли ошибок 503 Слишком дохуя запросов и корректно ли отрабатывает лимит.
Сука! А как рассчитать эту 20m памяти? Щаа…
Пардон, хуйню сморозил, давай на котиках:
Пример расчёта для 20m (20 мегабайт = 20 × 1024 × 1024 = 20 971 520 байт):
20 971 520 / 128 = 163 840
То есть, зона 20m может хранить лимиты примерно для 163 тысяч уникальных клиентов одновременно.
А откуда взялось 128?
Как я написал выше nginx хранит каждого уникального клиента (ключ $binary_remote_addr или другое значение) в зоне, используя примерно 128 байт на запись.
Есть такая табличка:
| Клиентов | Размер зоны |
|---|---|
| < 20 000 | 4m |
| ~80 000 | 10m |
| ~160 000 | 20m |
| 500 000+ | 64m |
Да, еще можно настроить burst для резких пиков. burst позволяет временно превышать rate, прежде чем включится жёсткий лимит.
Например:
limit_req zone=bashdays burst=400 nodelay;
Клиенту разрешается до 400 мгновенных запросов, а затем он попадает в ограничение 200r/s.
nodelay означает, что первые 400 запросов проходят сразу, а потом начинается строгий лимит.
Теперь по маппингам и гео хуйне.
В первом блоке про гео:
- Все пользователи получат
$limited = 1(по умолчанию). - Те, кто с
192.168.1.1или из10.0.0.0/24, получат$limited = 0и не будут ограничены.
Во втором блоке про маппинг:
- Если
$limited = 1, то$limit = $binary_remote_addr(IP-адрес клиента). - Если
$limited = 0, то$limit = "", и клиент не попадает вlimit_req_zone.
Короче делаем что-то вроде белого списка и кто в него не входит — идёт нахуй! Надеюсь понятно объяснил.
Теперь чтобы вся эта поебота заработала, нужно прописать в нужный локейшен:
location / {
limit_req zone=bashdays burst=10 nodelay;
try_files $uri $uri/ /index.php?$args;
}
Про burst и nodelay выше уже рассказывал.
Ну а чтобы отдавать нужный статус при достижении лимита, делаем:
location / {
limit_req_status 429;
limit_req zone=bashdays burst=10 nodelay;
try_files $uri $uri/ /index.php?$args;
}
Возвращаем 429 Слишком дохуя запросов, вместо стандартного 503.
Ну и сыпь в комменты свои варианты!
