Search

COME2US

Subject
MSA 기반 E-Commerce 플랫폼
Key Tech
AWS
EKS
Terraform
Istio
Jenkins
ArgoCD
HA
Description
Monolith → ECS → EKS 3단계 전환 | Terraform 전체 인프라 코드화 | EBS RCA
Role: Infra & DevOps — 아키텍처 설계 | 인프라 구축 | CI/CD | Istio 도입 Period: 2025.10 – 2025.12 Stack: AWS(EKS · EBS · ALB · SSM · RDS · ElastiCache · MSK) Terraform · Istio · ArgoCD · Jenkins · Prometheus · Grafana Link: [GitHub] [RCA Report]

1. 프로젝트 요약

트래픽 증가 상황을 가정한 MSA 기반 전자상거래 플랫폼입니다. 단일 EC2 기반 구조에서 출발해 ECS MSA를 거쳐, 최종적으로 AWS EKS 중심의 Cloud-Native 인프라로 고도화했습니다. 이 과정에서 VPC 설계, EKS 클러스터 구축, 데이터 계층 분리, IAM 최소 권한 구성, Terraform 기반 IaC, 운영 지표 기반 RCA까지 AWS 인프라 전 영역에 대한 설계·구축을 주도했습니다.

핵심 성과

Terraform 기반 AWS 인프라를 코드로 재현 가능한 구조로 구축
EKS 전환을 통해 서비스와 인프라 운영 경계를 분리
Karpenter Spot 기반으로 온디맨드 대비 비용 절감
CloudWatch 지표 분석으로 Jenkins EBS 병목 원인 규명 및 구조 개선
Multi-AZ 기반 서브넷 분리와 계층형 보안 구조 설계

아키텍처 개선 흐름

구분
Phase 1
Phase 2
Phase 3
인프라
Monolith EC2
ECS MSA
EKS + Istio
IaC
Terraform (VPC·EC2·RDS)
Terraform (ECS·EBS·ALB)
Terraform (EKS·MSK 등)
배포 방식
수동 배포
Jenkins + Terraform (Blue/Green)
ArgoCD GitOps
장애 격리
서비스 디스커버리
Eureka (IP 불일치 문제)
Kubernetes 네이티브
정책 변경
재배포 필요
재배포 필요
Manifest PR → ArgoCD Sync
Spring Cloud
의존
의존
완전 제거
시크릿 관리
환경변수 직접
환경변수 직접
External Secrets + SSM
단순히 기술을 교체한 것이 아니라, 각 구조가 만들어낸 운영 문제를 경험하고 그 근거로 다음 단계를 결정했습니다.

2. 아키텍처 전환 배경

[이미지 1] Event-Driven MSA Architecture

Phase 1 → Phase 2: 단일 서버 구조의 한계

초기 구조는 단일 EC2 위에 애플리케이션을 올리는 Monolith였습니다. 빠르게 MVP를 배포할 수 있었지만, 두 가지 구조적 문제가 분명했습니다.
확장성 한계 — 특정 서비스에만 부하가 증가해도 서버 전체를 키워야 하는 수직 확장에 의존
장애 전파 — 서비스 간 경계가 없었기 때문에 하나의 서비스 장애가 동일 서버의 다른 기능에도 영향을 줄 수 있는 구조
→ 서비스 독립성과 수평 확장을 위해 MSA + ECS로 전환했습니다.

Phase 2 → Phase 3: Spring Cloud 의존의 구조적 한계

ECS 기반 MSA 전환 이후 장애 격리와 서비스 분리는 개선되었지만, 서비스 수가 늘어나면서 새로운 운영 부담이 생겼습니다.
ECS Fargate의 서비스 디스커버리 문제 — ECS의 내부 IP/외부 접근 경로 차이로 서비스 등록 관리 부담 발생
Spring Cloud 의존 증가 — 서비스 디스커버리, 라우팅, 정책 변경이 애플리케이션 스택에 결합
정책 관리 지점 분산 — 인증/인가 및 트래픽 제어를 플랫폼 레이어로 끌어올릴 필요성 증가
이 문제를 해결하기 위해 서비스 디스커버리와 로드밸런싱을 Kubernetes 네이티브 기능으로 이관하고, 인프라/플랫폼 레이어에서 정책을 일관되게 관리할 수 있도록 EKS 기반 Cloud-Native 구조로 전환했습니다.
추가로, 인증/인가와 라우팅 정책을 서비스 코드가 아닌 플랫폼 레이어에서 통합 제어하기 위해 Istio Service Mesh 도입을 결정했습니다. 금전 거래가 포함된 E-Commerce 서비스 특성상 서비스 간 통신 암호화(mTLS)도 보안적으로 유의미한 목표였습니다.

