콘텐츠로 이동

11. Ingress와 Gateway API

HTTP 트래픽을 클러스터 안으로

flowchart LR
    subgraph L4["L4 — Service 만으로"]
      NP["NodePort<br/>포트가 30000번대<br/>서비스마다 외워야 한다"]
      LB["LoadBalancer<br/>서비스마다 LB 하나씩<br/>비싸다"]
    end
    subgraph L7["L7 — 필요한 것"]
      ING["LB 하나 뒤에서<br/>호스트·경로로 나눠준다"]
    end
    L4 -.->|"호스트 이름이나 경로로<br/>나눌 수 없다 ❌"| L7

    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    class NP,LB bad
    class ING ok

LB 하나 뒤에서 호스트/경로로 여러 서비스에 나눠주는 L7 계층이 필요하다. 그것이 Ingress이고, 그 후속이 Gateway API다.

flowchart LR
    C["클라이언트"] --> LB["LoadBalancer Service<br/>컨트롤러 앞단"]
    LB --> IC["Ingress 컨트롤러 Pod<br/>nginx · traefik …"]
    IC -->|"규칙을 읽는다"| IR["Ingress 리소스<br/>★ 규칙일 뿐 아무것도 안 한다"]
    IC --> S1["Service A"] --> P1["Pod"]
    IC --> S2["Service B"] --> P2["Pod"]
    NOC["컨트롤러가 없으면"] -.->|"Ingress 를 만들어도<br/>아무 일도 안 일어난다 ❌"| IR

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class IC key
    class IR warn
    class NOC bad
    class C,LB,S1,S2,P1,P2 mute
  • Ingress 리소스는 규칙일 뿐이다. 아무것도 하지 않는다
  • 실제 라우팅은 Ingress 컨트롤러 Pod(nginx 등)이 한다
  • 컨트롤러가 없으면 Ingress를 만들어도 아무 일도 안 일어난다
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port: { number: 8080 }
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port: { number: 80 }
Terminal window
kubectl create ingress web --class=nginx \
--rule="shop.example.com/api*=api-svc:8080" --rule="shop.example.com/*=web-svc:80"
flowchart LR
    P["path: /api<br/>pathType: Prefix"]
    P -->|"매칭 ✅"| M1["/api"]
    P -->|"매칭 ✅"| M2["/api/v1"]
    P -->|"매칭 안 됨 ❌"| M3["/apidocs"]
    NOTE["경로 요소 단위 접두사다<br/>문자열 접두사가 아니다"] -.- P

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    class M1,M2 ok
    class M3 bad
    class P,NOTE key
pathType 매칭
Prefix 경로 요소 단위 접두사. /api/api, /api/v1 에 매칭. /apidocs안 된다
Exact 완전히 일치해야 한다
ImplementationSpecific 컨트롤러가 알아서 (nginx는 정규식을 허용)
  • pathType은 필수 필드다. 빠뜨리면 생성이 거부된다
  • 여러 규칙이 매칭되면 가장 긴 경로가 이긴다
Terminal window
kubectl get ingress
kubectl describe ingress web # Rules와 Events, 백엔드 상태를 본다

IngressClass — 어느 컨트롤러가 처리하나

