
Статья подготовлена вместе с экспертом
Сергей Новиков, менеджер продукта Managed Kubernetes

Представьте типичный кластер Kubernetes: в нём работают 10–30 HTTP-сервисов, например frontend, API, личный кабинет и несколько внутренних инструментов. Для них нужны публичные адреса вида app.example.com и api.example.com, маршрутизация по путям /api и /healthz, HTTPS-сертификаты с автоматическим продлением, а иногда — увеличенные таймауты для долгих запросов, ограничение частоты запросов или canary-выкатка.
Самый очевидный способ опубликовать такие сервисы — поставить Ingress-контроллер и описать маршруты ресурсами Ingress. На старте это выглядит просто: один внешний балансировщик, несколько YAML-манифестов, ingressClassName, правила для доменов и путей, TLS через cert-manager.
Проблема проявляется позже — когда требований становится больше или контроллер нужно заменить. Базовый манифест Ingress переносим: другой контроллер обычно понимает host, path, pathType, tls и defaultBackend. Но рабочая конфигурация редко ограничивается этим набором. Таймауты, rewrite, rate limiting, липкие сессии, gRPC, canary-маршрутизация и другие настройки часто задаются аннотациями конкретного контроллера — например, nginx.ingress.kubernetes.io/*. Новый контроллер такие аннотации не прочитает: манифест применится, но часть маршрутов, ограничений или поведения приложения может измениться.
Эта статья поможет принять решение для сценария, где нужно:
Разберём устройство Ingress, критерии выбора контроллера, базовый манифест с TLS и cert-manager, риски аннотаций и переход к Gateway API. В качестве сквозного примера будем использовать сервис shop: интерфейс доступен на shop.example.com, API — по пути /api, проверка состояния — по /healthz, а сертификат выпускает cert-manager. Исходные условия намеренно близки к небольшому или среднему production-кластеру: несколько команд, общая точка входа и необходимость менять конфигурацию без сюрпризов для пользователей.

Сергей Новиков, менеджер продукта Managed Kubernetes
Ingress — объект API networking.k8s.io/v1, который описывает правила HTTP- и HTTPS-маршрутизации внешнего трафика к сервисам кластера. Один внешний адрес обслуживает десятки приложений, а запросы распределяются по домену и пути.
Сам объект Ingress трафик не обрабатывает. Он описывает желаемое поведение, а правила исполняет Ingress-контроллер — под в кластере, который следит за объектами Ingress и переводит их в конфигурацию своего прокси. Без контроллера правила не работают.
Альтернатива на уровне L4 — Service типа LoadBalancer. В этой схеме у каждого приложения свой внешний адрес и балансировщик. Она дороже и не разбирает HTTP: маршрутизация по хосту, пути и заголовкам возможна только на L7.
Полный путь запроса от браузера до контейнера выглядит так:

Две первые стрелки относятся к облачной платформе, остальное описывают манифесты. Внутрикластерная часть маршрута — Service, DNS и CNI — разобрана в отдельном материале про сеть в Kubernetes.
Ingress намеренно ограничен: он описывает HTTP- и HTTPS-маршрутизацию, но не делит ответственность между платформенной командой и разработчиками. Таймауты, canary, ограничение частоты запросов и gRPC не входят в его схему и настраиваются аннотациями конкретного контроллера. Отсюда растёт непереносимость настроек, а также необходимость Gateway API, где похожие сценарии вынесены в типизированные ресурсы с разделением ролей 2. Права между командами по-прежнему разделяются средствами кластера: RBAC в Kubernetes работает независимо от контроллера.
Переносимое ядро включает версию API networking.k8s.io/v1, поле ingressClassName, правила rules с хостом, путём и pathType, блок tls с именем секрета и defaultBackend. Если конфигурация ограничена этими возможностями, при смене контроллера достаточно изменить одно поле.
Всё остальное остаётся за пределами ядра. Аннотации nginx.ingress.kubernetes.io/* понимает только ingress-nginx, у Traefik своя модель с CRD и middlewares, у HAProxy — ключи haproxy.org/*, у Istio — собственные ресурсы.
Отдельная ловушка — pathType: ImplementationSpecific. Документация прямо оставляет его трактовку за реализацией: контроллер может обрабатывать это значение как Prefix, Exact или иначе. Манифест применится на новом контроллере без ошибки, но маршрут может начать работать иначе.
Утилита ingress2gateway переносит часть аннотаций ingress-nginx в ресурсы Gateway API. Это ассистент, а не автопилот: результат конвертации должен проверить человек.
Проверяемых признака зрелости три: дата последнего релиза, статус проекта в CNCF и наличие процесса выпуска security-бюллетеней. Формулировки вроде «самый быстрый» лучше оставить за рамками сравнения, ведь свежих независимых бенчмарков нет, а вендоры проводят замеры по своим сценариям.
| Контроллер | Релиз на август 2026 | Статус проекта | Лицензия ядра | Слабая сторона |
| ingress-nginx | controller-v1.15.1 от 2026-03-19 | Kubernetes SIG Network, проект закрыт | Apache 2.0 | Новых патчей не будет |
| Traefik Proxy | v3.7.10 от 2026-07-31 | Traefik Labs | MIT | Две CVE от 2026-08-03, одна позволяет перехватывать трафик между namespace |
| HAProxy Kubernetes Ingress | v3.2.12 от 2026-07-03 | HAProxy Technologies | Apache 2.0 | Тонкая настройка живёт в аннотациях haproxy.org/*, за пределами переносимого ядра |
| Istio ingress gateway | 1.30.3 от 2026-07-16 | CNCF, Graduated | Apache 2.0 | Вес и сложность: это service mesh целиком, а не только точка входа |
| Envoy Gateway | v1.8.3 от 2026-07-22 | CNCF / Envoy | Apache 2.0 | Ingress не поддерживает, управляется только ресурсами Gateway API |
| kgateway | v2.4.2 от 2026-08-03 | CNCF, Sandbox | Apache 2.0 | Sandbox — низшая ступень зрелости в CNCF |
У живых проектов релизы выходили в июле и августе 2026 года, у ingress-nginx последний тег датирован 19 марта. Раскрытые уязвимости ingress-nginx получили патчи, но новых уже не будет. Репозиторий перевели в режим read-only 24 марта 2026 года.
По оценке Datadog, около половины cloud-native-окружений завязаны на ingress-nginx. Это оценка по наблюдаемым окружениям, а не по всему рынку.
Спецификация Gateway API дошла до v1.6.1 16 июля 2026 года, и её заявляют все живые контроллеры. Поэтому строка «поддерживает Gateway API» уже почти ничего не говорит. Сравнивать нужно полноту и зрелость конкретной реализации: у части контроллеров поддержано только подмножество ресурсов, а не вся спецификация. Практическая разница между подходами разобрана в материале про KGateway и Ingress NGINX.
Переезд на Gateway API не закрывает вопрос безопасности. 3 августа 2026 года в Traefik закрыли CVE-2026-71327 с оценкой High, 7.6 по CVSS v4. Идентичности маршрутов Gateway API склеивались из полей через дефис без экранирования границ. У двух разных маршрутов могла оказаться одна идентичность, что открывало перехват трафика чужого namespace.
Вторая уязвимость того же дня, CVE-2026-71325, обходила запрет allowCrossNamespace=false через backendRef TraefikService. Обе закрыты в v3.7.10 и v3.6.25, вторая также в v2.11.54.
Gateway API устраняет проблемы модели Ingress: разделяет роли, использует типизированные ресурсы и не прячет настройки в строковых аннотациях. Контроллер всё равно нужно регулярно патчить.
Ingress отвечает за трафик снаружи внутрь. Если трафиком нужно управлять внутри кластера, одного Ingress уже недостаточно. Например, могут понадобиться mTLS между сервисами, политики для каждой пары сервисов и сквозная трассировка. Для таких задач используют service mesh, а Ingress gateway становится частью его конфигурации.
Istio закрывает этот сценарий, но добавляет вес и эксплуатационную сложность целой платформы. Ставить mesh только ради публикации одного сервиса наружу не стоит.
Проектировать всё под будущую миграцию — плохая идея. Отказ от аннотаций означает отказ от canary-выкатки, ограничения частоты запросов и липких сессий на уровне точки входа. Команде, которая каждую неделю раскатывает релизы долями трафика, canary в аннотациях нужнее гипотетического переезда через год.
Рабочий компромисс — заранее провести границу и записать её.
| Держим в переносимом ядре | Осознанно принимаем как lock-in |
| ingressClassName, rules с host и pathType, блок tls, defaultBackend | Canary по заголовку и весу |
| pathType: Prefix и Exact, без ImplementationSpecific | Ограничение частоты запросов и числа соединений |
| Выпуск сертификатов через cert-manager | Липкие сессии на cookie, тонкие таймауты, rewrite с регулярными выражениями |
Параметры из правой колонки лучше держать в одном месте рядом с манифестами. Тогда при смене контроллера будет понятно, что и где нужно перенастроить, а сама миграция займёт несколько дней. Если же такие настройки разошлись по десяткам Ingress-объектов разных команд, на переезд может уйти квартал.
Начинаем с класса. Поле ingressClassName в spec заменило аннотацию kubernetes.io/ingress.class, которую документация называет устаревшей. Само поле появилось в Kubernetes 1.18. Формулировка «аннотация устарела с 1.18» неточна: в этой версии появилась замена.
Чаще всего путают две похожие аннотации:
apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: name: nginx annotations: # Аннотация ставится на IngressClass, а не на Ingress ingressclass.kubernetes.io/is-default-class: "true" spec: controller: k8s.io/ingress-nginx
Дальше сам Ingress: домен, два пути с разными типами совпадения, TLS и запасной бэкенд:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: shop namespace: shop spec: ingressClassName: nginx tls: - hosts: - shop.example.com secretName: shop-tls rules: - host: shop.example.com http: paths: - path: /api pathType: Prefix backend: service: name: api port: number: 8080 - path: /healthz pathType: Exact backend: service: name: api port: number: 8080 defaultBackend: service: name: fallback port: number: 80
Prefix сравнивает путь по элементам: /api/v1 подойдёт, /apiv1 — нет. Exact требует точного совпадения с учётом регистра. defaultBackend получает всё, что не подошло под правила. Сервисы api и fallback должны существовать в кластере до применения Ingress [NEEDS_LINK: деплой приложения в Kubernetes].
pathType: Prefix — не строковый префикс. Поле обязательно и принимает три значения: Exact, Prefix, ImplementationSpecific. При Prefix путь сравнивается по элементам, разделённым символом /. Документация формулирует это прямо: /foo/bar совпадает с /foo/bar/baz, но не совпадает с_ /foo/barbaz_. Разработчик ждёт поведения strings.HasPrefix, а получает 404 на /foo/barbaz.
ImplementationSpecific оставляет поведение контроллеру. Правило, написанное для ingress-nginx с этим типом и регулярным выражением, при смене контроллера может измениться без ошибки применения манифеста.
defaultBackend ловит остаток, но не решает вопрос TLS. Если spec.rules не задан, обязателен spec.defaultBackend. Запасной бэкенд подходит для собственной страницы 404 вместо стандартной страницы контроллера. TLS не работает на правиле без host: сертификат пришлось бы выпускать на все возможные поддомены, поэтому hosts в блоке tls должны совпадать с host в rules.
Ingress не хранит сертификаты, а ссылается на Secret. Требования зафиксированы в документации: тип kubernetes.io/tls и два обязательных ключа —_ tls.crt_ и tls.key.
apiVersion: v1 kind: Secret metadata: name: shop-tls namespace: shop type: kubernetes.io/tls data: tls.crt: <base64> tls.key: <base64>
Из готовой пары файлов Secret создаётся одной командой:
kubectl create secret tls shop-tls \ --cert=fullchain.pem --key=privkey.pem -n shop
Несколько доменов используют один порт 443 через SNI, если контроллер его поддерживает. Отдельный внешний адрес на каждый домен не нужен.
Ручной выпуск сертификата раз в 90 дней почти неизбежно превращается в инцидент, поэтому команды ставят cert-manager. Актуальная версия cert-manager — v1.21.1 от 29 июля 2026 года. Версии из поисковой выдачи и пересказов часто отстают, поэтому сверяйтесь с тегами релизов.
Модель ресурсов:
# HTTP-01 проще, но wildcard-сертификат не выдаст apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: devops@example.com privateKeySecretRef: name: letsencrypt-prod-account-key solvers: - http01: ingress: ingressClassName: nginx --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: shop-tls namespace: shop spec: secretName: shop-tls issuerRef: name: letsencrypt-prod kind: ClusterIssuer dnsNames: - shop.example.com
Способ подтверждения владения доменом выбирают по двум условиям: нужен ли wildcard-сертификат и доступен ли домен снаружи.
HTTP-01 проверяет файл по пути /.well-known/acme-challenge/ на порту 80. Домен должен резолвиться в публичный адрес, а порт 80 — быть доступен извне. Wildcard-сертификат этим способом не получить. DNS-01 проверяет TXT-запись _acme-challenge. Это единственный способ получить wildcard вида *.example.com. DNS-01 подходит и для сервисов без публичного входа, но требует API-доступа к DNS-провайдеру. Синтаксис блока solvers.dns01 зависит от провайдера, его нужно брать из документации cert-manager.
У удостоверяющего центра есть лимиты на выпуск. Конкретные лимиты Let's Encrypt менялись, поэтому проверяйте их в день настройки и учитывайте в конвейере. Массовый перевыпуск при каждом прогоне сборки быстро упрётся в лимит [NEEDS_LINK: CI/CD в Kubernetes].
# Статус выпуска сертификата kubectl get certificate,certificaterequest,order,challenge -n shop kubectl describe certificate shop-tls -n shop # Какой сертификат действительно отдаётся по SNI openssl s_client -connect shop.example.com:443 -servername shop.example.com </dev/null 2>/dev/null \ | openssl x509 -noout -subject -issuer -dates # Проверка маршрута без изменения DNS curl -sk --resolve shop.example.com:443:<INGRESS_IP> \ https://shop.example.com/api/v1 -o /dev/null -w '%{http_code}\n' # Демонстрация pathType: Prefix curl -s -o /dev/null -w '%{http_code}\n' https://shop.example.com/api/v1 # 200 curl -s -o /dev/null -w '%{http_code}\n' https://shop.example.com/apiv1 # 404
Собрали в таблицу часто используемые аннотации ingress-nginx. Полный список ключей поддерживает документация контроллера:
| Задача | Аннотация | Значения |
| Таймауты до бэкенда | proxy-connect-timeout, proxy-send-timeout, proxy-read-timeout | Число |
| Размер тела запроса | proxy-body-size | Строка |
| Перезапись пути | rewrite-target | URI |
| Ограничение нагрузки | limit-connections, limit-rps, limit-rpm | Число |
| Липкие сессии | affinity: cookie, affinity-mode, session-cookie-name | cookie, balanced или persistent |
| Протокол бэкенда | backend-protocol | HTTP, HTTPS, AUTO_HTTP, GRPC, GRPCS, FCGI |
| Canary-выкатка | canary, canary-by-header, canary-weight | "true", строка, число |
Все ключи используют префикс nginx.ingress.kubernetes.io/.
В документации сказано: «Все значения таймаутов указываются без единиц и считаются в секундах». Поэтому нужно писать 60, а не 60s: второй вариант сработает не так, как ожидается.
Ограничение частоты запросов применяется к каждой реплике Ingress-контроллера отдельно, а не ко всему Ingress. Если указать limit-rps: 100 и запустить три реплики, суммарный лимит составит 300 запросов в секунду. При горизонтальном автоскейлинге он будет меняться вместе с количеством реплик. Поэтому лимит может оказаться выше, чем задано в манифесте.
Если путь во внешнем запросе отличается от пути, который обрабатывает приложение, его нужно переписать с помощью rewrite-target. Например, приложение слушает /, а Ingress принимает запросы по /api. Без перезаписи запросы к /api и вложенным путям вернут 404.
rewrite-target стал триггером уязвимости CVE-2026-3288 в ingress-nginx: инъекция проходила через переменную пути, а блок rewrite попадал в конфигурацию только при непустом значении цели перезаписи. Дефект характерен для модели Ingress: настройка приходит строкой в аннотации и попадает в конфигурацию прокси.
Балансировка на уровне Service работает по соединению, а не по запросу. Долгоживущие соединения gRPC и HTTP/2 остаются на одной реплике до разрыва. Ingress-контроллер разбирает этот трафик на L7, а backend-protocol: GRPC включает нужный режим.
В VK Cloud Containers Ingress-контроллер на базе NGINX подключается аддоном из личного кабинета или через Terraform. Сам по себе в кластере он не появляется. Установка доступна в трёх вариантах: стандартная на узлы, выбранные планировщиком, на выделенную группу worker-узлов и быстрая без изменения кода настройки.
Перед установкой учтите несколько вещей:
Для кластеров с автоскейлом полезна аннотация пода контроллера cluster-autoscaler.kubernetes.io/safe-to-evict: "false". Она не даёт Cluster Autoscaler вытеснить контроллер вместе с узлом.
По таблице версий компонентов аддону Ingress NGINX для кластеров 1.34.x и 1.33.x соответствует чарт 4.12.1, cert-manager — 1.16.3.
Аддон устанавливает community-контроллер kubernetes/ingress-nginx. Документация отсылает за полным кодом настройки к values.yaml его чарта, а Service после установки называется ingress-nginx-controller и находится в namespace ingress-nginx. Поэтому для него подходят аннотации nginx.ingress.kubernetes.io/*.
В ручных сценариях документация устанавливает другой контроллер — от NGINX Inc. Он использует семейство nginx.org/*, и аннотации аддона к нему не применяются.
Внешний адрес после установки:
kubectl get svc ingress-nginx-controller -n ingress-nginx

Возможности сервиса и запуск Kubernetes-кластеров
К любому Service типа LoadBalancer и к Ingress-контроллеру платформа привязывает выделенный TCP-балансировщик, в том числе к контроллеру из аддона. Балансировщик построен на OpenStack Octavia с HAProxy в основе. Он проксирует HTTP, HTTPS, TCP и UDP, поддерживает HTTP/2 вместе с HTTP/1.1 и работает парой active/standby с переключением по VRRP. Для кластера это один балансировщик.
Точка терминации TLS в Cloud Containers зависит от типа балансировщика.
| Вариант | Где терминируется TLS | Как виден реальный IP клиента |
| Контроллер за TCP-балансировщиком, сценарий аддона | На Ingress-контроллере: TCP-балансировщик не терминирует SSL-соединения | Через proxy protocol. Контроллер NGINX от VK Cloud его поддерживает и уже настроен |
| Контроллер за отдельным HTTP(S)-балансировщиком | На балансировщике, контроллер получает HTTP | На контроллере нужно включить ExternalTrafficPolicy: Local, после этого доступны все заголовки |
Если контроллер установлен аддоном, HTTPS терминируется на нём, а реальный адрес клиента передаётся через proxy protocol. При ручной установке нельзя забывать про протокол: иначе приложение увидит IP балансировщика вместо IP пользователя, и логика по IP — гео, блок-листы, антифрод — сломается без явной ошибки.
У схемы с HTTP-балансировщиком есть ограничение: worker-узлы нужно вручную добавлять в правила балансировщика и при изменении размера группы, и при включённом автомасштабировании. Для динамических кластеров это аргумент в пользу TCP-балансировщика.
Альтернатива для Gateway API в Cloud Containers — аддон Kgateway. Маршруты, TLS и политики доступа в нём задаются централизованно ресурсами Gateway API. Аддон доступен только для кластеров второго поколения, требует CPU от 5m до 50m и RAM от 40Mi до 512Mi. При создании ресурса Gateway с внешним трафиком он добавляет один стандартный балансировщик и один floating IP.
Traefik в списке аддонов отсутствует. Документация приводит его как пример стороннего контроллера, который устанавливают вручную через Helm, и отмечает, что описанные подходы можно адаптировать под другие контроллеры.
Для внутрикластерной сети поддерживаются две подсистемы:
Собственного слоя сетевых политик платформа не добавляет: действуют стандартные Kubernetes NetworkPolicy, которые применяет выбранная CNI. L7-фильтрация доступна только с Cilium. Обе CNI работают с платформой через SDN Sprut, совместимую с OpenStack Neutron API.
Cloud Containers сертифицирован CNCF Certified Kubernetes — Hosted и совместим со стандартным Kubernetes API. Доступны версии 1.34.2, 1.33.3, 1.32.1 и 1.31.4, каждая поддерживается 14 месяцев с даты релиза в сервисе. Платить нужно только за потреблённые ресурсы. Платформа развёрнута в четырёх геозонах. Отказоустойчивый кластер распределяет master-узлы по всем зонам доступности региона и работает, пока доступно больше половины мастеров.
Service типа LoadBalancer работает на L4 и даёт приложению отдельный внешний адрес с отдельным балансировщиком. Ingress работает на L7: один адрес обслуживает много приложений, а разбор идёт по домену и пути. За исполнение правил Ingress отвечает контроллер, без него объект ничего не делает.
Сравнивайте по трём проверяемым признакам: дата последнего релиза, статус проекта в CNCF и наличие процесса security-бюллетеней. На август 2026 года ingress-nginx закрыт как проект (последний релиз 19 марта 2026 года 4), живые линии релизятся ежемесячно. Второй пласт выбора — объём тонкой настройки, которую вы согласны потерять при переезде.
Чаще всего дело в pathType: Prefix: он сравнивает путь поэлементно, а не как строку. Правило /foo/bar совпадёт с /foo/bar/baz и не совпадёт с /foo/barbaz. Вторая частая причина — расходящиеся внешний и внутренний пути без rewrite-target. Начинать разбор стоит с kubectl describe ingress
Через cert-manager: он выпускает сертификат по ресурсу Certificate или по аннотации на Ingress и сам перевыпускает его до истечения срока. Сертификат попадает в секрет типа kubernetes.io/tls, на который ссылается блок tls в Ingress. В Cloud Containers cert-manager ставится аддоном.
Первый шаг — kubectl describe ingress: там видны итоговые правила и события. Дальше зависит от слоя: с Cilium поток на L3–L7 показывает Hubble, в связке с Istio для карты сервисов используют Kiali. Отдельный класс проблем ловится проверкой адреса клиента: если приложение видит адрес балансировщика, значит потерян proxy-протокол или ExternalTrafficPolicy: Local.
Базовый Ingress можно настроить за вечер: выбрать класс, описать правила с host и pathType, создать Secret типа kubernetes.io/tls и подключить cert-manager для перевыпуска сертификатов. Дальше нужно решить, какие настройки контроллера команда готова привязать к конкретной реализации. Каждая аннотация для тонкой настройки уменьшает переносимость конфигурации.
Указывайте ingressClassName явно, а класс по умолчанию назначайте аннотацией на IngressClass. Для pathType используйте Prefix и Exact. Сертификаты выпускайте через cert-manager, а не вручную. Список принятых lock-in-настроек храните в репозитории рядом с манифестами. При смене контроллера это избавит от ручного поиска зависимостей.
После раскатки проверьте маршруты через curl --resolve, сертификат — через openssl s_client с SNI, а конфигурацию Ingress — командой kubectl describe ingress.
В VK Cloud Ingress-контроллер и cert-manager устанавливаются аддонами, а балансировщик платформа привязывает к контроллеру автоматически. В схеме с TCP-балансировщиком HTTPS терминируется на контроллере, а реальный IP клиента передаётся через proxy protocol. Аддон уже настроен для такой работы.

Будем держать в курсе новостей и облачных трендов




