безопасно перевести внешний трафик кластера с Ingress на Gateway API

Kubernetes Gateway API: паттерны миграции с Ingress

14 минут

Ingress смешивает маршрутизацию, TLS и политики в одном объекте и опирается на аннотации контроллера. Gateway API разделяет GatewayClass, Gateway и HTTPRoute по ролям — мигрируйте параллельно с ingress2gateway, ReferenceGrant и RequestMirror, и только потом переключайте DNS.

Почему Ingress становится узким местом платформы

Ingress всё ещё широко используют, но его модель мешает масштабировать владение. Один объект смешивает внешние слушатели, терминацию TLS и прикладную маршрутизацию. Нетривиальное поведение — лимиты запросов, правка заголовков, веса canary — обычно живёт в аннотациях контроллера, поэтому переход с nginx на другую реализацию — это переписывание. Ingress рассчитан на HTTP и HTTPS; TCP, UDP и нормальный gRPC требуют отдельных CRD. Команда платформы и команды приложений правят одни и те же объекты — отсюда трения RBAC. SIG-Network считает Gateway API преемником: иерархия ресурсов, переносимые возможности маршрутизации и контракт без привязки к одному контроллеру. Миграция — не поиск и замена: сначала сосуществование, конвертация маршрутов, проверка паритета, затем DNS.

Владение: GatewayClass, Gateway и HTTPRoute

GatewayClass выбирает реализацию контроллера, по смыслу как StorageClass; им владеют администраторы платформы. Gateway — край инфраструктуры: слушатели, порты, сертификаты TLS и то, каким namespace разрешено цеплять маршруты; этим владеет платформа или инфраструктура. HTTPRoute (и соседние типы вроде GRPCRoute) задаёт хосты, совпадения, фильтры и взвешенные backendRefs; ими владеют команды приложений и цепляют через parentRefs. Держите один Gateway на окружение или зону доступности, а не на каждый сервис. Ограничивайте слушатели селекторами allowedRoutes по меткам namespace. Ссылки на Secret или Service из другого namespace требуют явного ReferenceGrant в целевом namespace.

Паттерн миграции: параллельная работа

Установите CRD Gateway API и совместимый контроллер — Envoy Gateway, Istio, Cilium или nginx Gateway Fabric. Создайте GatewayClass и Gateway с теми же портами и сертификатами, что у текущего Ingress. Переведите правила Ingress в HTTPRoute с parentRef на новый Gateway, пока Ingress ещё обслуживает продакшен. Выделите второй VIP или хост для Gateway, проверьте статусы Accepted и Programmed, сравните задержку и ошибки, затем сдвиньте вес DNS или балансировщика. Удаляйте Ingress только когда путь через Gateway уже основной. Не ждите, что веса HTTPRoute сами разделят трафик между Ingress и Gateway — они балансируют backend за одним parent; переключение делают DNS, CDN или балансировщик.

YAML · GatewayClass и production Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: production
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production
  namespace: infra
spec:
  gatewayClassName: production
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              shared-gateway: "true"
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls
            kind: Secret
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              shared-gateway: "true"
YAML · HTTPRoute с весами backend и матчем по заголовку
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-service
  namespace: app-team-a
spec:
  parentRefs:
    - name: production
      namespace: infra
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: api-v1
          port: 8080
          weight: 90
        - name: api-v2
          port: 8080
          weight: 10
    - matches:
        - headers:
            - name: X-Debug
              value: "true"
      backendRefs:
        - name: api-debug
          port: 8080

Конвертация через ingress2gateway и миграция по namespace

Инструмент SIG-Network ingress2gateway читает объекты Ingress и выдаёт YAML Gateway и HTTPRoute. Проверьте результат: пути и хосты обычно переводятся чисто; аннотации контроллера часто нужно вручную перенести в фильтры HTTPRoute или политики реализации. Мигрируйте по одному namespace приложений — повесьте метку доступа к Gateway, примените маршруты, проверьте статус httproute, затем снимите Ingress этого namespace. Храните сконвертированные манифесты в Git с тем же продвижением, что и остальной конфиг платформы.

