새로운 사이드 프로젝트를 하는 도중 프론트 배포를 무엇으로 할지 고민에 빠졌다.
💡 우리의 서비스는
웹소켓 기반의 실시간 투표 서비스
아직 정적 파일은 많지 않지만, 앞으로 투표 이미지나 아이템 등 시각적 요소들이 들어갈 가능성 多
제목의 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;
}
}
도움이 되셨다면 ❤️댓글이나 공유도 큰 힘이 됩니다!
'TIL' 카테고리의 다른 글
SSL 인증서 발급과 IP 주소 (0) | 2025.03.03 |
---|---|
요청에 쿠키가 안 담겨요.. SameSite=None? Strict? (0) | 2025.03.02 |
npx? npm? pnpm? (2) | 2024.11.10 |
모달창 구현하기 - RootLayout에 위치 vs createPortal (0) | 2024.07.10 |
Next.js Server-Action과 낙관적 업데이트 (0) | 2024.07.02 |