Урок 07 · Kubernetes на практике

Ingress и Gateway API

Что-то не запустилось или осталось непонятным? Задайте вопрос в комментариях к разбору этого урока в канале — разберёмся вместе.

Service из урока 4 дал стабильный адрес внутри кластера, а снаружи — что-то вроде 192.168.49.2:31742. Порт случайный, выдан кластером, и на каждый новый сервис будет свой такой же. Никто не пускает пользователей в интернет по такому адресу: снаружи нужны имена (shop.example.com), пути (/api, /admin) и HTTPS — и всё это на одном-единственном входе. В Kubernetes за такой вход отвечали и отвечают два поколения объектов: старый Ingress, который вы точно встретите в чужих кластерах, и пришедший ему на смену Gateway API, на котором мы и сделаем практику.

Ingress — это правила. Исполняет их контроллер

Здесь спотыкаются почти все, кто впервые пробует выпустить сервис наружу: вы пишете манифест, применяете его, kubectl get ingress показывает объект — а по адресу ничего не отвечает. Дело в том, что Ingress сам по себе ничего не делает. Это просто запись в базе кластера вида «запросы к хосту hello.local отправляй в Service hello-fastapi».

Чтобы правила заработали, в кластере должен жить контроллер — обычный Pod с настоящим веб-сервером внутри (чаще всего nginx). Он следит за объектами маршрутизации, на лету превращает их в свой конфиг и принимает трафик снаружи. Разделение труда простое:

  • Объект маршрутизации (его пишете вы) — что куда направлять: хост, путь, целевой Service и его порт.
  • Контроллер (ставится в кластер один раз) — кто эти правила исполняет и держит на себе внешний вход.

Запомните это разделение: оно одинаково верно и для Ingress, и для Gateway API, и именно его непонимание порождает большинство вопросов «почему не работает».

Схема: браузер обращается к hello.local, контроллер сверяется с правилами маршрутизации и направляет запрос в Service, который раздаёт его Pod'ам БРАУЗЕР hello.local КОНТРОЛЛЕР веб-сервер внутри кластера правила маршрутизации host: hello.local path: / → hello-fastapi:8000 SERVICE hello-fastapi Pod #1 Pod #2 один вход снаружи — сколько угодно сервисов за ним
Объект маршрутизации — это конфиг для контроллера. Сам трафик всегда идёт через контроллер, а дальше — в обычный Service из урока 4.

Как выглядит Ingress-манифест

Применять его мы не будем — но прочитать нужно уметь, потому что в живых кластерах таких файлов тысячи:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-fastapi
spec:
  ingressClassName: nginx
  rules:
    - host: hello.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-fastapi
                port:
                  number: 8000

Читается сверху вниз: правило срабатывает для хоста hello.local, путь / с типом Prefix означает «этот путь и всё, что начинается с него», а запрос уходит в Service hello-fastapi на порт 8000 — порт Service, а не контейнера. Строка ingressClassName говорит, какой именно контроллер должен взять это правило.

Всё, что Ingress умеет выразить, — примерно это и есть. Чуть больше — редиректы, таймауты, переписывание путей, CORS — делалось аннотациями: строчками вида nginx.ingress.kubernetes.io/rewrite-target: / в metadata. У каждого контроллера аннотации были свои, несовместимые с чужими, и перенести конфиг с одного контроллера на другой означало переписать его целиком. Именно из этой боли и вырос Gateway API.

Статус на 2026 год — важно Ingress жив, но развитие остановлено, а самый популярный его контроллер закрыт:
  • Ingress API заморожен. В документации Kubernetes прямо сказано: «The Kubernetes project recommends using Gateway instead of Ingress. The Ingress API has been frozen». Заморожен — не значит удалён: объект остаётся стабильным (GA), удалять его не планируют, существующие манифесты продолжат работать. Но новых возможностей у него не будет никогда.
  • ingress-nginx ушёл на покой. Тот самый контроллер от сообщества Kubernetes, который стоял примерно в половине кластеров мира и который поднимает команда minikube addons enable ingress, был объявлен к выводу в ноябре 2025 года, а 24 марта 2026 года его репозиторий заархивирован. Обновлений и патчей безопасности больше не выходит — найденные с тех пор уязвимости не будут исправлены никогда.
