새로운 사이드 프로젝트를 하는 도중 프론트 배포를 무엇으로 할지 고민에 빠졌다.
우리의 서비스는 웹소켓을 이용한 투표 서비스이고, 정적파일을 저장할 일이 아직까지는 많지 않다.
제목의 EC2 + NginX 와 CloudFront + S3 두 방법의 특징과 장단점, 현재 프로젝트에 무엇이 더 적합할지 알아보았다.
CloudFront + S3
- Amazon S3(Simple Storage Service)
- 정적파일(HTML, CSS, JS, 이미지, 폰트 등)을 저장하는 스토리지
- 정적파일이란?
-
더보기서버의 가공 없이 그대로 제공할 수 있는 자원
HTML, CSS파일, JS파일, 이미지, 폰트, 비디오/오디오 - 장점
- 서버리스로 서버 관리가 필요 없음
- 무제한 확장성
- 내구성(데이터 유실 위험 거의 X)
- 단점
- 웹소켓 지원 X
- 트래픽이 많아지면 직접 트래픽 감당해야함
- Amazon CloudFront(CDN, Content Delivery Network)
- S3에 저장된 정적파일을 전 세계에 빠르고 안전하게 배포
- 장점
- 전 세계에 분포한 Edge Location(엣지 서버)를 통해 클라이언트와 가장 가까운 서버에서 빠르게 데이터 제공
- 자주 요청되는 파일 캐싱 - 서버 부하 down, 빠른 응답
- 정적파일을 자동으로 압축 전송 - 네트워크 대역폭 절감
- 보안강화
- SSL/TLS 인증서 적용으로 HTTPS 적용가능
- AWS WAF(웹 방화벽)와 연동해 DDos 공경, 악성 트래픽 차단 가능
- Signed URL, Signed Cookie로 리소스 접근 제어
- 비용절감
- 단점
- 웹소켓, API 호출 직접 처리 불가 - 서버나 다른 API Gateway 서비스와 연결해야 함
- 캐시로 인해 변경 사항 반영이 다소 느릴 수 있음
- HTTP-GET 요청만 가능
- 이미지 업로드/수정/삭제(POST, PUT, DELETE) 자체적으로는 불가
클라이언트 → 별도의 API 서버(EC2) → S3 필요
Presigned URL(클라이언트 → S3 직접 수정 가능 임시 접근 권한) 발급 후에는 직접 수정 가능
But, 초기 Presigned URL을 요청 시에는 EC2 필요
- 흐름도
-
더보기클라이언트 → CloudFront → S3에서 파일 가져옴 + CloudFront에 캐시 저장 → 클라이언트
ㄴ 캐시 있으면? → 바로 클라이언트로 응답
EC2 + NginX
- NginX
- 웹 서버이자 리버스 프록시 서버(중간 관리자)
- 웹 서버(Web Server)
클라이언트 요청(HTTP)을 받아서 정적 파일(HTML, CSS, JS, 이미지 등)을 전달 - 리버스 프록시(Reverse Proxy)
백엔드 서버를 외부에 노출시키지 않고,
외부에서 들어오는 요청을 내부 서버(EC2)로 안전하게 전달 - 로드 밸런서(Load Balancer)
서버 부하를 분산 -> 트래픽이 몰릴 때까지 서버 다운 방지 - 캐시 서버(Cache Server) - CloudFront처럼 캐싱 가능
- 보안
SSL 인증서, 보안헤더 설정가능 - 동적/정적파일 모두 서빙 가능
- EC2 서버에 정적 파일을 두고 바로 제공 가능
-
더보기/var/www/html/ ← EC2 서버의 디렉토리
├── index.html
├── styles.css
└── script.js - EC2 서버로 API 요청 / 웹소켓 요청 동적요청 안전하게 전달(NginX가 필수는 X)
- 웹 서버(Web Server)
- 웹 서버이자 리버스 프록시 서버(중간 관리자)
- 흐름도
- NginX가 요청의 헤더(Upgrade: websocket)을 보고 웹소켓 요청인지 HTTP 요청인지 구분하여 각 웹소켓 서버 / API 서버로 분배
- ⚠️ 주의점
- BrowserRouter: React에서 SPA를 구현할 때 사용하는 라우팅 도구
- 사용자가 URL을 변경하면, 서버에 새로운 요청을 보내는 것이 아니라 React 내부에서 URL 경로에 따라 컴포넌트만 바꿔서 렌더링 해주는 기능
- 새로고침을 하지 않아도 화면이 전환됨
- 만약 새로고침을 누르면(ex. /about -> F5새로고침) 브라우저는 서버에 /about 경로의 리소스 요청
- /about 은 React 내부의 가상경로 = 서버에는 실제로 /about 이라는 경로의 리소스를 가지고 있지 않음 => 404 Error
- BrowserRouter: React에서 SPA를 구현할 때 사용하는 라우팅 도구
- 해결방법
- 서버가 어떤 경로로 요청이 와도 항상 React 앱의 index.html을 반환하도록 설정해야함
-
더보기// NginX
location / {
try_files $uri /index.html;
}
웹 소켓
- HTTP와 웹소켓은 모두 TCP기반이지만 각각 HTTP, 웹소켓 프로토콜을 따름
- 웹소켓 연결이 성립되면, 웹소켓 서버가 클라이언트 서버와 직접 통신
-
더보기// 응답흐름
클라이언트 → Nginx(프록시) → 백엔드 EC2 (웹소켓 서버) → 클라이언트 - 직접 소통한다? 그럼 만약 웹소켓으로 채팅 중에 이미지 업로드(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️⃣ 연결 성립 후 실시간 통신 [웹소켓 서버] ↔ [클라이언트]
- 그럼 웹소켓 서버는 클라이언트를 어떻게 기억하는 걸까?
-
더보기
웹소켓 서버는 연결이 성립되면, 클라이언트와의 연결을 소켓 객체(Socket)로 관리
즉, 연결된 소켓 객체를 통해 클라이언트와 통신을 유지
-
- 클라이언트 → 프론트 NginX(EC2) → 백엔드 EC2 의 요청에서 백엔드가 웹소켓 요청인지 API 요청인지 어떻게 구별할까?
- 웹소켓 연결을 맺을 때 특별한 헤더를 포함
-
더보기
// 일반 API 요청 헤더
POST /api/upload HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer xxxxx// 클라이언트 → 서버 웹소켓 요청 헤더
GET /ws/chat HTTP/1.1
Host: example.com
Upgrade: websocket → HTTP에서 웹소켓으로 연결 전환
Connection: Upgrade → 프로토콜 전환을 명시
Sec-WebSocket-Key: xxxxx
Sec-WebSocket-Version: 13// 프론트에서 웹소켓 연결 시 (자동으로 Upgrade 헤더 추가)
const socket = io('http://localhost:3000');// 서버 → 클라이언트 응답 헤더
HTTP/1.1 101 Switching Protocols → 프로토콜 전환(101 Switching Protocols)됨 의미
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: yyyyy
- ⚠️ 주의점: 프로토콜(http vs https)
- 🔒
웹 사이트가 https라면 → 웹소켓도 wss(보안 웹소켓) 사용
웹사이트가 http라면 → 웹소켓도 ws(일반 웹소켓) 사용
const socket = io('wss://frontend.example.com');
결론
이미지 등 정적파일 없이 웹소켓만 쓸 거면 NginX + EC2를 사용하는 게 좋을 것 같다.
하지만 현재 서비스는 투표 현황이나 아이템, 투표에 올릴 이미지 등 정적파일이 꽤나 필요할 것 같아서
NginX + EC2 + S3를 사용할 예정이고, 배포는 CI/CD 자동화를 위해 CSR 서비스지만 Docker를 이용할 예정이다.
- 웹소켓은 NginX 설정에서 → 백엔드 웹소켓 서버로
- 정적파일은 NginX 설정에서 → 백엔드 API 서버로
EC2 기본파일 구조
/
├── 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
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' 카테고리의 다른 글
npx? npm? pnpm? (2) | 2024.11.10 |
---|---|
모달창 구현하기 - RootLayout에 위치 vs createPortal (0) | 2024.07.10 |
Next.js Server-Action과 낙관적 업데이트 (0) | 2024.07.02 |
무한스크롤 intersection-observer 없이 구현해보기 (0) | 2024.07.01 |
2024.06.20 SQL문으로 기존 배열에 데이터 갈아끼우지 않고 추가하기 (0) | 2024.06.22 |