Gateway API GAMMA 활용 K8S East-West 트래픽 제어

Gateway API GAMMA 는 Kubernetes 환경에서 서비스 메시 기반 East-West 트래픽 제어를 표준화하기 위해 등장한 이니셔티브입니다.
기존에는 Istio, Linkerd 등 서비스 메시마다 서로 다른 전용 CRD와 설정 방식을 사용해야 했고, 이로 인해 메시 벤더 변경이나 혼합 환경 구성 시 설정 이식성이 떨어지고 운영 복잡도가 증가하는 문제가 있었습니다.

Gateway API GAMMA는 이러한 문제를 해결하기 위해 Kubernetes의 표준 네트워크 스펙인 Gateway API를 클러스터 내부(East-West) 트래픽 관리까지 확장합니다.
특히 HTTPRoute 리소스를 Service에 직접 연결함으로써, 외부(North-South)와 내부(East-West) 트래픽을 하나의 선언적 API 모델로 일관되게 관리할 수 있도록 합니다.

이번 포스트에서는 Gateway API GAMMA를 활용하여 Kubernetes 클러스터 내부 트래픽을 제어하는 방법을 다음과 같이 살펴보겠습니다.

  • HTTPRoute를 Service에 직접 연결하는 GAMMA 방식의 트래픽 제어 구성
  • 헤더 기반 라우팅을 통한 East-West 트래픽 분기 동작 확인

목차

1. Gateway API GAMMA란?
2. 환경/버전 정보
3. Gateway API GAMMA 활용 설정 구성
4. HTTPRoute 기반 트래픽 라우팅 확인
5. 결론

1. Gateway API GAMMA 란?

Gateway API GAMMA(Gateway API for Mesh Management and Administration)는 Kubernetes의 표준 네트워크 스펙인 Gateway API를 사용하여 클러스터 내부 트래픽(East-West)을 관리하기 위한 표준 이니셔티브입니다.

기존 서비스 메시 환경에는 Istio의 VirtualService, Linkerd의 ServiceProfile 등 서비스 메시마다 각기 다른 전용 CRD와 설정을 사용해야 했습니다. 이로 인해 메시 벤더 변경이나 혼합 환경 구성 시 설정 이식성이 떨어지고, 운영 복잡도가 증가하는 문제가 있었습니다.

GAMMA는 이러한 문제를 해결하기 위해 Gateway API의 표준 리소스(HTTPRoute)를 서비스 메시 트래픽 제어에도 그대로 활용하도록 정의합니다.
이를 통해 외부(North-South) 트래픽과 내부(East-West) 트래픽을 하나의 API 모델로 일관되게 관리할 수 있습니다.

GAMMA의 가장 큰 기술적 특징은 라우팅 규칙을 연결하는 대상에 있습니다.

  • Ingress (North-South): HTTPRouteGateway에 연결
  • GAMMA (East-West): HTTPRouteService에 연결

즉, GAMMA에서는 “이 Service로 향하는 모든 내부 트래픽은 이 규칙을 따른다” 고 정의하는 방식을 사용합니다. 이를 통해 개발자는 서비스 메시 벤더 종속 없이 Kubernetes 표준 API만으로 정교한 내부 트래픽 제어를 수행할 수 있습니다.

2. 환경/버전 정보

구성 요소버전
Kubernetesv1.32.2
Istio1.28.1 (minimal)
Gateway APIv1.3.0
namespacedevopssong

3. Gateway API GAMMA 활용 환경 구성

Gateway API의 HTTPRoute 리소스를 통해 클러스터 내부 트래픽을 제어하기 위해, GAMMA를 지원하는 Service Mesh 구현체로 Istio minimal 버전을 Kubernetes 클러스터에 구성하고, Pod가 배포될 네임스페이스에 Istio sidecar 컨테이너가 삽입되도록 레이블을 구성했습니다.
Istio를 클러스터에 배포하는 방법은 Istio Service Mesh를 클러스터에 배포하기 포스트를 참고하세요.

$ kubectl get namespaces devopssong --show-labels 

NAME         STATUS   AGE    LABELS
devopssong   Active   262d   istio-injection=enabled,kubernetes.io/metadata.name=devopssong

