TIL

배포방법 고민 - 🛠 EC2 + NginX vs CloudFront + S3

inz1234 2025. 1. 11. 00:39

새로운 사이드 프로젝트를 하는 도중 프론트 배포를 무엇으로 할지 고민에 빠졌다.

💡 우리의 서비스는

웹소켓 기반의 실시간 투표 서비스
아직 정적 파일은 많지 않지만, 앞으로 투표 이미지 아이템 등 시각적 요소들이 들어갈 가능성 多

제목의 EC2 + NginX 와 CloudFront + S3 두 방법의 특징과 장단점, 현재 프로젝트에 무엇이 더 적합할지 알아보았다.


1️⃣ CloudFront + S3

📦 Amazon S3 (Simple Storage Service)

  • 정적 파일 저장 전용: HTML, CSS, JS, 이미지, 폰트 등
  • 서버리스 기반, 서버 관리 ❌

✅ 장점

  • 무제한 확장성 & 높은 내구성 (데이터 유실 걱정 거의 없음)
  • 서버 관리 필요 없음 → 운영 부담 낮음

❌ 단점

  • 웹소켓 미지원
  • GET 요청만 처리 가능
    → 이미지 업로드, 수정, 삭제는 Presigned URL 등을 따로 설정해야 함
  • 캐시로 인해 변경사항 반영 느릴 수 있음

🌍 Amazon CloudFront (CDN)

  • 전 세계에 분포한 Edge Location(엣지 서버)를 통해 클라이언트와 가장 가까운 서버에서 빠르게 데이터 제공
  • 자주 요청되는 파일 캐싱 - 서버 부하 down, 빠른 응답
  • 정적파일을 자동으로 압축 전송 - 네트워크 대역폭 절감
  • 보안강화
    • SSL/TLS 인증서 적용으로 HTTPS 적용가능
    • AWS WAF(웹 방화벽)와 연동해 DDos 공경, 악성 트래픽 차단 가능
    • Signed URL, Signed Cookie로 리소스 접근 제어
  • 비용절감

✅ 장점

  • 빠른 로딩 속도 🚀
  • SSL 인증으로 HTTPS 적용 가능 🔒
  • 악성 트래픽 차단 및 리소스 접근 제어 가능

❌ 단점

  • 웹소켓 불가
  • API 호출 불가 → 반드시 서버나 다른 API Gateway 서비스와 연결해야 함
  • 캐시로 인해 변경 사항 반영이 다소 느릴 수 있음
  • HTTP-GET 요청만 가능
  • 이미지 업로드/수정/삭제(POST, PUT, DELETE) 자체적으로는 불가
    클라이언트 → 별도의 API 서버(EC2)  S3 필요
    Presigned URL(클라이언트 → S3 직접 수정 가능 임시 접근 권한) 발급 후에는 직접 수정 가능
    But, 초기 Presigned URL을 요청 시에는 EC2 필요

🧭 흐름도

클라이언트 → CloudFront → S3  
     ㄴ 캐시 있음 → 바로 응답  
     ㄴ 캐시 없음 → S3에서 정적 파일 가져옴

2️⃣ EC2 + NginX

🌐 NginX란?

  • 웹 서버(Web Server)
    클라이언트 요청(HTTP)을 받아서 정적 파일(HTML, CSS, JS, 이미지 등)을 전달
  • 리버스 프록시(Reverse Proxy)
    백엔드 서버를 외부에 노출시키지 않고,
    외부에서 들어오는 요청을 내부 서버(EC2)로 안전하게 전달
  • 로드 밸런서(Load Balancer)
  • 캐시 서버(Cache Server) - CloudFront처럼 캐싱 가능
  • 프론트 요청을 정적 파일/웹소켓/백엔드 API로 적절히 분기

✅ 장점

  • 정적/동적 파일 모두 제공 가능
  • 웹소켓 완벽 지원 💬
  • 라우팅 제어 가능 (SPA 라우팅 문제 대응 가능)
  • SSL, 보안 헤더 등 보안 설정 자유도 높음
  • 향후 로드밸런싱, 캐싱 기능까지 확장 가능

