Що потрібно знати перед міграцією: пʼять несподіваних особливостей поведінки 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 з 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 (Ingress-NGINX)

Тепер припустимо, що у вас є Ingress з анотацією nginx.ingress.kubernetes.io/use-regex: "true", але ви хочете маршрутизувати до httpbin запити зі шляхом, що точно дорівнює /headers. На жаль, ви зробили одруківку і встановили шлях /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 (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 у наступний маршрут 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, ви хочете маршрутизувати до httpbin запити зі шляхом, що точно дорівнює /headers. Однак ви випадково зробили одруківку і встановили шлях /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 інших обʼєктів Ingress з хостом 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
...

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

Підсумок

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

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

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


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