3. 입구 ① — 외부 IP와 라우팅
2026년 온프렘 입구의 답은 바뀌었다 — ingress-nginx는 은퇴했고 Gateway API가 그 자리다
이 장에서 처음 나오는 말6개
MetalLB- 온프렘에서
type: LoadBalancer를 성립시키는 구현체. IP 풀에서 IP를 할당하고 사내 네트워크에 광고한다. 트래픽을 직접 나르지는 않는다. ARPAddress Resolution Protocol- 같은 네트워크 안에서 "이 IP는 이 장비 것"이라고 알려 주는 프로토콜. MetalLB L2 모드가 이걸로 IP를 광고한다.
BGPBorder Gateway Protocol- 라우터끼리 "이 대역은 나를 거쳐 가라"고 알리는 라우팅 프로토콜. MetalLB BGP 모드는 사내 라우터와 이 관계를 맺는다.
Ingress- HTTP(S) 라우팅을 선언하는 쿠버네티스 기본 리소스. 표준이 빈약해 컨트롤러마다 annotation으로 기능을 덧붙였고, 그 파편화가 Gateway API가 나온 이유다.
Gateway API- Ingress를 대체하는 새 표준. 역할별로 리소스를 나눈다 — 인프라팀의
GatewayClass·Gateway, 앱팀의HTTPRoute. v1.5가 2026-02에 나왔다. ingress2gateway- Ingress 리소스를 Gateway API 리소스로 변환해 주는 공식 도구. 1.0이 2026-03에 나왔다.
문제 — EXTERNAL-IP가 영원히 <pending>이다
섹션 제목: “문제 — EXTERNAL-IP가 영원히 <pending>이다”밖에서 서비스에 닿으려면 ① 외부 IP가 붙고 ② 그 IP로 온 트래픽이 노드에 도착하고 ③ HTTP 규칙에 따라 백엔드로 갈라져야 한다. 클라우드는 ①②를 관리형 LB가, ③을 ALB나 Ingress 컨트롤러가 했다. 온프렘에는 ①②를 할 주체가 아예 없다.
kubectl get svc -A | grep LoadBalancer# NAMESPACE NAME TYPE EXTERNAL-IP PORT(S)# gateway envoy LoadBalancer <pending> 80:31234/TCP,443:31235/TCP# ^^^^^^^^^ 아무도 IP를 안 준다NodePort로 우회할 수는 있지만 포트가 30000번대라 사용자에게 줄 수 없고,
노드 IP를 직접 알려 주면 그 노드가 죽는 순간 서비스가 끊긴다. 제대로 채워야 한다.
①② IP 할당과 광고 — MetalLB
섹션 제목: “①② IP 할당과 광고 — MetalLB”MetalLB는 LoadBalancer 서비스를 감시하다가 미리 정해 둔 IP 풀에서 IP를 하나 꺼내
주고, 그 IP를 사내 네트워크에 광고한다.
# IP 풀 — 노드와 같은 L2 대역이면서 DHCP가 나눠주지 않는 예약 구간이어야 한다apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: edge-pool namespace: metallb-systemspec: addresses: - 192.168.10.240-192.168.10.250---apiVersion: metallb.io/v1beta1kind: L2Advertisementmetadata: name: edge-l2 namespace: metallb-systemspec: ipAddressPools: [edge-pool]구성 요소는 둘이고 둘 다 클러스터 안의 평범한 파드다 (노드에 패키지로 깔지 않는다).
| 구성 | 형태 | 하는 일 |
|---|---|---|
| controller | Deployment 1개 | 풀에서 IP를 할당 |
| speaker | DaemonSet (모든 노드) | 그 IP를 광고(ARP 또는 BGP). hostNetwork로 동작 |
L2와 BGP — 무엇이 다른가
섹션 제목: “L2와 BGP — 무엇이 다른가”| L2 (ARP) | BGP | |
|---|---|---|
| 광고 방식 | 한 노드가 “이 IP는 내 것”이라고 ARP 응답 | 노드들이 사내 라우터와 피어링 |
| 분산 | 안 된다 — 한 IP는 노드 1대가 전부 받는다 | ECMP로 여러 노드에 실제 분산 |
| 장애 시 | 다른 노드가 그 IP를 승계(페일오버) | 라우터가 살아 있는 경로로 |
| 요구 | 같은 L2 세그먼트면 끝 | BGP를 말할 수 있는 사내 라우터 |
| 언제 | 대부분의 시작점 | 규모가 커지거나 단일 노드 대역폭이 한계일 때 |
③ HTTP 라우팅 — 2026년에 바뀐 답
섹션 제목: “③ HTTP 라우팅 — 2026년에 바뀐 답”여기가 이 장의 본론이다. 오랫동안 온프렘의 표준 답이던 ingress-nginx가 은퇴했다.
| 시점 | 일어난 일 |
|---|---|
| 2025-11 | 쿠버네티스 프로젝트가 ingress-nginx 은퇴 예고 — 소수 자원봉사자에 의존하던 유지보수가 한계에 도달 |
| 2026-01 | Steering·Security Response 위원회 공식 성명 |
| 2026-03 | 은퇴 — 릴리스 없음, 버그픽스 없음, 보안 취약점이 나와도 패치 없음 |
| — | 후속으로 예고됐던 InGate도 성숙하지 못해 함께 정리됐다 |
결정표 — 지금 무엇을 골라야 하나
섹션 제목: “결정표 — 지금 무엇을 골라야 하나”-
지금 새로 세우는 중이다 → Gateway API + 구현체로 바로 간다. Ingress를 새로 배우고 나중에 또 옮길 이유가 없다.
-
ingress-nginx가 이미 떠 있고 규칙이 몇 개다 →
ingress2gateway로 변환해 옮긴다.터미널 창 # 클러스터의 Ingress를 읽어 Gateway API 리소스로 변환해 출력한다ingress2gateway print --namespace prod -
ingress-nginx가 떠 있고 규칙이 수백 개다 → 두 입구를 한동안 병행한다. 새 서비스는 Gateway로, 기존은 순차 이전. 그 사이 ingress-nginx는 관리 대역에서만 접근 가능하게 좁히거나 앞에 다른 계층을 둔다.
-
어느 구현체를 고를지 못 정했다 → 아래 표.
| 구현체 | 성격 | 온프렘에서 |
|---|---|---|
| Envoy Gateway | Envoy 기반, 벤더 중립. Gateway API를 1급으로 지원하고 mTLS·JWT 검증·rate limit이 내장 | 서비스 메시 없이 기능을 다 쓰고 싶을 때. 가장 무난한 기본값 |
| NGINX Gateway Fabric | NGINX의 Gateway API 네이티브 후속작 | ingress-nginx의 nginx 설정 감각을 이어가고 싶을 때 |
| Traefik | 가볍고 운영이 단순. Gateway API 직접 지원 | 소·중 규모 클러스터, 개발 친화 |
| Cilium Gateway | CNI가 곧 게이트웨이 | 이미 Cilium을 CNI로 쓰고 있을 때 (2장) |
| Istio | 메시의 게이트웨이 | 이미 메시를 도입했을 때만. 게이트웨이만 쓰려고 들이지 않는다 |
Gateway API가 Ingress와 다른 점 — 역할 분리
섹션 제목: “Gateway API가 Ingress와 다른 점 — 역할 분리”Ingress의 진짜 문제는 표현력이 아니라 한 리소스에 인프라 설정과 앱 라우팅이 섞여 있던 것이다. 그래서 컨트롤러마다 annotation으로 기능을 덧붙였고, 그 annotation은 컨트롤러를 바꾸는 순간 전부 무효였다.
| Ingress | Gateway API | 무엇이 나아지나 |
|---|---|---|
Ingress 하나에 전부 | GatewayClass / Gateway / HTTPRoute로 분리 | 앱팀이 TLS·리스너를 건드릴 일이 없다 |
| annotation으로 기능 확장 | 표준 필드 (헤더 조작 · 트래픽 분할 · 재시도) | 구현체를 바꿔도 대부분 그대로 |
| 네임스페이스 넘는 라우팅이 애매 | allowedRoutes · ReferenceGrant로 명시 허용 | 누가 어디로 붙일 수 있는지가 선언된다 |
| HTTP 중심 | HTTPRoute · GRPCRoute · TCPRoute · TLSRoute | gRPC·TCP도 같은 모델로 |
최소 구성 예제
섹션 제목: “최소 구성 예제”apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: name: edge namespace: gateway-systemspec: gatewayClassName: envoy-gateway # 설치한 구현체가 등록한 클래스 listeners: - name: https protocol: HTTPS port: 443 hostname: "*.example.internal" tls: mode: Terminate certificateRefs: - name: wildcard-tls # cert-manager가 채운다 (4장) allowedRoutes: namespaces: from: Selector # 아무 네임스페이스나 붙지 못하게 selector: matchLabels: gateway-access: "true"이 Gateway의 서비스가 곧 MetalLB에게 IP를 받는 type: LoadBalancer다 —
구현체가 자동으로 만들어 준다.
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: grafana namespace: observabilityspec: parentRefs: - name: edge namespace: gateway-system hostnames: ["grafana.example.internal"] rules: - matches: - path: { type: PathPrefix, value: / } backendRefs: - name: grafana port: 3000앱팀은 자기 네임스페이스의 HTTPRoute만 만든다. 인증서도 포트도 건드리지 않는다.
전체 경로 — 사용자 요청이 지나는 길
섹션 제목: “전체 경로 — 사용자 요청이 지나는 길”패킷이 실제로 통과하는 파드는 Gateway와 백엔드 둘뿐이다. MetalLB도 Service도 길을 깔아 주는 장치일 뿐 경로상의 홉이 아니다 — 장애를 가를 때 이 구분이 중요하다.
접근 제한 — 두 겹으로 건다
섹션 제목: “접근 제한 — 두 겹으로 건다”관리자 UI(Argo CD · Grafana · Alertmanager)는 인증만으로 부족할 때가 많다. 네트워크 레벨과 애플리케이션 레벨을 나눠서 건다.
| 위치 | 방법 | 막는 것 |
|---|---|---|
| Service(LoadBalancer) | spec.loadBalancerSourceRanges: [CIDR…] | LB IP 자체에 닿는 출발 IP (굵은 차단) |
| Gateway 구현체 | 구현체의 정책 리소스 (Envoy Gateway의 SecurityPolicy 등) | 특정 호스트·경로로 오는 출발 IP |
| 애플리케이션 | oauth2-proxy · Keycloak (5장) | 사용자 신원 |
# ① IP가 붙었나 — 이게 pending이면 아래는 볼 필요도 없다kubectl get svc -A | grep LoadBalancerkubectl -n metallb-system logs ds/speaker | tail -20 # 할당·광고 실패 이유
# ② Gateway가 준비됐나 (Programmed=True 여야 실제로 리스닝한다)kubectl get gateway -Akubectl describe gateway edge -n gateway-system | sed -n '/Conditions/,$p'
# ③ Route가 Gateway에 붙었나 (Accepted=False면 allowedRoutes·hostname 불일치)kubectl get httproute -Akubectl describe httproute grafana -n observability
# ④ 실제로 응답하는가 — DNS를 건너뛰고 IP로 직접curl -kv --resolve grafana.example.internal:443:192.168.10.240 \ https://grafana.example.internal/| 증상 | 흔한 원인 | 확인 |
|---|---|---|
EXTERNAL-IP가 <pending> | IPAddressPool 미설정 · 풀 소진 | kubectl -n metallb-system get ipaddresspool, speaker 로그 |
| IP는 받았는데 안 닿음 | 노드와 다른 L2 세그먼트 · IP 충돌 | 같은 대역인지, DHCP 범위와 안 겹치는지 |
Gateway가 Programmed=False | 인증서 Secret 없음 · 클래스 미설치 | describe gateway의 Conditions (4장) |
HTTPRoute가 Accepted=False | allowedRoutes 라벨 누락 · hostname이 리스너와 불일치 | describe httproute의 Parents |
| 404가 게이트웨이에서 난다 | hostname 오타 · Route가 다른 Gateway에 붙음 | 게이트웨이 액세스 로그 |
3장 요약
섹션 제목: “3장 요약”- 온프렘에서
type: LoadBalancer를 성립시키는 건 MetalLB다 — IP를 할당하고 광고할 뿐, 트래픽을 나르지 않는다. L2는 분산이 아니라 페일오버다 - ingress-nginx는 2026-03에 은퇴했다 — 보안 패치가 없다. 경계에 두는 물건으로는 끝났다
- 지금의 답은 Gateway API v1.5 + 구현체(Envoy Gateway가 무난한 기본값).
이전은
ingress2gateway로 시작한다 - Gateway API의 핵심은 역할 분리 — 인프라팀의
Gateway, 앱팀의HTTPRoute - 접근 제한은 네트워크(소스 IP) + 애플리케이션(신원) 두 겹. 소스 IP SNAT를 먼저 확인한다