Лимит запросов отказывает конкретному вызывающему, который прислал больше своей доли. Он настраивается для каждого edge из central, поэтому менять его можно без передеплоя, а каждая политика начинается в режиме, который только считает. Нужен зарегистрированный edge и оператор с правом edges:manage; с edges:read политики видны, но не меняются. У auth есть свои лимиты для канала без mutual TLS, они описаны ниже.
Лимиты запросов и защита от перегрузки
Они отвечают на разные вопросы и никогда не делят счетчик.
| Лимиты запросов | Защита от перегрузки | |
|---|---|---|
| Вопрос | Этот вызывающий превысил свою долю? | Может ли этот под принять еще работу прямо сейчас? |
| Отказывает | Тому, кто превысил свою корзину; остальные не затронуты. | Любому, кто пришел сверх возможностей пода, по приоритету. |
| Лимит | Число, которое вы задаете в политике. | Вычисляется по времени вызовов и задержке исполнителя. |
| Ответ | 429 с Retry-After | 503 с Retry-After |
Защита от перегрузки стоит снаружи: при перегрузке запросу отказывают там, раньше, чем его увидит лимит запросов.
Начните с рекомендуемого набора
Чтобы не подбирать числа наугад, central отдает стартовый набор. Add recommended policies в консоли (или GET /configuration/edges/rate-limits/recommended) дает пять политик, все в shadow:
| Политика | Что считает | Лимит |
|---|---|---|
per-client | каждого OAuth-клиента | 120 000 в минуту, всплески по 5 000 |
per-user | каждого пользователя | 300 в минуту и 5 000 в час, всплески по 60 |
per-user-endpoint | каждого пользователя на каждом endpoint | 120 в минуту, всплески по 30 |
per-address | каждый адрес: неудачные и неаутентифицированные попытки и входы | 600 в минуту, всплески по 100 |
per-certificate | каждый клиентский сертификат на listener с mutual TLS | 60 000 в минуту, всплески по 2 000 |
Числа щедрые намеренно: они ловят вышедшего из-под контроля клиента, пользователя или адрес, а не занятого. Они действуют на каждый под edge, пока вы не задали число подов (см. Поды edge). per-address имеет смысл, только когда edge видит настоящий адрес клиента, см. Какой адрес.
Политики
Политика — набор полей. Набор заменяется целиком. У API нет неявных значений: отправляйте каждое поле, с обычным значением, если предпочтений нет (консоль так и делает).
| Поле | По умолчанию | Значение |
|---|---|---|
name | нет | От 1 до 48 символов: строчная буква, затем строчные буквы, цифры, _ или -. Уникально в пределах edge. Подписывает метрики и отказ. |
by | нет | По чему считается корзина: client, subject, session, jkt (ключ DPoP), ip или certificate. Пусто — одна корзина на весь scope. ip и certificate используются только поодиночке. |
scope | edge-wide | edge-wide, resource или endpoint. Более узкий scope ведет корзину на ключ и ресурс или на ключ и зарегистрированный endpoint. |
limits | нет | От 1 до 8 окон, каждое: limit запросов за windowSeconds. Должны пройти все окна. Окна одной политики различны. |
burst | 1 | Сколько запросов может прийти разом. Не больше наименьшего limit. 1 — строгие интервалы. |
cost | 1 | Сколько запрос забирает из корзины. Не больше burst. |
mode | shadow | off игнорирует политику, shadow считает, что было бы отказано, и ничего не отказывает, enforce отказывает. |
Не больше 64 политик на edge, limit от 1 до 1 000 000, windowSeconds от 1 до 86 400. Запрос проверяется по всем подходящим политикам и списывается со всех, только если прошел все.
Где проверяется запрос
| Точка | Когда | Ключи |
|---|---|---|
| R1, адрес | До чтения токена, на обычном listener | Адрес вызывающего. Считает только неудачные и неаутентифицированные попытки, плюс каждый вызов точек входа (/login, /complete, /native/start, /native/complete, /logout). Успешный вызов по адресу никогда не ограничивается. |
| R1, сертификат | До чтения токена, на listener с mutual TLS | SHA-256 отпечаток проверенного клиентского сертификата. Считается каждый вызов. |
| R2 | После проверки access token, до проверки отзыва и DPoP-доказательства | client, subject, session, jkt токена |
| R3 | После определения endpoint и разрешений вызывающего, до /userinfo и ресурса | Ресурс и зарегистрированный endpoint вместе с ключами токена |
Запрос, отказанный на R2, не доходит ни до проверки отзыва, ни до проверки DPoP, ни до /userinfo, ни до вашего ресурса. У вызова, отказанного на R3, DPoP-доказательство уже записано. Обновление токена, которое edge делает для cookie-сессии, происходит до R2 и отдельно не считается.
Лимит по адресу считает только неудачи, потому что за одним адресом оператора связи или офиса сидит много честных пользователей: если считать их успешные вызовы, их отказывали бы друг за друга. Он приблизителен при параллельности: запросы одного адреса, уже летящие в момент, когда корзина опустела, проходят все, так что всплеск параллельных неудач может превысить лимит примерно на параллельность адреса.
Настройка в консоли
Откройте Edges, откройте edge и нажмите Edit на карточке Rate limits. Редактор открывается в боковой панели с блоком на каждую политику: имя, режим, по чему считать, scope, окна, а burst и cost — в Advanced. В пустом edge есть Add recommended policies; Add policy добавляет пустую.


