목록으로

Programming Notes

마이그레이션 전에: 반드시 알아야 할 Ingress-NGINX의 놀라운 동작 5가지

2025년 11월에 발표된 바와 같이, Kubernetes는 2026년 3월에 Ingress-NGINX를 지원 종료합니다. 널리 사용됨에도 불구하고, Ingress-NGINX에는 오늘날 여러분의 클러스터에 존재할 수 있는 예상치 못한 기본 설정과 부작용들이 가득합니다. 이...

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를 사용하면 정규식 경로 매칭을 위해 typeRegularExpressionHTTP 경로 매치를 사용할 수 있습니다. 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/HeaderExact 경로와 매치되지 않으므로, /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는 ExactPrefix 매치를 정규식 패턴으로 자동으로 변환하거나 해석하지 않습니다. 따라서 위 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 재작성 필터를 사용하여 경로 재작성을 구성할 수 있으며, 이 필터는 ExactPrefix 매치를 정규식 패턴으로 자동으로 변환하지 않습니다. 그러나 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 경로에서 /IPExact 매치 유형이므로, /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/
...

pathTypePrefix로 변경해도 동일하게 적용됩니다. 그러나 경로가 정규식 패턴이면 리디렉션이 발생하지 않습니다.

규격에 맞는 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로 정규화합니다. 이제 요청이 /uuidExact 경로와 매치되므로, 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 필터와 같은 기능들이 정식 기능으로 승격되었습니다.


  1. Istio를 다른 서비스 메시 기능 없이 순수하게 Gateway API 컨트롤러로 사용할 수 있습니다. ↩︎