Dev V1 인프라 구성 - 100-hours-a-week/KTB4-2nd-Cloud GitHub Wiki

V1 인프라 구성

여담 V1은 App EC2 한 대와 AI Worker EC2 한 대로 운영합니다. 사진 업로드와 조회를 받는 서버와 AI 처리를 하는 서버를 나눴고, 원본 사진은 S3에 보관합니다.

서버를 나눈 이유와 규모 산정은 EC2 + Docker Compose 초기 배포 설계에 있습니다.


전체 구성

flowchart LR
  U["사용자"] -. "DNS 조회" .-> R53["Route 53<br/>yeodam-2gether.com"]
  U -- "HTTPS" --> EIP["Elastic IP"]
 
  subgraph APP["App EC2 (t3.medium)"]
    NG["Nginx<br/>80 / 443"]
    FE["Frontend<br/>127.0.0.1:3000"]
    BE["Backend<br/>127.0.0.1:8080"]
    DB[("MySQL<br/>별도 EBS")]
    NG --> FE
    NG --> BE
    BE --> DB
  end
 
  subgraph WK["Worker EC2 (c7i.xlarge)"]
    AI["AI 컨테이너<br/>API, Worker, Qdrant"]
  end
 
  EIP --> NG
  BE -- "8000<br/>App SG만 허용" --> AI
  BE -- "원본 저장" --> S3[("S3<br/>원본 사진")]
  AI -- "원본 조회" --> S3
Loading

인터넷에서 들어올 수 있는 곳은 App의 Nginx 80, 443뿐입니다. Frontend와 Backend는 App 안에서만 열려 있고, Worker의 8000번은 App Security Group에서 온 요청만 받습니다.


서버 구성

App EC2 Worker EC2
인스턴스 t3.medium (CPU Credit unlimited) c7i.xlarge
디스크 Root gp3 30GiB, MySQL 전용 gp3 10GiB Root gp3 30GiB
실행 Nginx, MySQL (Host), FE, BE (컨테이너) AI 컨테이너 1개
외부 주소 Elastic IP 실행 중에만 Public IPv4
운영 상시 작업이 있을 때만 실행
접속 SSM Session Manager SSM Session Manager

App은 한 대에서 Nginx, MySQL, FE, BE가 CPU를 같이 씁니다. CPU Credit이 떨어져 성능이 묶이면 서비스뿐 아니라 부하 테스트 결과도 흔들릴 수 있어서 unlimited로 두었습니다.

실제로 사진 150장 여행을 처리할 때 App CPU는 최대 81%까지 올랐고, 그때도 CPU Credit은 550 이상 남아 있었습니다.

Worker는 여행 한 건을 처리하는 동안 몇 분씩 CPU를 계속 씁니다. 그래서 버스트 계열 대신 c7i.xlarge를 골랐고, 150장 기준 AI 처리 약 3분 동안 CPU는 50% 안팎이었습니다.


데이터 저장 위치

데이터 위치 보존 방식
사용자, 여행, 사진 메타데이터 MySQL (App의 별도 EBS) App EC2를 교체해도 유지
원본 사진 S3 trip-uploads/ Versioning, 삭제 방지
일괄 다운로드 ZIP S3 trip-downloads/ 1일 뒤 자동 삭제
AI 벡터 Worker의 Docker Volume Worker 교체 시 함께 삭제

MySQL 데이터 EBS는 인스턴스와 별도 리소스로 만들고 Terraform에서 prevent_destroy를 걸었습니다. App EC2를 다시 만들어도 DB 디스크는 그대로 다시 붙일 수 있습니다.

S3는 공개 접근을 모두 막고, HTTPS가 아닌 요청은 거부합니다. App Role은 trip-uploads/를 읽고 쓰고 지울 수 있고, Worker Role은 읽기만 합니다.


요청 경로

flowchart LR
  B["Browser"] --> N["Nginx"]
  N -- "/api/trips*<br/>timeout 600s" --> BE["Backend"]
  N -- "/api/*" --> BE
  N -- "/*" --> FE["Frontend"]
Loading

처음에는 Backend가 루트 경로를 그대로 쓰고 있었습니다. 그런데 FE의 /auth/callback, /trips/create 같은 페이지와 BE의 /auth/csrf, /trips API가 같은 경로 아래에 섞여 있어서, Nginx에서 페이지별로 예외를 두어야 했습니다.

Backend 팀과 이야기해서 BE API 전체에 /api를 붙였고, 그 뒤로 Nginx 규칙은 /api/*와 나머지 두 갈래로 정리됐습니다.

/api/trips 경로만 timeout을 600초로 늘렸습니다. 사진 업로드의 마지막 요청은 AI 처리가 끝날 때까지 기다렸다가 응답하기 때문입니다. 업로드 한 요청의 크기 제한은 150m입니다.


관련 문서

⚠️ **GitHub.com Fallback** ⚠️