1 · 역할 분리
인프라 담당자와 앱 개발자가 다른 리소스를 만진다.
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
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: webspec: 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 }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은 필수 필드다. 빠뜨리면 생성이 거부된다kubectl get ingresskubectl describe ingress web # Rules와 Events, 백엔드 상태를 본다apiVersion: networking.k8s.io/v1kind: IngressClassmetadata: name: nginx annotations: ingressclass.kubernetes.io/is-default-class: "true"spec: controller: k8s.io/ingress-nginxflowchart 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
kubectl get ingressclassspec.ingressClassName으로 지정한다kubernetes.io/ingress.class 애노테이션은 deprecated다kubectl create secret tls web-tls --cert=./tls.crt --key=./tls.keyspec: 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: 80flowchart 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
kubernetes.io/tls 타입이어야 하고 Ingress와 같은 네임스페이스에 있어야 한다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: 50mflowchart 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와 가장 큰 실무적 차이다.
# Standard 채널 CRD 설치kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yamlkubectl get crd | grep gatewaykubectl api-resources | grep gateway.networkingflowchart 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/v1kind: Gatewaymetadata: { 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/v1kind: HTTPRoutemetadata: name: shop namespace: shopspec: 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: 80rules: - 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: 80flowchart 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에서 컨트롤러별 애노테이션이었던 것들이 전부 스펙 안에 있다.
HTTPRoute가 다른 네임스페이스의 Service를 백엔드로 쓰려면 그쪽의 허가가 필요하다.
apiVersion: gateway.networking.k8s.io/v1beta1kind: ReferenceGrantmetadata: name: allow-shop-routes namespace: backend # 참조 "당하는" 쪽에 만든다spec: from: - group: gateway.networking.k8s.io kind: HTTPRoute namespace: shop to: - group: "" kind: Serviceflowchart 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
| 리소스 | 프로토콜 | 상태 |
|---|---|---|
| HTTPRoute | HTTP/HTTPS | Standard 채널, v1 |
| GRPCRoute | gRPC | Standard 채널 (v1.4에서 승격) |
| TCPRoute / UDPRoute | 원시 TCP/UDP | v1.6에서 GA로 승격 |
| TLSRoute | TLS 패스스루 | Experimental 채널 |
| Ingress | Gateway API | |
|---|---|---|
| 클래스 지정 | IngressClass |
GatewayClass |
| 진입점 | Ingress 리소스에 섞임 | Gateway로 분리 |
| 라우팅 규칙 | rules |
HTTPRoute |
| 재작성·헤더 조작 | 컨트롤러 애노테이션 | 표준 필드(filters) |
| 트래픽 분할 | 애노테이션 | weight |
| 네임스페이스 교차 | 불가 | ReferenceGrant |
| 설치 | 컨트롤러만 | CRD + 컨트롤러 |
flowchart TB
subgraph I["Ingress — 하나에 다 섞여 있다"]
ING["Ingress 리소스<br/>클래스 + 호스트 + 경로 + TLS + 애노테이션"]
end
subgraph G["Gateway API — 역할별로 쪼갠다"]
GC2["GatewayClass"] --> GW2["Gateway<br/>포트 · 프로토콜 · TLS"]
GW2 --> HR2["HTTPRoute<br/>호스트 · 경로 · 필터 · weight"]
end
I -.->|"동결 frozen · 계속 지원된다"| G
classDef old fill:#fef3c7,stroke:#d97706,color:#78350f
classDef new fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
class ING old
class GC2,GW2,HR2 newIngress는 없어지지 않는다. 동결(frozen)된 상태로 계속 지원된다. 시험에서도 둘 다 나올 수 있으니 양쪽 문법을 알아야 한다.
# Ingresskubectl get ingresskubectl describe ingress web # Rules / Events / ADDRESSkubectl get ingressclasskubectl get pods -n ingress-nginx # 컨트롤러가 살아 있는가kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# Gateway APIkubectl get gatewayclasskubectl get gateway -Akubectl describe gateway prod-gateway -n infra # ★ status.conditionskubectl get httproute -Akubectl describe httproute shop -n shop # ★ parents[].conditionsGateway 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 없음 |
pathType은 필수이고 Prefix는 경로 요소 단위다ingressClassName이 없고 기본 클래스도 없으면 아무 일도 안 일어난다status.conditions — Accepted / Programmed / ResolvedRefs