섹션 제목: “IngressClass — 어느 컨트롤러가 처리하나”
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: k8s.io/ingress-nginx
flowchart TD
    I["Ingress 생성"] --> Q1{"ingressClassName 이<br/>있는가"}
    Q1 -->|있다| OK["그 클래스의 컨트롤러가 처리 ✅"]
    Q1 -->|없다| Q2{"기본 클래스가<br/>지정되어 있는가"}
    Q2 -->|있다| OK
    Q2 -->|없다| NG["아무 컨트롤러도 집어가지 않는다<br/>ADDRESS 가 영원히 비어 있다 ❌"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class OK ok
    class NG bad
    class I,Q1,Q2 mute
Terminal window
kubectl get ingressclass
  • Ingress에 spec.ingressClassName으로 지정한다
  • 기본 클래스가 지정되어 있으면 생략 가능하다
  • 옛날 방식kubernetes.io/ingress.class 애노테이션은 deprecated
Terminal window
kubectl create secret tls web-tls --cert=./tls.crt --key=./tls.key
spec:
ingressClassName: nginx
tls:
- hosts:
- shop.example.com
secretName: web-tls
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
flowchart LR
    C["클라이언트"] -->|"HTTPS"| IC["Ingress 컨트롤러<br/>여기서 TLS 종료 terminate"]
    IC -->|"평문 HTTP"| S["백엔드 Service"] --> P["Pod"]
    SEC["Secret web-tls<br/>type kubernetes.io/tls<br/>★ Ingress 와 같은 네임스페이스"] -.-> IC

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class IC key
    class SEC warn
    class C,S,P mute
  • Secret은 kubernetes.io/tls 타입이어야 하고 Ingress와 같은 네임스페이스에 있어야 한다
  • TLS는 컨트롤러에서 종료(terminate)되고, 백엔드로는 평문으로 간다
spec:
defaultBackend: # 어느 규칙에도 안 맞으면 여기로
service:
name: fallback-svc
port:
number: 80

애노테이션은 컨트롤러마다 다르다 — 표준이 아니다.

metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 50m
flowchart LR
    NEED["실무에 필요한 것<br/>재작성 · 타임아웃 · 카나리 · 헤더 조작"] --> ANN["전부 컨트롤러별 애노테이션"]
    ANN -->|"컨트롤러를 바꾸면"| REWRITE["전부 다시 써야 한다 ❌"]
    ANN -.->|"이것이 Gateway API 가 나온 이유"| GW["Gateway API 는 표준 필드"]

    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class REWRITE bad
    class GW ok
    class NEED,ANN mute

바로 이것이 Ingress의 한계다. 재작성·타임아웃·카나리·헤더 조작 — 실무에 필요한 거의 모든 것이 표준 밖의 애노테이션이라 컨트롤러를 바꾸면 전부 다시 써야 한다.

Ingress의 문제를 세 방향에서 푼다.

1 · 역할 분리

인프라 담당자와 앱 개발자가 다른 리소스를 만진다.

2 · 표현력

헤더 매칭·가중치 분배·재작성이 표준 필드다. 애노테이션이 아니다.

3 · 프로토콜 확장

HTTP뿐 아니라 gRPC·TCP·TLS를 같은 모델로.

Gateway API는 CRD로 제공된다. 클러스터에 기본 탑재가 아니다 — 직접 설치해야 한다. 이 점이 Ingress와 가장 큰 실무적 차이다.

Terminal window
# Standard 채널 CRD 설치
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
kubectl get crd | grep gateway
kubectl api-resources | grep gateway.networking
flowchart TB
    GC["GatewayClass<br/>인프라 제공자<br/>어떤 구현체를 쓸 것인가"] --> GW["Gateway<br/>클러스터 운영자<br/>어떤 포트·프로토콜·호스트를 열 것인가"]
    GW --> R1["HTTPRoute<br/>앱 개발자 · shop 팀<br/>그 안에서 어떻게 라우팅할 것인가"]
    GW --> R2["HTTPRoute<br/>다른 팀"]
    R1 --> S1["Service A"]
    R2 --> S2["Service B"]
    AR["allowedRoutes<br/>어느 네임스페이스가 붙을 수 있는지<br/>운영자가 통제"] -.- GW

    classDef infra fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef op fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef dev fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class GC infra
    class GW,AR op
    class R1,R2 dev
    class S1,S2 mute
리소스 누가 만드나 무엇을 정하나
GatewayClass 인프라 제공자 어떤 구현체를 쓸 것인가 (IngressClass에 대응)
Gateway 클러스터 운영자 어떤 포트·프로토콜·호스트를 열 것인가
HTTPRoute 앱 개발자 그 안에서 어떻게 라우팅할 것인가
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: { name: prod-gateway, namespace: infra }
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces: { from: All } # All | Same | Selector
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs: [{ name: web-tls }]
allowedRoutes:
namespaces:
from: Selector
selector: { matchLabels: { team: shop } }

allowedRoutes가 핵심이다. 운영자가 “어느 네임스페이스가 이 Gateway에 붙을 수 있는지”를 통제한다.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
namespace: shop
spec:
parentRefs:
- name: prod-gateway
namespace: infra # 다른 네임스페이스의 Gateway에 붙는다
hostnames:
- "shop.example.com"
rules:
- matches:
- path: { type: PathPrefix, value: /api }
headers:
- name: x-version
value: "v2"
backendRefs:
- name: api-v2
port: 8080
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: web-svc
port: 80

가중치 분배와 필터 — 애노테이션 없이

섹션 제목: “가중치 분배와 필터 — 애노테이션 없이”
rules:
- backendRefs:
- name: web-v1
port: 80
weight: 90 # 카나리 배포가 표준 필드다
- name: web-v2
port: 80
weight: 10
- matches:
- path: { type: PathPrefix, value: /old }
filters:
- type: RequestRedirect
requestRedirect:
statusCode: 301
path: { type: ReplacePrefixMatch, replacePrefixMatch: /new }
- type: RequestHeaderModifier
requestHeaderModifier:
add: [{ name: x-source, value: gateway }]
backendRefs:
- name: web-svc
port: 80
flowchart LR
    R["HTTPRoute rule"] --> W["weight 90 / 10"]
    W --> V1["web-v1 · 90%"]
    W --> V2["web-v2 · 10%"]
    R --> F["filters"]
    F --> F1["RequestRedirect"]
    F --> F2["RequestHeaderModifier"]
    F --> F3["URLRewrite"]

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class W,F key
    class V1,V2 ok
    class R,F1,F2,F3 mute

Ingress에서 컨트롤러별 애노테이션이었던 것들이 전부 스펙 안에 있다.

네임스페이스를 넘을 때 — ReferenceGrant

섹션 제목: “네임스페이스를 넘을 때 — ReferenceGrant”

HTTPRoute가 다른 네임스페이스의 Service를 백엔드로 쓰려면 그쪽의 허가가 필요하다.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-shop-routes
namespace: backend # 참조 "당하는" 쪽에 만든다
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: shop
to:
- group: ""
kind: Service
flowchart LR
    HR["HTTPRoute<br/>네임스페이스 shop"] -->|"backendRef"| SVC["Service<br/>네임스페이스 backend"]
    RG["ReferenceGrant<br/>★ backend 네임스페이스에 만든다"] -->|"허가"| HR
    NO["ReferenceGrant 가 없으면"] -.->|"ResolvedRefs: False ❌"| HR

    classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class RG key
    class NO bad
    class HR,SVC mute
  • 참조당하는 쪽이 허가한다 — 몰래 트래픽을 뺏어가지 못하게 하는 설계
  • TLS 인증서 Secret을 다른 네임스페이스에서 참조할 때도 필요하다
리소스 프로토콜 상태
HTTPRoute HTTP/HTTPS Standard 채널, v1
GRPCRoute gRPC Standard 채널 (v1.4에서 승격)
TCPRoute / UDPRoute 원시 TCP/UDP v1.6에서 GA로 승격
TLSRoute TLS 패스스루 Experimental 채널
  • 설치 매니페스트가 Standard / Experimental 두 채널로 나뉜다
  • 실습·시험에서는 Standard를 쓰면 된다
Ingress Gateway API
클래스 지정 IngressClass GatewayClass
진입점 Ingress 리소스에 섞임 Gateway로 분리
라우팅 규칙 rules HTTPRoute
재작성·헤더 조작 컨트롤러 애노테이션 표준 필드(filters)
트래픽 분할 애노테이션 weight
네임스페이스 교차 불가 ReferenceGrant
설치 컨트롤러만 CRD + 컨트롤러

Ingress는 없어지지 않는다. 동결(frozen)된 상태로 계속 지원된다. 시험에서도 둘 다 나올 수 있으니 양쪽 문법을 알아야 한다.

Terminal window
# Ingress
kubectl get ingress
kubectl describe ingress web # Rules / Events / ADDRESS
kubectl get ingressclass
kubectl get pods -n ingress-nginx # 컨트롤러가 살아 있는가
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# Gateway API
kubectl get gatewayclass
kubectl get gateway -A
kubectl describe gateway prod-gateway -n infra # ★ status.conditions
kubectl get httproute -A
kubectl describe httproute shop -n shop # ★ parents[].conditions

Gateway API는 status.conditions에 답이 다 있다.

flowchart TD
    C{"status.conditions"}
    C -->|"Accepted: False"| A["Gateway · Route 설정 자체가 잘못됐다<br/>필드·값을 다시 본다"]
    C -->|"Programmed: False"| B["설정은 맞지만<br/>데이터 플레인에 반영되지 않았다<br/>→ 컨트롤러 로그"]
    C -->|"ResolvedRefs: False"| D["백엔드 Service 를 못 찾는다<br/>또는 ReferenceGrant 없음"]
    C -->|"전부 True"| OK["정상 ✅"]

    classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
    class OK ok
    class A,B,D bad
    class C mute
Condition
Accepted: False Gateway/Route 설정 자체가 잘못됐다
Programmed: False 설정은 맞지만 데이터 플레인에 반영되지 않았다
ResolvedRefs: False 백엔드 Service를 못 찾는다 또는 ReferenceGrant 없음
  • Ingress 리소스는 규칙일 뿐, 일은 Ingress 컨트롤러 Pod이 한다
  • pathType필수이고 Prefix경로 요소 단위
  • ingressClassName이 없고 기본 클래스도 없으면 아무 일도 안 일어난다
  • Ingress의 고급 기능은 전부 컨트롤러별 애노테이션 — 이식성이 없다
  • Gateway API = GatewayClass(제공자) + Gateway(운영자) + HTTPRoute(개발자)
  • CRD라서 직접 설치해야 한다. Standard 채널을 쓴다
  • 재작성·헤더·가중치가 표준 필드, 네임스페이스 교차는 ReferenceGrant
  • 진단은 status.conditionsAccepted / Programmed / ResolvedRefs