3. AWS 인프라 아키텍처 설계

VPC 설계부터 IAM 정책까지, 전체 인프라를 설계하고 Terraform으로 코드화했습니다.

3-1. 네트워크 / VPC 설계

애플리케이션, 인프라, 데이터 레이어를 서브넷 단위로 격리하고, 외부 노출 경로를 최소화했습니다.
 서브넷 구성 — 3계층 격리
서브넷
CIDR
용도
come2us-public-a/b
10.0.1.0/24 - 10.0.2.0/24
Bastion, ALB, NLB (2개 AZ)
come2us-private-a/b
10.0.11.0/24 - 10.0.12.0/24
EKS 노드 (2개 AZ)
come2us-db-private-a/b
10.0.21.0/24 - 10.0.22.0/24
RDS · ElastiCache · MSK (2개 AZ)
애플리케이션 워크로드와 데이터 레이어를 별도 서브넷으로 분리해, 데이터 서비스의 직접 노출을 최소화하고 접근 경로를 명확히 구분했습니다.
 NAT Gateway — 비용과 가용성의 트레이드오프
프라이빗 서브넷의 EKS 노드가 ECR, SSM, STS 등 AWS 서비스와 외부 API(Toss Payment API 등)에 접근할 수 있도록 NAT Gateway를 구성했습니다. 다만, 비용 제약을 고려해 단일 NAT Gateway를 선택했습니다. 이는 비용 관점에서는 합리적이지만 운영 환경에서는 AZ별 NAT Gateway 구성이 더 적절합니다.
 보안 그룹 — SG 참조 방식
외부 사용자 → Route53 → NLB/ALB → Ingress → Pod → RDS/ElastiCache
CIDR 직접 지정 대신 보안 그룹 참조 방식을 사용했습니다. IP 변경에 덜 민감하고, 허용 관계를 계층별로 명시적으로 드러낼 수 있습니다.
계층
인바운드 허용
ALB
0.0.0.0/0 → 443
EKS 노드
ALB SG / Control Plane SG / Bastion SG에서 필요한 포트만 허용
RDS · ElastiCache
EKS 노드 SG에서 서비스 포트만 허용
Bastion 접근은 SSH 대신 SSM Session Manager 포트포워딩을 사용해 운영 키 관리 부담을 줄이고, 세션 로그 기반 감사 추적이 가능하도록 구성했습니다.
 트래픽 진입 구조 분리
[앱 트래픽] Route53 → NLB → Istio IngressGateway → Pod [운영 도구] Route53 → ALB → Kubernetes Ingress → Pod (ArgoCD, Grafana 등)
Plain Text
복사
앱 트래픽: NLB를 통해 IngressGateway까지 트래픽 전달, L7 라우팅 및 보안 정책은 서비스 메시 계층에서 관리
운영 도구: ALB + ACM + 호스트 기반 라우팅으로 운영 도구를 단일 진입점에서 처리
이 구조를 통해 애플리케이션 트래픽과 운영 도구의 노출 경로를 분리하고, 각 경로의 요구사항에 맞는 로드밸런서를 적용했습니다.

3-2. Terraform IaC 설계

인프라를 코드로 정의하고 생명주기가 다른 리소스는 상태 파일을 분리했습니다.
 디렉토리 구조 — 관심사 분리
come2us-eks/ ├── bootstrap/ # S3 버킷, DynamoDB 락 테이블, IAM 초기 구성 ├── dns/ # Route53 Hosted Zone, ACM 인증서 (별도 state) │ └── backend.hcl ├── modules/ │ ├── network/ # VPC, 서브넷, 라우팅 테이블, NAT Gateway, IGW │ ├── sg/ # 보안 그룹 │ ├── bastion/ # EC2 + SSM IAM Role │ ├── rds/ # PostgreSQL RDS │ ├── elasticache/ # Redis │ ├── iam/ # EKS Access Entry, IRSA 연계 │ ├── ssm/ # SSM Parameter Store │ ├── route53/ # Hosted Zone, 레코드, 인증서 연계 │ └── msk/ # MSK Kafka ├── eks.tf # EKS 클러스터 ├── karpenter.tf # Karpenter IAM / SQS / EventBridge ├── iam.tf # IRSA (ALB Controller, ExternalDNS, External Secrets) └── scripts/ ├── tunnel.sh # SSM 포트포워딩 터널 └── bootstrap-k8s.sh # ALB Controller, ArgoCD 초기 설치
Plain Text
복사
Route53 · ACM을 별도 state로 분리한 이유는 생명주기가 메인 인프라와 다르기 때문입니다. 클러스터 재구성이나 테스트성 destroy/apply가 발생하더라도 도메인과 인증서의 삭제를 방지하도록 설계했습니다.
 모듈 설계
