На что я заменяю ingress-nginx и как переношу трафик без остановки бизнеса
Сменить ingressClassName можно быстро. Обнаружить после этого, что авторизация получает другой URI, загрузка документов обрывается, а ограничение по IP пропускает посторонних, — тоже. Я — Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш». В этой статье покажу, как я подхожу к замене ingress-nginx: что выбираю, какие настройки переношу отдельно и по каким признакам разрешаю переключать рабочий трафик. IT-директору здесь важнее понимать условия приёмки и отката, чем запоминать названия новых ресурсов Kubernetes.
1. Мой основной выбор — Envoy Gateway, но сначала проверяю ограничения
В марте 2026 года поддержка community-проекта ingress-nginx завершилась; 24 марта его репозиторий архивировали. Установленные контроллеры от этого не выключились. Проблема в другом: рассчитывать на дальнейшие исправления проекта, включая безопасность, уже нельзя. Это относится именно к kubernetes/ingress-nginx, а не ко всем продуктам с названием NGINX. Сам Kubernetes Ingress API тоже не исчез. [Объявление Kubernetes](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/), [репозиторий ingress-nginx](https://github.com/kubernetes/ingress-nginx).
Для самостоятельного входного контура без уже выбранного корпоративного стандарта я начинаю с Envoy Gateway и Gateway API. Мне нравится разделение ответственности: инфраструктурная команда управляет Gateway, команда приложения — HTTPRoute. Host, пути и веса становятся явными полями, которые удобнее проверять при ревью. Однако Gateway API — спецификация, а обслуживает запросы конкретная реализация. Её расширения, ограничения и поддерживаемые функции всё равно придётся изучить. [Руководство Gateway API](https://gateway-api.sigs.k8s.io/guides/getting-started/migrating-from-ingress/).
Мой выбор не универсален. Если критичная интеграция держится на особенностях NGINX, немедленная смена движка может оказаться дороже поэтапной миграции. Если команда уже эксплуатирует подходящий Gateway, второй продукт добавит обслуживание без очевидной пользы. Я выбираю по трём вещам: необходимые функции, способность команды сопровождать решение и возможность проверить поведение до переключения. Маркетинговое обещание совместимости в этот список не входит.
- Traefik рассматриваю для переходного этапа: его провайдер совместимости читает многие nginx-аннотации. Но, например, сочетание auth-url и rewrite-target меняет URI, передаваемый авторизации. Это уже требует проверки приложения. [Ограничения совместимости Traefik](https://doc.traefik.io/traefik/reference/routing-configuration/kubernetes/ingress-nginx/).
- F5 NGINX Ingress Controller рассматриваю, когда важно сохранить NGINX и привычную модель эксплуатации. Это отдельный продукт с собственными аннотациями и ресурсами; таблица соответствий помогает переносу, но не гарантирует одинакового поведения. [Миграция на F5 NIC](https://docs.nginx.com/nginx-ingress-controller/install/migrate-ingress-nginx/).
- Cilium Gateway API проверяю первым, если Cilium уже является стандартом платформы. Его документация прямо показывает, какие nginx-настройки имеют аналоги, а какие требуют переработки. [Таблица миграции Cilium](https://docs.cilium.io/en/stable/network/servicemesh/ingress-to-gateway/nginx-annotations-migration/).
2. Разбор «Вектора»: сначала фиксируем поведение
Возьмём условное ООО «Вектор»: производственная компания на 120 рабочих мест, личный кабинет дилеров и обмен документами. Это модельный учебный проект: параметры и разбор заданы для иллюстрации, а не взяты из закрытого клиентского отчёта. Исходная конфигурация — Kubernetes 1.35.x, ingress-nginx 1.15.1, 24 Ingress, 12 сервисов, три публичных имени. Целевая — Envoy Gateway 1.9.1 и Gateway API 1.6.1. Связка проверена по документации на 5 сентября 2026 года. [Последний релиз ingress-nginx](https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.15.1), [матрица Envoy Gateway](https://gateway.envoyproxy.io/news/releases/matrix/).
Для модели закладываю три worker-узла по 4 vCPU и 8 ГиБ памяти, две реплики нового прокси на разных узлах. Начальные requests каждой реплики — 500m CPU и 512Mi памяти. Это бюджет для испытаний, а не минимальные требования продукта или обещание производительности. Нагрузочный профиль задаю отдельно: 300 запросов в секунду, загрузки до 100 МиБ и WebSocket-соединения продолжительностью до часа.
Перед кластером уже работает внешний L7-балансировщик. Он завершает клиентский TLS, устанавливает HTTPS-соединения с контроллерами и передаёт правильные Host и SNI. Поэтому старый и новый входы можно держать одновременно, меняя долю трафика на балансировщике. Публичный DNS остаётся прежним. Это удобное исходное условие; ниже разберу и случай, когда переключать приходится DNS.
До установки нового контроллера я собираю Ingress, ConfigMap, параметры запуска, настройки Service и реестр сертификатов. Но главным документом считаю таблицу «запрос → ожидаемый результат». В неё попадают код ответа, backend, URI после переписывания, авторизация и клиентский IP. Конвертер ingress2gateway использую как генератор черновика. Его результат не заменяет эту таблицу и проверку живого маршрута. [Назначение ingress2gateway](https://kubernetes.io/blog/2026/03/20/ingress2gateway-1-0-release/).
- rewrite-target и use-regex: фиксирую фактическое преобразование пути, регистр, завершающий слеш и query string.
- auth-url и auth-signin: отдельно описываю проверку доступа, перенаправление на вход и передачу служебных заголовков.
- proxy-read-timeout: проверяю смысл таймаута. Интервал между чтениями и полная длительность запроса — разные ограничения.
- proxy-body-size, buffering и affinity: проверяю загрузки, потоковые ответы и необходимость закрепления пользовательской сессии.
- configuration-snippet и server-snippet: разбираю каждую директиву. Ненужные настройки удаляю, оставшиеся превращаю в проверяемые требования.
3. TLS: сохранить сертификат мало, нужно сохранить его продление
Для первого переключения я сохраняю существующий TLS Secret. В примере namespace apps, Secret portal-tls и сервисы приложения уже существуют; Envoy Gateway с CRD установлен. GatewayClass выбирает реализацию, Gateway описывает HTTPS-вход. Здесь разрешены маршруты только из того же namespace. Это минимальный фрагмент для одного имени, а не полный комплект установки. [TLS termination в Envoy Gateway](https://gateway.envoyproxy.io/v1.9/tasks/security/secure-gateways/). ```yaml apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: eg spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: public namespace: apps spec: gatewayClassName: eg listeners: - name: https hostname: portal.example.com port: 443 protocol: HTTPS tls: mode: Terminate certificateRefs: - name: portal-tls allowedRoutes: namespaces: from: Same ```
Secret по умолчанию ищется в namespace Gateway. Ссылка через границу namespace требует ReferenceGrant на стороне сертификата. Но я стараюсь не усложнять первый перенос: оба контроллера читают один управляемый Secret, который обновляет один Certificate. В модели «Вектора» используется DNS-01. Проверяю также ownerReferences: удаление старого Ingress не должно неожиданно удалить объект, обеспечивающий выпуск сертификата. Сертификат внешнего балансировщика обслуживается отдельно. [Правила ссылок на TLS-сертификаты](https://gateway-api.sigs.k8s.io/guides/user-guides/tls/).
Если выпуск автоматизирован аннотациями cert-manager, перенос тоже отдельный: issuer-аннотация относится к Gateway, а не к HTTPRoute. В актуальной конфигурации поддержка включается через config.gatewayAPI.enabled=true. Для HTTP-01 потребуется доступный извне HTTP listener на порту 80 и настроенный gatewayHTTPRoute solver. Приведённый HTTPS-only Gateway этого не обеспечивает. Также HTTPS listener сам по себе не создаёт перенаправление с HTTP: его задают отдельным правилом RequestRedirect. [cert-manager и Gateway](https://cert-manager.io/docs/usage/gateway/), [HTTP-01 solver](https://cert-manager.io/docs/configuration/acme/http01/).
- Проверяю имя в сертификате, цепочку доверия и срок на обоих участках: клиент → балансировщик и балансировщик → Gateway.
- Отдельно проверяю продление. Успешное открытие сайта сегодня ничего не говорит о следующем выпуске сертификата.
- TLS passthrough, клиентский mTLS и HTTPS до backend оформляю отдельными сценариями: приведённый Gateway настраивает только завершение входящего TLS.
4. Canary: одинаковые проценты ещё не означают одинаковую аудиторию
В HTTPRoute canary задаётся весами backendRefs. Ниже обычные запросы распределяются 95/5, а запрос с X-Canary: always выбирает тестовую версию. Оба Service существуют в apps и публикуют именно Service port 8080. Это конфигурация для этапа проверки canary; во время переноса самого контроллера я сначала направляю весь поток на stable. [HTTPRoute](https://gateway-api.sigs.k8s.io/reference/api-types/httproute/). ```yaml apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: portal namespace: apps spec: parentRefs: - name: public sectionName: https hostnames: - portal.example.com rules: - matches: - path: type: PathPrefix value: / headers: - type: Exact name: X-Canary value: always backendRefs: - name: portal-canary port: 8080 - matches: - path: type: PathPrefix value: / backendRefs: - name: portal-stable port: 8080 weight: 95 - name: portal-canary port: 8080 weight: 5 ```
Вес означает долю относительно суммы весов, а не процент пользователей. На небольшой выборке точных пяти процентов не будет; закрепления человека за версией тоже не появляется. Если раньше использовались canary-by-cookie и sticky sessions, одной заменой аннотаций на числа задачу не решить. Нужно воспроизвести правила выбора версии и хранения сессии. Заголовок в примере — способ маршрутизации, а не защита тестового приложения. [Распределение трафика Gateway API](https://gateway-api.sigs.k8s.io/guides/user-guides/traffic-splitting/).
Правило с заголовком здесь выигрывает при одинаковом пути; считать, что header всегда сильнее любого другого match, нельзя. Ещё опаснее ожидать автоматического спасения при ошибке canary. В документации Envoy Gateway показан случай, когда неверный порт backend приводит к HTTP 500 для его доли запросов. Поэтому перед выставлением веса проверяю ссылки, endpoints и принудительный маршрут в canary. При откате убираю и вес, и отдельное правило с заголовком. [Поведение weighted backendRefs](https://gateway.envoyproxy.io/v1.9/tasks/traffic/http-traffic-splitting/).
5. Source IP: сначала рисую цепочку доверия
Под source IP часто понимают сразу три вещи: адрес TCP-соседа, адрес из X-Forwarded-For и клиента, которого приложение записывает в аудит. При L4-балансировщике, сохраняющем исходный адрес пакета, externalTrafficPolicy: Local помогает избежать межузлового SNAT. Но трафик должен приходить на узлы с локальными готовыми endpoints. Нужны корректные health checks и размещение реплик. Адрес, уже заменённый внешним балансировщиком, настройка Local не восстановит. [Kubernetes: сохранение source IP](https://kubernetes.io/docs/tutorials/services/source-ip/).
У «Вектора» перед Gateway один доверенный L7-прокси. Он очищает входные поддельные значения и формирует X-Forwarded-For из реального адреса клиента. Для этой конкретной цепочки задаю numTrustedHops: 1. Если перед балансировщиком появится CDN, настройку придётся пересмотреть. Сам Gateway закрыт от прямого внешнего доступа. [ClientTrafficPolicy Envoy Gateway](https://gateway.envoyproxy.io/v1.9/tasks/traffic/client-traffic-policy/). ```yaml apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy metadata: name: client-ip namespace: apps spec: targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: public clientIPDetection: xForwardedFor: numTrustedHops: 1 ```
Если внешний узел завершает TCP и передаёт адрес через PROXY protocol, его поддержку согласовывают на обеих сторонах. В Envoy Gateway 1.9 строгий приём задаётся через spec.proxyProtocol.optional: false в ClientTrafficPolicy. Обычный TLS-клиент без PROXY-заголовка такой listener не обслужит, что касается и проверок балансировщика. После настройки я проверяю полный внешний путь, логи приложения и allowlist с разрешённого и запрещённого адресов. [Настройки PROXY protocol](https://gateway.envoyproxy.io/v1.9/api/extension_types/#proxyprotocolsettings).
- Подставляю заведомо ложный X-Forwarded-For и проверяю, что он не позволяет пройти ограничение доступа.
- Проверяю доверенные прокси также в приложении: исправная настройка Gateway не исправляет автоматически небезопасный разбор заголовков backend.
- Настройку создаваемого Service закрепляю через EnvoyProxy, чтобы контроллер не перезаписал ручную правку.
6. Где ломается перенос на стенде и что исправляем
В модельном проекте выделим три проблемных маршрута. Первый — API с /api(/|$)(.*) и rewrite-target: /$2. Для обычного /api/orders достаточно PathPrefix /api и URLRewrite с ReplacePrefixMatch: /. Но исходный ingress-nginx при rewrite-target включает регистронезависимое regex-сопоставление для путей всего host. Поэтому /API/orders и соседние маршруты нельзя считать автоматически перенесёнными. Я включаю в таблицу проверки /api, /api/, /apix, верхний регистр и параметры запроса. [Исходная семантика ingress-nginx](https://kubernetes.github.io/ingress-nginx/user-guide/ingress-path-matching/), [URLRewrite](https://gateway-api.sigs.k8s.io/guides/user-guides/http-redirect-rewrite/).
Второй маршрут — формирование отчёта, которому в сценарии требуется 45 секунд. Для него явно задаю в правиле HTTPRoute timeouts.request: 90s и timeouts.backendRequest: 80s. Это осознанный бюджет операции, а не буквальная копия proxy-read-timeout. Параллельно проверяю таймаут внешнего балансировщика: иначе запрос оборвётся раньше, чем сработает настройка Gateway. Для потоковых ответов отдельно нужны проверки пауз между данными. [Таймауты Envoy Gateway](https://gateway.envoyproxy.io/v1.9/tasks/traffic/http-timeouts/).
Третий маршрут — загрузка документа. Маленький health check не проверяет ни ограничение тела, ни буферизацию, ни поведение приложения при передаче большого файла. До подключения пользователей прогоняю загрузку 100 МиБ, отмену загрузки и повтор запроса. Новый TLS-вход проверяю с диагностического узла командой ниже: адрес демонстрационный, его заменяют адресом Gateway. --resolve сохраняет правильное имя для SNI и проверки сертификата; -k здесь использовать нельзя. Сам тест напрямую не проверяет цепочку доверия внешнего балансировщика. [Документация curl](https://curl.se/docs/manpage.html). ```bash curl --resolve portal.example.com:443:203.0.113.20 \ https://portal.example.com/healthz ```
Итог модельного разбора — определённая схема переноса: TLS Secret сохраняется, обычные пути переходят в HTTPRoute, rewrite получает отдельную таблицу проверок, долгий отчёт — явный бюджет времени, source IP — политику доверия. Canary включается после миграции входа. Здесь нет выдуманных замеров «стало быстрее на 30%»: конфигурационные примеры проверены по синтаксису, но кластерный прогон не выполнялся. Для реального проекта завершением будет протокол приёмки на рабочем профиле нагрузки.
- Gateway имеет Accepted и Programmed, HTTPRoute — Accepted и ResolvedRefs для нужного родителя; observedGeneration соответствует актуальной конфигурации.
- Каждый критичный сценарий сохраняет ожидаемые backend, URI, код ответа и решение авторизации.
- При 300 запросах в секунду p95 не ухудшается более чем на согласованные 10%; это пример критерия, а не полученный результат.
- Нет новых ошибок TLS и авторизации; рост 5xx оценивается отдельно по маршрутам, чтобы общий график не скрыл отказ редкой операции.
7. Переключение без простоя — это параллельная работа и проверенный откат
Я сохраняю старый endpoint и запускаю новый рядом, с независимыми ресурсами и адресом. Оба обслуживают прежние имена и те же приложения. Именно такой параллельный подход рекомендует руководство Gateway API. На внешнем балансировщике сначала перевожу малую долю трафика, затем увеличиваю её после проверки. Долю считаю по фактическим запросам в логах: вес балансировщика и процент HTTP-запросов могут расходиться из-за соединений и его алгоритма. [Параллельная миграция Gateway API](https://gateway-api.sigs.k8s.io/guides/getting-started/migrating-from-ingress-nginx/).
Если общего балансировщика нет, заранее уменьшаю TTL DNS и перед сменой адреса выжидаю прежний TTL. Старый вход продолжаю обслуживать: DNS-кеши и уже открытые соединения не исчезнут одновременно. Откат DNS также не мгновенный. WebSocket или gRPC-поток нельзя пересадить между прокси сменой записи. Для длинных соединений нужны draining, согласованный предел ожидания и проверенное переподключение клиента. [TTL DNS](https://developers.cloudflare.com/dns/manage-dns-records/reference/ttl/).
Graceful shutdown нового Envoy не настраивает завершение старого ingress-nginx автоматически. У Envoy Gateway по умолчанию drainTimeout составляет 60 секунд; долгим операциям этого может не хватить. Время draining согласовываю с terminationGracePeriodSeconds и поведением балансировщика. Обещать сохранение любого бесконечного соединения я не буду. Реалистичное обязательство — отсутствие недоступности сервиса, контролируемое завершение соединений и понятный откат. [Graceful shutdown Envoy Gateway](https://gateway.envoyproxy.io/v1.9/tasks/operations/graceful-shutdown/).
- До переключения проверяю новый вход, сертификаты, авторизацию, IP, большие запросы и отказ одной реплики.
- Фиксирую версии приложения и направляю оба контроллера на stable, чтобы не смешивать инфраструктурную миграцию с релизом.
- Перевожу трафик ступенями, например 1% → 10% → 50% → 100%. Время наблюдения определяется достаточной выборкой критичных операций.
- При новых ошибках авторизации, TLS или превышении согласованного порога 5xx возвращаю новые запросы на старый вход.
- Сохраняю старый контроллер до завершения соединений и проверки характерного рабочего цикла, включая редкие интеграции.
- После приёмки удаляю старые ресурсы, проверяю продление сертификатов и назначаю владельца обновлений нового Gateway.
Частые вопросы
Обязательно ли переходить именно на Gateway API?
Нет. Можно выбрать поддерживаемый Ingress-контроллер. Я предпочитаю Gateway API для долгосрочного развития, если нужные функции подтверждены выбранной реализацией.
Можно ли сохранить существующий TLS Secret?
Да. Проверьте доступность Secret для Gateway, соответствие сертификата имени и независимость его продления от удаляемого Ingress.
Достаточно ли конвертировать аннотации автоматически?
Нет. Конвертация создаёт конфигурацию, но не подтверждает прежнюю семантику rewrite, авторизации, таймаутов и сессий.
Можно ли гарантировать, что ни одно соединение не оборвётся?
Для произвольно долгих соединений — нет. Можно обеспечить доступность сервиса, draining старого входа и проверенное переподключение клиентов.
Я помогу вашей команде составить карту зависимостей ingress-nginx, проверить спорные маршруты и подготовить переключение с откатом. В «АйТи-Фреш» начнём с вашей конфигурации и критичных бизнес-операций — по ним определим объём миграции и условия приёмки.
Бесплатная консультация →

