Rate limiting и JWT-маршрутизация в APISIX

Published: 2026-03-03

Главное преимущество Apache APISIX перед ingress-контроллерами на базе nginx — система плагинов. Там, где nginx требует Lua-модулей и сложной правки конфигурации, APISIX маршрутизирует, ограничивает и трансформирует запросы несколькими строками YAML через Kubernetes CRD. Вот как мы используем это на практике.


ApisixRoute: базовая единица

yamlapiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: api-route
  namespace: app
spec:
  http:
    - name: main
      match:
        hosts:
          - api.example.com
        paths:
          - /*
      backends:
        - serviceName: my-api-service
          servicePort: 80
      plugins:
        - name: redirect
          enable: true
          config:
            http_to_https: true

Маршруты задаются на уровне spec.http. Несколько записей в одном ApisixRoute или несколько ApisixRoute на один хост — оба варианта работают.


Rate limiting по JWT-клейму agency-id

Наш паттерн: извлекаем клейм из JWT и берём его как ключ rate limit — так разные клиенты получают разные лимиты.

yamlplugins:
  # 1. Глобальный лимит (все клиенты вместе)
  - name: limit-count
    enable: true
    config:
      count: 17
      time_window: 1
      rejected_code: 429
      key_type: constant

  # 2. Извлечение agency-id из JWT через serverless-функцию
  - name: serverless-pre-function
    enable: true
    config:
      phase: rewrite
      functions:
        - |-
          return function(conf, ctx)
            local core = require("apisix.core")
            local jwt  = require("resty.jwt")
            local token = core.request.header(ctx, "Authorization")
            if token then
              local _, _, t = string.find(token, "Bearer%s+(.+)")
              if t then
                local obj = jwt:load_jwt(t)
                if obj.valid then
                  local agency = obj.payload["agency-id"]
                  core.request.set_header(ctx, "X-Agency-Id", agency)
                end
              end
            end
          end

Lua-функция выполняется в фазе rewrite — до проксирования запроса. Она читает JWT, извлекает agency-id и выставляет заголовок, по которому работают последующие плагины.

Лимит по agency

yaml  - name: limit-count
    enable: true
    config:
      count: 100
      time_window: 60
      rejected_code: 429
      key_type: var
      key: http_x_agency_id
      group: agency-rate-limit

Каждое уникальное значение X-Agency-Id получает свой счётчик. VIP-агентства можно исключить, проверив значение в Lua-функции.


Маршрутизация по regex-пути

yamlspec:
  http:
    - name: airshopping
      match:
        hosts: [api.example.com]
        paths: [/*]
        exprs:
          - subject:
              scope: Path
            op: RegexMatchCaseInsensitive
            value: "^/api/order/(airshopping|offerprice)$"
      backends:
        - serviceName: search-service
          servicePort: 80

    - name: booking
      match:
        hosts: [api.example.com]
        paths: [/api/order/createorder]
      backends:
        - serviceName: booking-service
          servicePort: 80

    - name: fallback
      match:
        hosts: [api.example.com]
        paths: [/*]
        priority: -1
      backends:
        - serviceName: default-service
          servicePort: 80

exprs — предикатное сопоставление по пути, заголовкам или query-параметрам. priority определяет приоритет при совпадении нескольких правил.


ApisixUpstream: healthcheck и балансировка

yamlapiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  name: my-api-upstream
  namespace: app
spec:
  loadbalancer:
    type: roundrobin
  healthCheck:
    active:
      type: http
      httpPath: /healthz
      interval: 10
      timeout: 2
      successThreshold: 1
      failureThreshold: 3
      httpStatusCode: [200]
  externalNodes:
    - type: Service
      name: my-api-service
      port: 80
      weight: 100

Активный healthcheck опрашивает /healthz каждые 10 секунд. После трёх неудач подряд upstream помечается как нездоровый. APISIX обрабатывает это в data plane без участия Kubernetes.


Canary-деплой через traffic splitting

yamlbackends:
  - serviceName: api-stable
    servicePort: 80
    weight: 90
  - serviceName: api-canary
    servicePort: 80
    weight: 10

10% трафика уходит в canary. Никаких дополнительных инструментов — только соотношение весов. Регулируйте через GitOps или kubectl patch.


Key-auth плагин для доступа по API-ключу

yamlplugins:
  - name: key-auth
    enable: true
    config:
      header: "X-API-Key"
      query: "apikey"

Создайте ApisixConsumer с допустимым ключом:

yamlapiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
  name: my-client
  namespace: app
spec:
  authParameter:
    keyAuth:
      value:
        key: "my-secret-api-key"

Запросы без корректного X-API-Key получают 401 на шлюзе — до upstream неаутентифицированный трафик не доходит.


Трансформация запросов

yamlplugins:
  - name: proxy-rewrite
    enable: true
    config:
      headers:
        remove:
          - Authorization
          - X-Agency-Id
        add:
          X-Forwarded-Service: "api-gateway"
      regex_uri:
        - "^/api/v1/(.*)"
        - "/internal/$1"

Отладка маршрутов

bashkubectl get apisixroute -n app
kubectl describe apisixroute api-route -n app

# Маршруты через admin API
kubectl port-forward -n ingress-apisix svc/apisix-admin 9080:9080
curl -s http://localhost:9080/apisix/admin/routes | jq '[.list[] | {name: .value.name, uri: .value.uri}]'