구분
모듈
이유
직접 작성
network · sg · bastion · rds · elasticache · iam · ssm · route53 · msk
프로젝트별 요구사항과 연결 관계가 명확
공개 모듈 활용
terraform-aws-modules/eks
복잡성이 높은 영역은 검증된 모듈 재사용
 상태 관리
S3 백엔드와 DynamoDB 락을 통해 상태 파일 무결성을 유지하고, 동시 terraform apply에 의한 충돌을 방지했습니다.
Terraform으로 관리한 주요 AWS 리소스
VPC · Subnet · Route Table · NAT Gateway · Security Group · EKS · Node Group · EC2(Bastion) · RDS · ElastiCache · MSK · IAM Role/Policy · SSM Parameter Store · Route53 · ACM

3-3. IAM / 보안 설계

각 컴포넌트에 필요한 최소 권한만 부여하고, 시크릿은 코드베이스에서 분리했습니다.
 IRSA
Pod가 AWS API를 호출할 때 노드 IAM Role을 공유하면 최소 권한 원칙을 지키기 어렵습니다. 이를 방지하기 위해 ServiceAccount 단위로 IAM Role을 분리했습니다.
컴포넌트
IAM Role 권한 범위
AWS Load Balancer Controller
ALB · Target Group 생성/관리
ExternalDNS
특정 Hosted Zone에 대한 Route53 레코드 관리
External Secrets Operator
SSM Parameter Store 읽기 전용
Karpenter Controller
EC2 프로비저닝 관련 권한, PassRole 분리
 시크릿 관리
Git에는 민감값을 직접 포함하지 않고, 애플리케이션 런타임 시점에만 SSM 값을 Secret으로 동기화하도록 구성했습니다.
저장소: AWS SSM Parameter Store
JWT Key
RDS/Redis 접속 정보
외부 API Key
동기화: External Secrets Operator → Kubernetes Secret

3-4. 컴퓨팅 / 클러스터 설계

인프라 워크로드와 앱 워크로드를 분리하고, 확장은 Karpenter Spot으로 처리했습니다.
 노드 그룹 분리 전략
노드 그룹
구성
용도
이유
infra_ng
t3.medium × 4, On-Demand
Karpenter · ArgoCD · Istio · 모니터링
핵심 인프라 컴포넌트의 안정성 우선
app_ng
t3.medium × 2, On-Demand
기본 애플리케이션 워크로드
최소 처리 용량 보장
Karpenter 동적 노드
Spot, m/c 계열 4·8 vCPU
트래픽 증가 시 앱 워크로드 확장
비용 최적화
infra 노드 그룹에 Taint를 적용해 인프라 컴포넌트가 애플리케이션 부하에 밀려 Pending 상태가 되지 않도록 했습니다.
 스토리지 전략 — gp3
Prometheus, Loki, Tempo 같은 운영 컴포넌트는 쓰기 I/O가 지속적으로 발생합니다. 과거 Jenkins 환경에서 gp2 BurstBalance 고갈로 장애를 경험해 EKS 기본 StorageClass는 gp3로 설정했습니다.

3-5. 비용 최적화

비용 절감만이 아니라, 어떤 리스크를 감수하는지 인지하며 의사결정했습니다.
결정
기대 효과
트레이드오프
Karpenter Spot 활용
온디맨드 대비 비용 절감
Spot Interruption 대응 필요
NAT Gateway 단일 구성
Multi-AZ NAT 대비 비용 절감
NAT 단일 장애점 존재
gp3 default StorageClass
비용·성능 균형 개선
별도 단점 없음
운영 도구 ALB 통합
ALB 개수 축소
라우팅 규칙 관리 필요
AWS Cost Explorer와 Budgets 알람을 통해 비용을 관리했습니다.

4. 트러블슈팅 및 운영 리스크 대응

