Search

Gateway API

Tags
k8s
network
gateway-api
Created
2024/10/09 10:52
Created time
2024/10/09 01:52
category
kubernetes

개요

트래픽을 외부로부터 받기 위한 전통적인 방법은 Ingress인데, 운영 환경이 거대해지면서 더 정교하고 유연하고 확장성 있는 형태의 네트워크 트래픽 처리가 필요할 때 Gateway API를 사용할 수 있음에 따라 이를 탐색
Ingress에서는 비교적 복잡한 라우팅 시나리오에 제한적이고, HTTP외의 프로토콜에는 지원이 부족한 단점이 있음

개념

Gateway API는 헤더 기반 라우팅, 헤더 변조, 트래픽 미러링 등 Ingress에 비해 조금 더 풍부한 기능을 제공

특징

Improved Resource Model
→ GatewayClass, Gateway 및 Route 같은 CRD를 도입하여 라우팅 규칙을 표현력 있게 정의하는 방법을 추구
Protocol Agnostic
→ HTTP 뿐만 아니라 TCP, UDP, TLS 등의 여러 프로토콜을 지원
Enhanced Security
→ 빌트인 TLS를 지원하고 세분화된 ACL을 지원
Cross-Namespace Support
→ 서로 다른 네임스페이스에 존재하는 Service로 트래픽을 라우팅하여 유연한 아키텍쳐를 구축 가능
Extensibility
→ CRD 및 정책을 통해 API를 쉽게 확장 가능
Role-Oriented
→ 클러스터 운영자, 개발자, 보안 담당자 간의 명확한 분리
→ API도 역할에 따라 동작과 권한을 유연하게 제공할 수 있으며, 각 권한 별로 라우팅 규칙을 지정하여 분리된 네임스페이스 내에서 편리하게 관리 가능

구성 요소

GatewayClass, Gateway, HTTPRoute, TCPRoute, Service
GatewayClass는 공통 설정을 통해 Gateway들을 정의하는 리소스
Gateway는 클라우드 LB처럼 트래픽을 처리하는 인프라 리소스
HTTPRoute는 Service를 엔드포인트로 하며, HTTP 관련 규칙으로 트래픽을 핸들링하는 리스너 리소스
GatewayClass는 인프라 제공자가 담당
Gateway는 클러스터 운영자가 담당
HTTPRoute는 개발자가 담당

통신 흐름

클라이언트 → (HTTP 요청) → Gateway → HTTPRoute → (라우팅 규칙) → Service

종류

Gateway API로는 Gloo Gateway, Cilium Gateway API, Istio, Envoy Gateway 등이 있음

Gloo Gateway

Gloo Gateway는 기능이 풍부하고 유연하며, Envoy Proxy 위에 구축된 k8s 네이티브 Ingress Controller
Gloo Gateway의 구조는 여기를 참고

내부 동작 방식

위 컴포넌트들은 Gloo와 k8s Gateway API의 커스텀 리소스를 해석하여 Envoy 설정에 적용하기 위해 상호 동작
1.
Config, Secret을 담당하는 gloo Pod 내부의 컴포넌트가 k8s Gateway API와 Gloo Gateway 리소스 (Gateway, HTTPRoute, RouteOptions) 를 통해 클러스터를 감시
2.
Config와 Secret의 감시자가 새로운 리소스 혹은 수정된 리소스를 발견하게 되면, 리소스의 설정을 Gloo Gateway의 Translation Engine으로 전송
3.
Translation Engine은 k8s Gateway API와 Gloo Gateway 리소스를 Envoy 설정으로 해석하게 되고, 이러한 모든 설정은 xDS 스냅샷 형태로 정리
4.
Reporter는 Translate Engine이 처리한 모든 리소스의 상태를 수신
5.
Reporter는 해당 리소스의 상태를 ETCD에 기록
6.
xDS 스냅샷이 glo Pod 내의 Gloo Gateway의 xDS 서버 컴포넌트로 제공
7.
클러스터 내부의 Gateway 프록시들은 가장 최신의 Envoy 설정을 Gloo Gateway xDS 서버로부터 읽음
8.
사용자가 Gateway 프록시로 노출한 IP 주소 혹은 호스트 이름으로 요청을 전송
9.
Gateway 프록시는 리스너와 라우팅을 결정하고 요청을 클러스터로 전달하기 위한 xDS 스냅샷으로 제공되는 라우팅 설정을 이용