Редактор проверяет те же правила, что и central, пока вы печатаете, и не отправляет набор, который их нарушает. Remove all policies отправляет пустой набор после подтверждения. Изменение доходит до edge при его следующей синхронизации конфигурации, в пределах интервала обновления (по умолчанию 5 минут); пока central недоступен, edge держит последний полученный набор.
Настройка через API
Все политики edge — один ресурс: GET и PATCH /configuration/edges/policies?edgeId=<id>. Секции: overload, accessLog и rateLimits. Отсутствующая в теле секция остается как есть, null удаляет ее, объект заменяет ее целиком. Секция, не прошедшая проверку, или ключ, не являющийся секцией, дают 400 без тела: первая найденная проблема — в журнале central. Консоль проверяет те же правила до отправки, поэтому 400 — ошибка скрипта. Все поля политики и replicas обязательны; неявных значений нет. Схемы — EdgePolicies и EdgePoliciesPatch в OpenAPI-файле central.
{
"rateLimits": {
"replicas": 3,
"policies": [
{
"name": "per-user",
"by": ["subject"],
"scope": "edge-wide",
"limits": [{ "limit": 300, "windowSeconds": 60 }],
"burst": 60,
"cost": 1,
"mode": "enforce"
}
]
},
"expectedRevisions": { "rateLimits": 4 }
}
Две вещи делают вызов безопасным из скрипта. expectedRevisions называет по секциям ревизию, которую вы прочитали последней (GET возвращает revisions и updatedAt): если названная секция с тех пор изменилась, ничего не записывается, а ответ — 409 без тела (прочитайте состояние заново через GET); у секции, которую вы не назвали, предусловия нет, поэтому клиенту, который правит только свою секцию, оно не нужно. И dryRun=true проверяет тело по тем же правилам, что применяет edge, и отвечает состоянием, которое получилось бы, не записывая его. Ответ на настоящий patch — новое состояние с новыми ревизиями.
Лимиты, которые объявляет владелец API
Лимит можно объявить и там, где объявлен сам API: его владелец защищает свое место, и лимит едет вместе с ним. Это та же идея, что и у политики, без полей, которые здесь не нужны. Владелец клиента ничего не задает о том, сколько тот может брать у API: это делает ресурс.
| Где | Как задать | Когда проверяется | Корзина |
|---|---|---|---|
| Endpoint ресурса | Свернутая панель Rate limit в Edit endpoints или rateLimit в endpoint у POST и PUT /configuration/resources | На R3, для этого endpoint | На ключ и зарегистрированный endpoint |
| Клиент из аудитории ресурса | Свернутый блок Rate limits в Edit audience или audienceRateLimits у ресурса в POST и PUT /configuration/resources | На R3, для вызовов этого клиента к этому ресурсу, на любом endpoint | На ключ, для этого клиента на этом ресурсе |
| Поле | Обычное значение | Смысл |
|---|---|---|
limits | нет | От 1 до 8 окон, в каждом limit запросов за windowSeconds, как в политике. |
burst | 1 | Как в политике. Не больше наименьшего limit. |
by | none | none: одна корзина на endpoint или на клиента на ресурсе. subject: по одной на пользователя. client: по одной на OAuth-клиента, только на endpoint; для клиента аудитории это значило бы то же, что none, поэтому central его отклоняет. |
mode | shadow | off, shadow или enforce, как в политике. |
cost всегда равен 1. central применяет правила политики и отвечает 400 без тела, если правило нарушено (он же отклоняет клиента, которого нет в аудитории, и клиента, указанного дважды); причина — в журнале central. Отправляйте burst, by и mode вместе с limits: неявных значений нет, а audienceRateLimits обязателен при создании ресурса ([], если лимитов нет). Отсутствующий rateLimit — лимита нет. Endpoint записывается целиком, поэтому при правке отправьте его rateLimit снова, иначе лимит будет снят. audienceRateLimits — список { "clientId", "rateLimit" }: у PUT он заменяет список целиком, отсутствующее поле оставляет его как есть, а клиент, вышедший из аудитории, теряет свою запись.
{
"resourceId": "orders-api",
"audienceRateLimits": [
{
"clientId": "acme-web",
"rateLimit": {
"limits": [{ "limit": 600, "windowSeconds": 60 }],
"burst": 50,
"by": "subject",
"mode": "shadow"
}
}
]
}
Объявленный лимит дополняет политики edge, а не заменяет их. Запрос должен пройти каждый лимит, который к нему относится, поэтому действует наименьший. Объявленный лимит делится на replicas, как и остальные. В метриках он называется политикой resource-endpoint или resource-client, но никогда не именем endpoint, ресурса или клиента, поэтому набор меток остается закрытым, а отказ называет ту же политику. Эти два имени зарезервированы: политика edge их занять не может. Каждое объявление хранит до 10 000 ключей.


