Search

CINEBOX

Subject
영화 예매 서비스
Key Tech
AWS
EKS
ArgoCD
Terraform
Kustomize
CloudFront
PLG Stack
Description
서비스 특성 예측 기반 DB 설계 | GitOps Dev/Prod 환경 분리 | CoreDNS 트러블슈팅
Role: BE, Infra & DevOps Lead — 아키텍처 설계 | 인프라 구축 | CI/CD | 모니터링 Period: 2025.02 – 2025.03 Stack: AWS · Terraform · EKS · ArgoCD · Kustomize Prometheus · Grafana · Loki · CloudFront Link: [GitHub]

1. 프로젝트 요약

실시간 좌석 점유와 예매가 발생하는 서비스 특성을 고려해 구축한 영화 예매 서비스입니다. AWS EKS 기반 인프라를 단독으로 설계https://github.com/Cloud-Engineering2/cinebox실시간 좌석 점유와 예매가 발생하는 서비스 특성을 고려해 구축한 영화 예매 서비스입니다. AWS EKS 기반 인프라를 설계·구축하고, 서비스 특성에 대한 예측을 바탕으로 DB 구조를 사전에 설계하고 모니터링으로 검증했으며, GitOps 기반 배포 구조로 환경 재현성을 확보했습니다.

핵심 성과

CloudFront CDN 적용을 통해 이미지 조회 응답 속도 개선 및 S3 트래픽 비용 절감
조회 트래픽으로의 비중이 높을 것으로 판단해 DB Read Replica를 도입하고 읽기 부하를 분산
NAT Instance + S3 Gateway Endpoint 조합으로 NAT 트래픽 비용 절감
Kustomize 기반 환경별 배포 재현성 확보
[이미지 1] CINEBOX 아키텍처

2. 배경 / 문제

영화 예매 서비스 특성상 다음과 같은 운영 요구사항이 있었습니다.
 이미지 조회 성능 영화 포스터에 대한 이미지 조회가 빈번하게 발생합니다. 이를 S3에서 직접 제공하면 지연이 크고 비용이 증가하므로, CloudFront CDN으로 콘텐츠를 캐싱하여 응답 속도를 개선하고자 했습니다.
 읽기 트래픽 집중 서비스 특성상 조회 요청이 가장 많이 발생될 것으로 예상되었습니다. 영화 목록, 상세 정보, 상영 정보 조회처럼 사용자 탐색 단계에서 반복적으로 발생하는 읽기 요청이 데이터 계층 부하의 주요 원인이 될 가능성이 높다고 보았습니다.
 환경 일관성 Dev와 Prod 환경을 동시에 운영하면서 kubectl 직접 적용 방식은 실제 클러스터 상태와 코드 베이스가 달라지는 드리프트 문제를 유발할 수 있었습니다. 따라서 모든 변경을 Git 기반으로 추적 가능한 구조가 필요했습니다.

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

네트워크 계층 분리, 비용 최적화, 보안성을 고려한 설계를 수행했습니다.

3-1. 네트워크 / VPC 구성

 서브넷 구성 — 계층 분리
계층
위치
용도
Public Subnet
3개 AZ
Web Server · Batch Server · Bastion
Private Subnet
3개 AZ
WAS · 모니터링 도구 · DB
외부 노출이 필요한 서비스는 Public Subnet에, 내부 워크로드와 DB는 Private Subnet에 배치해 외부 노출을 최소화하고 보안성을 확보했습니다.
 NAT Instance 운영 환경이라면 관리형 서비스인 NAT Gateway가 적절하겠지만, 트래픽이 많지 않은 가상 환경임을 고려해 비용을 최소화하기 위한 NAT Instance를 선택했습니다.
 S3 Gateway Endpoint Private Subnet에서 S3로의 트래픽을 Gateway Endpoint로 라우팅했습니다. 이를 통해 NAT를 거치지 않고 S3에 접근하도록 구성해 외부 노출 경로와 NAT 추가 트래픽 비용을 줄였습니다.
 ECR — Private ECR + Interface Endpoint Private ECR을 사용하고 VPC Endpoint로 연결하여 컨테이너 이미지 Pull 시 외부 인터넷 경유를 차단했습니다.
 Bastion — 우회 접근 EKS 노드로의 직접 접근 대신 Bastion을 통한 우회 접근 구조로 보안성을 확보했습니다.