❌ 단점

  • 직접 서버 관리 필요 🔧
  • 보안/확장성 등은 직접 설정해야 함

🧭 흐름도

클라이언트 ─▶ NginX (프론트 EC2)
   ├─▶ 정적 파일 제공 (/usr/share/nginx/html)
   ├─▶ /api → 백엔드 API 서버(동적)
   └─▶ /ws → 웹소켓 서버(동적)

⚠️  주의점

  • BrowserRouter: React에서 SPA를 구현할 때 사용하는 라우팅 도구
    • 사용자가 URL을 변경하면, 서버에 새로운 요청을 보내는 것이 아니라 React 내부에서 URL 경로에 따라 컴포넌트만 바꿔서 렌더링 해주는 기능
    • 즉, 새로고침을 하지 않아도 화면이 전환됨
    • 만약 새로고침을 누르면(ex. /about -> F5새로고침) 브라우저는 서버에 /about 경로의 리소스 요청
    •  /about  은 React 내부의 가상경로 = 서버에는 실제로 /about 이라는 경로의 리소스를 가지고 있지 않음 => 404 Error 
  • BrowserRouter 사용 시, 새로고침 오류 방지 설정도 필수!
location / {
  try_files $uri /index.html;
}
        │
        │ (HTTP 요청)
        ▼
[Nginx (프론트엔드 EC2)]  ───▶ [정적 자원 제공 (React 앱)]
        │
        │ (웹소켓 연결 요청)
        ▼
[Nginx 프록시 설정]
        │
        ▼
[백엔드 EC2 (웹소켓 서버 + DB)]
        │
        │ (실시간 데이터 전송)
        ▼
[클라이언트]

🔌 웹소켓은 어떻게 작동할까?

1️⃣ 연결 성립 과정

클라이언트 → 프론트 NginX → 백엔드 NginX → 웹소켓 서버
  • 연결 후엔 클라이언트와 웹소켓 서버가 직접 통신
  • 연결은 1번만 하면 되고, 이후엔 계속 소켓 객체로 관리

2️⃣ 웹소켓 요청인지 API 요청인지 어떻게 구별할까: 헤더를 통해 구분

// 일반 API 요청
POST /api/upload HTTP/1.1
Content-Type: application/json

// 웹소켓 요청
GET /ws/chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade

🔒 HTTPS vs 웹소켓

  • 사이트가 https라면 웹소켓도 wss:// 사용해야 함
const socket = io('wss://your-domain.com');

🖼 채팅 중 이미지 전송 흐름 예시

  • 직접 소통한다? 그럼 만약 웹소켓으로 채팅 중에 이미지 업로드(HTTP 요청)를 하면 웹소켓 연결이 끊어지나?
1. 채팅 중 DB에 저장된 이미지를 친구에게 보내기

1️⃣ 이미지 URL 요청 (HTTP)
[클라이언트 A] → [Nginx(프론트)] → [백엔드 API 서버] → [S3](DB)

2️⃣ 이미지 URL 전송 (웹소켓)
[클라이언트 A] → [웹소켓 서버] → [클라이언트 B]

3️⃣ 이미지 로딩 (HTTP)
[클라이언트 B] → [CloudFront] → [S3] → 이미지 표시

2. 채팅 중 이미지 업로드 후 친구에게 보내기
1️⃣ 이미지 업로드 (HTTP)
[클라이언트 A] → [Nginx(프론트)] → [백엔드 API 서버] → [S3]

2️⃣ 업로드 완료 알림 (웹소켓)
[백엔드 API 서버] → [웹소켓 서버] → [클라이언트 B] (이미지 URL 전송)
        ㄴ 이미지 url 또는 식별자 API 서버 - 웹소켓 서버로 직접 연결하여 넘김

