Що потрібно знати перед міграцією: пʼять несподіваних особливостей поведінки Ingress-NGINX

Як було оголошено в листопаді 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 — це контролер Ingress, який підтримується та керується спільнотою Kubernetes і який буде виведений з експлуатації в березні 2026 року. NGINX Ingress — це контролер Ingress від F5. Обидва використовують NGINX як рівень обробки даних, але в іншому не повʼязані між собою. Відтепер у цій публікації ми будемо обговорювати лише 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 з type типу RegularExpression для зіставлення шляхів за регулярним виразом. Зіставлення RegularExpression залежать від конкретної реалізації, тому перевірте реалізацію Gateway API, щоб переконатися в семантиці зіставлення RegularExpression. Популярні реалізації Gateway API на основі Envoy, такі як Istio1, Envoy Gateway та Kgateway, виконують повне співставлення з урахуванням регістру.

Отже, якщо ви не знаєте, що шаблони Ingress-NGINX є префіксами і не чутливі до регістру, і, не знаючи про це, клієнти або застосунки надсилають трафік до /uuid (або /uuid/some/other/path), ви можете створити такий маршрут HTTP.

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 виконує повне співставлення з урахуванням регістру, вищевказаний маршрут HTTP не буде відповідати запиту зі шляхом /uuid. Таким чином, вищевказаний маршрут HTTP спричинить збій, оскільки запити, які Ingress-NGINX маршрутизував до httpbin, завершаться з помилкою 404 Not Found на шлюзі.

Щоб зберегти співставлення регулярних виразів без урахування регістру, ви можете використовувати наступний маршрут HTTP.

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-NGINX) Ingresses

Тепер припустимо, що у вас є 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 не відповідає Exact шляху /Header. Однак, оскільки Ingress regex-match-ingress має анотацію nginx.ingress.kubernetes.io/use-regex: "true" і хост regex-match.example.com, всі шляхи з хостом regex-match.example.com розглядаються як регулярні вирази у всіх (Ingress-NGINX) Ingress. Оскільки шаблони регулярних виразів є префіксними збігами, що не чутливі до регістру, /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 як шаблони регулярних виразів. Тому, якщо ви перетворили вищезазначені Ingresses у наступний маршрут HTTP і зберегли типи помилок і збігів, запити до /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. Переписування цільового обʼєкта передбачає використання регулярного виразу

У цьому випадку припустимо, що ви хочете переписати шлях запитів із /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 обробляє path інших інгресів з хостом rewrite-target.example.com як префіксні шаблони, що не розрізняють регістр. Виконання команди

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

дає результат, який виглядає так

{
  "headers": {
    ...
  }
}

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

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 у вашому маршруті HTTP, запити до /ip будуть відповідати кодом 404 Not Found замість 200 OK. Аналогічно, запити до /headers також будуть відповідати кодом 404 Not Found замість 200 OK. Таким чином, цей маршрут HTTP порушить роботу застосунків і клієнтів, які покладаються на маршрути /ip і /headers.

Щоб виправити це, ви можете змінити збіги в маршруті HTTP на збіги регулярних виразів і змінити шаблони шляхів на збіги префіксів, що не чутливі до регістру, як показано нижче.

  - 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 не збігається з Exact шляхом /my-path/. Однак 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-запитів наступним чином:

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-адреси в канонічну форму перед її зіставленням з правилами 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. Тепер, коли запит відповідає шляху Exact /uuid, 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
...

Ваші бекенди можуть покладатися на реалізацію API Ingress/Gateway для нормалізації URL-адрес. При цьому більшість реалізацій API Gateway мають стагжартно увімкнену функцію нормалізації шляхів. Наприклад, Istio, Envoy Gateway та Kgateway нормалізують сегменти . та .. без додаткових налаштувань. Для отримання детальнішої інформації перегляньте документацію для кожної реалізації API Gateway, яку ви використовуєте.

Підсумок

Оскільки ми всі поспішаємо реагувати на виведення з експлуатації Ingress-NGINX, сподіваюся, що ця публікація додасть вам впевненості в тому, що ви зможете безпечно та ефективно здійснити міграцію, незважаючи на всі складнощі Ingress-NGINX.

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

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


  1. Ви можете використовувати Istio виключно як контролер Gateway API без інших функцій сервісної мережі. ↩︎