Лимиты запросов — Versola Docs
/docs
До 1.00.7.5versola.ioGitHub

Лимиты запросов

Как edge и auth ограничивают, сколько может присылать каждый клиент, пользователь, адрес или сертификат, политики и рекомендуемый стартовый набор, где проверяется каждая, что видит отказанный вызывающий и как включать лимиты.

Лимит запросов отказывает конкретному вызывающему, который прислал больше своей доли. Он настраивается для каждого edge из central, поэтому менять его можно без передеплоя, а каждая политика начинается в режиме, который только считает. Нужен зарегистрированный edge и оператор с правом edges:manage; с edges:read политики видны, но не меняются. У auth есть свои лимиты для канала без mutual TLS, они описаны ниже.

Лимиты запросов и защита от перегрузки

Они отвечают на разные вопросы и никогда не делят счетчик.

Лимиты запросовЗащита от перегрузки
ВопросЭтот вызывающий превысил свою долю?Может ли этот под принять еще работу прямо сейчас?
ОтказываетТому, кто превысил свою корзину; остальные не затронуты.Любому, кто пришел сверх возможностей пода, по приоритету.
ЛимитЧисло, которое вы задаете в политике.Вычисляется по времени вызовов и задержке исполнителя.
Ответ429 с Retry-After503 с 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каждого пользователя на каждом endpoint120 в минуту, всплески по 30
per-addressкаждый адрес: неудачные и неаутентифицированные попытки и входы600 в минуту, всплески по 100
per-certificateкаждый клиентский сертификат на listener с mutual TLS60 000 в минуту, всплески по 2 000

Числа щедрые намеренно: они ловят вышедшего из-под контроля клиента, пользователя или адрес, а не занятого. Они действуют на каждый под edge, пока вы не задали число подов (см. Поды edge). per-address имеет смысл, только когда edge видит настоящий адрес клиента, см. Какой адрес.

Политики

Политика — набор полей. Набор заменяется целиком. У API нет неявных значений: отправляйте каждое поле, с обычным значением, если предпочтений нет (консоль так и делает).

ПолеПо умолчаниюЗначение
nameнетОт 1 до 48 символов: строчная буква, затем строчные буквы, цифры, _ или -. Уникально в пределах edge. Подписывает метрики и отказ.
byнетПо чему считается корзина: client, subject, session, jkt (ключ DPoP), ip или certificate. Пусто — одна корзина на весь scope. ip и certificate используются только поодиночке.
scopeedge-wideedge-wide, resource или endpoint. Более узкий scope ведет корзину на ключ и ресурс или на ключ и зарегистрированный endpoint.
limitsнетОт 1 до 8 окон, каждое: limit запросов за windowSeconds. Должны пройти все окна. Окна одной политики различны.
burst1Сколько запросов может прийти разом. Не больше наименьшего limit. 1 — строгие интервалы.
cost1Сколько запрос забирает из корзины. Не больше burst.
modeshadowoff игнорирует политику, 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 TLSSHA-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 добавляет пустую.

Карточка перечисляет каждую политику с режимом, окнами и тем, по чему она считает
Панель edge с карточкой Rate limits: три политики, число подов edge и кнопка Remove all policies
Пустой редактор: рекомендуемый набор в один клик
Панель Edit rate limits без политик: пояснение, поле Edge pods и кнопка Add recommended policies

Редактор проверяет те же правила, что и 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, как в политике.
burst1Как в политике. Не больше наименьшего limit.
bynonenone: одна корзина на endpoint или на клиента на ресурсе. subject: по одной на пользователя. client: по одной на OAuth-клиента, только на endpoint; для клиента аудитории это значило бы то же, что none, поэтому central его отклоняет.
modeshadowoff, 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 ключей.

Панель Rate limit одного endpoint: режим, по чему считать, окна и burst
Панель Rate limit у endpoint: режим enforce, счет по пользователю, 30 запросов за 60 секунд, burst 5.
Тот же редактор для клиента из аудитории, по секции на клиента
Блок Rate limits страницы Edit audience с раскрытым клиентом: режим shadow, счет по пользователю, 600 запросов за 60 секунд, burst 50.

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-ipsocketsocket — адрес собеседника по соединению. forwarded читает заголовок из client-ip-header.
client-ip-headerX-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>, как в черновике IETF draft-ietf-httpapi-ratelimit-headers. Черновик менялся между версиями, поэтому эти поля отправляются только при отказе, никогда в успешном ответе;
  • без тела. Политика названа в RateLimit-Policy, ключ — никогда.

Клиенту стоит подождать не меньше Retry-After, добавить случайную добавку и повторять ограниченное число раз. Отказ дешев: он считается, а строка в журнале следует правилам успешных запросов (пишется, если медленный или попал в выборку), поэтому поток отказов не превращается в поток строк журнала. Оператору вместо этого доступна одна строка в минуту на каждую политику, которая что-то отказала: WARN, если политика применяла отказ, и INFO, если только считала в shadow, с именем политики и счетчиками, без ключей.

Как включать

  1. Добавьте рекомендуемый набор и оставьте его в shadow минимум на неделю реального трафика с пиком.
  2. Читайте edge_rate_limit_decisions_total{policy,result="would_limit"}. Около нуля — политика не мешает. Много would_limit для честных вызывающих — число слишком мало для них: сначала поднимите его.
  3. Задайте replicas равным числу подов edge.
  4. Переключите одну политику в enforce на одном edge, следите за limited и ошибками ваших клиентов через пик, затем сделайте следующую. shadow или off — это выключатель.
  5. Политику по адресу включайте последней, когда 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.

Что дальше