Translate Engine 처리 흐름

1.
Translation는 Envoy 클러스터의 설정을 업스트림과 Service를 통해 생성하면서 시작되는데, 여기서 언급된 Envoy 클러스터란 비슷한 호스트들을 묶은 그룹을 의미 (각 업스트림은 타입에 따라 처리 방법이 정해져 있기 때문에, 올바르게 설정된 업스트림과 Service들은 타입 매칭을 통해 클러스터 메타 데이터를 포함하여 Envoy 클러스터로 적절히 변환되어 생성)
2.
업스트림의 함수와 관련된 클러스터 메타 데이터를 추가하여 나중에 함수 관련 Envoy 필터가 처리할 수 있도록 함
3.
모든 Envoy 경로들이 HTTPRoute와 RouteOption 리소스에 정의한 라우팅 규칙을 토대로 생성하게 되고, 경로 생성 후에 Translation Engine은 VirtualHostOption, ListenerOption, HttpListenerOption 리소스들을 이용하여 Envoy Virtual Host로 집계하고 Envoy HTTP Connection Manager 설정으로 추가하는 처리 과정을 수행
4.
필터 플러그인은 필터 설정에 질의를 통해 동작하고, HTTP와 TCP 필터 목록을 생성하여 Envoy 리스너에 추가
5.
xDS 스냅샷은 유효한 엔드포인트 (EDS), 클러스터 (CDS), 경로 설정 (RDS), 리스너(LDS)로 구성하게 되고, 이러한 스냅샷은 Gloo Gateway xDS 서버로 전달되어 Gateway 프록시가 xDS 서버를 바라보며 새로운 설정을 확인할 수 있도록 도움 (Gateway 프록시는 이 과정에서 새로운 설정을 발견하면 이를 읽어서 이용)

배포 패턴

Single Ingress
Shared Gateway
Shared Gateway with Central Ingress
API Gateway for a Service Mesh

실습 환경 준비

Kind 클러스터를 이용하기 앞 서, Docker Desktop 설정에서 Rosetta 이용 설정을 비활성화
# Kind 클러스터로 이용할 manifest 생성 cat << EOT > kind-1node.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 30000 hostPort: 30000 - containerPort: 30001 hostPort: 30001 - containerPort: 30002 hostPort: 30002 EOT # 클러스터 생성 kind create cluster --image kindest/node:v1.30.0 --config kind-1node.yaml --name myk8s # 기본 툴을 노드에 설치 docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bsdmainutils bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'
Shell
복사

실습

Gateway API 설치

# CRD 설치 kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml # CRD 조회 kubectl get crd
Shell
복사

glooctl 설치

# Control Plane 접속 docker exec -it myk8s-control-plane bash # glooctl 설치 curl -sL https://run.solo.io/gloo/install | GLOO_VERSION=v1.17.7 sh # 환경 변수 추가 export PATH=$HOME/.gloo/bin:$PATH # glooctl 버전 확인 glooctl version
Shell
복사

Gloo Gateway 설치

# Gloo Gateway 차트 설치 helm repo add gloo https://storage.googleapis.com/solo-public-helm helm repo update helm install -n gloo-system gloo gloo/gloo \ --create-namespace \ --version 1.17.7 \ --set kubeGateway.enabled=true \ --set gloo.disableLeaderElection=true \ --set discovery.enabled=false # Gloo Control Plane을 배포 kubectl rollout status deployment/gloo -n gloo-system
Shell
복사
# gloo-system 네임스페이스의 리소스를 확인 # rollout 중인 Pod는 시간이 지나면 삭제되므로 Completed 상태의 0/1이라면 무시 kubectl get po,svc,endpointslices -n gloo-system
Shell
복사
# gatewayclass 리소스 매뉴얼 확인 kubectl explain gatewayclasses # Gloo Gate 설치 확인 kubectl get gatewayclasses
Shell
복사