Обратите внимание на границы: не-EOL остались и сам Ingress API, и веб-сервер nginx, и отдельный коммерческий контроллер от F5/NGINX — закрыт именно community-проект kubernetes/ingress-nginx. Практический вывод для вас: читать Ingress уметь надо, а новое строить — на Gateway API. Поэтому дальше в уроке мы идём именно туда.
Источник Kubernetes Blog: Ingress NGINX Retirement · Kubernetes Docs: Ingress (баннер о заморозке — в начале страницы)

Gateway API: три объекта вместо одного

Gateway API решает ту же задачу — впустить трафик в кластер, — но разбирает её на три объекта вместо одного. Причина не в любви к сложности, а в том, что за разные части этой задачи в реальной команде отвечают разные люди:

ОбъектЧто описываетКто обычно владеет
GatewayClass Какая реализация обслуживает шлюзы — «мы используем nginx» / «мы используем Envoy». Создаётся один раз при установке контроллера. Поставщик инфраструктуры
Gateway Сам вход: какие порты слушаем, по каким протоколам, с какими сертификатами и для каких доменов. Платформенная команда
HTTPRoute Куда девать конкретный запрос: хост, путь, заголовки → в такой-то Service. Живёт рядом с приложением. Разработчик сервиса

В Ingress всё это было слито в один файл, и чтобы добавить своему сервису маршрут, разработчику приходилось лезть в общий объект, где рядом лежат сертификаты и чужие правила. В Gateway API маршрут — отдельный объект: команда платформы один раз поднимает Gateway, а дальше каждая команда добавляет свои HTTPRoute, ничего общего не трогая. Плюс то, что раньше жило в аннотациях конкретного контроллера — разделение трафика по весам, работа с заголовками, редиректы, — теперь часть самого API и переносится между реализациями.

Схема Gateway API: GatewayClass задаёт реализацию, Gateway описывает вход и порт, два HTTPRoute направляют запросы по разным путям в разные Service GATEWAYCLASS nginx GATEWAY hello-gateway listener :80 / HTTP hello.local HTTPROUTE path: / → hello-fastapi:8000 HTTPROUTE path: /api → другой Service Service Service один вход маршруты добавляют команды сервисов — независимо друг от друга
Вход описан один раз в Gateway, а маршруты к нему подключаются отдельными объектами HTTPRoute — их можно держать рядом с кодом сервиса.
Источник Официальная документация — Kubernetes Gateway API

Практика: выпустите сервис наружу через Gateway API

