Istio + Kubernetes Gateway API로 외부 트래픽 라우팅하기 (Gateway → HTTPRoute → Service)

목차

개요

Kubernetes에서 외부 트래픽을 받는 방법은 오랫동안 Ingress 중심이었지만, 지금은 Gateway API가 사실상 표준으로 자리 잡고 있습니다. Istio도 Gateway API 컨트롤러로 동작할 수 있어서, Gateway/HTTPRoute만으로 “도메인 → 라우팅 → 서비스” 흐름을 구성할 수 있습니다.

이 글은 “Istio 기반 환경에서 Gateway API로 애플리케이션이 정상 통신하는지”를 재현 가능한 체크리스트로 정리한 것입니다. (Sidecar / Ambient 모두 동일한 구조로 검증 가능)


목표(성공 조건)


전체 구조(개념)

도식 1Host: app.example.com 요청이 LoadBalancer, Istio Gateway(Envoy), HTTPRoute, Service를 차례로 거쳐 Pod에 닿는다.Host: app.example.comClientLoadBalancerIstio Gateway EnvoyHTTPRouteServicePod
Host: app.example.com 요청이 LoadBalancer, Istio Gateway(Envoy), HTTPRoute, Service를 차례로 거쳐 Pod에 닿는다.

Gateway API는 “라우팅 의도(명세)”이고, 실제 구현(컨트롤러)은 Istio가 담당합니다. 그래서 성공 시 응답 헤더에 istio-envoy가 찍히는 게 정상입니다.


선 설명

  • 설정 참조·허용 조건
  • 논리적 요청·응답 흐름
Istio Gateway API: 설정 관계와 요청 흐름설정층: Gateway가 gatewayClassName istio로 GatewayClass를 참조한다. Namespace demo 라벨이 allowedRoutes selector와 맞으면 route attach가 허용된다. HTTPRoute는 parentRefs로 Gateway의 http 리스너에 연결되고 hostnames app.example.com, PathPrefix /, backendRefs demo:80을 정한다. Istio가 이 설정을 Gateway Envoy로 구현한다. 요청층: Client가 Host app.example.com으로 LoadBalancer에 보내고, Istio Gateway Envoy를 거쳐 Service demo:80, app demo 라벨의 Pod nginx로 간다.설정층: 점선은 참조와 허용 조건요청층: 실선은 논리적 요청 흐름Namespace demoshared-gateway-access=trueGatewayClassistioGatewaygateway / istio-systemHTTPRoutedemo / demogatewayClassName: istioselector 일치 → attach 허용parentRefs: gatewaysectionName: httphostnames: app.example.comPathPrefix: /backendRefs: demo:80Istio가 이 설정을Envoy로 구현ClientHost:app.example.comLoadBalancerIstio GatewayEnvoyServicedemo:80Podnginx설정된 backendapp: demo 선택 Istio Gateway API: 설정 관계와 요청 흐름설정층: Gateway가 gatewayClassName istio로 GatewayClass를 참조한다. Namespace demo 라벨이 allowedRoutes selector와 맞으면 route attach가 허용된다. HTTPRoute는 parentRefs로 Gateway의 http 리스너에 연결되고 hostnames app.example.com, PathPrefix /, backendRefs demo:80을 정한다. Istio가 이 설정을 Gateway Envoy로 구현한다. 요청층: Client가 Host app.example.com으로 LoadBalancer에 보내고, Istio Gateway Envoy를 거쳐 Service demo:80, app demo 라벨의 Pod nginx로 간다.설정층: 점선은 참조·허용 조건GatewayClassistioGatewaygateway / istio-systemgatewayClassName: istioNamespace demoshared-gateway-access=trueHTTPRoutedemo / demoselector 일치→ attach 허용parentRefs: gatewaysectionName: http요청층: 실선은 논리적 요청 흐름ClientLoadBalancerIstio Gateway Envoy설정을 Istio가 구현Servicedemo:80PodnginxHost: app.example.com설정된 backendapp: demo 선택backendRefs
Gateway가 받은 요청을 HTTPRoute 규칙이 Service와 Pod로 보내는 논리 흐름이다. Namespace에서 Gateway로 향한 점선은 allowedRoutes selector의 라벨 일치가 attach 허용 조건이라는 관계다.
그림 관계를 글로 읽기
  1. Gateway(gateway, istio-system)가 gatewayClassName: istio로 GatewayClass istio를 참조한다.
  2. Namespace demo의 라벨 shared-gateway-access=true가 Gateway allowedRoutes selector와 맞아야 route attach가 허용된다.
  3. HTTPRoute demo가 parentRefs(gateway, istio-system, sectionName: http)로 Gateway에 연결된다.
  4. HTTPRoute가 hostnames app.example.com, PathPrefix /, backendRefs demo:80을 정한다.
  5. Gateway API는 라우팅 의도이고, Istio가 이 설정을 Istio Gateway Envoy로 구현한다.
  6. 요청: Client가 Host: app.example.com으로 LoadBalancer에 보낸다.
  7. LoadBalancer에서 Istio Gateway Envoy로 간다.
  8. Envoy가 설정된 backend인 Service demo:80으로 보낸다.
  9. Service가 app: demo 라벨로 Pod(nginx)를 선택한다.

1) 준비물

