Що потрібно знати перед міграцією: пʼять несподіваних особливостей поведінки 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.
Ви можете використовувати Istio виключно як контролер Gateway API без інших функцій сервісної мережі. ↩︎