Ingress: HTTP-маршрутизация снаружи

Как пустить десятки HTTP-сервисов через один внешний адрес, маршрутизируя по домену и пути.

Ingress — это объект Kubernetes с правилами HTTP-маршрутизации (хост + путь → Service), который исполняет отдельный компонент — ingress controller.

В базовых разделах мы открывали приложение наружу через Service типа LoadBalancer. Это работает, но плохо масштабируется: на каждый сервис облако поднимает отдельный балансировщик с отдельным внешним IP, за каждый из которых вы платите. Десять микросервисов — десять балансировщиков и десять IP. А ещё LoadBalancer работает на уровне L4 (TCP/порт) и ничего не знает про HTTP: он не умеет смотреть на домен или путь запроса.

Зачем Ingress вместо кучи LoadBalancer

Ingress решает эту проблему: один внешний вход (один балансировщик, один IP) принимает весь HTTP/HTTPS-трафик и раскидывает его по внутренним сервисам по правилам уровня L7. Запрос на shop.example.com уходит во фронтенд, api.example.com/users — в сервис пользователей, api.example.com/orders — в сервис заказов. Снаружи это один адрес, внутри — много сервисов.

Аналогия: LoadBalancer — это отдельная входная дверь в каждую комнату прямо с улицы. Ingress — это одна парадная с консьержем, который по табличке на конверте relays вас в нужный кабинет.

Ingress сам по себе ничего не делает: нужен controller

Ключевой момент, на котором спотыкаются новички: объект Ingress — это только правила, декларация намерения. Их кто-то должен исполнять. Этот «кто-то» — ingress controller: под (обычно Deployment + Service типа LoadBalancer), внутри которого крутится реальный обратный прокси — чаще всего nginx, либо HAProxy, Traefik, Envoy. Controller следит за объектами Ingress в кластере и на лету перенастраивает прокси под их правила.

Поэтому в свежем кластере одного применения YAML с Ingress мало — правила будут лежать мёртвым грузом, пока не установлен controller. В minikube его включают аддоном:

minikube addons enable ingress
kubectl get pods -n ingress-nginx

В облаке ingress-nginx ставят манифестом или Helm-чартом; managed-кластеры часто предлагают свой controller.

Правила хостов и путей

Опишем маршрутизацию: домен api.example.com делим по префиксу пути между двумя сервисами.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /users
            pathType: Prefix
            backend:
              service:
                name: users-svc
                port:
                  number: 80
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: orders-svc
                port:
                  number: 80

Поле ingressClassName говорит, какой controller обслуживает это правило (их в кластере может быть несколько). pathType: Prefix матчит всё, что начинается с /users; альтернатива — Exact (точное совпадение). Трафик controller направляет на Service по имени и порту — то есть Ingress стоит «над» сервисами, а не заменяет их.

Несколько объектов Ingress с одним хостом controller объединяет в один server-блок — это удобно, когда команды владеют своими сервисами и описывают правила в отдельных манифестах. Запрос, не подошедший ни под одно правило, уходит на default backend controller'а (обычно отдаёт 404). Полезные поведения за пределами базовой схемы — редиректы, ограничение скорости, размер тела запроса, basic-auth — задают аннотациями на объекте Ingress (например, nginx.ingress.kubernetes.io/...); это «ручки» конкретного controller, и в Traefik или Envoy они называются иначе.

TLS-терминация

Ingress — естественное место, чтобы завершать (терминировать) HTTPS: расшифровать TLS на входе, а внутрь кластера пустить уже обычный HTTP. Сертификат и ключ кладут в Secret типа kubernetes.io/tls, а в Ingress ссылаются на него:

spec:
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-cert
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80

Сам Secret создают из файлов сертификата:

kubectl create secret tls api-tls-cert \
  --cert=tls.crt --key=tls.key

На практике сертификаты обычно не создают руками: ставят cert-manager, который автоматически выпускает и продлевает их через Let's Encrypt, складывая в нужный Secret. Ingress подхватывает обновлённый сертификат без вашего участия.

Как это работает под капотом

Controller (возьмём ingress-nginx) держит внутри настоящий nginx. Он подписан на API-сервер и наблюдает за объектами Ingress, Service и Endpoints. При любом изменении он генерирует новый nginx.conf с server- и location-блоками под ваши хосты и пути и делает nginx -s reload. Внешний трафик приходит на Service самого controller (тип LoadBalancer или NodePort), попадает в nginx, тот по заголовку Host и пути выбирает upstream и проксирует запрос прямо на поды бэкенда (через их Endpoints, минуя лишний kube-proxy hop). Так один прокси обслуживает сколько угодно правил.

Частые ошибки

Самая частая — применить Ingress в кластере без установленного controller и ждать, что заработает: внешнего IP у объекта так и не появится, а правила бездействуют. Вторая — забыть ingressClassName (или указать класс, которого нет): тогда ни один controller не возьмёт правило в работу. Третья — путаница с pathType: Exact вместо Prefix на /api приведёт к 404 на /api/users. Ещё про переписывание путей: nginx по умолчанию отдаёт бэкенду путь как есть; чтобы срезать префикс, нужна аннотация rewrite-target — её отсутствие или неверный синтаксис ломает маршрут. И помните: Ingress — это только HTTP/HTTPS; не-HTTP трафик (произвольный TCP/UDP, например база данных) через стандартный Ingress не пускают — для него используют Service или специальные расширения controller.

Итоги

  • Ingress — правила L7-маршрутизации (хост + путь → Service); один внешний вход вместо балансировщика на каждый сервис.
  • Правила исполняет ingress controller (nginx, Traefik, Envoy) — без него Ingress бездействует.
  • ingressClassName привязывает правило к нужному controller; маршрутизация идёт по host и path/pathType.
  • TLS-терминация: сертификат в Secret типа tls, ссылка в spec.tls; на практике автоматизируют cert-manager'ом.
  • Ingress стоит над сервисами и работает только с HTTP/HTTPS.
Проверьте себя
1. Что произойдёт, если применить объект Ingress в кластере, где не установлен ни один ingress controller?
AKubernetes автоматически поднимет nginx и начнёт маршрутизировать трафик
BПравила будут записаны, но не будут исполняться — трафик никуда не пойдёт
CAPI-сервер отклонит манифест с ошибкой валидации
DКаждое правило path создаст отдельный Service типа LoadBalancer
2. Зачем использовать один Ingress вместо нескольких Service типа LoadBalancer для десяти HTTP-сервисов?
ALoadBalancer не поддерживает HTTPS, а Ingress поддерживает
BОдин внешний вход и один IP вместо десяти балансировщиков, плюс маршрутизация по хосту и пути (L7)
CService вообще не нужен, если есть Ingress
DIngress работает быстрее, потому что не использует kube-proxy