Перейти к основному содержимому

Миграция с Ingress Controller на Gateway API

Ingress Controller vs Gateway API

Чем плох Ingress

Ingress — это старый API (networking.k8s.io/v1), созданный с минимальным функционалом: хост + путь → сервис. Всё остальное (TLS, таймауты, заголовки, редиректы, auth) реализуется через аннотации — строки вида nginx.ingress.kubernetes.io/configuration-snippet: |. Это:

  • не переносимо (аннотации nginx не работают в Contour, Istio и др.)
  • небезопасно (configuration-snippet позволяет инъектировать произвольный nginx-конфиг, поэтому в новых версиях он отключён по умолчанию)
  • нет типизации — ошибку в аннотации узнаешь только в рантайме

Что такое Gateway API

Gateway API (gateway.networking.k8s.io) — это новый стандарт, поддержанный всеми крупными реализациями. Он состоит из трёх ресурсов:

GatewayClass  ←  кто провайдер (nginx, envoy, istio...)
└─ Gateway ← точка входа (IP, порт, TLS-сертификат)
└─ HTTPRoute ← правила маршрутизации (хост, путь, заголовки → сервис)

Ключевые отличия:

IngressGateway API
Routinghost + pathhost + path + headers + method + query
Traffic weightнетесть (canary, A/B)
Header manipulationаннотацииRequestHeaderModifier / ResponseHeaderModifier в spec
Redirects/Rewritesаннотациивстроено в spec
TLSв Ingress specв Gateway spec
Кто управляетвсё в одном ресурсеразделено по ролям
Переносимостьнетда

Применительно к текущей архитектуре Media

Текущая схема (три уровня)

Internet

Nginx Ingress Controller ← TLS termination, инжектирует X_APP_STATE заголовок

public-gateway (Envoy pod) ← маршрутизация по хосту/пути + maintenance check

web-client-[brandzone] ← фронтенды по брендзонам
↓ (для /public)
web-client-api-gateway (Envoy pod) ← API routing

public-gateway/envoy.yaml — монолитный файл ~790 строк, где каждая брендзона (~20 штук) описана дублирующимся блоком с maintenance-маршрутом. Добавить брендзону = дописать ещё один блок из 30+ строк.

Целевая схема с Gateway API (два уровня)

Internet

Gateway (Envoy Gateway / nginx-gateway-fabric) ← TLS + маршрутизация

web-client-[brandzone] / web-client-api-gateway ← напрямую

public-gateway и web-client-api-gateway как отдельные Envoy-поды исчезают. Их логика переезжает в HTTPRoute-ресурсы — по одному файлу на брендзону.

Сравнение: брендзона mysport

Сейчас (фрагмент envoy.yaml + блок в clusters):

virtual_hosts:
- name: mysport
domains: ["mysport.rr-dev.ru", "mysport.photo"]
routes:
- match: { prefix: "/public" }
route: { cluster: web-client-api-gateway }
- match:
prefix: "/"
headers: [{name: "X_APP_STATE", exact_match: "maintenance"}]
route: { cluster: maintenance }
- match: { prefix: "/" }
route: { cluster: web-client-mysport }
# + отдельный блок в clusters на каждый сервис

С Gateway API (HTTPRoute, один файл на брендзону):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mysport
spec:
parentRefs:
- name: public-gateway
hostnames:
- "mysport.rr-dev.ru"
- "mysport.photo"
rules:
- matches:
- path:
type: PathPrefix
value: /public
backendRefs:
- name: web-client-api-gateway
port: 80
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-client-mysport
port: 80

Техобслуживание (maintenance mode)

Текущий подход (хрупкий)

Трёхходовка: значение appState в Helm → аннотация nginx proxy_set_header X_APP_STATE "maintenance" → Envoy проверяет заголовок и маршрутизирует на maintenance.

Вариант 1 — weight (рекомендуется)

Нативный способ Gateway API. В values.yaml один флаг:

appState: normal  # или: maintenance

Helm-шаблон генерирует HTTPRoute с весами:

rules:
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: web-client-mysport
port: 80
weight: {{ if eq .Values.appState "maintenance" }}0{{ else }}100{{ end }}
- name: maintenance
port: 80
weight: {{ if eq .Values.appState "maintenance" }}100{{ else }}0{{ end }}

Изменить состояние = поменять одно значение и задеплоить чарт.

Вариант 2 — отдельный HTTPRoute-оверрайд

Держать отдельный HTTPRoute с maintenance-override, который catch-all для всех хостов Gateway. Деплоится только когда нужен:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: maintenance-override
spec:
parentRefs:
- name: public-gateway
# без hostnames = catch-all для всех хостов этого Gateway
rules:
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: maintenance
port: 80

Включить техобслуживание = задеплоить этот один файл. Выключить = удалить его (kubectl delete httproute maintenance-override).

Вариант 3 — редирект на страницу обслуживания

rules:
- backendRefs: []
filters:
- type: RequestRedirect
requestRedirect:
hostname: maintenance.mysport.photo
statusCode: 302

Рекомендуемые реализации

РеализацияПлюсы для данного кейса
Envoy GatewayРодная замена текущим Envoy-подам, тот же движок, богатые ExtensionPolicy
nginx-gateway-fabricЗнакомая экосистема, если уже используется nginx-ingress
IstioЕсли нужен service mesh, но значительно тяжелее

Envoy Gateway — наиболее логичная замена: движок тот же (Envoy), но вместо монолитного YAML-конфига используются стандартные Kubernetes-ресурсы.


План миграции

  1. Установить реализацию (Envoy Gateway) — один Helm chart в infrastructure
  2. Создать GatewayClass и Gateway-ресурс (один на namespace) с TLS-конфигурацией
  3. Перенести каждый блок из envoy.yaml в отдельный HTTPRoute-файл в public-gateway/templates/
  4. Проверить маршрутизацию на dev-окружении
  5. Удалить public-gateway и web-client-api-gateway как отдельные Deployment/Service
  6. Заменить логику maintenance на weight-подход в Helm-шаблонах

Главный выигрыш

Из монолитного нечитаемого envoy.yaml на 790 строк получаются ~20 маленьких понятных файлов по одному на брендзону, каждый из которых можно менять независимо. Добавить новую брендзону = создать один новый HTTPRoute-файл.