3-2. 콘텐츠 전송 — CloudFront + S3

CINEBOX에서 제공되는 영화 포스터는 외부 API와 Image Batch Server를 통해 DB와 S3에 저장됩니다. 이미지 조회와 저장이 빈번하게 발생하는 서비스 특성에 맞게 CloudFront로 S3 콘텐츠를 캐싱했습니다.
S3 직접 제공 방식 대신 엣지 로케이션 캐싱으로 이미지 조회 응답속도를 개선하고, S3로의 직접 트래픽을 줄여 비용을 절감했습니다.

3-3. 데이터 계층 설계

 RDS Read Replica + Multi-AZ
Write 요청 → RDS Primary
Read 요청 → RDS Read Replica
영화 예매 서비스는 예매∙결제보다 조회 요청이 더 빈번하게 발생하는 구조라고 판단했습니다. 이에 따라 초기 설계 단계에서 읽기 부하를 분산할 수 있도록, 조회 요청은 Read Replica에서 처리하고 쓰기 요청은 Primary에서 처리하는 구조를 적용했습니다.
또한, RDS를 Multi-AZ로 구성함으로써, AZ 장애나 인프라 장애 발생 시 다른 AZ의 인스턴스로 자동 장애 조치가 이뤄질 수 있도록 설계했습니다.
 Redis (ElastiCache) — Session 관리 사용자 인증 상태를 관리하기 위해 Redis를 세션 저장소로 사용했습니다. 이를 통해 애플리케이션 인스턴스 간 세션 상태를 일관되게 유지하고, 다중 인스턴스 환경에서도 사용자 인증정보를 안정적으로 처리할 수 있도록 구성했습니다.
 EBS — 모니터링 데이터 영속성 Prometheus∙Loki∙Grafana 등 모니터링 스택의 데이터를 EBS에 프로비저닝해 파드 재시작 시에도 모니터링 데이터가 유지되도록 했습니다.

3-4. Kubernetes 클러스터 설계

 Taint + nodeAffinity — 서브넷별 Pod 스케줄링 제어
Public/Private 서브넷을 분리한 네트워크 구성에 맞게 각 Pod가 올바른 서브넷의 노드에 스케줄링되어야 했습니다.
각 노드에 Taint 적용
각 Pod에 해당 Taint에 대한 Toleration 설정
nodeAffinity로 스케줄링 노드를 일관되게 고정
이 구조로 Web Server는 Public 노드에, WAS와 모니터링은 Private 노드에 배치되도록 보장했습니다.

3-5. Terraform IaC

EKS 클러스터를 포함한 인프라를 Terraform으로 구성했습니다.
cinebox-terraform/ ├── vpc.tf # VPC, 서브넷, 라우팅 테이블, NAT ├── eks.tf # EKS 클러스터 및 노드 그룹 ├── rds.tf # RDS MySQL (Primary + Read Replica) ├── elasticache.tf # ElastiCache Redis ├── bastion.tf # Bastion EC2 ├── variables.tf ├── outputs.tf └── provider.tf
Plain Text
복사
CloudFormation도 선택지였지만, VPC, EKS, RDS, Bastion 등 AWS 리소스를 책임 단위로 나누어 관리하고 반복적으로 수정 및 검증해야 했습니다. 따라서 구조 분리와 변경 추적이 용이한 Terraform을 선택해, 인프라를 코드 기반으로 재현 가능하게 관리했습니다.

4. 모니터링 체계

모니터링으로 설계의 타당성을 검증하고, 운영 이상 징후를 식별했습니다.
인프라∙애플리케이션 메트릭 → Prometheus → Grafana 파드 로그 → Promtail → Loki → Grafana
 수집 지표
CPU∙Memory∙Node Disk I/O 등 시스템 메트릭
HTTP 응답 시간 및 상태값
Method별 요청 빈도, 로그 레벨, 도메인별 요청 패턴
[이미지 2] Node Metrics
[이미지 3] Application Metrics
[이미지 4] Application Logs
로그와 메트릭을 통해 GET 요청 비중과 movie 도메인 요청 집중 양상을 확인할 수 있었고, 이를 통해 초기 설계에서 읽기 부하를 우선 고려한 접근이 타당했는지 검증할 수 있었습니다.

5. GitOps — Dev/Prod 환경 분리와 배포 재현성 확보

