У тебя в кластере крутится десяток сервисов. Снаружи им нужен один вход: api.example.com, shop.example.com, grafana.example.com - все на 443, с валидным TLS, с маршрутизацией по хосту и пути. Поднимать на каждый сервис по облачному балансировщику - дорого и глупо: пять сервисов это пять внешних IP, пять счетов и зоопарк правок DNS. Городить свой nginx на отдельной VM, который проксирует в NodePort - значит вернуться в 2015 год и руками держать конфиг в актуальном состоянии.
Kubernetes придумал на это два слоя абстракции. Сначала был Ingress - декларативное правило "хост плюс путь -> Service". Потом, когда стало ясно, что Ingress слишком беден и его раздули аннотациями до неузнаваемости, появился Gateway API - его официальный преемник. И тут важная новость 2026 года, которую ты обязан знать: самый популярный контроллер ingress-nginx ретирован (best-effort поддержка закончилась в марте 2026), а его планируемая замена InGate так и не взлетела и тоже свёрнута. Поэтому разговор про kubernetes ingress сегодня - это во многом разговор про то, куда с него мигрировать. Давай по порядку: что такое Ingress под капотом, почему ingress nginx был стандартом, и почему профессиональный выбор на новых кластерах - Gateway API.

Механика Ingress: правило и контроллер - это две разные вещи
Главное, что путает новичков: ресурс Ingress сам по себе НЕ принимает трафик. Это просто запись в etcd, набор правил. Чтобы она ожила, в кластере должен крутиться ingress-контроллер - реальный под (обычно Deployment или DaemonSet) с прокси внутри (nginx, Envoy, HAProxy, Traefik), который следит за объектами Ingress через API-сервер и на лету перестраивает свой конфиг. Создал Ingress без контроллера - он просто висит и ничего не делает.
Снаружи на контроллер обычно смотрит Service типа LoadBalancer: облако (Yandex Managed Kubernetes, VK Cloud) выдаёт ему один внешний IP и один сетевой балансировщик L4, а дальше уже под-контроллер раскидывает HTTP по правилам L7. То есть один внешний IP обслуживает все твои домены. Вот минимальный реальный Ingress:
Код: Выделить всё
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: prod
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-web
port:
number: 80
- ingressClassName - какой контроллер обслуживает это правило. В кластере их может быть несколько (внутренний и внешний, например), и каждый берёт только "свои" Ingress. Раньше класс задавали аннотацией kubernetes.io/ingress.class - это устаревший способ, на новых версиях используй поле.
- pathType - Prefix (совпадение по префиксу пути с учётом сегментов), Exact (точное совпадение) или ImplementationSpecific (как решит контроллер). Тут классические грабли: Prefix /foo матчит /foo и /foo/bar, но НЕ /foobar - матчинг идёт по сегментам, а не по символам.
- host - маршрутизация по Host-заголовку. Без host правило ловит любой домен, что почти всегда не то, что тебе нужно.
Код: Выделить всё
$ kubectl get ingress -n prod
NAME CLASS HOSTS ADDRESS PORTS AGE
shop nginx shop.example.com 84.201.xxx.xx 80, 443 12m
TLS и cert-manager: автоматический kubernetes tls без боли
Терминировать HTTPS руками, обновляя сертификаты раз в три месяца через cron - путь к ночному инциденту "сертификат протух". Стандарт индустрии - cert-manager: оператор, который выпускает и продлевает сертификаты автоматически, в том числе бесплатные от Let's Encrypt по протоколу ACME.
Схема такая. Ты ставишь cert-manager, создаёшь объект Issuer или ClusterIssuer (кто и как выдаёт сертификаты), вешаешь на Ingress аннотацию - и cert-manager сам проходит ACME-челлендж, кладёт готовый сертификат в Secret, а потом следит за сроком и перевыпускает за 30 дней до истечения. Никакого cron.
Код: Выделить всё
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Код: Выделить всё
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- shop.example.com
secretName: shop-tls
Код: Выделить всё
$ kubectl get certificate -n prod
NAME READY SECRET AGE
shop-tls True shop-tls 3m
$ kubectl get certificaterequest,order,challenge -n prod
Аннотации: где Ingress начал трещать по швам
Базовый Ingress умеет только host/path. Всё остальное - rate-limit, переписывание путей, canary, таймауты, размер тела запроса - в ingress-nginx навешивалось аннотациями, специфичными для контроллера. Примеры из боевой эксплуатации:
Код: Выделить всё
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/limit-rps: "20"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
Gateway API: стандартная замена Ingress в 2026
Gateway API - это не "ещё один контроллер", а новый набор ресурсов (CRD), который SIG Network делает официальным преемником Ingress. Ключевые ресурсы GatewayClass, Gateway и HTTPRoute давно стабильны (v1, GA), а версия Gateway API 1.5, вышедшая в начале 2026, перевела в Standard ещё кучу фич. Главная идея - разделение ролей. Раньше один Ingress смешивал заботы платформенной команды (порты, TLS, инфраструктура) и заботы команды приложения (пути, бэкенды). Gateway API режет это на три слоя:
- GatewayClass - "какой реализацией рулим" (аналог StorageClass для дисков). Ставит вендор/инфра-команда: Envoy Gateway, Cilium, Istio, NGINX Gateway Fabric, облачный.
- Gateway - конкретная точка входа: слушатели (listeners) на портах, протоколы, TLS-сертификаты. Это зона ответственности платформенной команды.
- HTTPRoute (а также GRPCRoute, TCPRoute, TLSRoute) - правила маршрутизации, которые пишет команда приложения в своём неймспейсе, ссылаясь на общий Gateway.
Код: Выделить всё
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gw
namespace: gateway-system
spec:
gatewayClassName: envoy
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- name: wildcard-example-tls
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
Код: Выделить всё
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
namespace: prod
spec:
parentRefs:
- name: prod-gw
namespace: gateway-system
hostnames:
- shop.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: shop-web
port: 80
- Canary и веса - это поле, а не аннотация. Под одним match можно дать несколько backendRefs с weight: 90 и weight: 10 - и это часть стандартной спеки, переносимой между контроллерами. Никаких canary-weight в строках.
- filters вместо аннотаций: requestHeaderModifier, urlRewrite, requestRedirect, requestMirror - типизированные поля с валидацией на API-сервере.
- allowedRoutes - платформенная команда явно решает, какие неймспейсы вправе цепляться к Gateway. Multi-tenant из коробки.
- Не только HTTP: GRPCRoute для gRPC, TCPRoute/TLSRoute для L4 - то, чего в Ingress не было в принципе.
Код: Выделить всё
$ kubectl get gateway prod-gw -n gateway-system
NAME CLASS ADDRESS PROGRAMMED AGE
prod-gw envoy 84.201.xxx.xx True 5m
$ kubectl get httproute shop -n prod -o wide
NAME HOSTNAMES AGE
shop ["shop.example.com"] 4m
L4 против L7: что где терминируется
Чтобы не путаться. L4-балансировка (транспорт, TCP/UDP) - это про IP и порт, она не знает про HTTP-хосты и пути. Service типа LoadBalancer и облачный сетевой балансировщик - это L4. L7-балансировка (приложение) понимает HTTP: хост, путь, заголовки, может терминировать TLS и раздавать canary. Ingress-контроллер и Gateway с HTTPRoute - это L7.
В типичном кластере они работают в связке: облачный L4-балансировщик ловит трафик на внешнем IP и просто перебрасывает пакеты на поды контроллера, а уже контроллер (L7) разбирает HTTP и маршрутизирует. Знать разницу критично: rate-limit по URL или canary по весу ты сделаешь только на L7; а если нужен голый TCP (база, брокер, кастомный протокол) - тебе хватит L4 Service-а или TCPRoute, и тащить туда HTTP-прокси бессмысленно.
Миграция Ingress -> Gateway API и выбор контроллера
Раз ingress-nginx ретирован, а InGate не состоялся, оставаться на нём после марта 2026 - это сознательно держать незакрываемые уязвимости на главном входе в кластер. Steering Committee прямо предупреждает: drop-in замены нет, мигрировать надо осознанно. Практические варианты:
- Остаёшься на модели Ingress, но меняешь контроллер: Traefik (богатый, свои CRD), HAProxy Ingress (предсказуемая производительность), NGINX Gateway Fabric от F5 (это уже не ingress-nginx, а отдельный проект на Gateway API). На k3s по умолчанию едет Traefik.
- Переходишь на Gateway API - стратегически верный путь: Envoy Gateway, Cilium (если уже на eBPF-CNI - Gateway встроен), Istio, облачные реализации в Yandex Managed Kubernetes и VK Cloud.
Типичные грабли и антипаттерны
- Несколько контроллеров без ingressClassName/gatewayClassName - правило подхватывают сразу два, конфликт. Всегда указывай класс явно.
- Терминируешь TLS на Ingress и снова на приложении (двойной HTTPS) без нужды - лишняя задержка и путаница с сертификатами. Решай, где именно граница TLS.
- http01-челлендж на домене, который не резолвится снаружи - Let's Encrypt не достучится, сертификат вечно Pending. Внутренние домены - только dns01.
- Prefix-матчинг как подстрока - /api матчит /api/v1, но не /apitest. Не путай с regex.
- Тяжёлая логика в аннотациях ingress-nginx - именно отсюда росли CVE. На новых кластерах не строй на этом, иди в Gateway API с типизированными filters.
- rewrite-target без понимания capture-групп - классический источник 404: путь переписался не туда. Тестируй на staging.
- Подними локальный кластер (kind или k3s). На k3s Traefik уже внутри - удобно.
- Поставь cert-manager: kubectl apply -f с официальным манифестом релиза, дождись Running всех подов в namespace cert-manager.
- Разверни простой Deployment с echo-сервером и Service на порт 80.
- Вариант A (Ingress): создай Ingress с host и pathType: Prefix, проверь kubectl get ingress - появился ли ADDRESS. Запроси curl -H "Host: app.local" http://<ADDRESS>/.
- Вариант B (Gateway API): поставь CRD Gateway API и Envoy Gateway, создай Gateway с listener на 80 и HTTPRoute на тот же бэкенд. Сравни kubectl describe httproute - найди condition Accepted и ResolvedRefs.
- Бонус: добавь второй backendRef с weight и проверь, что часть запросов уходит на canary-версию.
- Почему сам по себе объект Ingress не принимает трафик и что обязательно должно быть в кластере, чтобы он заработал?
- Чем http01-челлендж cert-manager отличается от dns01 и в каком случае http01 в принципе не сработает?
- Как Gateway API реализует разделение ролей между платформенной командой и командой приложения - какие ресурсы кому принадлежат?
- Где проходит граница L4 и L7 в типичной связке "облачный балансировщик + ingress-контроллер" и почему canary по весу нельзя сделать на L4?
Ingress дал декларативный L7-вход, но беднота спеки утянула всю мощь в непереносимые и небезопасные аннотации - и в 2026-м флагманский ingress-nginx ушёл на покой. Профессиональный стандарт сегодня - Gateway API: типизированный, с разделением ролей, с canary/filters как полями, с поддержкой gRPC и L4. TLS в обоих мирах автоматизирует cert-manager поверх Let's Encrypt. На существующих кластерах планируй миграцию Ingress -> Gateway API, на новых - начинай сразу с Gateway. Это не мода, а закрытие реального риска на самом критичном периметре кластера.