EKS · Central VPC 인프라
실제 서비스급 트래픽을 감당하는 쿠버네티스 플랫폼을 설계하고 싶어 시작한 프로젝트입니다. EKS 기반 애플리케이션 플랫폼과 Central VPC 중앙 관제 네트워크를 구축하고, QA 환경에서 2,000 RPS · 12만 요청으로 구조의 유효성을 직접 검증했습니다.
담당 역할: 팀장으로 전체 일정과 방향을 조율하면서 EKS 중심 아키텍처 설계를 주도했습니다. '부하가 걸렸을 때 어디서 먼저 터지는가'를 미리 예측하고 대응하는 것에 집중했습니다.

Prod / QA / Dev / DR / Central VPC 5개 환경으로 나누고, Prod·QA는 CloudFront → ALB(Ingress) → EKS Pod 흐름을 멀티 AZ로 구성했습니다. 데이터 계층은 Aurora + RDS Proxy, Central VPC에 GitLab·모니터링·DNS 보안 관측을 모았습니다.
설계 근거
ECS도 검토했지만 KEDA·Karpenter 같은 오픈소스로 오토스케일링을 세밀하게 제어하고 싶었고, Helm 차트로 환경별 설정을 일관되게 관리하고 싶어서 EKS를 택했습니다. Central VPC는 처음에 없었는데, 환경마다 따로 모니터링을 붙이다 보니 알람이 흩어져서 전체 그림을 보기 어렵다는 문제를 느꼈고, 공통 서비스를 한 곳에 모아 관리하는 중앙 관제형으로 전환했습니다. DR은 Active-Active까지 갈 예산이 없어서, 평시에는 최소 리소스만 유지하고 장애 시 빠르게 확장하는 Pilot Light 전략으로 비용과 복구 시간의 균형을 맞췄습니다.
5개 환경을 콘솔에서 수동으로 맞추면 실수가 반복될 게 뻔했고, 특히 DR 환경은 평소에 안 쓰다가 장애 시 재현해야 하므로 코드로 정의하지 않으면 의미가 없다고 판단했습니다.
ECS와 고민했지만, KEDA로 RPS 기반 스케일링을 하고 Karpenter로 노드까지 자동 확장하려면 쿠버네티스 생태계가 필요했습니다. '트래픽이 올라갈 때 Pod만이 아니라 노드까지 같이 늘어나는 구조'를 만들고 싶었습니다.
HPA의 CPU 기반 스케일링은 실제 사용자 트래픽과 괴리가 컸습니다. Prometheus에서 수집한 Pod당 평균 RPS를 기준으로 확장해야 실제 부하에 맞게 반응할 수 있었고, 최소 45개 Pod 선기동으로 Cold Start 문제도 함께 해결했습니다.
KEDA가 Pod를 늘려도 노드가 부족하면 Pending이 발생합니다. Cluster Autoscaler는 반응이 느렸고, Karpenter는 Pending Pod를 감지해 필요한 사양의 노드를 빠르게 생성해줘서 Pod-노드 확장을 동시에 맞출 수 있었습니다.
kubectl apply를 직접 치면 '지금 운영 환경이 Git과 같은 상태인지' 확신할 수 없었습니다. Git을 단일 소스로 삼아 배포 상태와 이력을 추적할 수 있어야 5개 환경을 관리할 수 있다고 판단했습니다.
노드에 IAM Role을 붙이면 그 노드 위의 모든 Pod가 같은 권한을 갖게 됩니다. Pod마다 필요한 권한만 ServiceAccount에 매핑해 보안 범위를 최소화하고 싶었습니다.
- 이슈
- 부하 테스트 시 Spring Boot Pod가 부팅 완료 전에 트래픽을 받아 에러가 나고, HPA 기반 확장은 트래픽 증가를 따라가지 못해 응답 지연이 발생했습니다.
- 분석
- Spring Boot는 부팅에 10~15초가 걸리는데 readiness probe 없이 바로 서비스에 등록됐고, HPA는 CPU 기준이라 실제 요청량 증가와 반응 시점이 달랐습니다. Pod는 늘어나도 노드가 부족해 Pending이 발생하는 문제도 겹쳤습니다.
- 해결
- startup/readiness/liveness probe를 분리해 부팅 완료 전 트래픽 유입을 차단하고, KEDA로 Pod당 평균 RPS 기준 스케일링으로 전환했습니다. 최소 45개 Pod를 선기동해 초반 부하를 흡수하고, Karpenter로 Pending 시 노드를 자동 확장해 Pod-노드 확장 타이밍을 맞췄습니다.
- 결과
- QA에서 약 2,000 RPS를 60초간 유지하며 총 12만 요청을 무중단 처리했습니다.
- 이슈
- 워커 노드가 EKS API와 통신하지 못해 Join에 실패하거나 NotReady 상태로 빠졌습니다.
- 분석
- 처음에는 노드 자체의 문제인 줄 알았는데, 실제로는 Private Subnet의 라우팅 테이블이 NAT Gateway를 거치지 않아 컨트롤플레인에 도달하지 못한 네트워크 설정 오류였습니다.
- 해결
- 서브넷 라우팅과 DNS 설정을 바로잡고, 인스턴스 타입별 MaxPods 한계도 함께 조정했습니다. 이 경험 이후 EKS 네트워크 체크리스트를 만들어 환경 추가 시 같은 실수를 반복하지 않도록 했습니다.
- 결과
- 노드 Join 문제가 해소되어 Karpenter 기반 오토스케일링이 안정적으로 동작했습니다.
'설계한 구조가 실제 부하에서 버티는가'를 직접 검증해본 경험이 가장 컸습니다. 도면 위의 아키텍처와 트래픽이 들어온 뒤의 아키텍처는 다른 것이라는 걸 체감했습니다.
다만 이 프로젝트에서 더 오래 남은 건 AI를 쓰는 태도 쪽이었습니다. Terraform 모듈이든 KEDA ScaledObject든 Karpenter NodePool이든, 초안을 만드는 속도는 AI를 쓰면 확실히 빨라졌습니다. 그런데 빨라진 건 구현이지 판단이 아니었습니다. HPA 대신 KEDA를 쓴 이유, Cluster Autoscaler 대신 Karpenter를 택한 이유를 제가 설명하지 못하면, 생성된 설정은 그냥 '돌아가는 YAML'에 머물렀습니다. 실제로 스케일 정책의 근거를 제대로 잡지 않은 채 값을 넣었다가 부하 테스트에서 AWS 비용을 초과한 적이 있는데, 그건 도구의 문제가 아니라 기준 없이 지시한 제 문제였습니다.
그래서 순서를 바꿨습니다. 왜 이 기술이어야 하는지를 먼저 정리하고, 그 결정을 코드로 옮기는 구간에 AI를 붙였습니다. 그러자 같은 도구가 전혀 다르게 작동했습니다. 'AI가 있어서 겨우 할 수 있었던 일'이 아니라 'AI 덕분에 같은 시간에 더 많은 구성을 검증해볼 수 있었던 일'이 됐습니다. 기술 선택의 이유를 이해하고 있을수록 AI의 효용이 커진다는 것이 이 프로젝트의 결론이었습니다. Karpenter 설정을 GitOps 흐름에 끝까지 통합하지 못한 채 마무리된 건 여전히 아쉬움으로 남아 있습니다.