트래픽 제어를 확인하기 위해 사용할 Pod, Service 리소스를 네임스페이스에 배포합니다.

  • my-server-v1, my-server-v2 : 서로 다른 응답을 반환하는 Pod
  • curl-client : curl 요청을 보낼 클라이언트 Pod
  • my-server(Service) : my-server-v1, my-server-v2 2개의 Pod를 모두 endpoint로 설정하는 Service
app.yaml
# Server V1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-server-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-server
      version: v1
  template:
    metadata:
      labels:
        app: my-server
        version: v1
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        # V1임을 식별하기 위해 index.html 변경
        lifecycle:
          postStart:
            exec:
              command: ["/bin/sh", "-c", "echo 'Hello from Version 1' > /usr/share/nginx/html/index.html"]
---
# Server V2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-server-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-server
      version: v2
  template:
    metadata:
      labels:
        app: my-server
        version: v2
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        # V2임을 식별하기 위해 index.html 변경
        lifecycle:
          postStart:
            exec:
              command: ["/bin/sh", "-c", "echo 'Hello from Version 2' > /usr/share/nginx/html/index.html"]
---
# Server Service
apiVersion: v1
kind: Service
metadata:
  name: my-server
  labels:
    app: my-server
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: my-server
---
# Curl 요청 전송용 Client 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: curl-client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: curl-client
  template:
    metadata:
      labels:
        app: curl-client
    spec:
      containers:
      - name: curl
        image: curlimages/curl
        command: ["/bin/sleep", "3650d"]
$ kubectl apply -f app.yaml -n devopssong

service/my-server created
deployment.apps/my-server-v1 created
deployment.apps/my-server-v2 created
deployment.apps/curl-client created

$ kubectl get po,svc -n devopssong

NAME                                            READY   STATUS    RESTARTS   AGE
pod/curl-client-79b487649f-5xvwh                2/2     Running   0          57m
pod/my-server-v1-6b86849b47-6q7dn               2/2     Running   0          57m
pod/my-server-v2-6658455b85-jthm5               2/2     Running   0          57m

NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                       
                                           AGE
service/my-server                      ClusterIP   10.100.190.205   <none>        80/TCP                                        
                                           57m                                    
                                           153d

my-server-v1, my-server-v2 2개의 Pod를 각각 endpoint로 설정하는 service 2개를 배포합니다.
앞서 배포한 service는 selectorapp: my-server 레이블만 구성되어 있었으나, 이번에 배포할 service는 version 레이블을 추가하여 각각의 Pod를 endpoint로 설정합니다.

app-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-server-v1
  labels:
    app: my-server
    version: v1
spec:
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: my-server
    version: v1
---
apiVersion: v1
kind: Service
metadata:
  name: my-server-v2
  labels:
    app: my-server
    version: v2
spec:
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app: my-server
    version: v2
$ kubectl apply -f app-service.yaml -n devopssong

service/my-server-v1 created
service/my-server-v2 created

$ kubectl get svc -n devopssong

NAME                           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                               
                                   AGE
my-server                      ClusterIP   10.100.190.205   <none>        80/TCP                                                
                                   57m
my-server-v1                   ClusterIP   10.102.196.249   <none>        80/TCP                                                
                                   48m
my-server-v2                   ClusterIP   10.102.249.83    <none>        80/TCP                                                
                                   48m

배포한 service의 endpoints를 확인하면 my-server-v1, my-server-v2 service는 각각 하나의 Pod를, my-server service는 2개의 Pod를 endpoint로 가지고 있습니다.

$ kubectl get endpoints -n devopssong

NAME                           ENDPOINTS                                                       AGE
my-server                      10.0.134.50:80,10.0.27.86:80                                    11m
my-server-v1                   10.0.134.50:80                                                  2m53s
my-server-v2                   10.0.27.86:80                                                   2m53s

East-West 트래픽을 제어하기 위한 Gateway API 리소스인 HTTPRoute를 배포합니다.

기존 North-South 트래픽을 관리하는 HTTPRoute 리소스의 경우 parentRefs 대상을 gateway로 지정했으나, Gateway API GAMMA 활용 East-West 트래픽 제어를 위해 service를 대상으로 지정합니다.