3️⃣ 이미지 로딩 (HTTP)
[클라이언트 B] → [CloudFront] → [S3] → 이미지 표시
  • 웹소켓이 연결된 뒤에는 웹소켓 서버와 클라이언트가 직접 소통한다고 했는데, 그럼 프론트에서는 초기 요청은 프론트 NginX로, 연결된 후 요청은 바로 웹소켓 서버로 보내야 하는 건가?
No! 웹소켓 연결은 한 번만 하면 되고,
이후 모든 통신은 자동으로 연결된 웹소켓 서버와 진행

// 웹소켓 연결 후 통신 흐름
1️⃣ 웹소켓 연결 요청 [클라이언트] → [프론트 Nginx] → [백엔드 Nginx] → [웹소켓 서버]
2️⃣ 연결 성립 후 실시간 통신 [웹소켓 서버] ↔ [클라이언트]

🧩 결론

CloudFront + S3

  • 정적 파일만 필요하면 좋음
  • 웹소켓 불가 → 웹소켓 서비스에선 부적절

EC2 + NginX

  • 웹소켓 완벽 지원
  • 정적/동적 요청 모두 처리 가능
  • SPA 대응 가능

📌 나의 선택은?

🎯 현재 서비스에선
**"이미지 포함된 투표 기능" + "웹소켓 기반 실시간 통신"**이 핵심이므로,

✅ EC2 + NginX + S3 조합 사용 예정!

  • 정적 파일: NginX가 직접 서빙 or S3 연동
  • 웹소켓: 프록시 설정으로 웹소켓 서버에 연결
  • 배포: Docker + GitHub Actions로 CI/CD 자동화

📁 EC2 기본 디렉토리 구조 (참고)

/
├── home/ubuntu         ← 사용자 디렉토리
├── usr/share/nginx/html  ← ✅ React 정적 파일 위치
├── var/www              ← NginX 웹 파일 디렉토리
├── etc/nginx/nginx.conf ← NginX 설정
└── ...
/
├── bin       # 기본 명령어 실행 파일 (ls, cp, mv 등)
├── boot      # 부팅 관련 파일
├── dev       # 장치 파일
├── etc       # 시스템 설정 파일 (Nginx, MySQL 설정 파일)
├── home      # 사용자 홈 디렉토리
│   └── ubuntu  # Ubuntu EC2의 기본 사용자 디렉토리(파일 업로드 후 이동) 
├── lib       # 시스템 라이브러리
├── media     # 미디어 장치 마운트
├── mnt       # 임시 마운트 디렉토리
├── opt       # 추가 패키지 설치 경로(확장)
├── root      # 루트 계정 홈 디렉토리
├── run       # 실행 중인 프로세스 정보
├── sbin      # 시스템 관리 명령어
├── srv       # 서비스 데이터(확장)
├── tmp       # 임시 파일 저장소
├── usr       # 사용자 프로그램 및 라이브러리
│   └── share/nginx/html  # ✅ Nginx의 기본 웹 파일 경로
├── var       # 로그 파일, 웹 서버 파일
│   ├── www   # 웹 서버 파일 경로 (Nginx, Apache)
│   └── log   # 로그 파일 (Nginx, 시스템 로그)

🔧 NginX 설정 예시

더보기

// NginX

server {
    listen 80;
    server_name your-domain.com;

    # ✅ API 요청은 백엔드 EC2로 프록시
    location /api/ {
        proxy_pass http://백엔드-EC2-IP:4000; #API 서버
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # ✅ 웹소켓 요청은 백엔드 EC2로 프록시
    location /ws/ {
        proxy_pass http://백엔드-EC2-IP:5000;  # 웹소켓 서버
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'Upgrade';
    }

    # ✅ 정적 파일 요청은 Nginx가 직접 제공
    location / {
        root /usr/share/nginx/html;  # React 빌드 파일
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}

도움이 되셨다면 ❤️댓글이나 공유도 큰 힘이 됩니다!

참고
https://suvera.tistory.com/79