단순한 증상 대응이 아닌, AWS 지표를 근거로 원인을 좁히고 구조적으로 해소했습니다.

Issue 1. EBS gp2 IOPS Throttling → Jenkins Node Hang

 상황
Docker 이미지 빌드 중 Jenkins UI가 504를 반환하고, 이후 SSH 접속 불가 상태로 전환되어 CI 파이프라인이 중단됐습니다. CPU, Memory는 정상 범위였기 때문에 단순 리소스 부족이 아닌 다른 병목 가능성을 의심했습니다.
 원인 분석
CloudWatch 지표를 통해 다음과 같은 현상을 확인했습니다.
[이미지 2] BurstBalance 고갈
[이미지 3] VolumeQueueLength 급증
[이미지 4] VolumeTotalReadTime 급증
BurstBalance 고갈
VolumeTotalReadTime 급증
VolumeQueueLength 급증
단일 Root EBS(gp2)에서 OS I/O와 Docker I/O가 경합하고 있었고, gp2의 크레딧 기반 IOPS 구조가 한계에 도달한 것으로 판단했습니다.
gp2 볼륨의 IOPS Throttling을 근본 원인으로 규명
 해결
1.
Root/Data 볼륨 분리 → 빌드 워크로드의 I/O 영향을 OS 레이어와 격리
2.
gp3 전환 → 크레딧 기반 가변 IOPS에서 고정 IOPS 구조로 전환
 결과
Jenkins 504 / Node Hang 현상 해소
VolumeQueueLength, VolumeTotalReadTime 지표 안정화
[이미지 5] QueueLength 안정화 (피크 후 안정화)
[이미지 6] TotalReadTime 안정화 (피크 후 안정화)

Issue 2. HPA와 Karpenter의 확장 타이밍 차이를 고려한 예방적 스케일링 설계

상황
Karpenter 도입 시, HPA에 의해 Pod가 증가하더라도 신규 노드가 Ready 상태가 되기까지는 시간이 필요하다는 점을 고려했습니다. 이에 따라 급격한 트래픽 증가 상황에서는 일시적인 Pending이 발생할 수 있다고 판단했습니다.
설계 판단
핵심 서비스와 비핵심 서비스의 중요도를 구분해 minReplicas를 차등 적용해 기본 버퍼를 확보했습니다.
주문∙결제: 높은 minReplicas로 기본 처리 여유 확보
조회성 서비스: 낮은 minReplicas로 비용 효율 유지
기대 효과
핵심 도메인 서비스는 급격한 스케일 아웃 상황에서도 초기 처리 여유를 확보하고, 전체적으로는 비용과 안정성 간 균형을 맞추는 예방적 설계를 의도했습니다.

5. 결과 / 성과 요약

항목
변화
서비스 디스커버리
Eureka 의존 구조 → Kubernetes 네이티브 전환
정책 반영 방식
애플리케이션 재배포 중심 → Manifest PR 기반 반영
시크릿 관리
환경변수 직접 관리 → SSM + External Secrets
인프라 재현성
수동 설정 반복 → Terraform 기반 재구성
인프라 경계
단일 서버 중심 → 네트워크/보안/데이터 계층 분리
Docker Build 시간
레이어 캐싱을 통한 빌드 타임 단축 1분 3초 → 3.2초
Jenkins 장애
EBS 병목 해소 후 제거
운영 가시성
CloudWatch RCA, Prometheus/Grafana 기반 모니터링 확보
이하 섹션은 AWS 인프라 구축 위에서 적용한 플랫폼 및 운영 레이어의 의사결정입니다.

6. 플랫폼 레이어 확장 — Istio 도입

트래픽 제어 지점을 정리하고, 정책을 플랫폼 레이어로 이동했습니다.
ECS 환경에서의 Spring Cloud 의존과 서비스 디스커버리 이슈를 계기로, EKS에서는 네트워크∙정책 제어를 애플리케이션이 아닌 플랫폼 레이어로 분리하고자 했습니다.
앱 트래픽 경로에는 NLB → Istio IngressGateway → Service 구조를 적용해 Ingress 지점에서 L7 라우팅과 정책 제어를 일관되게 수행하도록 설계했습니다.
RequestAuthentication + AuthorizationPolicy로 JWT 검증/권한 처리
Gateway/VirtualService 기반으로 라우팅 정책을 선언적으로 관리
OTel 연동으로 Mesh 내부 호출 흐름 가시성 확보
Istio 도입은 오버엔지니어링이 될 수 있지만, 서비스 확장 단계에서의 정책 변경 비용과 관측 공백이 운영 리스크로 커진 시점에 도입이 합리적이라고 판단했습니다. Istio 도입을 통해 인증∙인가∙라우팅 정책을 서비스 코드 변경이 아닌 선언적으로 처리할 수 있는 구조를 마련했습니다.

