AWS EKS 환경에서 Istio를 통한 Gateway API 도입 사례
안녕하세요, 디비디랩에서 테크 리드를 맡고 있는 정윤의입니다.
저희 디비디랩은 리서치옵스라는 목표를 달성하기 위해 ‘Diby’, ‘우쥬테스트’, ‘UPA’ 등 유저 리서치에 필요한 여러 프로덕트를 동시에 개발 및 운영하고 있습니다. 저희는 서로 의존하는 모놀리식(monolithic) 프로덕트 여러 개를 동시에 운영하며 여러가지 기술적인 문제를 마주하였는데요. 예를 들면 각 모놀리식 프로덕트가 너무 비대해진 탓에 특정 로직이 각각의 프로덕트에 중복 구현되거나, 공통으로 필요한 부분이 하나의 프로덕트에 종속되는 등의 문제를 겪었습니다. 이러한 문제를 해결하기 위해 서비스 전반을 모놀리식 아키텍처에서 쿠버네티스(Kubernetes)를 통한 마이크로서비스 아키텍처(Microservices Architecture; MSA)로 전환하고 있습니다.
이에 따라 저희 개발팀은 MSA 전환의 일환으로 클라우드 인프라의 전반적인 구성을 고도화하고 있습니다. 그러다 최근에 쿠버네티스의 새로운 표준 게이트웨이 스펙인 Gateway API가 정식 릴리스가 되었다는 사실을 알게 되었고, 검토 끝에 Gateway API를 도입하기로 하였습니다. Gateway API를 도입하여 API 게이트웨이의 요구사항을 충실히 만족하면서도 Istio 백엔드를 통해 서비스 메시(service mesh)의 기능까지 커버할 수 있을 것이라 판단했기 때문입니다.
저희는 이 아티클을 통해 AWS EKS 환경에서 Istio를 컨트롤러로 사용하여 어떻게 Gateway API를 도입할 수 있는지 구체적인 사례를 보여드리고자 합니다.
배경
API 게이트웨이와 인그레스(Ingress)
MSA에서 신경써야 할 부분은 클라이언트와 서비스 사이의 트래픽(North-south 트래픽)과 서비스 사이의 상호 통신(East-west 트래픽)입니다. 이 중, north-south 트래픽을 관리하기 위해 API 게이트웨이가 발전하였고, east-west 트래픽을 관리하기 위해 서비스 메시(service mesh)라는 개념이 등장하였습니다. 이번 아티클에서는 north-south 트래픽에 집중하도록 하겠습니다.
지금까지 쿠버네티스에서는 외부 트래픽을 처리하기 위해 인그레스(ingress)라는 표준 스펙을 지원해 왔습니다. Ingress는 인그레스 컨트롤러로 하여금 특정 호스트 및 특정 경로의 요청을 매칭하여 원하는 서비스로 라우팅하도록 규칙을 설정하는 쿠버네티스 표준 자원입니다. 다만 기존 Ingress는 기능이 너무 단순하여 헤더 기반 매칭, 가중치 기반 트래픽 라우팅, 트래픽 스플리팅 등의 기능이 필요하다면 비표준 사용자 정의 자원(CRD)을 사용해야만 했습니다. 이로 인해 Istio, Emissary 등을 포함하여 비표준 API 게이트웨이 자원 정의가 난립하게 되었습니다. 또한 인그레스 컨트롤러와 인그레스라는 두 단계의 얕은 구성으로 인해 각각의 라우팅 규칙이 인프라에 지나치게 종속적이라는 단점 또한 가지고 있습니다.
Kubernetes Gateway API
이러한 상황을 해결하기 위해 쿠버네티스에서는 Gateway API라는 표준 API 게이트웨이 자원 정의를 발표하였고, 2023년 10월 31일에 v1.0 GA(Generally available) 릴리스하였습니다. 기존 Ingress 자원은 동결되어 신기능은 앞으로 Gateway API를 통해서만 추가가 될 것이고, 현재 Istio, Emissary, Contour 등을 포함하여 20개 이상의 게이트웨이 컨트롤러가 구현되어 있습니다.
쿠버네티스 Gateway API에는 GatewayClass -> Gateway -> HTTPRoute(GRPCRoute, TCPRoute 등)로 자원이 Ingress에 비해 한 계층 더 추가되었습니다. 이를 통해 HTTPRoute로 대표되는 서비스 라우팅 정의로부터 GatewayClass로 표현되는 인프라에 대한 종속 관계가 느슨해졌습니다. 그러므로 각각의 역할에 따라 인프라 프로바이더의 경우 GatewayClass를, 클러스터 오퍼레이터의 경우 Gateway를, 애플리케이션 개발자의 경우 HTTPRoute로 관심사를 한정할 수 있습니다.
도입 방법
저희는 Gateway API의 컨트롤러로서 Istio를 채택하였습니다. 이러한 결정에는 여러가지 요인이 있습니다만, Gateway API의 east-west 트래픽 관리가 아직 프로덕션 준비가 되지 않았다는 판단이 가장 크게 작용하였습니다. 또한 2023년 11월 14일 Istio 1.20 출시와 함께 Gateway API 전체가 지원되고, Istio 공식 문서에 Gateway API를 사용하는 방법을 다수 추가한 점도 선택에 도움이 되었습니다.
그리하여 아래와 같이 AWS EKS 환경 위에 Istio를 통해 Gateway API를 적용하였습니다.
선행 조건
- AWS EKS 클러스터가 프로비저닝되어 있어야 합니다. 저희가 사용한 버전은 1.28입니다.
- AWS 로드밸런서 컨트롤러(Load Balancer Controller, 이하 AWS LBC)가 설치되어 있어야 합니다. 구체적인 설치 방법은 공식 문서를 참조해 주세요.
Gateway API CRD 설치
Gateway API는 CRD의 형태로 출시되었습니다. 이에 따라 현재 사용중인 쿠버네티스 클러스터의 버전에 큰 제약이 없이(최신 5개 마이너 버전까지 지원) CRD를 설치하는 것으로 클러스터에 Gateway API 기능을 적용할 수 있습니다. 아래와 같이 현재 클러스터에 원하는 버전의 CRD를 설치할 수 있습니다. Gateway API 버저닝 방법 중 스탠다드 채널을 사용하였습니다.
export GATEWAY_API_VERSION="v1.0.0"
kubectl apply -f <https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEWAY_API_VERSION)/standard-install.yaml>
Istio 설치
여러 가지 설치 방법 중, istioctl
을 통해 설치하였습니다. 공식 문서를 참조해 주세요.
저희가 사용한 설치 프로파일은 Istio의 자체 게이트웨이를 사용하지 않는 minimal
입니다. Istio의 게이트웨이를 동시에 사용하는 것도 가능하며, 이 경우 다른 프로파일을 선택해야 합니다.
istioctl install --set profile=minimal
Gateway 정의
아래는 클러스터 오퍼레이터의 관심사인 Gateway 리소스의 정의입니다.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: diby-gateway
namespace: istio-gateway
annotations:
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-attributes: "load_balancing.cross_zone.enabled=true"
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "CERT-ARN"
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
spec:
gatewayClassName: istio
listeners:
- name: http
hostname: "*.diby.io"
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
- name: https
hostname: "*.diby.io"
port: 443
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
spec.gatewayClassName
을 istio
로 지정하여 게이트웨이 컨트롤러로서 Istio를 사용하겠다고 선언합니다.
spec.listeners
아래에 각각의 리스너를 지정합니다. 호스트 이름이 *.diby.io, 포트가 80, 프로토콜이 HTTP인 경우 매칭합니다. 또한 호스트 이름이 *.diby.io, 포트가 443, 프로토콜이 HTTP인 경우에도 매칭합니다.
- 이 예시에서는 HTTPS로 지정하지 않습니다. 아래에 언급하겠지만, NLB에서 TLS termination을 진행한 뒤 트래픽이 들어올 예정입니다.
allowedRoutes
옵션을 통해 해당 게이트웨이가 어떤 HTTPRoute를 볼 수 있는지 지정합니다.
metadata.annotations
부분은 Istio의 동작과 AWS LBC의 동작의 한계로 인해 지정해 주어야 하는 애노테이션입니다.
- Gateway API의 게이트웨이가 정의될 때마다 Istio는 자동으로 LoadBalancer 타입의 서비스를 생성하며, 이에 따라 AWS LBC는 네트워크 로드밸런서(Network Load Balancer, 이하 NLB)를 프로비저닝합니다.
- 다만 AWS LBC에서 NLB를 생성하는 경우 로드밸런서 스킴의 기본값이 ‘internal’이기 때문에, 외부 트래픽을 받을 수 없습니다.
- 그러므로 LoadBalancer 서비스에
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
애노테이션으로 명시적으로 스킴을 지정해 주어야 합니다. - 다만 LoadBalancer 서비스는 Istio가 관리하므로, 시스템적으로 관리되는 서비스를 직접 조작하는 것은 안전하지 않습니다. 공식 문서에 따르면 Gateway에 정의된 애노테이션과 레이블은 서비스로 복사되므로, Gateway 정의에 지정해 주면 됩니다.
- 같은 원리로 NLB에 TLS 터미네이션을 위의 설정과 같이 추가해 줄 수 있습니다. Amazon Certificate Manager로 발급받은 인증서의 ARN을 입력하여 간단하게 초기 TLS 설정을 마칠 수 있습니다.
네임스페이스 수정
게이트웨이가 인식할 수 있는 HTTPRoute는 네임스페이스의 레이블로 결정되도록 설정했으므로, HTTPRoute가 들어갈 네임스페이스를 아래와 같이 수정해 줍니다.
apiVersion: v1
kind: Namespace
metadata:
labels:
kubernetes.io/metadata.name: diby-example
shared-gateway-access: "true"
name: diby-example
# omit ...
HTTPRoute 정의
아래는 게이트웨이에서 매칭된 조건과 맞는 요청을 실제 서비스와 매핑하는 HTTPRoute의 정의입니다.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: diby-route
namespace: diby-example
spec:
parentRefs:
- name: diby-gateway
namespace: istio-gateway
hostnames:
- example.diby.io
rules:
- backendRefs:
- name: diby-example-service
port: 80
spec.parentRefs
를 통해 해당 라우트가 어떤 게이트웨이에 부착될 예정인지 지정할 수 있습니다. 예시의 경우 윗 섹션에서 정의한 게이트웨이를 지정했습니다.spec.hostnames
를 통해 좀 더 구체적인 호스트 이름을 지정할 수 있습니다.spec.rules.backendRefs
를 통해 해당 라우트 규칙을 통해 요청이 전달될 백엔드 서비스를 지정할 수 있습니다.- 주의해야 할 점은, 기본적으로 HTTPRoute가 속한 네임스페이스와 백엔드 서비스는 같은 네임스페이스 안에 있어야 한다는 것입니다.
- 만약 다른 네임스페이스에 있는 자원을 참조할 필요가 있다면, ReferenceGrant라는 자원으로 명시적으로 접근을 허용해야 합니다.
DNS 설정
Route 53 등의 DNS 관리 서비스를 통해 프로비저닝된 게이트웨이 NLB를 향하도록 원하는 도메인의 A 레코드를 지정합니다.
현재까지의 결과
- Gateway 생성에 따라 Istio가 LoadBalancer 타입의 서비스를 생성하였습니다.
- 여기에 반응하여 AWS LBC가 AWS NLB를 프로비저닝합니다. 이와 함게 쿠버네티스 노드 그룹을 타겟그룹으로 지정하고, 보안 그룹을 설정합니다.
- 이때 NLB의 scheme은 “internet-facing”이 되어 외부 트래픽을 받을 수 있게 설정됩니다.
- DNS와 NLB를 연결하였으므로, 원하는 호스트 이름을 가지고 쿠버네티스 클러스터로 요청을 보낼 수 있습니다.
- 현재 도입의 편의를 위해 TLS termination을 NLB에서 하도록 하였는데, mTLS(mutual TLS)와 제3자와의 통신(OAuth2 등) 등을 위해 추후 클러스터 내부에서 TLS 관리를 처리해 줄 필요성이 있습니다.
마무리
위와 같이 AWS EKS 환경 위에 Istio를 통해 Gateway API를 설정해 보았습니다. 이제 Gateway API를 통하여 Ingress보다 좀 더 세밀한 라우트 설정, 트래픽 컨트롤 등을 달성할 수 있게 되었습니다. 또한 컨트롤러로 Istio를 채택함으로써 서비스 메시의 기능 또한 커버할 수 있으며, Istio에서 제공하는 여러 추가 기능을 동시에 사용할 수 있게 되었습니다.
Gateway API는 Ingress에 대한 기능 추가를 동결하고 현재 주력으로 개발 및 발전되고 있는 새로운 표준이므로, 새로운 프로젝트를 시작한다면 적극적으로 도입 여부를 검토해 보시기를 권장합니다. 다만 Ingress가 deprecate될 예정이 없이 무기한으로 지원될 예정이므로, 현재 사용중인 Ingress만으로 필요한 기능을 모두 충족할 수 있다면 굳이 공수를 들여 바꿀 필요는 없어 보입니다. 또한 2024년 1월 기준으로 상대적으로 새로운 기술 스펙인 관계로 관련된 리소스가 매우 부족한 것도 장애물이 될 수 있겠습니다.