Learning Platform
Глоссарий Troubleshooting
Урок 10.02 · 25 мин
Продвинутый
Ingress rulespathTypeTLSSecretannotationscert-managerdefaultBackend

Ingress rules: host, path, TLS, annotations

Теперь — как именно Ingress маршрутизирует. Концептуально просто: пришёл HTTP-запрос с Host: app.example.com и path /api/v1/users, controller матчит правило, форвардит в указанный Service. На практике три ловушки: pathType ведёт себя неинтуитивно, TLS требует Secret правильного формата, и annotations — это vendor lock-in, который рушит миграцию между controllers.

Разберём каждый аспект.


L4 vs L7 load balancing: transport против application

Структура rules

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

Иерархия: rules[] → каждое правило с host + http.paths[] → каждый path с pathType + backend.service.

Маршрутизация запроса в Ingress
Incoming HTTPHTTP-запрос приходит в controller (nginx). Controller читает Host-заголовок и URL path. На этой основе матчит rules.
match host
Match HostСначала controller матчит host — выбирает rule с host == api.example.com. Если host не совпал ни с одним rule, controller либо отдаёт defaultBackend (если задан), либо возвращает 404.
match path
Match PathВнутри matching host controller перебирает paths[]. Алгоритм: ищет path с самым длинным префиксом, который матчит. Здесь /v2 prefix-матчит /v2/orders, /v1 не подходит.
forward to upstream
Backend ServiceController форвардит запрос в указанный backend Service. nginx делает upstream-запрос на ClusterIP:port этого Service, дальше kube-proxy балансирует в Pods.

pathType: три варианта и их подвох

С v1.18 поле pathType стало обязательным. Три значения:

Exact

Точное совпадение path. Никаких префиксов:

- path: /healthz
  pathType: Exact
  backend:
    service:
      name: health
      port:
        number: 80
  • /healthz → match
  • /healthz/НЕ match (trailing slash считается)
  • /healthz/v1 → НЕ match
  • /healthzz → НЕ match

Prefix

Префикс path, разделённый по /:

- path: /api
  pathType: Prefix
  • /api → match
  • /api/ → match
  • /api/v1/users → match
  • /api/v1/users?x=1 → match (query string игнорируется)
  • /apifooНЕ match — это другой path element
WARNING

Это самый коварный момент. Многие думают, что Prefix /foo матчит ВСЁ, что начинается с /foo, как regex ^/foo.*. Это НЕ так. Prefix матчит по segments, разделённым /. Поэтому /foo матчит /foo, /foo/, /foo/bar, но НЕ /foobar. Это поведение по спецификации, и controllers обязаны его соблюдать.

Корректнее всего думать о Prefix как о regex ^/foo($|/.*).

ImplementationSpecific

Поведение зависит от controller. Например, ingress-nginx интерпретирует path: /api(/.*)? как regex с capture group для rewrite. На других controllers этот path вообще не сработает.

- path: /api(/.*)?
  pathType: ImplementationSpecific

Используется когда нужна функциональность за рамками стандарта (regex paths, rewrite). Не переносится между controllers.


Алгоритм матчинга путей

Когда есть несколько paths под одним host, controller выбирает самый длинный matching prefix (longest-match wins):

rules:
  - host: app.example.com
    http:
      paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: web
              port: { number: 80 }
        - path: /api
          pathType: Prefix
          backend:
            service:
              name: api
              port: { number: 80 }
        - path: /api/v2
          pathType: Prefix
          backend:
            service:
              name: api-v2
              port: { number: 80 }

Запрос /api/v2/orders → matching paths: /, /api, /api/v2. Самый длинный — /api/v2. Запрос идёт в api-v2.

Запрос /api/v1/users → matching: /, /api. Самый длинный — /api. Запрос идёт в api.

Запрос /about → matching: /. Запрос идёт в web.

Exact имеет приоритет над Prefix той же длины.


host: virtual host routing

rules:
  - host: api.example.com
    http: { ... }
  - host: web.example.com
    http: { ... }
  - host: "*.example.com"   # wildcard, только один уровень
    http: { ... }
  • Без host — catch-all для любого Host-заголовка (используется когда у вас один IP без DNS)
  • Конкретный host — точное совпадение Host-заголовка
  • Wildcard *.example.com — матчит ровно один уровень: foo.example.com да, foo.bar.example.com нет
NOTE