‘배포했다’에서 마치는 것이 아닌, 환경 충돌을 구조적으로 차단하고 변경을 추적 가능하게 만들었습니다.
 배포 흐름
GitHub (main/develop 브랜치 Push) ↓ GitHub Actions (이미지 빌드 · ECR 푸시 · Manifest 버전 업데이트) ↓ ArgoCD (변경 감지 → 동기화 · 배포) ├── main → Production 환경 └── develop → Development 환경
Plain Text
복사
 Kustomize — 환경별 리소스 격리
k8s-manifests/ ├── base/ # 공통 리소스 정의 └── overlays/ ├── dev/ # Dev 환경별 설정 └── prod/ # Prod 환경별 설정
Plain Text
복사
개발 검증이 완료된 경우에만 Prod 환경으로 배포하는 흐름을 표준화했습니다.
 결과
환경별 설정 충돌 방지
선언적 배포 재현성 확보
배포 이력 추적

6. 트러블슈팅

Issue. CoreDNS Taint 충돌 → RDS 연결 실패

증상에서 원인까지 가설을 좁혀가는 방식으로 해결했으며, 애플리케이션 증상을 클러스터 레이어 원인까지 연결해 해결한 사례였습니다.
 상황
애플리케이션 Pod가 RDS와 연결에 실패하며 CrashLoopBackOff 상태로 반복 재시작했습니다.
 원인 추적
1.
RDS 설정, Security Group, 환경변수 확인
→ 이상 없음. 설정 문제가 아니라는 것을 확인
2.
연결 실패가 IP가 아닌 endpoint 기반이라는 점에 주목
→ DNS 해석 문제 의심
3.
CoreDNS Pod 상태 확인
→ Pending 상태 확인
4.
모든 노드에 Taint가 적용되어 있음 확인
→ CoreDNS Toleration 미설정으로 인해 어느 노드에도 스케줄링되지 못한 것이 원인
 근본 원인
CoreDNS가 Pending 상태로 실행되지 못해 클러스터 내부 DNS 해석 불가로 RDS endpoint를 IP로 변환하지 못한 연결 실패였습니다.
 해결
CoreDNS Deployment에 tolerations를 추가해 모든 노드에서 스케줄링 가능하도록 변경했습니다. → CoreDNS 정상 스케줄링, 애플리케이션-RDS 연결 정상화

7. 결과 / 성과 요약

항목
변화
이미지 조회 성능
CloudFront CDN 도입 → 엣지 캐싱으로 응답속도 개선 및 S3 트래픽 비용 절감
네트워크 비용
S3 Gateway Endpoint → NAT 트래픽 비용 절감
DB 읽기 부하
Read Replica 분리로 읽기 트래픽 부하 분산
세션 관리
ElastiCache Redis → 분산 환경에서 중앙 세션 저장소로 활용
환경 드리프트
GitOps 기반 선언적 관리로 제거
배포 재현성
Kustomize 구조로 Dev/Prod 설정 충돌 방지

8. 한계와 개선 방향

8-1. NAT Instance 단일 장애점

비용 절감을 위해 NAT Instance를 선택했지만 NAT Gateway 대비 가용성이 낮고, 단일 인스턴스 장애 시 프라이빗 서브넷 전체의 외부 통신이 단절됩니다. 운영 환경에서는 AZ당 NAT Gateway를 구성해 단일 장애점을 제거해야 합니다.

8-2. Terraform 상태 파일 로컬 관리

Terraform으로 인프라를 구성했지만 상태 파일을 로컬에서 관리했습니다. 인프라를 단독 관리하여 협업 안정성에는 문제가 없었지만 로컬 상태 의존은 재현성과 추후의 협업 안정성을 떨어뜨릴 수 있기 때문에 운영환경에서는 S3 백엔드로 상태 파일을 원격 관리하고 DynomoDB Lock을 적용하여 일관게 관리하는 것이 적절합니다.

8-3. Batch Service의 Public Subnet 배치

외부 API를 통해 영화 포스터 이미지를 주기적으로 수집하는 Batch Service를 Public Subnet에 배치했습니다. NAT Instance를 통한 외부 API 트래픽이 부담될 것을 우려한 결정이었지만, Batch Service는 DB와 연결되는 서비스임으로 Private Subnet에 배치하고 NAT를 통해 외부 API에 접근해야 합니다.