Понадобится Deployment из урока 3 и Service hello-fastapi из урока 4 — проверьте, что они на месте: kubectl get deploy,svc. Ingress-аддон minikube включать не нужно; если вы уже успели его включить, выключите: minikube addons disable ingress.

  1. Установите сам Gateway API. В отличие от Ingress, он не встроен в Kubernetes — это набор CRD (пользовательских типов объектов), которые ставятся отдельно:
    GWAPI=https://github.com/kubernetes-sigs/gateway-api/releases/download
    
    kubectl apply --server-side -f $GWAPI/v1.6.1/standard-install.yaml

    Проверить, что типы появились в кластере: kubectl get crd | grep gateway. Сейчас в кластере есть описание объектов — но ещё нет никого, кто бы их исполнял.

  2. Поставьте контроллер. Возьмём NGINX Gateway Fabric — реализацию Gateway API поверх того же nginx, живую и поддерживаемую (это не тот проект, что закрылся в марте):
    NGF=https://raw.githubusercontent.com/nginx/nginx-gateway-fabric
    
    kubectl create namespace nginx-gateway
    kubectl apply --server-side -f $NGF/v2.7.0/deploy/crds.yaml
    kubectl apply -f $NGF/v2.7.0/deploy/default/deploy.yaml

    Дождитесь готовности контроллера и убедитесь, что он зарегистрировал свой класс:

    kubectl get pods -n nginx-gateway
    kubectl get gatewayclass

    В выводе последней команды должен появиться класс nginx со значением ACCEPTED: True — это и есть тот самый GatewayClass из таблицы выше, вы его не создавали, его принёс с собой контроллер.

  3. Опишите вход — файл gateway.yaml:
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: hello-gateway
    spec:
      gatewayClassName: nginx
      listeners:
        - name: http
          port: 80
          protocol: HTTP
          hostname: "hello.local"
          allowedRoutes:
            namespaces:
              from: Same

    listeners — это порты, которые шлюз слушает снаружи; здесь один, обычный HTTP на 80. allowedRoutes задаёт, чьи маршруты этот вход готов принимать: Same — только из своего пространства имён. Это та самая ролевая граница — владелец шлюза решает, кого к себе пускать.

  4. Опишите маршрут — файл httproute.yaml:
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: hello-fastapi
    spec:
      parentRefs:
        - name: hello-gateway
          sectionName: http
      hostnames:
        - "hello.local"
      rules:
        - matches:
            - path:
                type: PathPrefix
                value: /
          backendRefs:
            - name: hello-fastapi
              port: 8000

    Сравните с Ingress-манифестом выше — содержание то же самое, но появился parentRefs: маршрут сам заявляет, к какому шлюзу он подключается. Именно поэтому его можно писать и применять, не трогая чужой общий объект.

  5. Примените оба файла и проверьте, что кластер их принял:
    kubectl apply -f gateway.yaml -f httproute.yaml
    kubectl get gateway
    kubectl describe httproute hello-fastapi

    У Gateway в колонке PROGRAMMED должно появиться True — контроллер поднял под этот шлюз настоящий nginx. В выводе describe для маршрута ищите условия Accepted: True и ResolvedRefs: True: первое означает, что шлюз маршрут принял, второе — что Service hello-fastapi найден.

  6. Откройте шлюз наружу. Под Gateway контроллер создал отдельный Deployment с nginx и Service типа LoadBalancer — в minikube такой Service получает внешний адрес только при запущенном туннеле. В отдельном терминале запустите и оставьте работать:
    minikube tunnel

    Команда спросит пароль — ей нужны права, чтобы поднять сетевой маршрут на вашей машине.

  7. В первом терминале посмотрите, какой адрес получил шлюз:
    kubectl get svc

    Найдите сервис, имя которого начинается с hello-gateway, и возьмите его EXTERNAL-IP. Пропишите этот адрес в /etc/hosts (на Windows — C:\Windows\System32\drivers\etc\hosts, Блокнотом от администратора), подставив вместо <EXTERNAL-IP> увиденное значение:

    sudo sh -c 'echo "<EXTERNAL-IP> hello.local" >> /etc/hosts'
  8. Проверьте результат — по имени и без всякого порта:
    curl http://hello.local/
    curl http://hello.local/health

    И в браузере: http://hello.local/docs. Тот же сервис, что в уроке 4 отвечал по 192.168.49.2:31742, теперь доступен по нормальному имени на стандартном 80-м порту.

Проверьте, что маршрут живой — не угадывая Если curl молчит или отдаёт 404, не переустанавливайте всё заново — у Gateway API очень разговорчивые статусы, и почти всегда он прямо говорит, что не так:
kubectl describe gateway hello-gateway
kubectl describe httproute hello-fastapi
Accepted: False у маршрута — шлюз его не принял: чаще всего не совпал parentRefs.name или маршрут лежит в другом пространстве имён, чем разрешает allowedRoutes. ResolvedRefs: False — не найден Service из backendRefs: опечатка в имени или неверный порт. Пустой EXTERNAL-IP — не запущен minikube tunnel. Это заметное улучшение по сравнению с Ingress, где о причинах приходилось догадываться по логам контроллера.
Если у вас уже есть Ingress-манифесты Переписывать вручную не обязательно: у SIG Network есть официальная утилита ingress2gateway, которая читает существующие Ingress-объекты (в том числе распространённые аннотации ingress-nginx) и печатает готовые Gateway и HTTPRoute. Результат всё равно нужно вычитать глазами, но 90% рутины она снимает.
Лайфхак Gateway API не заменяет Service, а опирается на него: трафик всегда идёт «шлюз → Service → Pod». Поэтому после появления шлюза тип NodePort из урока 4 больше не нужен — верните Service обычный type: ClusterIP, и снаружи останется ровно один вход, а не два. А чтобы выпустить второй сервис на том же домене, не нужно трогать Gateway вообще: пишете ещё один HTTPRoute с тем же parentRefs и своим путём — например, /api. Ради этого разделения Gateway API и придуман.

Проверьте себя

Что именно произошло с Ingress к 2026 году?

Разработчику нужно выпустить наружу новый сервис на уже работающем шлюзе. Что он создаёт?

У HTTPRoute в статусе видно ResolvedRefs: False. О чём это говорит?

Напишите команду, которая покажет, какие реализации Gateway API зарегистрированы в кластере:

Напишите команду, которая покажет условия Accepted и ResolvedRefs для маршрута hello-fastapi: