Скрытые ловушки Ingress-NGINX при миграции на Gateway API

1. Совпадения по регулярным выражениям основаны на префиксе и нечувствительны к регистру

Как было объявлено в ноябре 2025 года, Kubernetes прекратит поддержку Ingress-NGINX в марте 2026 года. Несмотря на широкое распространение, Ingress-NGINX полон неочевидных настроек по умолчанию и побочных эффектов, которые с высокой вероятностью присутствуют и в вашем кластере. Эта статья описывает подобные поведения, чтобы вы могли безопасно провести миграцию и осознанно решить, какие из них стоит сохранить. Здесь также сравниваются Ingress-NGINX и Gateway API и показано, как воспроизвести поведение Ingress-NGINX средствами Gateway API. Сквозной риск во всех разделах один и тот же: внешне корректный перенос конфигурации всё равно может привести к сбоям, если не учесть особенности Ingress-NGINX.

Предполагается, что читатель уже знаком с Ingress-NGINX и Ingress API. В большинстве примеров в качестве бэкенда используется httpbin.

Обратите также внимание, что Ingress-NGINX и NGINX Ingress — это два разных Ingress-контроллера. Ingress-NGINX — контроллер, поддерживаемый сообществом Kubernetes, который прекращает работу в марте 2026 года. NGINX Ingress — контроллер от компании F5. Оба используют NGINX в качестве плоскости данных (dataplane), но в остальном никак не связаны. Далее в статье речь идёт исключительно об Ingress-NGINX.

1. Совпадения по регулярным выражениям основаны на префиксе и нечувствительны к регистру

Предположим, вы хотите направлять все запросы, путь которых состоит ровно из трёх заглавных букв, к сервису httpbin. Для этого можно создать следующий Ingress с аннотацией nginx.ingress.kubernetes.io/use-regex: "true" и шаблоном регулярного выражения /[A-Z]{3}.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: regex-match-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: regex-match.example.com
    http:
      paths:
      - path: "/[A-Z]{3}"
        pathType: ImplementationSpecific
        backend:
          service:
            name: httpbin
            port:
              number: 8000

Однако поскольку совпадения по регулярным выражениям работают как нечувствительные к регистру префиксные совпадения, Ingress-NGINX направит к httpbin любой запрос, путь которого начинается с любых трёх букв:

curl -sS -H "Host: regex-match.example.com" http://<your-ingress-ip>/uuid

Вывод будет примерно таким:

{
  "uuid": "e55ef929-25a0-49e9-9175-1b6e87f40af7"
}

Примечание: Эндпоинт /uuid в httpbin возвращает случайный UUID. Наличие UUID в теле ответа означает, что запрос успешно дошёл до httpbin.

В Gateway API для сопоставления пути по регулярному выражению можно воспользоваться HTTP path match с типом RegularExpression. Совпадения типа RegularExpression определяются реализацией, поэтому уточните у своей реализации Gateway API, какова семантика такого сопоставления. Популярные реализации Gateway API на базе Envoy — например, Istio, Envoy Gateway и Kgateway — выполняют полное совпадение с учётом регистра.

Таким образом, если вы не знали, что шаблоны Ingress-NGINX работают как нечувствительные к регистру префиксы, и при этом клиенты или приложения отправляют запросы на /uuid (или /uuid/some/other/path), вы можете создать такой HTTPRoute:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: regex-match-route
spec:
  hostnames:
  - regex-match.example.com
  parentRefs:
  - name: <your gateway> # Измените в соответствии с вашим сценарием
  rules:
  - matches:
    - path:
        type: RegularExpression
        value: "/[A-Z]{3}"
    backendRefs:
    - name: httpbin
      port: 8000

Однако если ваша реализация Gateway API выполняет полное совпадение с учётом регистра, этот HTTPRoute не будет соответствовать запросу с путём /uuid. Это приведёт к сбою: запросы, которые Ingress-NGINX ранее направлял в httpbin, получат ответ 404 Not Found на шлюзе (gateway).

Чтобы сохранить нечувствительное к регистру совпадение по регулярному выражению, используйте следующий HTTPRoute:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: regex-match-route
spec:
  hostnames:
  - regex-match.example.com
  parentRefs:
  - name: <your gateway> # Измените в соответствии с вашим сценарием
  rules:
  - matches:
    - path:
        type: RegularExpression
        value: "/[a-zA-Z]{3}.*"
    backendRefs:
    - name: httpbin
      port: 8000

Как вариант, упомянутые прокси поддерживают флаг (?i) для указания нечувствительности к регистру. С его помощью шаблон будет выглядеть как (?i)/[a-z]{3}.*.

2. Аннотация nginx.ingress.kubernetes.io/use-regex распространяется на все пути хоста во всех Ingress-ресурсах (Ingress-NGINX)

Допустим, у вас есть Ingress с аннотацией nginx.ingress.kubernetes.io/use-regex: "true", но вы хотите направлять запросы с точным путём /headers к httpbin. При этом вы допустили опечатку и указали путь /Header вместо /headers.

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: regex-match-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: regex-match.example.com
    http:
      paths:
      - path: "<some regex pattern>"
        pathType: ImplementationSpecific
        backend:
          service:
            name: <your backend>
            port:
              number: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: regex-match-ingress-other
spec:
  ingressClassName: nginx
  rules:
  - host: regex-match.example.com
    http:
      paths:
      - path: "/Header" # опечатка: должно быть /headers
        pathType: Exact
        backend:
          service:
            name: httpbin
            port:
              number: 8000

Большинство ожидало бы, что запрос к /headers получит ответ 404 Not Found, так как /headers не совпадает точно с путём /Header при типе Exact. Однако поскольку Ingress regex-match-ingress содержит аннотацию nginx.ingress.kubernetes.io/use-regex: "true" и хост regex-match.example.com, все пути с хостом regex-match.example.com трактуются как регулярные выражения — во всех Ingress-ресурсах (Ingress-NGINX). Так как шаблоны регулярных выражений — это нечувствительные к регистру префиксные совпадения, /headers соответствует шаблону /Header, и Ingress-NGINX направляет такие запросы к httpbin. Выполнив команду:

curl -sS -H "Host: regex-match.example.com" http://<your-ingress-ip>/headers

вы получите вывод вида:

{
  "headers": {
    ...
  }
}

Примечание: Эндпоинт /headers в httpbin возвращает заголовки запроса. Наличие заголовков запроса в теле ответа означает, что запрос успешно дошёл до httpbin.

Gateway API не преобразует молчаливо совпадения типа Exact и Prefix в шаблоны регулярных выражений. Поэтому если вы перенесёте приведённые Ingress-ресурсы в следующий HTTPRoute, сохранив опечатку и типы совпадений, запросы к /headers будут получать ответ 404 Not Found вместо 200 OK.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: regex-match-route
spec:
  hostnames:
  - regex-match.example.com
  rules:
  ...
  - matches:
    - path:
        type: Exact
        value: "/Header"
    backendRefs:
    - name: httpbin
      port: 8000

Чтобы сохранить нечувствительное к регистру префиксное совпадение, замените:

- matches:
  - path:
      type: Exact
      value: "/Header"

на:

- matches:
  - path:
      type: RegularExpression
      value: "(?i)/Header"

Или, что ещё лучше, исправьте опечатку и укажите:

- matches:
  - path:
      type: Exact
      value: "/headers"

3. Аннотация rewrite-target неявно включает режим регулярных выражений

Предположим, вы хотите переписать путь запросов с /ip на /uuid перед отправкой в httpbin и, как в разделе 2, направлять запросы с точным путём /headers к httpbin. Однако вы случайно допускаете опечатки: указываете /IP вместо /ip и /Header вместо /headers.

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rewrite-target-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: "/uuid"
spec:
  ingressClassName: nginx
  rules:
  - host: rewrite-target.example.com
    http:
      paths:
      - path: "/IP"
        pathType: Exact
        backend:
          service:
            name: httpbin
            port:
              number: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rewrite-target-ingress-other
spec:
  ingressClassName: nginx
  rules:
  - host: rewrite-target.example.com
    http:
      paths:
      - path: "/Header"
        pathType: Exact
        backend:
          service:
            name: httpbin
            port:
              number: 8000