Wildcards в Ingress — не глобальные. *.example.com НЕ матчит сам example.com (нужно отдельное правило) и не матчит a.b.example.com (это два уровня subdomain).


defaultBackend: catch-all

Если ни одно правило не подошло, можно отдать запрос в defaultBackend:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: error-page
      port:
        number: 80
  rules:
    - host: app.example.com
      http:
        paths: [ ... ]

Без defaultBackend controller вернёт 404. С defaultBackend — запрос пойдёт на custom error-page service. Часто используется для красивых 404-страниц или fallback на статический сайт.


TLS: HTTPS termination

Ingress умеет терминировать TLS — controller сам делает SSL handshake и отдаёт backend трафик уже plain HTTP:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

spec.tls[] — список TLS-конфигураций. Каждая привязывает SNI hosts к Secret с certificate.

Secret типа kubernetes.io/tls

Secret должен быть именно типа kubernetes.io/tls, с ключами tls.crt и tls.key:

apiVersion: v1
kind: Secret
metadata:
  name: app-tls
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded PEM certificate>
  tls.key: <base64-encoded PEM private key>

Создать через kubectl:

kubectl create secret tls app-tls \
  --cert=fullchain.pem \
  --key=privkey.pem
WARNING

Secret должен быть в том же namespace, что и Ingress. Cross-namespace ссылки на TLS Secret из Ingress спецификации не поддерживаются (это решено в Gateway API через ReferenceGrant — увидим в lessons 4-5).

Несколько TLS-сертификатов на одном Ingress

tls:
  - hosts: [api.example.com]
    secretName: api-tls
  - hosts: [web.example.com]
    secretName: web-tls

Controller использует SNI (Server Name Indication) — клиент в TLS handshake передаёт hostname, controller выбирает правильный сертификат.


cert-manager: автоматизация Let’s Encrypt

В production выпускать сертификаты руками раз в 90 дней — мука. Решение — cert-manager, controller, который автоматически выпускает и обновляет сертификаты через Let’s Encrypt.

Подход: добавить annotation на Ingress, cert-manager увидит и создаст Secret сам:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls   # cert-manager создаст этот Secret сам
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80
cert-manager flow
kubectl apply ingress.yamlПользователь создаёт Ingress с annotation cert-manager.io/cluster-issuer. tls.secretName указывает желаемое имя Secret, которого ещё нет.
watch Ingress + annotation
cert-manager controllercert-manager watch-ит Ingress с своими annotations. Видит запрос, создаёт объект Certificate, который запускает CertificateRequest и ACME challenge через ClusterIssuer.
ACME HTTP-01 challenge
Let's Encryptcert-manager проводит ACME challenge: создаёт временный Ingress с path /.well-known/acme-challenge/..., Let's Encrypt валидирует домен через HTTP-запрос. Альтернатива — DNS-01 challenge через DNS-провайдера.
store cert
Secret app-tlscert-manager сохраняет полученный сертификат в Secret типа kubernetes.io/tls с именем из Ingress.tls.secretName. Ingress controller это видит и подхватывает в nginx config.

Renewal происходит автоматически за 30 дней до истечения. Это де-факто стандарт в production Kubernetes.


Annotations: vendor lock-in

Vanilla Ingress API очень минималистичный. Реальные production-фичи (rewrite, rate-limit, auth, body size, timeouts, websocket support) задаются через annotations на Ingress, и эти annotations — специфичны для каждого controller.

Примеры ingress-nginx annotations

metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/validate"
    nginx.ingress.kubernetes.io/auth-signin: "https://auth.example.com/login"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    nginx.ingress.kubernetes.io/limit-rps: "10"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"

Примеры Traefik annotations

metadata:
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: default-redirect@kubernetescrd
    traefik.ingress.kubernetes.io/router.tls: "true"

Примеры AWS ALB annotations

metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-1:...
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
WARNING

Annotations не переносятся между controllers. Если вы написали Ingress с nginx.ingress.kubernetes.io/rewrite-target и потом решили переехать на Traefik или AWS ALB — все эти annotations нужно переписать или переделать через CRD. Это главная мотивация для Gateway API, которая native решает эти задачи через типизированные объекты (увидим в lessons 4-5).


Rewrite: классический пример