HTTP Bin 애플리케이션 배포

# HTTP Bin 애플리케이션 배포 # https://raw.githubusercontent.com/solo-io/solo-blog/main/gateway-api-tutorial/01-httpbin-svc.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/solo-blog/main/gateway-api-tutorial/01-httpbin-svc.yaml
Shell
복사
# HTTP Bin 애플리케이션 배포 확인 및 Rollout kubectl get deploy,po,svc,endpointslices,sa -n httpbin kubectl rollout status deploy/httpbin -n httpbin
Shell
복사
# Service를 NodePort로 설정 cat << EOT | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app: httpbin service: httpbin name: httpbin namespace: httpbin spec: type: NodePort ports: - name: http port: 8000 targetPort: 80 nodePort: 30000 selector: app: httpbin EOT # 접속 확인 open http://localhost:30000
Shell
복사

HTTP 리스너 Gateway 생성

# manifest 생성 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/02-gateway.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/02-gateway.yaml
Shell
복사
# Gateway 활성화 확인 kubectl get gateway -n gloo-system # Gateway 템플릿 확인 kubectl get gateway -n gloo-system -o yaml | k neat # Gateway 생성으로 gloo-proxy-http라는 Envoy 프록시 역할의 Pod 생성을 확인 kubectl get deployment gloo-proxy-http -n gloo-system # Envoy 사용 확인 kubectl describe pod -n gloo-system | grep Image:
Shell
복사
# gloo-proxy-http의 Service가 ExternalIP이고, Pending 상태임을 확인 kubectl get svc -n gloo-system gloo-proxy-http # NodePort를 30001로 고정 설정 cat << EOT | kubectl apply -f - apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/instance: http app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: gloo-proxy-http app.kubernetes.io/version: 1.17.7 gateway.networking.k8s.io/gateway-name: http gloo: kube-gateway helm.sh/chart: gloo-gateway-1.17.7 name: gloo-proxy-http namespace: gloo-system spec: ports: - name: http nodePort: 30001 port: 8080 selector: app.kubernetes.io/instance: http app.kubernetes.io/name: gloo-proxy-http gateway.networking.k8s.io/gateway-name: http type: LoadBalancer EOT # NodePort 설정 확인 kubectl get svc -n gloo-system gloo-proxy-http
Shell
복사

Gateway 접속을 위한 포트 포워딩

포트 포워딩의 자세한 내용은 여기를 참고
# Envoy의 Data Plane인 gloo-proxy-http에 외부에서 접속할 수 있도록 포트 포워딩 설정 # 라우팅 설정이 아직 없기 때문에 아무런 응답을 받을 수 없음 kubectl port-forward deployment/gloo-proxy-http -n gloo-system 8080:8080 &
Shell
복사

HTTPRoute 생성

parentRefs (required) : 어느 Gateway에 설정할 라우팅인지 명시
hostnames (optional) : HTTP 헤더에서 매칭될 호스트 이름을 명시
rules (required) : HTTP 요청이 어떻게 라우팅할지 결정하기 위한 규칙을 명시
각 규칙은 matches (required), filters (optional), backendRefs (optional), timeouts (optional) 필드로 구성
# HTTPRoute 생성 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/03-httpbin-route.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/03-httpbin-route.yaml
Shell
복사
# HTTPRoute 확인 kubectl get httproute -n httpbin
Shell
복사

정적 라우팅 설정

# /etc/hosts 수정 echo "127.0.0.1 api.example.com" | sudo tee -a /etc/hosts # 설정한 /get 경로 접속 open http://api.example.com:30001/get # 포트 포워딩으로 접속 # 라우팅 설정이 없어서 응답을 못 받던 것과 달리 응답 확인 가능 curl -is -H "Host: api.example.com" http://localhost:8080/get
Shell
복사

정규 표현식 라우팅 설정