Аннотация nginx.ingress.kubernetes.io/rewrite-target: "/uuid" приводит к тому, что у запросов, соответствующих путям в Ingress rewrite-target-ingress, путь перезаписывается на /uuid перед маршрутизацией к бэкенду.

Несмотря на то что ни один из Ingress-ресурсов не содержит аннотацию nginx.ingress.kubernetes.io/use-regex: "true", наличие аннотации nginx.ingress.kubernetes.io/rewrite-target в Ingress rewrite-target-ingress приводит к тому, что все пути с хостом rewrite-target.example.com трактуются как шаблоны регулярных выражений. Иными словами, nginx.ingress.kubernetes.io/rewrite-target молчаливо добавляет аннотацию nginx.ingress.kubernetes.io/use-regex: "true" со всеми описанными выше побочными эффектами. Например, запрос к /ip получит перезаписанный путь /uuid, так как /ip соответствует нечувствительному к регистру префиксному шаблону /IP в Ingress rewrite-target-ingress. После выполнения команды:

curl -sS -H "Host: rewrite-target.example.com" http://<your-ingress-ip>/ip

вывод будет примерно таким:

{
  "uuid": "12a0def9-1adg-2943-adcd-1234aadfgc67"
}

Аналогично примеру с nginx.ingress.kubernetes.io/use-regex, Ingress-NGINX трактует пути других Ingress-ресурсов с хостом rewrite-target.example.com как нечувствительные к регистру префиксные шаблоны. Выполнив команду:

curl -sS -H "Host: rewrite-target.example.com" http://<your-ingress-ip>/headers

вы получите вывод вида:

{
  "headers": {
    ...
  }
}

Перезапись путей в Gateway API настраивается с помощью фильтра HTTP URL rewrite, который не преобразует молчаливо совпадения типа Exact и Prefix в шаблоны регулярных выражений. Однако если вы не знаете о побочных эффектах аннотации nginx.ingress.kubernetes.io/rewrite-target и не осознаёте, что /Header и /IP — опечатки, вы можете создать следующий HTTPRoute:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rewrite-target-route
spec:
  hostnames:
  - rewrite-target.example.com
  parentRefs:
  - name: <your-gateway>
  rules:
  - matches:
    - path:
        type: Exact
        value: "/IP"
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplaceFullPath
          replaceFullPath: /uuid
    backendRefs:
    - name: httpbin
      port: 8000
  - matches:
    - path:
        # Это точное совпадение, независимо от других правил
        type: Exact
        value: "/Header"
    backendRefs:
    - name: httpbin
      port: 8000

Как и в разделе 2, поскольку /IP теперь является совпадением типа Exact в вашем HTTPRoute, запросы к /ip будут получать ответ 404 Not Found вместо 200 OK. Аналогично запросы к /headers тоже будут получать 404 Not Found вместо 200 OK. Таким образом, этот HTTPRoute нарушит работу приложений и клиентов, использующих маршруты /ip и /headers.

Чтобы исправить это, можно заменить типы совпадений в HTTPRoute на регулярные выражения и сделать шаблоны нечувствительными к регистру префиксными совпадениями:

- matches:
  - path:
      type: RegularExpression
      value: "(?i)/IP.*"
...
- matches:
  - path:
      type: RegularExpression
      value: "(?i)/Header.*"

Или сохранить тип совпадения Exact и исправить опечатки.

4. Запросы без завершающего слэша перенаправляются на тот же путь с завершающим слэшем

Рассмотрим следующий Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: trailing-slash-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: trailing-slash.example.com
    http:
      paths:
      - path: "/my-path/"
        pathType: Exact
        backend:
          service:
            name: <your-backend>
            port:
              number: 8000

Можно было бы ожидать, что Ingress-NGINX ответит на запрос к /my-path кодом 404 Not Found, так как /my-path не совпадает точно с путём /my-path/ при типе Exact. Однако Ingress-NGINX перенаправляет запрос на /my-path/ с кодом 301 Moved Permanently, поскольку единственное различие между /my-path и /my-path/ — завершающий слэш.

curl -isS -H "Host: trailing-slash.example.com" http://<your-ingress-ip>/my-path

Вывод будет выглядеть так:

