CloudWave에서 진행한 Ecommerce 인프라 구축 프로젝트를 통해, EKS를 이용해 Pod 기반의 유연한 스케일 인/아웃이 가능한 아키텍처를 설계하고 구축하였습니다.
EKS와 Karpenter를 이용한 아키텍처가 EC2와 ASG를 이용한 아키텍처에 비해 얼마나 비용 측면에서 뛰어난 지 확인하고, 아키텍처의 문제점을 파악하여 성능을 개선하고자 부하테스트를 진행했습니다.
부하테스트 도구로는 nGrinder를 사용했고, APM으로는 DataDog을 사용하여 vUser가 1500일 때, 단일 상품 조회 시 TPS를 측정하였습니다.
EKS cluster에 상품 조회와 주문 기능을 담당하는 products Pod를 미리 10개 프로비저닝 한 뒤, 1500 vUser를 견딜 수 있도록 32 vCPU, 64 GiB Memory를 가진 c5a.8xlarge type 인스턴스에 컨테이너로 nGrinder를 띄워 테스트를 진행했습니다.
run nGrinder:
docker pull ngrinder/controller
docker run -d -v ~/ngrinder-controller:/opt/ngrinder-controller --name controller -p 80:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller
docker pull ngrinder/agent
docker run -d --name agent --link controller:controller ngrinder/agent
부하테스트를 통한 성능 개선 과정
먼저, ElastiCache를 사용하지 않는 경우의 부하테스트 결과입니다. TPS가 2423으로 준수한 결과가 나왔지만 단일 상품 조회라는 것을 감안하면 만족스럽지 않은 결과였습니다. 특히 10개 띄운 Pod가 늘어나지 않을 정도로 낮은 부하가 서비스 단에 걸렸다는 것을 알 수 있었습니다.
이후 ElastiCache(Redis)를 적용한 뒤 5분 간의 부하테스트를 진행한 결과, 위와 같이 불안정한 결과가 나왔습니다.
DB만 사용했을 때보다 더 TPS가 낮아진 것을 확인할 수 있었고, 마찬가지로 Pod가 늘어나지 않았기 때문에 서비스 단의 문제는 아니라고 생각할 수 있었습니다.
이후 몇번의 부하테스트를 거쳤으나 비슷한 결과를 확인할 수 있었고 DataDog의 APM을 이용해 DB layer의 지연시간을 확인해 본 결과, 위와 같이 Redis와 AuroraDB 모두에서 큰 지연 시간이 발생했음을 알 수 있었습니다.
AWS 웹 콘솔을 통해 ElastiCache와 DB를 확인해본 결과, Redis와 Aurora의 인스턴스 타입을 모두 t타입으로 설정했기 때문에 credit을 모두 소진했거나 Redis OOM 이슈가 발생한 경우 이러한 성능 저하가 발생했을 것이라고 예상할 수 있었습니다.
따라서 DB와 Redis의 메모리를 r타입으로 변경한 후 다시 부하테스트한 결과, 다음과 같이 비교적 안정적인 그래프를 확인할 수 있었습니다.
하지만 그럼에도 여전히 갑작스럽게 TPS가 떨어지는 구간이 존재함을 발견할 수 있었습니다.
K9s를 이용해 Pod에 대한 부하를 모니터링하여 Pod가 늘어나기 시작할 때 이러한 구간이 발생함을 확인하였고,
HPA가 scale out을 시작해야할 cpu 사용률 기준을 너무 높게 잡아 pod가 너무 느린 시점에 늘어난다는 것을 알아낼 수 있었습니다.
이를 수정한 후 부하테스트를 진행한 결과, 다음과 같이 CPU와 Memory 사용률이 과도하게 높아지기 전에 pod와 node가 생성되는 것을 확인할 수 있었고,
다음과 같이 더 안정적인 TPS 그래프를 얻을 수 있었습니다.
부하테스트를 통해 DB 계층과 서비스 계층의 문제를 모두 해결할 수 있었고, 결과적으로 2400 TPS -> 3700 TPS로 상품 조회 서비스의 성능을 약 1.5배 개선할 수 있었습니다,
비용 절감 효과
1500의 vUser를 이용한 부하테스트 결과, 17개의 Pod와 7개의 노드가 생성되는 것을 확인할 수 있었습니다.
하지만 products pod는 1 core, 1 GiB mem을 사용하기 때문에 그 중 두 개의 노드는 coredns, metric-server, argocd와 DataDog 등의 다양한 kube-system, mgmt용 서비스가 사용한다고 가정했을 때(증설된 노드의 타입인 c5a.xlarge는 4 vCPU, 8 GiB mem),
3000 vUser 당 약 30개의 pod와 10개의 Node가 생성됨을 예상할 수 있었습니다.
또한 테스트후 일정 시간이 지나 안정화 되었을 때, Karpenter가 노드의 프로비저닝을 최적화하기위해, c5a.8xlarge의 노드(32 vCPU, 64 GiB mem)를 하나 생성한 뒤 Karpenter가 관리하는 모든 Pod를 해당 노드에 옮기고, 기존의 노드를 모두 삭제하는 것을 확인하였습니다. (17개의 Pod와 10개 내외의 mgmt용 Pod가 떠있을 때 기준)
따라서,
프로젝트의 목적이었던 동시 접속자 약 30000명의 부하를 견디기 위해서는 약 300개의 Pod가 필요하므로, c5a.8xlarge 타입의 노드 10개를 사전에 프로비저닝해야 한다고 예측할 수 있었습니다.
반면, 매우 큰 노드에 떠서 1 core, 1 Gib mem의 리소스를 온전히 활용할 수 있는 Pod와 달리 EC2 인스턴스는 CPU나 Memory를 100% 사용할 수는 없으므로 안전하게 최소 2 vCPU, 2 GiB mem을 사용해야 한다고 가정할 수 있습니다.
이 때, 300개의 Pod를 EC2로 대체하기 위해서는 300개의 c5a.large(2 vCPU, 4 GiB mem) 인스턴스가 필요합니다.
따라서, AWS 비용 계산기를 이용해 EKS, Karpenter와 EC2, ASG가 30000명의 부하를 하루 견디는 데 드는 비용을 비교하였을 때,
EKS와 Karpenter의 경우, 2287 USD
EC2와 ASG의 경우, 4204 USD
즉, EKS를 사용했을 때 EC2와 ASG를 이용할 때보다 약 45%의 비용을 절감했음을 확인할 수 있었습니다.
요약
부하테스트를 통해 다양한 문제점을 발견하고 수정하여 1.5배 성능을 개선하였음을 확인하였고, EKS와 Karpenter를 사용하여 EC2와 ASG를 사용했을 때보다 약 45%의 비용을 절감했음을 확인했습니다.
추가 및 수정 사항
처음 목표로 했던 동시 접속자 3만 명의 평균 응답 시간이 1초가 되기 위해서, TPS는 30000이 되어야 합니다.
즉, 시스템의 확장이 정상적으로 이루어진다면 vUser를 늘리면 그에 비례하여 TPS도 늘어나야 합니다.
예를 들어, 1500 vUser로 최종 부하테스트한 결과의 평균 TPS가 약 3700, MTT가 450 ms로 측정되었기 때문에 3000 vUser로 부하테스트한 결과는 약 7400 TPS가 되어야 합니다.
그러나 기존에 글 작성에 사용했던 최종 부하테스트 결과를 확인해보니 3000 vUser로 부하테스트한 결과였고, 1500 vUser로 테스트한 결과를 찾아보니 평균 TPS가 약 3700으로 3000 vUser로 부하테스트한 결과와 거의 같았습니다. 이는 3000 vUser로 테스트한 결과의 MTT가 800으로, 1500 vUser로 테스트한 결과인 470의 약 두 배 수준이기 때문이라고 예상할 수 있었습니다.
이유를 찾기 위해 고민하던 중, 1500 vUser로 5분 동안 테스트한 결과가 위와 같이 불안정한 것을 확인할 수 있었습니다. 처음엔 캐시의 유지 기간을 30초로 설정하여 30초 주기로 TPS가 하락하는 것으로 생각했으나, 확인 결과 캐시의 TTL이 1시간으로 설정되어 있었습니다.
또한 확인 과정에서 ElastiCache Redis의 maxmemory-policy parameter의 default 값이 volatile-lru이기 때문에, maxmemory에 도달하면 가장 오랫동안 사용되지 않은 Key가 삭제됨을 알 수 있었습니다. 즉, Redis OOM 이슈가 발생하지 않으며, 급격한 TPS 저하의 원인이 OOM 이슈가 아님을 알 수 있었습니다. (이 때 maxmemory 값은 캐시 인스턴스의 타입에 따라 설정되며 이 값은 수정할 수 없습니다.)
프로젝트 후 시간이 지났기 때문에 로그나 시각화된 데이터를 확인하는데 한계가 있어 고민하던 중, 부하테스트 과정에서 Karpenter와 HPA로 Node와 Pod가 확장되는 것은 확인했으나, 완전관리형 서비스인 Aurora와 ElastiCache의 인스턴스가 확장되는 것은 확인하지 못했다는 생각을 하게 되었습니다.
따라서 인프라 구축에 사용했던 테라폼 코드를 확인해보니, 서버리스가 아닌 Provisioned Aurora와 자체 클러스터 설계 ElastiCache를 사용했으나 Autoscaling 정책을 설정하지 않았음을 확인할 수 있었고, 이 때문에 서비스 Pod와 Node는 확장되지만 DB와 캐시는 확장되지 않아 DB와 캐시 인스턴스의 CPU와 메모리, 네트워크 단에 과부하가 걸려 이러한 문제가 발생했을 것이라 예상할 수 있었습니다.
결과적으로, Aurora와 ElastiCache의 설정 문제로 DB와 캐시 인스턴스가 자동으로 확장되지 않았기 때문에 3000명의 부하를 견딜 수 없었음을 알 수 있었고, Aurora와 ElastiCache를 서버리스로 사용하거나 Autoscaling 정책을 올바르게 설정한다면 3만 명의 부하를 충분히 견딜 수 있을 것이라 예상할 수 있었습니다.
'Cloud engineering' 카테고리의 다른 글
kind 이용해 로컬 환경에 K8s Cluster 구축하기 (1) | 2024.09.16 |
---|---|
RHCSA 9 후기 (5) | 2024.09.07 |
Karpenter 활용하기 (6) | 2024.09.01 |
Argo Rollouts 설치 및 활용 (0) | 2024.09.01 |
ArgoCD 설치 및 설정 (1) | 2024.09.01 |