# Gateway에서 사용할 /delay/* 경로는 설정이 되어 있지 않아 포트 포워딩으로는 접속 불가 curl -is -H "Host: api.example.com" http://localhost:8080/delay/1 # HTTP Bin 애플리케이션은 해당 경로를 지원하므로 NodePort로는 접속 가능 curl -is http://api.example.com:30000/delay/11
Shell
복사
# 정규 표현식 적용되어 /delay/*이 접속 가능하도록 manifest 수정 # matches의 Exact를 PathPrefix로 변경 # filter를 이용하여 /api/httpbin/을 /로 치환하도록 적용 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/04-httpbin-rewrite.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/04-httpbin-rewrite.yaml
Shell
복사
# 포트 포워딩으로 /api/httpbin/get 접속 # 8ms 소요 curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/get # 포트 포워딩으로 /api/httpbin/delay/11 접속 # 10016ms 소요 curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/delay/11
Shell
복사
# /api/httpbin/ 경로는 HTTPRoute에서 치환하여 접속하는 식이기 때문에 NodePort에선 동작하지 않음 curl http://api.example.com:30000/api/httpbin/get
Shell
복사

Bearer 토큰 주입 설정

라우팅 대상 서버 중 하나가 서버 to 서버에서 인증을 수행해야 하는 제약이 있고, 이를 클라이언트 상에서는 시스템 권한 부여에 사용될 API 키 요구를 노출시키고 싶지 않은 니즈가 있을 때 유용한 규칙
# Bearer 토큰을 주입 가능하도록 manifest 수정 # 프록시 서버 계층에서 간단히 Bearer 토큰을 주입하도록 규칙 설정 가능 # RequestHeaderModfiier라는 타입을 filter에 적용하여 static API 키를 서버 인증에 사용하도록 설정 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/05-httpbin-rewrite-xform.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/05-httpbin-rewrite-xform.yaml
Shell
복사
# /get 경로를 포트 포워딩 경로로 접속 시도 시 Authorization 헤더가 추가된 것을 확인 가능 curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/get
Shell
복사

Service 마이그레이션

Gateway API 표준에 따르면 헤더 기반 라우팅과 비율 기반의 카나리 배포를 지원하는데, 이를 이용하여 Service 마이그레이션이 가능 (해당 실습에서는 Dark Launch라는 방법을 이용하여 마이그레이션 실습)
Dark Launch
일부 사용자에게 새로운 기능을 노출하여 피드백을 수집하고 잠재적으로 더 큰 사용자 커뮤니티에 노출되기 전에 개선 사항을 파악하는 클라우드 마이그레이션 기술
# 마이그레이션에 사용할 2가지 버전의 워크로드 생성 # 워크로드는 FakeService를 이용 (HTTP, gRPC 지원) # v1, v2 my-workload를 생성 # https://github.com/nicholasjackson/fake-service # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/06-workload-svcs.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/06-workload-svcs.yaml # v1, v2 워크로드 생성 확인 kubectl get deploy,po,svc,endpointslices -n my-workload
Shell
복사
# v1 라우팅을 위한 HTTPRoute 생성 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/07-workload-route.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/07-workload-route.yaml # 포트 포워딩으로 접속 curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload
Shell
복사
# v2 라우팅을 위한 HTTPRoute 생성 # matches 규칙에 특정 헤더를 포함한 경우에만 v2로 라우팅되도록 선언적 정책을 정의 (대다수는 v1으로 라우팅) # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/08-workload-route-header.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/08-workload-route-header.yaml # 헤더가 없이 포트 포워딩 경로로 접속하면 v1 curl -is -H "Host: api.example.com" http://localhost:8080/api/my-workload # 헤더를 넣고 포트 포워딩 경로로 접속하면 v2 curl -is -H "Host: api.example.com" -H "version: v2" http://localhost:8080/api/my-workload
Shell
복사
# 기본 접속 비율 확인 시 100:0으로 v1, v2에 접속 for i in {1..100}; do curl -s -H "Host: api.example.com" http://localhost:8080/api/my-workload/ | grep body ; done | sort | uniq -c | sort -nr # HTTPRoute의 backendRefs에서 워크로드의 weight를 수정하여 비율을 50:50로 변경 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/09-workload-route-split.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/09-workload-route-split.yaml # 변경된 접속 비율 확인 시 50:50으로 v1, v2에 접속 for i in {1..100}; do curl -s -H "Host: api.example.com" http://localhost:8080/api/my-workload/ | grep body ; done | sort | uniq -c | sort -nr
Shell
복사