app-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-server-route
spec:
  parentRefs:
  - name: my-server
    kind: Service
    group: ""  # Kubernetes Service는 core group으로, 빈 값으로 구성합니다.
    port: 80

  rules:
  - matches:
    - headers:
      - name: version
        value: "new"
    backendRefs:
    - name: my-server-v2
      port: 80

  - backendRefs:
    - name: my-server-v1
      port: 80

위 HTTPRoute 리소스는 my-server service로 향하는 트래픽을 하단의 rules에 정의된 규칙에 따라 정의된 backendRefs로 전달합니다.

  • 기본적으로 my-server service로 향하는 트래픽은 my-server-v1 service로 전달됩니다.
  • 요청의 헤더에 ‘Version: new’ 가 포함된 경우, my-server-v2 service로 전달됩니다.
$ kubectl apply -f app-route.yaml -n devopssong 

httproute.gateway.networking.k8s.io/my-server-route created

4. HTTPRoute 기반 트래픽 라우팅 확인

앞서 배포한 클라이언트 Pod(curl-client) 내부로 진입하여, my-server 서비스로 요청을 보내보겠습니다.
“Version: new” 헤더 유무에 따라 트래픽이 v1v2로 정확히 분기되는지 확인합니다.

1. Pod 내부에서 curl 명령어를 사용하기 위해 컨테이너 내부의 쉘에 접속합니다.

$ kubectl exec -it -n devopssong curl-client-79b487649f-5xvwh -- sh

~ $ 

2. my-server service로 curl 요청을 전송하여 응답을 확인합니다.
기본 backendRefs로 지정된 my-server-v1 service(pod)의 응답을 반환합니다.

~ $ curl http://my-server
Hello from Version 1

~ $ curl http://my-server
Hello from Version 1

~ $ curl http://my-server
Hello from Version 1

~ $ curl http://my-server
Hello from Version 1

~ $ curl http://my-server
Hello from Version 1

3. my-server service로 “Version: new” 헤더를 추가하여 curl 요청을 전송해 응답을 확인합니다.
HTTPRoute에 정의된 rule에 따라 my-server-v2 service(pod)의 응답을 반환합니다.

~ $ curl -H "Version: new" http://my-server
Hello from Version 2

~ $ curl -H "Version: new" http://my-server
Hello from Version 2

~ $ curl -H "Version: new" http://my-server
Hello from Version 2

~ $ curl -H "Version: new" http://my-server
Hello from Version 2

5. 결론

이번 포스트에서는 별도의 Istio 전용 CRD(VirtualService)를 사용하지 않고, Gateway API GAMMA 스펙을 준수하는 HTTPRoute 리소스 하나만으로 클러스터 내부의 East-West 트래픽을 제어하는 방법을 알아봤습니다.

이번 포스트를 통해 Gateway API GAMMA의 다음과 같은 특징을 확인할 수 있었습니다.

  • HTTPRoute를 Gateway가 아닌 Service에 직접 연결함으로써 서비스로 향하는 모든 내부 트래픽을 선언적으로 제어할 수 있음
  • 헤더 기반 조건 분기 등 Ingress에서 사용하던 고급 라우팅 기능을 내부 통신에도 그대로 적용 가능
  • 서비스 메시 벤더별 전용 설정에 의존하지 않고, Kubernetes 표준 API만으로 일관된 트래픽 관리 모델을 구현할 수 있음

HTTPRoute 리소스는 이번 포스트에서 구현한 헤더 기반 라우팅뿐만 아니라, 경로(Path) 기반 라우팅, 가중치 기반 트래픽 분산(Canary/Blue-Green), 요청 메서드·쿼리 파라미터 기반 조건 분기 등 다양한 East-West 트래픽 제어 정책을 동일한 방식으로 확장 구성할 수 있습니다.

운영 중인 클러스터의 트래픽을 Service Mesh와 Gateway API를 통합하여 관리할 생각이신가요? NGINX STORE를 통해 문의하여 Istio 및 NGINX Gateway Fabric 도입을 상담하세요.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required