Tech_News

ingress-nginx 은퇴, 이제 뭘 써야 하나: Gateway API·Contour·Traefik 실전 마이그레이션

TeEm0 2026. 7. 11. 09:00
728x90

1. 왜 지금 이 얘기가 나오는가

2026년 3월, Kubernetes SIG Network가 관리하던 ingress-nginx 컨트롤러가 공식 은퇴했다. 여기서 오해하면 안 되는 게 하나 있다. Ingress API 자체가 없어지는 게 아니다. Ingress 리소스(networking.k8s.io/v1)는 여전히 살아있고 잘 동작한다. 은퇴하는 건 커뮤니티가 유지보수하던 ingress-nginx 컨트롤러 구현체다.

이게 왜 실무에서 큰 문제냐면, 국내 대부분의 온프렘·EKS·GKE 클러스터가 사실상 이 컨트롤러 하나로 트래픽을 다 받고 있기 때문이다. Helm chart 한 번 깔고 nginx.ingress.kubernetes.io/* 어노테이션으로 리라이트, 타임아웃, 인증서 설정 다 해온 팀이 압도적으로 많다. 은퇴 이후 상황을 원문은 이렇게 정리한다:

  • CVE 미패치 — 새 취약점이 터져도 공식 패치가 안 나온다. 인그레스는 클러스터 진입점이라 이게 제일 무섭다.
  • 기능 업데이트 중단 — 새 Kubernetes 버전과의 호환성 검증도 더 이상 보장 안 된다.
  • 커뮤니티 지원 종료 — 이슈 올려도 답이 안 온다.

당장 클러스터가 죽는 건 아니다. 하지만 "동작하니까 놔둔다"는 시한폭탄이다. 특히 금융·공공처럼 보안 감사받는 환경이면 미패치 CVE가 감사 지적사항으로 바로 걸린다.

2. 먼저 우리 클러스터 영향 범위부터 파악하자

대안을 고르기 전에, 지금 뭘 얼마나 쓰고 있는지 진단부터 해야 한다. 마이그레이션 난이도는 순전히 proprietary 어노테이션을 얼마나 썼느냐에 달려있다.

먼저 클러스터에서 nginx 어노테이션을 쓰는 인그레스가 몇 개나 되는지 훑어보자.

kubectl get ingress -A -o json \
  | jq -r '.items[]
    | select(.metadata.annotations != null)
    | .metadata.namespace + "/" + .metadata.name + " -> " +
      (.metadata.annotations | keys | map(select(startswith("nginx.ingress.kubernetes.io"))) | join(", "))'

실제 출력은 이런 식으로 나온다:

default/web-app -> nginx.ingress.kubernetes.io/rewrite-target, nginx.ingress.kubernetes.io/ssl-redirect
payment/api-gw -> nginx.ingress.kubernetes.io/proxy-body-size, nginx.ingress.kubernetes.io/proxy-read-timeout, nginx.ingress.kubernetes.io/configuration-snippet
monitoring/grafana -> nginx.ingress.kubernetes.io/auth-type, nginx.ingress.kubernetes.io/auth-secret

여기서 configuration-snippet이나 server-snippet이 보이면 긴장해야 한다. 이건 raw nginx 설정을 그대로 박아넣은 거라서, 어떤 대안 컨트롤러로 가든 1:1 자동 변환이 안 된다. 손으로 다시 짜야 한다. 이게 마이그레이션에서 제일 시간 잡아먹는 부분이다.

어노테이션 사용 빈도를 카운트해서 우선순위를 매기면 계획 세우기 편하다:

kubectl get ingress -A -o json \
  | jq -r '.items[].metadata.annotations // {} | keys[]' \
  | grep '^nginx.ingress.kubernetes.io' \
  | sort | uniq -c | sort -rn
     14 nginx.ingress.kubernetes.io/ssl-redirect
     11 nginx.ingress.kubernetes.io/rewrite-target
      8 nginx.ingress.kubernetes.io/proxy-body-size
      3 nginx.ingress.kubernetes.io/configuration-snippet
      1 nginx.ingress.kubernetes.io/auth-url

이 표만 봐도 "snippet 3개, auth 1개만 손보면 나머지는 표준 매핑으로 넘어가겠구나"가 보인다.

3. 대안 컨트롤러 비교와 두 갈래 길

원문은 큰 틀에서 두 가지 경로를 제시한다. 여기에 실무에서 자주 후보로 오르는 Traefik, Nginx Ingress Operator까지 얹어서 정리한다.

Path A: Lift-and-Shift — Ingress API 유지하고 컨트롤러만 교체

기존 Ingress YAML은 그대로 두고, ingressClass만 다른 Envoy 기반 컨트롤러(원문에서는 Contour를 예로 듦)로 바꾸는 방식이다. 라우팅 정의를 갈아엎지 않으니 당장의 충격이 적다.

단, 원문이 명확히 짚는 함정이 있다. base Ingress 리소스는 그대로여도, nginx.ingress.kubernetes.io/* 어노테이션은 전부 무효가 된다. Contour는 이걸 자기 어노테이션이나 CRD(HTTPProxy)로 다시 만들어야 한다. Traefik으로 가도 마찬가지로 traefik.ingress.kubernetes.io/* 체계로 갈아타야 한다.

Path B: Gateway API로 아키텍처 현대화

Gateway API는 원문 표현으로 "Ingress의 upstream-backed 후계자"다. 핵심은 역할 분리(role-oriented design)다.

비유하자면 이렇다. 기존 Ingress는 인프라팀이 건물 전체 설계도(모놀리식 정의)를 혼자 들고 있는 구조였다. Gateway API는 이걸 나눈다:

  • 인프라팀Gateway 리소스로 "이 건물의 출입구(리스너, 포트, 인증서)"를 관리하고
  • 개발팀HTTPRoute로 "내 사무실로 가는 복도(경로 라우팅)"를 각자 관리한다.

원문에 따르면 Gateway API는 traffic splitting, 고급 header matching, 안전한 cross-namespace 라우팅을 코어 스펙에 내장하고 있다. ingress-nginx 시절 어노테이션 지옥으로 풀던 걸 표준 API로 처리한다는 게 가장 큰 차이다.

비교표 (원문 기준 + 실무 코멘트)

항목 Path A: Contour/Traefik (Ingress API) Path B: Gateway API
마이그레이션 공수 낮음~중간 (어노테이션 번역) 높음 (라우팅 매니페스트 전면 재작성)
운영 패러다임 단일 소유자 — Ops가 모놀리식 정의 관리 역할 기반 — Ops는 Gateway, Dev는 HTTPRoute
미래 대응성 낮음 — Ingress API는 feature-frozen 높음 — upstream 활발히 개발 중
기능 proprietary 어노테이션에 크게 의존 고급 기능이 코어 스펙에 내장

실무 코멘트: Nginx Ingress Operator나 NGINX가 상업적으로 관리하는 별도 컨트롤러 라인도 존재하는데, 이건 커뮤니티판 ingress-nginx와는 별개 프로젝트다. 다만 라이선스·지원 정책이 다를 수 있으니 도입 전 반드시 공식 문서 확인이 필요하다. 원문은 이 부분을 상세히 다루지 않으니 여기서 단정하지 않겠다.

4. 마이그레이션 아키텍처 설계 전략

원문이 제시한 마이그레이션 전략은 3단계다. 실무 관점 살을 붙였다.

① Audit (감사)

위 2번 섹션에서 뽑은 어노테이션 인벤토리가 곧 기술부채 목록이다. configuration-snippet, server-snippet, auth-url(외부 인증) 같은 건 자동 변환이 안 되니 별도 트랙으로 뺀다.

② Tooling — ingress2gateway

Gateway API로 갈 거라면 ingress2gateway로 기존 Ingress를 자동 변환할 수 있다. 완벽하진 않지만 표준 필드는 잘 옮겨준다.

ingress2gateway print --input-file=web-app-ingress.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: web-app-gateway
  namespace: default
spec:
  gatewayClassName: contour
  listeners:
  - name: http
    port: 80
    protocol: HTTP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-app
  namespace: default
spec:
  parentRefs:
  - name: web-app-gateway
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: web-app-svc
      port: 80

주의할 건, rewrite-target이나 커스텀 snippet은 이 도구가 변환 못 하고 조용히 빠뜨리는 경우가 있다. 변환 결과를 그대로 믿지 말고 반드시 diff 떠서 검증해야 한다.

③ Incremental Rollout (점진적 전환)

원문의 핵심 권고다. 새 컨트롤러를 기존 것과 병렬로 띄우고, 비핵심 워크로드부터 옮긴다. ingressClass를 분리하면 두 컨트롤러가 한 클러스터에 공존할 수 있다. 결제 API 같은 건 맨 마지막에 옮긴다.

5. 흔한 함정과 실제 에러들

함정 1: ingressClassName 안 걸어서 아무도 안 받음

새 컨트롤러 깔았는데 트래픽이 안 온다. Ingress 오브젝트에 ingressClassName이 비어있거나 옛날 값이면 새 컨트롤러가 무시한다. 병렬 운영 중엔 특히 자주 터진다. 확인:

kubectl get ingress -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,CLASS:.spec.ingressClassName

함정 2: snippet 어노테이션이 그냥 씹힌다

Contour나 Traefik으로 옮긴 뒤 로그를 보면 이런 게 뜬다:

Warning  IngressUpdate  ingress default/web-app: annotation "nginx.ingress.kubernetes.io/configuration-snippet" is not supported by this controller and will be ignored

에러가 아니라 warning이라 CI/CD에서 그냥 통과되고, 배포는 성공한 것처럼 보인다. 그런데 실제로는 rate limit이나 커스텀 헤더 설정이 통째로 날아간 상태다. 프로덕션에서 "왜 갑자기 헤더가 안 붙지?" 하고 새벽에 깨는 전형적인 케이스다.

함정 3: Gateway API CRD 안 깔고 HTTPRoute 적용

Gateway API는 CRD를 별도로 설치해야 한다. 안 깔고 매니페스트 적용하면:

error: resource mapping not found for name: "web-app" namespace: "default" from "httproute.yaml":
no matches for kind "HTTPRoute" in version "gateway.networking.k8s.io/v1"
ensure CRDs are installed first

CRD부터 설치해야 한다:

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml

(버전 번호는 예시다. 실제 최신 버전은 Gateway API 릴리스 페이지에서 확인 필요.)

함정 4: cross-namespace 참조가 막혀있다

Gateway API의 장점인 cross-namespace 라우팅은 기본적으로 차단돼 있다. 다른 네임스페이스의 Gateway를 HTTPRoute가 참조하려면 ReferenceGrant를 명시적으로 만들어야 한다. 이걸 몰라서 "HTTPRoute는 Accepted인데 트래픽이 안 온다"고 헤매는 경우가 많다. 상태를 보면 힌트가 나온다:

kubectl describe httproute web-app -n default
Status:
  Parents:
    Conditions:
      Type:     ResolvedRefs
      Status:   False
      Reason:   RefNotPermitted
      Message:  Cross-namespace reference to Gateway is not allowed; create a ReferenceGrant

6. 정리: 누가 어느 길로 가야 하나

한 줄 요약: ingress-nginx는 은퇴했지만 Ingress API는 살아있다. 급하면 Contour/Traefik으로 옆걸음(Path A), 여유 있고 플랫폼 현대화 계획이 있으면 Gateway API로 갈아타라(Path B).

  • 시간 없고 리팩터 인력 부족 → Path A (Contour/Traefik). 원문 표현대로 "stopgap", 즉 임시방편이다. Ingress API는 feature-frozen이라 언젠가는 또 옮겨야 한다. 하지만 미패치 CVE 리스크를 당장 없애면서 계획 세울 시간을 번다.
  • 이미 플랫폼 현대화 중이거나 멀티팀 운영 → Path B (Gateway API). 공수는 크지만 역할 분리와 upstream 지원이라는 장기적 가치가 크다. 한 번 제대로 옮기면 어노테이션 지옥에서 벗어난다.

어느 쪽이든 공통 원칙은 같다. 감사 → 병렬 배포 → 비핵심부터 점진 전환. 결제·인증 게이트웨이는 절대 첫 타자로 옮기지 말 것. 그리고 전환 후엔 각 컨트롤러의 4xx/5xx 비율, 응답 지연을 옛날 nginx 지표와 나란히 놓고 최소 며칠은 비교 모니터링하자. snippet으로 걸어뒀던 숨은 설정이 빠졌는지는 트래픽을 태워봐야 드러난다.

참고 자료

※ 버전 번호와 명령어 출력은 예시이며, 실제 적용 전 각 프로젝트 공식 릴리스 문서에서 최신 버전과 호환성을 반드시 확인하세요.

728x90