디버깅

일반적으로 Gloo Gateway를 이용하면서 문제가 발생하는 경우는 업스트림의 참조가 잘못된 경우인 가능성이 높음
복사/붙여넣기 과정에서 존재하지 않는 backendRefs를 입력하거나 누락된 경우가 해당될 수 있는데, 이러한 상황을 가정하고 시뮬레이션으로 어떻게 감지하는지 확인
# 좌측 터미널 # HTTProute 리소스를 감시 kubectl get httproute -n my-workload my-workload -o yaml -w
Shell
복사
# 우측 터미널 # 존재하지 않는 my-bad-workload-v2를 HTTPRoute에 적용하도록 설정 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/10-workload-route-split-bad-dest.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/10-workload-route-split-bad-dest.yaml # 접속 확인 시 50%는 성공하고, 50%는 존재하지 않는 규칙에 의해 실패 for i in {1..100}; do curl -s -H "Host: api.example.com" http://localhost:8080/api/my-workload/ | grep body; done | sort | uniq -c | sort -nr
Shell
복사
# Control Plane 접속하여 glooctl로 디버깅 docker exec -it myk8s-control-plane bash # 존재하지 않는 서비스에 대한 위 경우에선 MisConnection을 발견하면서 단서 획득 가능 export PATH=$HOME/.gloo/bin:$PATH glooctl check
Shell
복사
# Control Plane에서 glooctl을 통해 얻은 단서로 k8s에서 추가 정보 획득 kubectl get httproute my-workload -n my-workload -o yaml
Shell
복사
# 존재하지 않는 backendRefs임을 확실히 했으니 manifest 변경하여 해결 # https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/09-workload-route-split.yaml kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo-gateway-use-cases/main/gateway-api-tutorial/09-workload-route-split.yaml # HTTPRoute 확인하여 문제 해결 확인 kubectl get httproute my-workload -n my-workload -o yaml
Shell
복사

모니터링

Envoy는 기본적으로 호스트의 지표들을 확인할 수 있도록 되어 있는데, 해당 실습에서는 3000개 정도의 개별 지표를 확인 가능
Envoy의 Statistics는 여기를 참고
# 지표는 19000 포트를 통해 제공되므로 Gateway와 별도인 포트 포워딩을 적용 kubectl -n gloo-system port-forward deployment/gloo-proxy-http 19000 &
Shell
복사
# 간단히 접속 확인하여 HTTP로 제공되는 지표들을 확인 open http://localhost:19000 # 프로메테우스 접속 open http://localhost:19000/stats/prometheus
Shell
복사
# 제공 받는 지표 중에 2XX와 5XX 응답 수를 확인할 수 있는 지표가 존재 curl -s http://localhost:19000/stats | grep -E "(^cluster.httpbin-httpbin-8000_httpbin.upstream.*(2xx|5xx))" # 2XX 응답 수만 보인다면 HTTP Bin의 API 중에 5XX 응답 수를 늘리는 API를 호출 curl -is -H "Host: api.example.com" http://localhost:8080/api/httpbin/status/500 # 변경된 응답 수를 확인 curl -s http://localhost:19000/stats | grep -E "(^cluster.httpbin-httpbin-8000_httpbin.upstream.*(2xx|5xx))"
Shell
복사
위와 같이 제공받은 2XX, 5XX 지표들을 포함하여 다양한 패널들을 구성하기 위해 Grafana 대시보드를 구성할 수 있고, 자세한 내용은 여기를 참고

실습 환경 삭제

# 아래 명령어로 편집기를 열고 127.0.0.1 api.example.com에 해당하는 도메인 이름 설정을 삭제 sudo vi /etc/hosts # 클러스터 삭제 kind delete cluster --name myk8s
Shell
복사