커뮤니티 유지보수 ingress-nginx 컨트롤러(kubernetes/ingress-nginx)는 2026년 3월부로 은퇴했다. 이후 신규 릴리스, 버그 수정, CVE 보안 패치가 전혀 나오지 않는다. 지금 이 컨트롤러를 그대로 두고 있다면 패치가 영원히 없는 취약점 위에 서비스를 올려둔 상태다. 대응은 두 갈래다. 단기로는 다른 Ingress 컨트롤러로 갈아타는 lift-and-shift, 장기로는 후속 표준인 Gateway API로 전환이다. 이 글은 무엇이 은퇴했고 무엇이 남는지 먼저 정리한 뒤, 실제 전환 순서와 명령을 다룬다.
1. 은퇴 대상과 남는 것 구분
이름이 비슷한 프로젝트가 여럿이라 혼동이 잦다. 먼저 대상을 정확히 못 박는다.
- 은퇴 대상: 쿠버네티스 커뮤니티가 유지하던
kubernetes/ingress-nginx.registry.k8s.io에서 받던 그 이미지다. 2026년 3월 이후 저장소는kubernetes-retired/조직으로 옮겨져 읽기 전용이 된다. 이미 배포된 것은 계속 동작하고 Helm 차트와 이미지 아티팩트도 남지만, 그 이상은 없다. - 은퇴 아님: F5/NGINX사가 상용으로 유지하는 NGINX Ingress Controller(
nginxinc/kubernetes-ingress). 별개 프로젝트이고 계속 유지된다. 헬름 차트 리포지터리와 CRD 이름이 다르다. - 은퇴 아님(기능 동결): 쿠버네티스의 Ingress API 자체.
networking.k8s.io/v1 Ingress리소스는 계속 지원된다. 다만 기능 동결(feature-frozen) 상태라 신규 기능은 Gateway API에서만 개발된다.
내 클러스터가 어느 쪽을 쓰는지부터 확인한다. 배포된 컨트롤러 이미지 출처를 보면 바로 갈린다.
$ kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.spec.containers[*].image}{"\n"}{end}' | grep -i ingress
ingress-nginx registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:...
registry.k8s.io/ingress-nginx/controller가 보이면 은퇴 대상이다. docker.io/nginx/nginx-ingress나 nginxinc/...라면 F5 상용판이라 이 글의 긴급 대응 대상이 아니다.
2. 은퇴한 컨트롤러를 그대로 두면 왜 위험한가
추상적인 경고가 아니다. ingress-nginx는 2025년 3월 IngressNightmare(CVE-2025-1974 등)를 겪었다. CVSS 9.8, 인증 없는 원격 코드 실행으로 클러스터 전체 탈취까지 가능한 치명 취약점이었다. 당시엔 패치가 나왔다.
핵심은 유지보수 종료 후다. 같은 급의 취약점이 또 나와도 이제는 공식 패치가 나오지 않는다. Ingress 컨트롤러는 클러스터 외부 트래픽이 처음 닿는 지점이라 공격 표면이 넓다. 그 지점에 패치되지 않는 소프트웨어를 두는 것은 시간 문제일 뿐인 리스크다. 쿠버네티스 스티어링/보안 대응 위원회가 별도 성명까지 낸 이유가 이것이다.
"지금 잘 돌아가는데"가 함정이다. 배포된 것은 계속 동작하므로 겉으로는 멀쩡해 보인다. 문제는 다음 CVE 공개일에 드러난다. 그날 대응할 수 없다.
3. 먼저 의존성 인벤토리부터
전환 경로를 고르기 전에 내 Ingress가 ingress-nginx에 얼마나 묶여 있는지 파악한다. 표준 Ingress 스펙만 쓰면 전환이 쉽고, nginx.ingress.kubernetes.io/* 전용 애노테이션이 많을수록 공수가 커진다. 특히 configuration-snippet, server-snippet 같은 raw nginx 설정 주입은 그 자체가 기술부채다. 어느 대체 컨트롤러에도 1:1 대응이 없다.
# 전체 Ingress에서 nginx 전용 애노테이션 집계
$ kubectl get ingress -A -o json \
| jq -r '.items[].metadata.annotations | keys[]' \
| grep '^nginx.ingress.kubernetes.io/' | sort | uniq -c | sort -rn
12 nginx.ingress.kubernetes.io/rewrite-target
7 nginx.ingress.kubernetes.io/ssl-redirect
4 nginx.ingress.kubernetes.io/proxy-body-size
2 nginx.ingress.kubernetes.io/configuration-snippet
configuration-snippet과 server-snippet이 몇 건인지가 난이도의 대부분을 결정한다. 이 두 건이 잡히면 해당 라우트는 수동 재설계 대상으로 따로 표시해 둔다. 나머지 rewrite, ssl-redirect, body-size 정도는 어느 컨트롤러든 대응 방법이 있다.
4. 경로 1: 대체 컨트롤러로 lift-and-shift (단기)
기존 Ingress YAML을 그대로 유지한 채 컨트롤러만 교체하는 방식이다. 라우팅 매니페스트를 다시 쓰지 않아도 되니 공수는 중간 이하다. 시간에 쫓기는 팀의 현실적인 임시방편(stopgap)으로 적합하다. Envoy 기반 Contour가 대표적이고, 그 외 대체 목록은 쿠버네티스 공식 문서에 정리돼 있다.
한 가지 함정. Ingress 표준 필드는 그대로 먹지만 nginx.ingress.kubernetes.io/* 전용 애노테이션은 자동으로 넘어가지 않는다. 컨트롤러마다 자체 애노테이션이나 CRD로 수동 번역해야 한다. 3절에서 뽑은 인벤토리가 여기서 작업 목록이 된다.
Contour를 예로 든 설치와 클래스 지정이다.
$ helm repo add contour https://projectcontour.github.io/helm-charts/ $ helm install contour contour/contour -n projectcontour --create-namespace $ kubectl get ingressclass NAME CONTROLLER PARAMETERS AGE contour projectcontour.io/contour <none> 40s nginx k8s.io/ingress-nginx <none> 210d
전환은 한 번에 하지 않는다. 두 컨트롤러를 병존시키고 Ingress의 ingressClassName을 비핵심 워크로드부터 하나씩 contour로 바꿔 검증한다.
$ kubectl patch ingress my-app -n staging \
--type=merge -p '{"spec":{"ingressClassName":"contour"}}'
ingress.networking.k8s.io/my-app patched
여기서 한 번 걸리는 지점이 있다. ssl-redirect나 rewrite-target이 붙은 라우트는 클래스만 바꾸면 동작이 달라지거나 무시된다. Contour는 이를 HTTPProxy CRD나 자체 애노테이션으로 처리하므로, 클래스 전환과 애노테이션 번역을 한 라우트 단위로 묶어서 하나씩 확인해야 한다. 전체를 sed로 일괄 치환하면 이 라우트들에서 조용히 깨진다.
5. 경로 2: Gateway API로 전환 (장기 권장)
Gateway API는 Ingress의 상위 표준 후속이다. 쿠버네티스 공식 은퇴 공지도 1순위로 이쪽을 권한다. 신규 기능 개발이 여기서만 진행되므로 방향은 명확하다. 대신 라우팅 매니페스트를 재작성해야 해서 공수는 경로 1보다 크다.
구조가 Ingress와 다르다. 역할이 세 리소스로 분리된다.
- GatewayClass: 어떤 구현체(컨트롤러)를 쓸지. 인프라 담당 영역.
- Gateway: 실제 리스너(포트, 프로토콜, TLS). 클러스터 운영자 영역.
- HTTPRoute: 경로/호스트 매칭과 백엔드 라우팅. 앱 개발자 영역.
이 역할 분리 덕에 앱 팀이 인프라 리스너를 건드리지 않고 자기 라우트만 관리한다. 트래픽 분할(가중치), 헤더 매칭, 크로스 네임스페이스 라우팅이 애노테이션 땜질이 아니라 표준 필드로 들어온다. snippet으로 우겨넣던 것들이 정식 스펙이 됐다.
먼저 CRD를 설치한다. Gateway API는 클러스터에 기본 포함되지 않는다.
$ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yaml customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created $ kubectl get crd | grep gateway.networking.k8s.io gatewayclasses.gateway.networking.k8s.io 2026-07-16T... gateways.gateway.networking.k8s.io 2026-07-16T... httproutes.gateway.networking.k8s.io 2026-07-16T...
버전 번호는 릴리스 상태에 따라 다를 수 있으니 릴리스 페이지에서 최신 안정판을 확인한다. 최소 하나의 Gateway API 구현체(Contour, Istio, Envoy Gateway 등)를 함께 배포해야 라우트가 실제로 서빙된다. CRD만으로는 트래픽이 흐르지 않는다.
HTTPRoute는 이런 모양이다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: staging
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: my-app-svc
port: 8080
6. ingress2gateway로 초벌 변환
Ingress에서 Gateway API로 매니페스트를 손으로 다 옮기지 않아도 된다. 공식 변환 도구 ingress2gateway가 기존 Ingress 리소스를 Gateway/HTTPRoute로 초벌 변환한다. 완성본이 아니라 초안 생성기로 쓰는 게 맞다.
$ ingress2gateway print --input-file ingress.yaml --providers ingress-nginx apiVersion: gateway.networking.k8s.io/v1 kind: Gateway ... --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute ...
클러스터에서 직접 읽어 변환할 수도 있다.
$ ingress2gateway print --providers ingress-nginx --namespace staging > gateway-out.yaml
변환되지 않는 것을 반드시 확인한다. 표준 필드는 잘 넘어오지만 configuration-snippet 같은 비표준 애노테이션은 대응 리소스가 없어 누락되거나 경고로 표시된다. 도구가 조용히 버리는 부분이 실제 서비스 동작이었을 수 있으니, 변환 후 원본 Ingress와 라우트별로 대조하는 과정을 빼면 안 된다.
7. 권장 전환 전략
시간과 팀 여력에 따라 갈린다.
- 당장 CVE 리스크만 끊어야 하고 재작성할 여력이 없다 → 경로 1(대체 컨트롤러). 매니페스트를 다시 쓰지 않는 임시방편이다.
- 중장기 현대화를 계획 중이고 라우팅을 정리할 의지가 있다 → 경로 2(Gateway API). 어차피 신규 기능은 이쪽에만 온다.
어느 쪽이든 순서는 같다. (1) 3절의 의존성 인벤토리로 snippet 등 기술부채를 먼저 드러낸다. (2) 스테이징의 비핵심 워크로드부터 전환해 검증한다. (3) 검증이 끝난 뒤 핵심 트래픽을 옮긴다. 컨트롤러를 병존시켜 두고 라우트 단위로 넘기면 롤백 지점이 명확해진다. 한 번에 전체를 갈아엎지 않는 것이 이 마이그레이션의 핵심 원칙이다.