아래 예시는 AWS 환경(NLB)에서 자주 부딪히는 포인트를 함께 적었습니다. 다른 환경은 LoadBalancer/방화벽 파트만 환경에 맞게 바꾸면 됩니다.


2) Gateway API CRD 설치

Gateway API는 CRD가 먼저 필요합니다. (운영이면 버전을 고정하는 것을 권장합니다.)


3) Istio 설치 (Sidecar / Ambient)

Sidecar 모드(일반적인 구성)

bash
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

helm upgrade --install istio-base istio/base -n istio-system --create-namespace
helm upgrade --install istiod istio/istiod -n istio-system

Ambient 모드(추가 컴포넌트 필요)

Ambient는 “사이드카 대신 ztunnel/waypoint 중심”이라 설치 구성요소가 더 많습니다.

bash
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

helm upgrade --install istio-base istio/base -n istio-system --create-namespace
helm upgrade --install istiod istio/istiod -n istio-system --set profile=ambient
helm upgrade --install istio-cni istio/cni -n istio-system --set profile=ambient
helm upgrade --install ztunnel istio/ztunnel -n istio-system

4) (공유) Gateway 만들기

운영에서는 보통 팀 공용 Gateway를 하나 두고, 각 서비스는 HTTPRoute만 추가하는 패턴이 관리가 편합니다.

아래는 예시입니다.

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
  namespace: istio-system
  annotations:
    # (AWS 예시) internet-facing
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  gatewayClassName: istio
  listeners:
    - name: http
      hostname: "*.example.com"
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              shared-gateway-access: "true"

생성 후 아래처럼 “Istio가 실제로 Gateway API를 컨트롤하고 있는지”를 먼저 확인해 두면 디버깅이 빨라집니다.

bash
kubectl get gatewayclass
kubectl -n istio-system get gateway gateway -o wide

5) 애플리케이션 네임스페이스/워크로드 준비

공통(라우트 attach 허용 라벨)

Gateway에서 allowedRoutes를 selector로 제한했으니, 해당 네임스페이스에 라벨을 붙여 줘야 합니다.

bash
kubectl create ns demo
kubectl label ns demo shared-gateway-access=true --overwrite

Ambient에서 추가(ambient opt-in)

Ambient는 네임스페이스 opt-in이 필요합니다.

bash
kubectl label ns demo istio.io/dataplane-mode=ambient --overwrite

6) 샘플 애플리케이션(nginx) 배포

“Gateway → HTTPRoute → Service”를 확인하는 게 목표이므로, 백엔드는 최대한 단순하게 둡니다.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: demo
  namespace: demo
spec:
  selector:
    app: demo
  ports:
    - name: http
      port: 80
      targetPort: 80
bash
kubectl apply -f demo.yaml
kubectl -n demo rollout status deploy/demo

7) HTTPRoute로 서비스 연결

HTTPRoute는 “어떤 Host/Path 요청을 어디 서비스로 보낼지”를 정의합니다.

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: demo
  namespace: demo
spec:
  parentRefs:
    - name: gateway
      namespace: istio-system
      sectionName: http
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: demo
          port: 80

8) DNS/접속 테스트

8-1) HTTPRoute 상태 확인

bash
kubectl -n demo get httproute demo -o wide
kubectl -n demo describe httproute demo

여기서 Accepted=True, ResolvedRefs=True(Ambient면 ResolvedWaypoints=True도)까지 확인되면, “라우팅 선언이 유효하게 붙었다”고 볼 수 있습니다.

8-2) Gateway가 가진 주소(LoadBalancer DNS) 확인

보통 아래 둘 중 하나로 확인합니다.

bash
kubectl -n istio-system get gateway gateway -o jsonpath='{.status.addresses[0].value}{"\n"}'

또는 (Istio가 만든) LoadBalancer Service에서:

bash
kubectl -n istio-system get svc gateway-istio -o jsonpath='{.status.loadBalancer.ingress[0].hostname}{"\n"}'

NLB가 막 생성된 직후에는 DNS 전파가 덜 되어 Could not resolve host가 잠깐 나올 수 있습니다. 몇 분 대기 후 재시도하면 해결되는 경우가 많습니다.

8-3) curl로 “Host 기반 라우팅” 검증

bash
export LB_DNS="<위에서 확인한 값>"
curl -i -H "Host: app.example.com" "http://${LB_DNS}/"

정상이라면:

8-4) (선택) 실제 도메인으로 접속하기

위 curl -H "Host: ..." 방식은 DNS 없이도 Host 라우팅이 되는지 확인하기 좋습니다.

운영에서 app.example.com 같은 실제 도메인으로 접속하려면, DNS에 아래처럼 연결합니다.

예시(CNAME):

txt
app.example.com.  CNAME  <LB_DNS>

9) 트러블슈팅 체크리스트

A. HTTPRoute Accepted=False

가장 흔한 원인은 allowedRoutes 조건 불일치입니다.

B. curl이 timeout

AWS(NLB) 조합에서 자주 나오는 이슈입니다.

C. Could not resolve host


정리

다음 단계로는:


참고

도식

100%

끌어서 이동, 두 손가락이나 휠로 확대·축소. 키보드: 화살표 이동, + − 확대·축소, 0 맞춤, Esc 닫기. 같은 내용은 그림 아래 “그림 관계를 글로 읽기”에 있다.