Role: Infra & DevOps Lead
•
Phase 1–2 (Monolith → ECS): 인프라·CI/CD 단독 담당 — AWS 아키텍처 설계, Terraform 전체 코드화, Blue/Green 배포
•
Phase 3 (EKS): 플랫폼 레이어 담당 — Istio 공통 정책, GitOps CI/CD, 관측성
Period: 2025.10 – 2025.12
Team: 7인
1. 프로젝트 소개
COME2US는 트래픽 증가 상황을 가정한 MSA 기반 전자상거래 플랫폼입니다.
초기에는 단일 EC2 기반 Monolith 구조로 MVP를 빠르게 검증했고, 이후 ECS Fargate 기반 MSA로 서비스를 분리했습니다. 그러나 서비스 수가 늘어나면서 서비스 디스커버리, 공통 정책 관리, 배포 운영 방식, 인프라 재현성 측면에서 한계가 드러났습니다.
이를 해결하기 위해 최종적으로 EKS 기반 Cloud-Native 환경으로 전환했고, VPC, EKS, RDS, ElastiCache, MSK, IAM, SSM, Route53 등 주요 AWS 리소스를 Terraform으로 코드화했습니다.
2. 왜 아키텍처 구조를 전환했는가
COME2US의 아키텍처는 Monolith → ECS → EKS로 전환을 거쳤습니다. 이는 단순히 새로운 기술을 적용하기 위한 전환이 아닌, 각 단계에서 드러난 운영 한계를 근거로 다음 구조를 선택했습니다.
[이미지 1] Event-Driven MSA Architecture
Phase 1. Monolith
초기 구조는 단일 EC2 위에 애플리케이션을 Docker 컨테이너로 배포해 MVP를 빠르게 검증했습니다. 이 구조는 초기 개발과 테스트에는 유리했지만, 운영 관점에서 한계가 분명했습니다.
•
확장성 한계 — 특정 서비스에만 부하가 증가해도 서버 전체를 키워야 하는 수직 확장에 의존
•
장애 전파 — 서비스 간 경계가 없었기 때문에 하나의 서비스 장애가 동일 서버의 다른 기능에도 영향을 줄 수 있는 구조
이 한계를 해결하기 위해 서비스 독립성과 수평 확장을 목표로 ECS Fargate 기반 MSA 구조로 전환했습니다.
Phase 2. ECS Fargate 기반 MSA
ECS 기반 MSA 전환 이후 장애 격리와 서비스 분리는 개선되었지만, 서비스 수가 늘어나면서 새로운 운영 부담이 생겼습니다.
•
Spring Cloud Eureka 기반 서비스 디스커버리 문제
◦
로컬에서는 정상 동작하던 디스커버리가 ECS Fargate 배포 후 실패
◦
Fargate Task는 고정 인스턴스 IP·메타데이터를 갖지 않아, Eureka에 등록된 주소와 실제 통신 가능한 주소가 불일치
◦
일시적으로 hostname을 환경변수로 직접 주입해 대응 → 서비스 증가 시 관리 비용 상승, 운영 확장성 저하
•
Spring Cloud 의존 증가
서비스 디스커버리, 라우팅, 정책 변경이 애플리케이션 스택에 결합되어 정책 변경이 서비스 재배포로 이어지는 구조
서비스 실행 환경과 공통 운영 정책을 더 선언적이고 추적 가능한 방식으로 관리할 필요가 있다고 판단했습니다.
Phase 3. EKS 기반 Cloud-Native 인프라
최종적으로 EKS 기반 환경으로 전환하며 Kubernetes, GitOps, Service Mesh를 활용한 운영 구조를 설계했습니다. 컴퓨팅, 네트워크, 보안, 데이터 계층을 AWS 인프라 위에서 일관된 방식으로 운영할 수 있는 기반을 만드는 것을 목표로 서비스별로 흩어질 수 있는 운영 책임을 줄이고, 인프라와 클러스터 상태를 코드 기준으로 관리할 수 있는 구조를 만들었습니다.
3. 네트워크와 보안 경계를 어떻게 설계했는가 — Network & Security
VPC 3계층 서브넷 설계
서비스의 외부 노출 범위를 최소화하고 계층별 보안 경계를 명확히 하기 위해 VPC 내부를 역할에 따라 계층화하고, Public Subnet, Private Subnet, Data Subnet으로 나누어 네트워크 경계를 설계했습니다.
Public Subnet | 외부 트래픽을 받는 Load Balancer, NAT Gateway 등 인터넷과 직접 연결이 필요한 리소스 배치 |
Private Subnet | EKS Worker Node와 애플리케이션 워크로드 배치 |
Data Subnet | RDS, ElastiCache, MSK 등 데이터 계층 리소스 배치 |
이 구조를 통해 애플리케이션은 외부 요청을 처리할 수 있으면서도, 데이터 계층은 외부에서 직접 접근할 수 없도록 격리했습니다.
Security Group 기반 접근 제어
보안 그룹은 IP 기반으로 넓게 열기보다, 가능한 한 리소스 간 참조 기반으로 접근 범위를 제한했습니다.
ALB → EKS 노드 → RDS/ElastiCache/MSK 순서로 허용 트래픽을 명시적으로 제한하며 각 계층은 이전 계층에서 오는 트래픽만 허용하는 최소 권한 네트워크 구조를 적용했습니다.
IAM / IRSA 기반 최소 권한
Kubernetes Pod가 AWS 리소스에 접근해야 하는 경우 IAM Role을 부여하는 IRSA를 활용했습니다. IRSA를 통해 External Secrets, AWS Load Balancer Controller, ExternalDNS 등 클러스터 컴포넌트가 필요한 권한만 갖도록 구성해 자격 증명 범위를 최소화했습니다.
Secret 관리
애플리케이션 시크릿은 Git 저장소에 직접 포함하지 않고, AWS SSM Parameter Store와 External Secrets를 활용해 관리했습니다. 민감 정보를 Git이 아닌 AWS 관리형 서비스에서 관리하도록 구성하며 코드베이스에 시크릿을 노출시키지 않도록 했습니다.
내부 통신 보안
서비스 간 내부 통신은 Istio mTLS를 활용해 평문 통신으로 인한 내부 네트워크 보안 위험을 차단했습니다.
4. 데이터 계층을 어떻게 분리하고 보호했는가 — Data Layer
데이터 계층 보안
COME2US는 E-Commerce MSA 프로젝트였기 때문에 애플리케이션 계층과 데이터 계층의 접근 경계를 분리하는 것이 중요했습니다. 따라서 데이터 계층은 별도의 Data Subnet(Private)에 격리해 외부 인터넷에서의 접근을 차단하고, 애플리케이션 레이어에서만 접근 가능하도록 보안 그룹을 제한했습니다.
또한 민감 정보는 코드나 Manifest에 직접 포함하지 않고 AWS SSM Parameter Store와 External Secrets를 통해 클러스터 내부로 주입되도록 구성했습니다.
데이터 계층 구성
트래픽 특성상 조회 요청이 쓰기 요청보다 많을 것으로 판단해 Read Replica를 분리해 조회 트래픽을 분산했습니다. Multi-AZ 구성으로 Primary 장애 시 자동 Failover를 보장했습니다.
세션 관리와 캐싱을 위한 ElastiCache Redis를 각각 구성했습니다.
•
세션 관리용
사용자 인증 세션을 저장하는 Redis는 장애 시 로그인 상태가 유실될 수 있기 때문에 Primary + Replica 구조로 구성해 가용성을 확보했습니다.
•
캐싱용
캐싱 데이터는 데이터 유실 시 DB에서 재조회하면 되기 때문에 단일 노드로 구성해 비용을 절감했습니다.
용도별로 가용성 요구사항이 다르다고 판단해 Redis 클러스터를 목적에 맞게 분리하고 구성 방식을 달리 적용했습니다.
SAGA 보상 트랜잭션 처리, 서비스 간 강결합 제거, 장애 전파 차단을 위해 주문∙결제 서비스에 Kafka를 적용했습니다.
5. 인프라를 어떻게 코드로 관리했는가 — Infrastructure as Code
재현성 확보
수동 구성에 의존하면 환경이 사람의 기억에 종속되고 재구성 시 드리프트가 발생할 수 있기 때문에, 이를 해결하기 위해 전체 AWS 인프라를 Terraform으로 코드화했습니다.
Terraform으로 관리한 주요 범위는 다음과 같습니다:
•
VPC, Subnet, Route Table, NAT Gateway
•
EKS Cluster, Node Group
•
RDS, ElastiCache, MSK
•
IAM Role, IRSA
•
SSM Parameter Store
•
Route53, ACM
Remote Backend 구성 — S3 + DynamoDB
Terraform state는 로컬 파일에 의존하지 않고 S3 Remote Backend로 관리했습니다. 인프라 상태를 중앙에서 관리함으로써, 로컬 환경 차이로 인한 state 유실이나 불일치 가능성을 줄이고자 했습니다.
또한 DynamoDB Lock Table을 함께 구성해, 동시에 Terraform이 실행될 때 state가 충돌하지 않도록 해 팀원들과 일관된 방식으로 상태를 관리할 수 있도록 구성했습니다.
State 분리
클러스터를 재구성할 때 생명주기가 다른 리소스가 변경되지 않도록 하고자 했습니다. Route53 Hosted Zone, ACM 인증서와 같이 외부 도메인 위임이나 인증 절차와 연결된 리소스는 일반 인프라와 생명주기가 다르기 때문에 별도의 state로 분리해 관리했습니다.
Terraform과 GitOps의 책임 경계 분리
Terraform이 관리해야 할 영역과 GitOps가 관리해야 할 영역을 분리했습니다.
•
Terraform: VPC, EKS, IAM, 데이터 계층, 클라우드 인프라 리소스 관리
•
ArgoCD: Kubernetes 내부 애플리케이션, 플랫폼 컴포넌트, Manifest 관리
인프라 레이어와 클러스터 운영 레이어의 변경 주기와 책임 범위가 다르다고 판단해, 이와 같이 책임 경계를 나눴습니다. 인프라는 Terraform으로 재현성을 확보하고, 클러스터 내부 리소스는 GitOps로 선언적 상태 수렴을 유지하는 방향을 선택했습니다.
Packer 기반 CI 환경 재구성
프로젝트 환경에서는 비용 제약으로 인해 AWS 리소스를 자주 Destroy/Recreate해야 했고, 이로 인해 Jenkins 서버와 Docker 환경을 매번 다시 구성해야 하는 문제가 있었습니다. 이 과정은 휴먼 에러와 설정 드리프트로 이어질 수 있었기 때문에, 이를 줄이기 위해 Packer를 사용했습니다.
[이미지 2] Packer 흐름
Packer를 통해 Docker가 사전 구성된 Golden AMI를 만들고, Terraform으로 Jenkins EC2, EBS, ALB, 컨테이너 기동까지 자동화했습니다.
그 결과 terraform apply만으로 CI 환경을 반복적으로 재구성할 수 있는 운영 모델을 만들었습니다.
6. EKS 운영 환경에서 확장성과 비용을 어떻게 고려했는가 — Compute, Scaling & Cost Optimization
EKS 기반 운영 환경에서는 워크로드 특성에 따라 어떤 노드에서 어떤 컴포넌트를 실행할지, 확장성과 비용 효율을 어떻게 함께 가져갈지가 중요하다고 보았습니다. COME2US에서는 핵심 운영 컴포넌트와 애플리케이션 워크로드가 서로 영향을 주지 않도록 노드 그룹을 분리하고, 기본 처리 용량은 On-Demand로 보장하되 트래픽 증가 구간은 Karpenter와 Spot Instance를 활용해 비용 효율적으로 확장하는 구조를 설계했습니다.
노드 그룹 분리
플랫폼 컴포넌트와 애플리케이션 워크로드가 같은 노드에서 경쟁할 경우, 애플리케이션 부하 증가로 인해 운영 컴포넌트가 Pending 상태가 될 수 있다고 판단했습니다. 이를 방지하기 위해 노드 그룹을 역할별로 분리했습니다.
구분 | 운영 방식 | 목적 |
Infra Node Group | On-Demand | Karpenter, ArgoCD, Istio, 모니터링 등 핵심 운영 컴포넌트 안정성 확보 |
App Baseline Node Group | On-Demand | 일반 트래픽 구간의 기본 애플리케이션 처리 용량 보장 |
Dynamic Node | Karpenter + Spot | 트래픽 증가 시 비용 효율적인 확장 처리 |
•
핵심 운영 컴포넌트 안정성 확보:
infra 노드 그룹에는 Taint를 적용해 핵심 운영 컴포넌트가 애플리케이션 부하에 밀려 Pending 상태가 되는 상황을 방지했습니다.
•
Karpenter + Spot 기반 확장:
애플리케이션 워크로드는 평상시에는 On-Demand 기반 노드에서 안정적으로 운영하고, 트래픽 증가 구간에서는 Karpenter가 Spot 노드를 동적으로 프로비저닝해 확장하도록 구성했습니다.
On-Demand / Spot 혼합 전략
클라우드 인프라에서는 비용 절감만큼이나 안정성과 가용성도 중요하다고 보았습니다. Spot Instance는 비용 측면에서 유리하지만 Interruption이 발생할 수 있다는 단점이 있습니다.
따라서 기본 워크로드는 On-Demand 노드에서 처리하도록 구성하고, 트래픽 증가 구간에서 추가로 필요한 용량은 Karpenter + Spot Instance를 통해 확보하도록 함으로써, 안정적인 처리와 가용성을 확보했습니다.
•
On-Demand: 기본 애플리케이션 처리 용량과 운영 컴포넌트 안정성 확보
•
Spot Instance: 트래픽 증가 시 추가 워크로드를 비용 효율적으로 처리
•
Karpenter: Pod 요구 리소스에 맞춰 필요한 노드를 동적으로 프로비저닝
Karpenter + Spot 기반 확장 구조
Karpenter는 Pod의 요구 리소스를 기준으로 필요한 노드를 동적으로 프로비저닝할 수 있기 때문에, 고정 ASG보다 유연한 확장이 가능했으며 프로비저닝 속도 또한 빠르다는 장점을 얻을 수 있었습니다.
이러한 확장 구간에 Spot Instance를 사용함으로써 확장 구간의 컴퓨팅 비용을 On-Demand 대비 약 70% 절감하는 구조를 확보했습니다(Spot 요금 기준).
HPA + Karpenter Race Condition — minReplicas 버퍼 전략
HPA는 메트릭 감지 즉시 Pod 생성을 요청하지만 Karpenter는 노드 프로비저닝에 약 60초가 소요됩니다. 이 시간 차이로 인해 트래픽이 급격히 증가할 경우, 노드가 Ready되기 전 Pod Pending이 누적되는 문제가 발생할 수 있다고 판단했습니다.
이를 완화하기 위해 핵심 도메인에는 더 높은 minReplicas를 설정하고, 조회성 서비스에는 상대적으로 낮은 minReplicas를 적용했습니다.
•
주문∙결제: 높은 minReplicas로 기본 처리 여유 확보
•
조회성 서비스: 낮은 minReplicas로 비용 효율 유지
7. 장애를 어떻게 분석했는가 — RCA
Issue 1. Jenkins Node Hang — EBS gp2 병목
CI 환경에서 Docker 이미지 빌드 중 노드 전체가 Hang 상태로 전환되는 문제가 발생했습니다. 일반적인 CPU와 Memory의 리소스 부족을 의심했지만 정상범위였습니다. 따라서 다른 병목 가능성을 확인하기 위해 CloudWatch 지표를 확인했습니다.
CloudWatch 지표를 통해 다음 패턴을 확인했습니다.
[이미지 3] BurstBalance 고갈
[이미지 4] VolumeQueueLength 급증
[이미지 5] VolumeTotalReadTime 급증
•
BurstBalance 고갈
•
VolumeTotalReadTime 급증
•
VolumeQueueLength 급증
Jenkins 서버는 단일 Root EBS(gp2)에서 OS I/O와 Docker I/O가 경합하는 구조였습니다. 따라서 Docker 이미지 빌드 과정에서 I/O가 집중되면서 gp2의 크레딧 기반 IOPS 구조가 한계에 도달했고, 그 결과 EBS I/O Throttling이 발생한 것으로 판단했습니다.
해결은 두 방향으로 진행했습니다.
1.
Root/Data 볼륨 분리
OS 레이어와 Docker 빌드 워크로드의 I/O 경합을 분리했습니다.
2.
gp2 → gp3 전환
크레딧 기반 가변 IOPS에서 고정 IOPS 구조로 전환했습니다.
Jenkins Node의 Node Hang 현상이 해소되었고, 지표도 안정화되었습니다.
이 경험을 바탕으로 EKS 환경에서 gp3를 Default StorageClass로 설정함으로써 Prometheus, Loki, Tempo와 같이 지속적인 쓰기 I/O가 발생하는 컴포넌트에서 유사한 문제가 반복되지 않도록 했습니다.
[이미지 6] QueueLength 안정화 (피크 후 안정화)
[이미지 7] TotalReadTime 안정화 (피크 후 안정화)
Issue 2. 부하 테스트 중 DB Socket Timeout — RDS CPU Credit 고갈
트래픽 증가 상황을 가정한 플랫폼이라는 전제를 검증하기 위해 JMeter로 사용자 시나리오 기반 부하 테스트를 설계•수행했습니다.
•
시나리오: 로그인 → 상품 조회 → 주문 (Throughput 3 : 6 : 1 배분)
•
부하 모델: 동시 사용자 기반 스레드 그룹, Zipf 분포로 인기 상품 조회 집중 재현, 목표 100 TPS
•
ramp-up 완료 구간에서 socket timeout 집중 발생
1.
커넥션 풀 — HikariCP maxPoolSize 확대 → 효과 없음. 풀 확대는 커넥션 수를 늘릴 뿐 반환 속도를 높이지 못함
2.
지표 확인 — CloudWatch에서 RDS(t3.medium) CPUUtilization 90~100% 고정, CPUCreditUsage 급증. Credit 고갈 시점과 timeout 구간 일치
3.
대조 실험 — 의도적으로 RDS 인스턴스를 대폭 스펙업해 동일 조건 재테스트 → 120 TPS, Error rate 1% 미만으로 안정화. DB 처리 성능이 병목임을 확정 후 다운사이징
단순한 인스턴스 스펙업은 비용 문제가 뒤따르기 때문에 근본 개선 방향으로 조회 경로 캐싱, N+1 쿼리 제거, Read Replica 수평 확장, CQRS 구조 개선을 도출했습니다. 이 테스트로 확인한 것은 애플리케이션 확장(HPA•Karpenter)이 동작하기 전에 데이터 계층이 먼저 한계에 도달한다는 점, 즉 확장의 1차 제약이 DB에 있다는 점이었습니다.
8. 어떤 결과를 만들었는가
영역 | 변화 |
클라우드 아키텍처 | Monolith → ECS → EKS로 운영 한계에 따라 구조 고도화 |
네트워크 격리 | Public / Private / Data Subnet 분리 |
보안 경계 | Security Group 참조, IAM / IRSA, SSM 기반 접근 제어 |
데이터 계층 | RDS, ElastiCache, MSK를 외부 노출 없이 분리 구성 |
인프라 재현성 | Terraform 기반 AWS 인프라 코드화 |
클러스터 운영 | EKS Node Group 분리와 Karpenter 기반 확장 구조 설계 |
비용 최적화 | Karpenter + Spot 기반 확장, gp3 전환 |
운영 가시성 | CloudWatch, Prometheus, Grafana, Loki, Tempo 기반 지표 확인 |
장애 대응 | EBS gp2 I/O 병목을 CloudWatch로 규명하고 gp3 전환 및 볼륨 분리로 개선 |
9. 운영 환경이라면 무엇이 달라져야 하는가
알림 체계 부재
Prometheus, Grafana, Loki로 지표와 로그를 수집하고 시각화하는 것까지는 구성했지만, Alertmanager 같은 알림 체계는 구성하지 못했습니다. 이상 상황을 사람이 대시보드를 보고 있어야 발견할 수 있는 구조였기 때문에, 운영 환경에서는 에러율이나 응답 지연 같은 핵심 지표에 임계값 알림을 걸어 문제를 먼저 인지할 수 있어야 합니다.
DB 인스턴스 선택과 남은 개선 과제
비용 제약으로 RDS를 t3 계열로 운영했고, 부하 테스트에서 CPU Credit 고갈이 병목으로 확인되었습니다(Issue 2). 캐싱, N+1 제거, Read Replica 증설 같은 개선 방향을 정리했지만 프로젝트가 종료되어 적용하지 못했습니다. 운영 환경이라면 credit 방식이 아닌 인스턴스를 사용하고, 개선안을 적용한 뒤 부하 테스트를 다시 수행해 HPA와 Karpenter까지 확장이 실제로 이어지는지 확인해야 합니다.
EKS 단계의 Rolling 배포
EKS 전환 이후의 배포는 Kubernetes 기본 방식인 Rolling Update로 구성했습니다. Pod가 순차적으로 교체되기 때문에 배포 중 중단은 없지만, 새 버전으로 넘어가는 트래픽 비율을 제어할 수 없고, 이상 여부를 지표로 판단해 자동으로 되돌리는 장치도 없습니다. 운영 환경이라면 일부 트래픽만 새 버전으로 보내 에러율과 지연을 확인한 뒤 전환 폭을 넓히는 Canary 방식이 적합하다고 판단했고, 이 부분은 프로젝트 종료 후 k3d 환경에서 Istio 기반 Canary 배포를 직접 구현하며 보완했습니다.