Bash · генерация манифестов Gateway API через ingress2gateway
#!/usr/bin/env bash
set -euo pipefail
# Install from upstream releases or: go install sigs.k8s.io/ingress2gateway@latest
ingress2gateway print --namespace app-team-a > ./gateway-manifests/app-team-a.yaml
kubectl get ingress -n app-team-a -o name
kubectl apply --dry-run=client -f ./gateway-manifests/app-team-a.yaml
kubectl get httproute -n app-team-a
kubectl describe gateway production -n infra

Аннотации → фильтры, политики и ReferenceGrant

До переключения проведите аудит всех аннотаций Ingress. Разложите их: нативные фильтры HTTPRoute (redirect, правка заголовков, mirror, URL rewrite), политики контроллера (BackendTrafficPolicy или ClientTrafficPolicy у Envoy Gateway) и то, что всё ещё требует расширения. Сначала берите переносимые фильтры. RequestMirror шлёт копию продакшен-запросов на backend за Gateway, пока Ingress остаётся основным — сравнивайте логи и метрики до смены DNS. Если Gateway ссылается на TLS Secret в другом namespace или маршрут целится в чужой Service, создайте ReferenceGrant в namespace владельца.

YAML · фильтры HTTPRoute и RequestMirror
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-shadow
  namespace: app-team-a
spec:
  parentRefs:
    - name: production
      namespace: infra
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      filters:
        - type: RequestHeaderModifier
          requestHeaderModifier:
            add:
              - name: X-Edge
                value: gateway-api
            remove:
              - X-Internal-Debug
        - type: RequestMirror
          requestMirror:
            backendRef:
              name: api-shadow
              port: 8080
      backendRefs:
        - name: api-v1
          port: 8080
YAML · ReferenceGrant для Service из другого namespace
apiVersion: gateway.networking.k8s.io/v1
kind: ReferenceGrant
metadata:
  name: allow-app-routes
  namespace: shared-services
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: app-team-a
  to:
    - group: ""
      kind: Service
YAML · BackendTrafficPolicy Envoy Gateway с retry
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: api-resilience
  namespace: app-team-a
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: api-service
  retry:
    numRetries: 3
    retryOn:
      triggers:
        - connect-failure
        - retriable-status-codes
      httpStatusCodes:
        - 502
        - 503

TCP и gRPC за пределами HTTP Ingress

Слушатели Gateway могут говорить TCP и принимать TCPRoute из experimental-канала, если контроллер это поддерживает — удобно для брокеров или прокси к БД, где раньше нужны были отдельные CRD. Перед выносом баз на общий Gateway продумайте частную сеть и жёсткую аутентификацию. GRPCRoute — штатный путь для сопоставления по gRPC service и method, когда канал CRD его включает. Держите протокольные маршруты на отдельных слушателях с allowedRoutes kinds только под TCPRoute или GRPCRoute, чтобы HTTP-команды не цепляли неверный тип.

YAML · TCP-слушатель и TCPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: tcp-gateway
  namespace: infra
spec:
  gatewayClassName: production
  listeners:
    - name: redis
      protocol: TCP
      port: 6379
      allowedRoutes:
        kinds:
          - kind: TCPRoute
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute
metadata:
  name: redis
  namespace: cache
spec:
  parentRefs:
    - name: tcp-gateway
      namespace: infra
      sectionName: redis
  rules:
    - backendRefs:
        - name: redis-primary
          port: 6379

Практика устойчивого переключения

Фиксируйте версии CRD Gateway API и обновляйте осознанно — стандартный и experimental каналы движутся с разной скоростью. Считайте Gateway и HTTPRoute GitOps-конфигом с CODEOWNERS по модели владения. Во время сосуществования держите парные дашборды с одними SLO для Ingress и Gateway. Когда namespace зелёный на Gateway, сразу удаляйте его Ingress — иначе два источника истины. Опишите, кто может менять GatewayClass, слушатели Gateway и прикладные маршруты. Миграция удалась, когда яснее владение, меньше аннотаций и управление трафиком стало переносимым — а не когда в YAML просто сменился kind.

Взвешенные backend и canary на HTTPRoute сочетаются с гайдом по progressive delivery: canary и feature flags.

Когда маршруты лежат в Git, держите оверлеи в порядке по гайду по структуре GitOps-репозиториев для нескольких окружений.