Миграция с 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 ← правила маршрутизации (хост, путь, заголовки → сервис)
Ключевые отличия:
| Ingress | Gateway API | |
|---|---|---|
| Routing | host + path | host + 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-ресурсы.
План миграции
- Установить реализацию (Envoy Gateway) — один Helm chart в
infrastructure - Создать
GatewayClassиGateway-ресурс (один на namespace) с TLS-конфигурацией - Перенести каждый блок из
envoy.yamlв отдельныйHTTPRoute-файл вpublic-gateway/templates/ - Проверить маршрутизацию на dev-окружении
- Удалить
public-gatewayиweb-client-api-gatewayкак отдельные Deployment/Service - Заменить логику maintenance на
weight-подход в Helm-шаблонах
Главный выигрыш
Из монолитного нечитаемого envoy.yaml на 790 строк получаются ~20 маленьких понятных файлов по одному на брендзону, каждый из которых можно менять независимо. Добавить новую брендзону = создать один новый HTTPRoute-файл.