Очень частая задача: серверу удобно, чтобы path был /orders, но снаружи он смотрится через /api/v2/orders. Нужно “снять” /api/v2 префикс при проксировании в backend.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /api/v2(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-v2
                port:
                  number: 80
  • path использует regex (ImplementationSpecific) с capture group (.*)
  • rewrite-target: /$2 подставляет вторую capture group в backend path

Запрос /api/v2/orders → backend получит /orders. На Traefik для этого был бы Middleware ReplacePathRegex — совсем другая конфигурация.


Полный пример: production-like Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/rate-limit: "1000"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - shop.example.com
        - api.shop.example.com
      secretName: shop-tls
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80
          - path: /static
            pathType: Prefix
            backend:
              service:
                name: cdn-proxy
                port:
                  number: 80
    - host: api.shop.example.com
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: api-v1
                port:
                  number: 80
          - path: /v2
            pathType: Prefix
            backend:
              service:
                name: api-v2
                port:
                  number: 80
  defaultBackend:
    service:
      name: error-page
      port:
        number: 80

Что здесь:

  • Два host: shop.example.com и api.shop.example.com
  • Внутри shop два path: / и /static (longest match выберет /static для статики)
  • Внутри api два path: /v1 и /v2
  • TLS на оба хоста через один Secret shop-tls (cert-manager выпустит multi-SAN сертификат)
  • defaultBackend на error-page для всего, что не подошло
  • Annotations: ssl-redirect, body size, rate limit

kubectl describe ingress

kubectl describe ingress shop

# Name:             shop
# Namespace:        default
# Address:          1.2.3.4
# Ingress Class:    nginx
# Default backend:  error-page:80
# TLS:
#   shop-tls terminates shop.example.com, api.shop.example.com
# Rules:
#   Host                    Path  Backends
#   ----                    ----  --------
#   shop.example.com
#                           /        web:80
#                           /static  cdn-proxy:80
#   api.shop.example.com
#                           /v1      api-v1:80
#                           /v2      api-v2:80
# Annotations:
#   cert-manager.io/cluster-issuer:  letsencrypt-prod
#   nginx.ingress.kubernetes.io/ssl-redirect: true
#   ...
# Events:
#   Type    Reason  Age   From                      Message
#   ----    ------  ----  ----                      -------
#   Normal  Sync    5m    nginx-ingress-controller  Scheduled for sync

Events показывают, обработал ли controller Ingress. Если Events: <none> — controller не watch-ит этот Ingress (часто из-за неправильной ingressClassName).


CKAD-сценарий: создать Ingress

Типовая задача с экзамена:

Создайте Ingress web в namespace default, ingressClassName nginx. Маршрут /api (Prefix) → service api port 80. Маршрут /web (Prefix) → service web port 80. Host оставьте пустым.

# Imperative (kubectl create ingress появилось в v1.19)
kubectl create ingress web \
  --class=nginx \
  --rule='/api*=api:80' \
  --rule='/web*=web:80'

* в --rule означает Prefix; без * — Exact. Это бесценный шорткат на экзамене, когда нет времени писать YAML.

Declarative:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80
          - path: /web
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Проверка:

kubectl describe ingress web
kubectl get ingress web -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

# С узла кластера (host пустой — нужен IP напрямую):
curl http://<INGRESS_IP>/api/health
curl http://<INGRESS_IP>/web/

Проверка знанийKnowledge check
В Ingress задан path `/foo` с `pathType: Prefix`. Какие из запросов будут смаршрутизированы в этот backend: `/foo`, `/foo/`, `/foobar`, `/foo/bar`, `/foo?x=1`? Почему важно понимать разницу?
ОтветAnswer
Будут смаршрутизированы: `/foo`, `/foo/`, `/foo/bar`, `/foo?x=1`. НЕ будет: `/foobar`. Причина: Prefix матчинг работает **по path segments**, разделённым `/`, а не как regex `^/foo.*`. Спецификация Kubernetes требует, что Prefix `/foo` матчит `/foo($|/.*)` — либо точное совпадение, либо `/foo/` с любым продолжением. `/foobar` — это другой path segment, не подпадает. Query string (`?x=1`) игнорируется при матчинге — он не часть path. Понимание этого критично: типичная production-багa — разработчик думает, что `/api` Prefix защитит endpoint, а на самом деле `/apiwithtypo` пойдёт в default backend или вернёт 404, а не в api-service. Также, если задумывался `/api/v1` как отдельный backend от `/api`, оба правила нужны явно — нельзя полагаться на 'prefix is enough'.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. Ingress определяет path `/api` с `pathType: Prefix`. Какие запросы будут смаршрутизированы в backend этого правила?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5