Edge и central, которые не знают эти поля, их игнорируют, поэтому обновлять можно в любом порядке. Начинайте каждый объявленный лимит с shadow, читайте would_limit, как в разделе Как включать, затем переключайте на enforce.
Поды edge
Каждый под считает сам: общего счетчика и сетевого вызова на пути запроса нет. Задайте replicas равным числу подов edge, и каждый под применяет limit / replicas каждого окна (и burst / replicas с округлением вверх, не меньше cost), так что edge в целом пропускает примерно записанный вами лимит. Точность равна равномерности вашего балансировщика. При значении 1 лимит действует на каждый под, и N подов пропускают в N раз больше. Меняйте его, когда меняется число подов, или поручите счет edge: задайте в его файле конфигурации replicas-from = "dns:<name>", где <name> — headless Service, перечисляющий готовые поды. Helm-чарт Kubernetes поставляет такой Service, <fullname>-edge-headless, и кладет его имя в переменную окружения VERSOLA_EDGE_HEADLESS_SERVICE подов edge, так что в файле конфигурации можно написать replicas-from = "dns:"${VERSOLA_EDGE_HEADLESS_SERVICE}. Edge разрешает его имя каждые 10 секунд, при сбое запроса оставляет последнее число, а до первого ответа берет replicas политики. Kubernetes сам по себе ничто не определяет: без ключа берется replicas. Число в действии и его источник (dns или policy) показывает edge_rate_limit_replicas{source}.
Какой адрес
Адрес вызывающего берется из файла конфигурации edge:
| Ключ | По умолчанию | Значение |
|---|---|---|
client-ip | socket | socket — адрес собеседника по соединению. forwarded читает заголовок из client-ip-header. |
client-ip-header | X-Real-IP | Заголовок, в который прокси кладет адрес клиента. |
Ставьте forwarded только когда edge доступен исключительно через прокси, который выставляет заголовок (сеть compose, сетевая политика чарта): иначе любой может записать заголовок и выбрать себе корзину. Отсутствующий заголовок или не-IP значение откатываются к адресу соединения. IPv4 считается по адресу, IPv6 по /64. За прокси с socket каждый вызывающий — это прокси, и лимит по адресу посчитал бы весь мир одним вызывающим; edge_client_address_total{source} (socket, forwarded, forwarded_missing) показывает, что увидел edge. Внутри кластера Kubernetes вызов через Service от пода на другой ноде приходит на edge с адресом ноды, поэтому за kube-proxy поды одной ноды делят один адрес; поставьте перед edge прокси, который выставляет заголовок, и используйте forwarded. Передает ли облачный балансировщик адрес клиента подам, зависит от платформы: проверьте это, прежде чем включать политику по адресу.
Что видит отказанный вызывающий
429 Too Many Requests с:
Retry-After: ожидание в целых секундах с округлением вверх (самое долгое среди отказавших политик);RateLimit-Policy: "<name>";q=<quota>;w=<window seconds>иRateLimit: "<name>";r=0;t=<wait>, как в черновике IETFdraft-ietf-httpapi-ratelimit-headers. Черновик менялся между версиями, поэтому эти поля отправляются только при отказе, никогда в успешном ответе;- без тела. Политика названа в
RateLimit-Policy, ключ — никогда.
Клиенту стоит подождать не меньше Retry-After, добавить случайную добавку и повторять ограниченное число раз. Отказ дешев: он считается, а строка в журнале следует правилам успешных запросов (пишется, если медленный или попал в выборку), поэтому поток отказов не превращается в поток строк журнала. Оператору вместо этого доступна одна строка в минуту на каждую политику, которая что-то отказала: WARN, если политика применяла отказ, и INFO, если только считала в shadow, с именем политики и счетчиками, без ключей.
Как включать
- Добавьте рекомендуемый набор и оставьте его в
shadowминимум на неделю реального трафика с пиком. - Читайте
edge_rate_limit_decisions_total{policy,result="would_limit"}. Около нуля — политика не мешает. Многоwould_limitдля честных вызывающих — число слишком мало для них: сначала поднимите его. - Задайте
replicasравным числу подов edge. - Переключите одну политику в
enforceна одном edge, следите заlimitedи ошибками ваших клиентов через пик, затем сделайте следующую.shadowилиoff— это выключатель. - Политику по адресу включайте последней, когда
edge_client_address_totalпоказываетforwarded, а неsocketза прокси.
Метрики
| Метрика | Что показывает |
|---|---|
edge_rate_limit_decisions_total{policy,result} | allowed, limited (отказано) и would_limit (что отказал бы shadow) по политикам. |
edge_rate_limit_tracked_keys{policy} | Ключи в памяти по политике. |
edge_rate_limit_overflow_hits_total{policy} | Новые ключи сверх лимита 100 000 на политику, которые делят одну корзину; рост означает поток разных ключей. |
edge_client_address_total{source} | Как edge нашел адрес вызывающего. |
edge_rate_limit_replicas{source} | На сколько подов каждый под делит лимит и откуда это число (dns или policy). |
Ключ никогда не бывает меткой метрики. На доске Grafana Versola есть строка лимитов запросов для edge и auth (отказано и было бы отказано по политикам, доля вызовов, которые отказывает политика, число ключей, число подов, на которое делится лимит, откуда берется адрес) и строка защиты от перегрузки у каждого сервиса.
Лимиты в auth
auth, доступный без edge, то есть не через mutual TLS, открыт напрямую, поэтому у него есть собственные лимиты, они задаются блоком limits в его файле конфигурации. Каждый ключ необязателен, блок может отсутствовать.
| Лимит | Применяется к | Считает | По умолчанию |
|---|---|---|---|
| Неудачная аутентификация клиента | POST /token, /par, /introspect, /revoke | Только неудачи адреса (401 или invalid_client) | enforce, 20 в минуту, всплески по 10 |
| Вход | /authorize | Каждый вызов по адресу | shadow, 600 в минуту, всплески по 100 |
Адресу, превысившему лимит неудачной аутентификации, отказывают с 429, Retry-After и полями RateLimit, без тела, до чтения его следующего запроса, какими бы верными ни были учетные данные в нем. Клиент, который аутентифицируется, не ограничивается никогда. Listener с mutual TLS так не ограничивается: сертификат нельзя подобрать.
limits {
failed-mode = "enforce" # off | shadow | enforce
failed-per-minute = 20
failed-burst = 10
entry-mode = "shadow"
entry-per-minute = 600
entry-burst = 100
client-ip = "socket" # socket | forwarded
ip-header = "X-Real-IP"
}
Значение вне диапазона останавливает запуск. client-ip работает так же, как у edge. Состояние хранится в каждом поде. Деплой, который намеренно массово проваливает аутентификацию клиентов с одного адреса (нагрузочная кампания, набор тестов), должен на время прогона поставить failed-mode в shadow. Метрики: auth_rate_limit_decisions_total{policy,result} (политики failed-client-auth-per-address и authorize-per-address), auth_rate_limit_tracked_keys и auth_rate_limit_overflow_hits_total.
Чего нет
- Ужесточения лимитов на edge партнера (режим partner не реализован).
- Счетчика, общего для нескольких подов; для ровного деления используйте
replicas.
Что дальше
- Защита от перегрузки — что делает edge, когда к нему приходит больше запросов, чем он обслужит
- Edges — экран, к которому эта страница добавляет карточку
- Конфигурация edge — файл, в котором живет
client-ip