2025년 11월에 발표된 바와 같이, Kubernetes는 2026년 3월에 Ingress-NGINX를 지원 종료합니다. 널리 사용됨에도 불구하고, Ingress-NGINX에는 오늘날 여러분의 클러스터에 존재할 수 있는 예상치 못한 기본 설정과 부작용들이 가득합니다. 이 블로그는 이러한 동작들을 집중 조명하여 여러분이 안전하게 마이그레이션하고 어떤 동작들을 유지할지 신중하게 결정할 수 있도록 돕습니다. 이 글은 또한 Ingress-NGINX를 Gateway API와 비교하고 Gateway API에서 Ingress-NGINX 동작을 유지하는 방법을 보여줍니다. 각 섹션의 반복되는 위험 패턴은 동일합니다. 겉보기에 올바른 변환이라도 Ingress-NGINX의 특이점을 고려하지 않으면 여전히 서비스 중단을 초래할 수 있습니다.
독자 여러분이 Ingress-NGINX와 Ingress API에 대해 어느 정도 알고 계신다고 가정합니다. 대부분의 예시에서는 httpbin을 백엔드로 사용합니다.
또한, Ingress-NGINX와 NGINX Ingress는 두 개의 별개의 Ingress 컨트롤러라는 점에 유의하십시오. Ingress-NGINX는 Kubernetes 커뮤니티에서 유지 관리하며 2026년 3월에 지원이 종료되는 Ingress 컨트롤러입니다. NGINX Ingress는 F5에서 제공하는 Ingress 컨트롤러입니다. 둘 다 NGINX를 데이터 플레인으로 사용하지만, 그 외에는 관련이 없습니다. 이후부터 이 블로그 게시물은 Ingress-NGINX에 대해서만 다룹니다.
1. 정규식 매치는 접두사 기반이며 대소문자를 구분하지 않습니다
경로가 세 개의 대문자로만 구성된 모든 요청을 httpbin 서비스로 라우팅하려고 한다고 가정해 봅시다. nginx.ingress.kubernetes.io/use-regex: "true" 어노테이션과 /[A-Z]{3} 정규식 패턴을 사용하여 다음 Ingress를 생성할 수 있습니다.
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"
}
참고: httpbin의 /uuid 엔드포인트는 무작위 UUID를 반환합니다. 응답 본문에 UUID가 있다는 것은 요청이 httpbin으로 성공적으로 라우팅되었음을 의미합니다.
Gateway API를 사용하면 정규식 경로 매칭을 위해 type이 RegularExpression인 HTTP 경로 매치를 사용할 수 있습니다. RegularExpression 매치는 구현에 따라 다르므로, Gateway API 구현에서 RegularExpression 매칭의 의미론을 확인하십시오. Istio[^1], Envoy Gateway, Kgateway와 같은 인기 있는 Envoy 기반 Gateway API 구현은 완전한 대소문자 구분 매치를 수행합니다.
따라서 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) Ingress에 걸쳐 호스트의 모든 경로에 적용됩니다
이제 nginx.ingress.kubernetes.io/use-regex: "true" 어노테이션이 있는 Ingress가 있지만, 정확히 /headers 경로를 가진 요청을 httpbin으로 라우팅하려고 한다고 가정해 봅시다. 불행히도, /headers 대신 /Header로 경로를 오타했습니다.
---
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가 /Header의 Exact 경로와 매치되지 않으므로, /headers에 대한 요청이 404 Not Found로 응답할 것으로 예상할 것입니다. 그러나 regex-match-ingress 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": {
...
}
}
참고: httpbin의 /headers 엔드포인트는 요청 헤더를 반환합니다. 응답 본문에 요청 헤더가 포함되어 있다는 것은 요청이 httpbin으로 성공적으로 라우팅되었음을 의미합니다.
Gateway API는 Exact 및 Prefix 매치를 정규식 패턴으로 자동으로 변환하거나 해석하지 않습니다. 따라서 위 Ingress들을 다음 HTTP 경로로 변환하고 오타와 매치 유형을 유지한다면, /headers에 대한 요청은 200 OK 대신 404 Not Found로 응답할 것입니다.
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. 재작성 대상은 정규식을 의미합니다
이 경우, httpbin으로 라우팅하기 전에 /ip 경로를 가진 요청의 경로를 /uuid로 재작성하고 싶다고 가정하고, 섹션 2에서처럼 정확히 /headers 경로를 가진 요청을 httpbin으로 라우팅하고 싶다고 가정합니다. 그러나 /ip 대신 /IP로, /headers 대신 /Header로 실수로 오타를 냅니다.
---
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" 어노테이션은 rewrite-target-ingress Ingress의 경로와 매치되는 요청의 경로를 백엔드로 라우팅하기 전에 /uuid로 재작성하도록 합니다.
어떤 Ingress에도 nginx.ingress.kubernetes.io/use-regex: "true" 어노테이션이 없더라도, rewrite-target-ingress Ingress에 nginx.ingress.kubernetes.io/rewrite-target 어노테이션이 존재하면 rewrite-target.example.com 호스트를 가진 모든 경로가 정규식 패턴으로 처리됩니다. 다시 말해, nginx.ingress.kubernetes.io/rewrite-target는 위에서 논의된 모든 부작용과 함께 nginx.ingress.kubernetes.io/use-regex: "true" 어노테이션을 묵시적으로 추가합니다.
예를 들어, /ip에 대한 요청은 rewrite-target-ingress Ingress에서 /IP의 대소문자를 구분하지 않는 접두사 패턴과 매치되므로 경로가 /uuid로 재작성됩니다. 다음 명령을 실행한 후
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는 rewrite-target.example.com 호스트를 가진 다른 Ingress의 path를 대소문자를 구분하지 않는 접두사 패턴으로 처리합니다. 다음 명령을 실행하면
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와 마찬가지로, 이제 HTTP 경로에서 /IP가 Exact 매치 유형이므로, /ip에 대한 요청은 200 OK 대신 404 Not Found로 응답할 것입니다. 마찬가지로, /headers에 대한 요청도 200 OK 대신 404 Not Found로 응답할 것입니다. 따라서 이 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
my-path는 /my-path/의 Exact 경로와 정확히 매치되지 않으므로, Ingress-NGINX가 /my-path에 404 Not Found로 응답할 것으로 예상할 수 있습니다. 그러나 Ingress-NGINX는 /my-path와 /my-path/의 유일한 차이가 후행 슬래시이기 때문에 요청을 301 Moved Permanently와 함께 /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에 대한 요청이 301 Moved Permanently 대신 404 Not Found로 응답하게 되어 서비스 중단을 초래할 것입니다. 다음처럼 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는 Ingress 규칙과 매치시키기 전에 URL을 정규화합니다. 예를 들어, 다음 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 응답 또는 /uuid로의 301 Moved Permanently 리디렉션으로 응답합니다.
다음 명령들에 대해
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은 또한 Ingress2Gateway에서 가장 일반적인 Ingress-NGINX 어노테이션(및 이러한 예상치 못한 동작 중 일부)을 지원하기 위해 노력해 왔으며, Ingress-NGINX 구성을 Gateway API로 변환하고 지원되지 않는 동작에 대한 대안을 제공하고 있습니다.
SIG Network은 오늘(2026년 2월 27일) 일찍이 Gateway API 1.5를 출시했는데, 여기에는 ListenerSet (앱 개발자가 TLS 인증서를 더 잘 관리할 수 있도록 함) 및 CORS 구성을 허용하는 HTTPRoute CORS 필터와 같은 기능들이 정식 기능으로 승격되었습니다.
-
Istio를 다른 서비스 메시 기능 없이 순수하게 Gateway API 컨트롤러로 사용할 수 있습니다. ↩︎