HTTP/1.1 301 Moved Permanently
...
Location: http://trailing-slash.example.com/my-path/
...

То же самое происходит при изменении pathType на Prefix. Однако редирект не срабатывает, если путь задан шаблоном регулярного выражения.

Соответствующие стандарту реализации Gateway API не настраивают никаких редиректов молчаливо. Если клиенты или сервисы рассчитывают на этот редирект, миграция на Gateway API без явной настройки перенаправления запросов приведёт к сбою: запросы к /my-path будут получать ответ 404 Not Found вместо 301 Moved Permanently. Редирект можно настроить явно с помощью фильтра HTTP request redirect:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: trailing-slash-route
spec:
  hostnames:
  - trailing-slash.example.com
  parentRefs:
  - name: <your-gateway>
  rules:
  - matches:
    - path:
        type: Exact
        value: "/my-path"
    filters:
      requestRedirect:
        statusCode: 301
        path:
          type: ReplaceFullPath
          replaceFullPath: /my-path/
  - matches:
    - path:
        type: Exact # или Prefix
        value: "/my-path/"
    backendRefs:
    - name: <your-backend>
      port: 8000

5. Ingress-NGINX нормализует URL

Нормализация URL (URL normalization) — это процесс приведения URL к канонической форме перед сопоставлением с правилами Ingress и маршрутизацией. Детали нормализации URL описаны в RFC 3986, раздел 6.2; примеры преобразований:

  • удаление сегментов пути, равных .: my/./path → my/path

  • удаление предыдущего сегмента при наличии ..: my/../path → /path

  • устранение дублирующихся слэшей в пути: my//path → my/path

Ingress-NGINX нормализует URL перед сопоставлением с правилами Ingress. Рассмотрим следующий Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-normalization-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: path-normalization.example.com
    http:
      paths:
      - path: "/uuid"
        pathType: Exact
        backend:
          service:
            name: httpbin
            port:
              number: 8000

Ingress-NGINX нормализует путь следующих запросов до /uuid. После этого запрос соответствует пути /uuid с типом Exact, и Ingress-NGINX возвращает либо ответ 200 OK, либо перенаправление 301 Moved Permanently на /uuid.

Для следующих команд:

curl -sS -H "Host: path-normalization.example.com" http://<your-ingress-ip>/uuid
curl -sS -H "Host: path-normalization.example.com" http://<your-ingress-ip>/ip/abc/../../uuid
curl -sSi -H "Host: path-normalization.example.com" http://<your-ingress-ip>////uuid

выводы будут примерно такими:

{
  "uuid": "29c77dfe-73ec-4449-b70a-ef328ea9dbce"
}
{
  "uuid": "d20d92e8-af57-4014-80ba-cf21c0c4ffae"
}
HTTP/1.1 301 Moved Permanently
...
Location: /uuid
...

Ваши бэкенды могут рассчитывать на нормализацию URL на стороне Ingress или реализации Gateway API. При этом большинство реализаций Gateway API по умолчанию выполняют некоторую нормализацию путей. Например, Istio, Envoy Gateway и Kgateway нормализуют сегменты . и .. из коробки. За подробностями обращайтесь к документации используемой вами реализации Gateway API.

Заключение

Пока мы все спешим отреагировать на завершение поддержки Ingress-NGINX, я надеюсь, что эта статья поможет вам почувствовать уверенность в том, что миграция может быть проведена безопасно и эффективно — несмотря на все тонкости Ingress-NGINX.

SIG Network также работает над поддержкой наиболее распространённых аннотаций Ingress-NGINX (и некоторых из этих неожиданных поведений) в Ingress2Gateway, чтобы помочь перевести конфигурацию Ingress-NGINX в Gateway API и предложить альтернативы для неподдерживаемых возможностей.

SIG Network выпустила Gateway API 1.5 сегодня (27 февраля 2026 года), выведя в статус стабильных такие функции, как ListenerSet (позволяет разработчикам приложений удобнее управлять TLS-сертификатами) и фильтр CORS для HTTPRoute, обеспечивающий настройку CORS.

Istio можно использовать исключительно как контроллер Gateway API, без прочих функций сервисной сетки (service mesh).

© 2026 meganuke