7. CI/CD 및 배포 구조 변화

인프라 구조가 Monolith → ECS → EKS로 고도화되면서, 배포 방식도 단순 자동화 수준에서 운영 효율성과 재현성을 확보하는 방향으로 함께 발전시켰습니다. 핵심 목표는 서비스 수 증가에 따른 파이프라인 관리 비용을 줄이고, 인프라 변경과 애플리케이션 배포를 더 일관된 방식으로 운영하는 것이었습니다.

Phase 1 — GitHub Actions

초기 Monolith 환경에서는 GitHub Actions를 이용해 기본적인 빌드·배포 자동화를 구성했습니다. 이 단계는 소규모 환경에서는 충분했지만, 서비스 분리와 인프라 복잡도 증가에 대응하기에는 한계가 있었습니다.

Phase 2 — Jenkins 도입

ECS 기반 마이크로서비스 환경에서 서비스 수 증가에 따라 파이프라인 중복과 관리 포인트가 늘어났습니다. 이를 해결하기 위해 Jenkins를 도입해 파이프라인을 표준화했습니다.
Shared Library를 활용해 서비스별 Jenkinsfile 중복을 줄이고 공통 배포 로직을 표준화
Terraform을 통한 Blue/Green 배포로 무중단 전환
CI 빌드 → Warm-up 서비스 생성 → Health Check → ALB Target Group 전환 → 이전 환경 정리
Plain Text
복사
BuildKit, Gradle Cache 적용을 통해 빌드 시간을 약 95% 단축
이 단계에서 CI/CD는 단순한 배포 자동화를 넘어, 서비스 증가에 따른 운영 부담을 줄이고 배포 절차를 일관되게 관리하는 역할을 수행했습니다.

Phase 3 — ArgoCD GitOps 전환

EKS 환경에서는 Jenkins 서버 자체를 운영해야 하는 부담을 줄이고, 선언형 배포와 재현성을 강화하기 위해 GitHub Actions와 ArgoCD 기반 GitOps 구조로 전환했습니다.
GitHub Actions: 이미지 빌드 및 레지스트리 푸시
ArgoCD: Git 상태를 기준으로 Kubernetes 리소스를 선언적으로 동기화
External Secrets + SSM Parameter Store: 애플리케이션 시크릿을 Git 저장소와 분리하여 관리
배포 파이프라인에서 서버 운영 부담을 줄이고, 인프라와 애플리케이션 변경을 GitHub를 단일 지점으로 설계했습니다.

정리

CI/CD 체계는 단계별로 도구를 바꾸는 데 목적이 있었던 것이 아닌, 환경 복잡도 증가에 맞춰 운영 부담을 줄이고 배포 재현성을 높이기 위한 방향으로 고도화했습니다.
Monolith: 기본 자동화
ECS: 표준화된 파이프라인과 인프라 연계 배포
EKS: GitOps 기반 선언형 운영과 시크릿 분리

8. 한계와 개선 방향

운영 환경과의 차이를 인지하고, 각 한계에 대한 개선을 다음과 같이 정리합니다.

8-1. ECR 접근 — 장기 자격증명 사용

현재 GitHub Actions∙Jenkins에서 ECR 접근에 Access Key를 사용했습니다. 운영 환경에서는 OIDC 기반 AssumeRole 방식으로 전환해 장기 자격 증명을 제거하는 것이 적절합니다.

8-2. NAT Gateway 단일 구성

비용 제약상 단일 NAT Gateway를 선택했습니다. NAT Gateway가 위치한 AZ에 장애가 발생할 시 프라이빗 서브넷에 위치한 애플리케이션이 외부 통신을 수행하지 못할 수 있기 때문에, 운영환경에서는 AZ별 NAT Gateway와 라우팅 테이블을 구성해 가용성을 높여야 합니다.

8-3. CloudWatch 알람 미구성

장애 분석은 수행했지만 사전 알람 없이 수동으로 지표를 확인했습니다. 주요 지표 악화를 사전에 감지해 대응을 하기 위해 임계값 기반 알람 체계를 관리하는 것이 적절합니다.