1 · 역할 분리
인프라 담당자와 앱 개발자가 다른 리소스를 만진다.
HTTP 트래픽을 클러스터 안으로
Service는 L4(전송 계층 — IP와 포트만 본다)에서 동작한다. 호스트 이름이나 URL 경로로 트래픽을 나누려면 L7(애플리케이션 계층 — HTTP 호스트·경로·헤더까지 본다)이 필요하다.
LB 하나 뒤에서 호스트/경로로 여러 서비스에 나눠주는 L7 계층이 필요하다. 그것이 Ingress이고, 그 후속이 Gateway API다.
시작하기 전에의 축으로 보면 — Deployment는 선언만 하면 내장 컨트롤러가 실제 상태로 만들어 주지만, Ingress를 조정하는 컨트롤러는 쿠버네티스에 내장되어 있지 않다. 직접 하나 골라 설치해야 “선언 → 실제 상태” 루프가 돌기 시작한다. 이 장의 함정 대부분이 여기서 나온다.
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"시험 중에는 공식 Ingress 문서의 예시 YAML을 복사해 이름·경로·서비스·포트만 바꾸는 게 가장 빠르다.
| 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-nginxkubectl get ingressclassspec.ingressClassName으로 지정한다kubernetes.io/ingress.class 애노테이션은 deprecated다시험 중 복사 원본은 공식 Ingress 문서의 TLS 절이다 —
라우팅 규칙을 가져온 그 페이지에서 스크롤만 내리면 spec.tls 예시가 있다.
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: 80kubernetes.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: 50m바로 이것이 Ingress의 한계다. 재작성·타임아웃·카나리·헤더 조작 — 실무에 필요한 거의 모든 것이 표준 밖의 애노테이션이라 컨트롤러를 바꾸면 전부 다시 써야 한다.
Ingress의 문제를 세 방향에서 푼다.
1 · 역할 분리
인프라 담당자와 앱 개발자가 다른 리소스를 만진다.
2 · 표현력
헤더 매칭·가중치 분배·재작성이 표준 필드다. 애노테이션이 아니다.
3 · 프로토콜 확장
HTTP뿐 아니라 gRPC·TCP·TLS를 같은 모델로.
Gateway API는 CRD(CustomResourceDefinition — 쿠버네티스에 새 리소스 종류를 추가하는 확장, 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.networking| 리소스 | 누가 만드나 | 무엇을 정하나 |
|---|---|---|
| GatewayClass | 인프라 제공자 | 어떤 구현체를 쓸 것인가 (IngressClass에 대응) |
| Gateway | 클러스터 운영자 | 어떤 포트·프로토콜·호스트를 열 것인가 |
| HTTPRoute | 앱 개발자 | 그 안에서 어떻게 라우팅할 것인가 |
이 세 리소스는 전부 선언이다 — 정책을 적어 둘 뿐 트래픽을 직접 만지지 않는다. 선언을 읽어 실제 트래픽을 제어하는 것은 따로 설치하는 Gateway 컨트롤러(구현체 — NGINX Gateway Fabric, Istio, Cilium 같은)다. “Ingress = 리소스 + 컨트롤러”의 축이 리소스만 셋으로 늘어난 채 그대로 성립한다.
apiVersion: gateway.networking.k8s.io/v1kind: GatewayClassmetadata: { name: nginx }spec: controllerName: gateway.nginx.org/nginx-gateway-controller # 이 클래스를 맡을 구현체의 식별자GatewayClass는 “이 클래스는 어느 구현체가 맡는다”를 적어 두는 API 오브젝트일 뿐,
그 자체가 프록시를 띄우지 않는다. controllerName이 가리키는 구현체가 설치되어 있어야
클래스가 Accepted되고 Gateway가 실체를 얻는다 — IngressClass와 Ingress 컨트롤러의
관계가 한 층 위로 복제된 것이다.
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에 붙을 수 있는지”를 통제한다.
certificateRefs의 kind와 group을 생략하면 같은 네임스페이스의 core Secret을 뜻한다.
다른 네임스페이스의 인증서를 참조하려면 그쪽의 ReferenceGrant가 추가로 허용해야 한다.
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: shop namespace: shopspec: parentRefs: - name: prod-gateway namespace: infra # 다른 네임스페이스의 Gateway에 붙는다 sectionName: http # 특정 listener에만 붙일 때 — Gateway의 listeners[].name 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: 80weight가 곧 카나리 배포다 — 새 버전에 트래픽 일부만 먼저 흘려보고,
문제가 없으면 비중을 올리는 방식이다.
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: Service| 리소스 | 프로토콜 | 상태 |
|---|---|---|
| HTTPRoute | HTTP/HTTPS | Standard 채널, v1 |
| GRPCRoute | gRPC | Standard 채널 (v1.4에서 승격) |
| TCPRoute / UDPRoute | 원시 TCP/UDP | v1.6에서 GA로 승격 |
| TLSRoute | TLS 패스스루 | Experimental 채널 |
깊게 팔 것은 HTTPRoute 하나다. GRPCRoute는 gRPC(HTTP/2 위에서 도는 원격 호출 프로토콜) 트래픽용으로 HTTPRoute와 문법이 거의 같고, TCPRoute·UDPRoute와 TLSRoute(TLS를 종료하지 않고 암호화된 채 백엔드까지 통과시키는 패스스루)는 이름과 용도만 알아두면 된다.
| Ingress | Gateway API | |
|---|---|---|
| 클래스 지정 | IngressClass | GatewayClass |
| 트래픽을 실제로 제어 | Ingress 컨트롤러 | Gateway 컨트롤러(구현체) |
| 진입점 | Ingress 리소스에 섞임 | Gateway로 분리 |
| 라우팅 규칙 | rules | HTTPRoute |
| 재작성·헤더 조작 | 컨트롤러 애노테이션 | 표준 필드(filters) |
| 트래픽 분할 | 애노테이션 | weight |
| 네임스페이스 교차 | 불가 | ReferenceGrant |
| 설치 | 컨트롤러만 | CRD + 컨트롤러 |
Ingress는 없어지지 않는다. 동결(frozen)은 제거 예정이라는 뜻이 아니라 새 기능이 더 추가되지 않는다는 뜻이고, 기존 기능은 계속 지원된다. 시험에서도 둘 다 나올 수 있으니 양쪽 문법을 알아야 한다.
기존 Ingress를 Gateway API로 옮길 때는 Ingress 한 장에 섞여 있던 필드를 Gateway와 HTTPRoute 두 곳으로 나눠 옮긴다. 진입점에 관한 것(포트·TLS)은 Gateway로, 라우팅에 관한 것(호스트·경로·백엔드)은 HTTPRoute로 간다.
| Ingress 필드 | 옮기는 자리 |
|---|---|
spec.ingressClassName | Gateway의 spec.gatewayClassName |
spec.tls[].secretName | Gateway의 listeners[].tls.certificateRefs[].name. listener는 protocol: HTTPS, port: 443, tls.mode: Terminate |
spec.tls[].hosts | Gateway의 listeners[].hostname |
rules[].host | HTTPRoute의 hostnames |
paths[].path와 pathType: Prefix | HTTPRoute의 rules[].matches[].path, type: PathPrefix |
pathType: Exact | type: Exact |
backend.service.name·port.number | HTTPRoute의 rules[].backendRefs[].name·port |
spec.defaultBackend | 대응 필드가 없다. 경로 /를 PathPrefix로 받는 규칙을 직접 둔다 |
| 컨트롤러 애노테이션 | filters 같은 표준 필드, 또는 구현체의 확장 |
HTTPRoute는 parentRefs로 Gateway에 붙는다. 이때 HTTPRoute의 hostnames가 Gateway listener의 hostname과
맞아야 하며, 맞지 않는 호스트의 규칙은 listener가 무시한다
(공식 전환 안내).
TLS Secret은 Gateway와 같은 네임스페이스에 있어야 하므로, Gateway는 보통 기존 Ingress와 같은 네임스페이스에 만든다.
실제 Ingress 한 장을 옮겨 보는 연습은 실전 과제에 있다.
# 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에 답이 다 있다.
여기서 데이터 플레인(data plane)은 실제 트래픽이 지나가는 프록시 — nginx 같은 구현체 — 를 말한다.
Gateway·HTTPRoute는 선언일 뿐이고, 그 선언이 프록시 설정에 반영됐는지를 conditions가 알려준다 —
시작하기 전에의 “선언과 실제 상태의 간극”을 리소스가 스스로 보고하는 셈이다.
| Condition | 뜻 |
|---|---|
Accepted: False | Gateway/Route 설정 자체가 잘못됐다 |
Programmed: False | 설정은 맞지만 데이터 플레인에 반영되지 않았다 |
ResolvedRefs: False | 백엔드 Service를 못 찾는다 또는 ReferenceGrant 없음 |
pathType은 필수이고 Prefix는 경로 요소 단위다ingressClassName이 없고 기본 클래스도 없으면 아무 일도 안 일어난다status.conditions — Accepted / Programmed / ResolvedRefs