Files
ssessesse-ki-battle/docs/deployment.md
T

98 lines
6.6 KiB
Markdown

# Git clone + PM2 운영
Linux 서버, Node.js 24 이상, MongoDB, 같은 호스트의 HTTPS nginx를 기준으로 한다. 앱은 PM2 fork 프로세스 **1개**만 실행한다. 다른 서버/컨테이너의 프록시를 쓰면 바인딩 주소와 신뢰 프록시 주소를 해당 구성에 맞게 변경한다.
## 최초 배포
배포할 코드와 새 파일을 먼저 커밋·push한다. 서버에서는 일반 서비스 계정으로 실행한다.
```bash
git clone https://gitea.horoli.kr/horoli/ssessesse-ki-battle.git dragon_ball
cd dragon_ball
npm ci
cp .config.example.json .config.json
chmod 600 .config.json
```
`.config.json`에서 다음을 설정한다.
- `MONGODB_URI`: 서버에서 연결 가능한 MongoDB 주소와 인증 정보. DB 포트를 인터넷에 공개하지 않는다.
- `MONGODB_DATABASE`: 저장소 이름과 같은 `ssessesse-ki-battle`을 사용한다. 해당 DB가 비어 있을 때 새 데이터로 시작하며, 기존 DB를 자동 삭제하거나 이전하지 않는다.
- `HOST=127.0.0.1`, `PORT=2100`, `TRUST_PROXY=loopback`: 같은 서버 nginx 전용. `TRUST_PROXY`는 쉼표로 구분한 신뢰 프록시 IP/CIDR 또는 `loopback`이며 직접 접속이면 비운다.
- `ADMIN_USER`, `ADMIN_PASSWORD`: 둘 다 지정하면 관리자 기능 활성화. 강한 전용 비밀번호를 사용한다.
- `STUN_URLS`, `TURN_URLS`, `TURN_SHARED_SECRET`: 친구전 ICE 설정. TURN 서버의 공유 비밀과 일치해야 하며 TURN 수신 포트·relay 포트 범위는 별도로 개방한다.
- `LOG_LEVEL=info`: 운영 로그 수준.
```bash
npm run typecheck
npm run build
npm install --global pm2
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup
```
`pm2 startup`이 출력하는 명령을 실행한다. Node 버전 관리자를 변경하면 startup 설정도 갱신한다. PM2는 저장소 루트를 사용하며 `.config.json`이 없거나 형식이 잘못되면 시작하지 않는다. 앱 설정은 이 JSON 파일만 읽으며 같은 이름의 환경변수나 기존 `.env`로 덮어쓰지 않는다. `npm run dev`와 `npm start`는 프로젝트 루트에서 실행한다.
JSON의 `PORT`는 숫자, `STUN_URLS`·`TURN_URLS`는 문자열 배열이다. URL이 없으면 `[]`를 사용한다. 로컬과 운영 모두 DB 이름은 `ssessesse-ki-battle`이므로 데이터를 분리하려면 `MONGODB_URI`가 서로 다른 MongoDB 인스턴스를 가리켜야 한다. 같은 인스턴스와 DB 이름을 사용하면 데이터를 공유한다. 설정 수정 후 프로세스를 재시작한다. 누락 시 로그의 `CONFIG_NOT_FOUND`, 형식 오류 시 `INVALID_CONFIG`를 확인한다.
기존 `.env` 배포에서 전환할 때는 `.config.example.json`을 `.config.json`으로 복사한 후 기존 값을 옮긴다. 이전 PM2 프로세스에 저장된 `--env-file` 실행 인자를 없애려면 앱 중지·빌드 후 `pm2 delete dragon-ball`, `pm2 start ecosystem.config.cjs`, `pm2 save` 순서로 등록한다. 기존 `.env`는 백업으로 보관할 수 있지만 앱에서 읽지 않는다.
```bash
curl --fail http://127.0.0.1:2100/health
curl --fail http://127.0.0.1:2100/ready
pm2 logs dragon-ball --lines 50
```
`/health`는 프로세스 응답, `/ready`는 DB 연결을 확인한다.
## HTTPS 및 Socket.IO
`deploy/nginx.conf.example`의 도메인과 인증서 경로를 교체해 nginx 설정에 추가한다. 해당 도메인의 인증서를 먼저 준비한다. 예시는 인증서 발급을 자동화하지 않는다.
```bash
sudo nginx -t
sudo systemctl reload nginx
```
외부에서는 HTTPS 주소로 접속한다. `/signal`은 Socket.IO namespace이며 프록시 전송 경로는 `/socket.io/`다. 예시는 WebSocket 업그레이드와 polling을 함께 전달한다. 인터넷에 직접 노출된 nginx가 `X-Forwarded-For`를 실제 접속 IP로 덮어쓴다. CDN/추가 프록시 뒤에서는 원본 IP 검증 구성을 먼저 맞춘다. 앱의 2100 포트는 외부에 개방하지 않는다.
앱 API의 IP별 요청 제한과 별도로 nginx는 Socket.IO 요청을 IP당 초당 10회, 순간 초과 40회까지 허용한다. 공유 IP 사용자가 많으면 실제 사용량을 보고 조정한다. TURN 트래픽은 nginx HTTP 프록시를 통과하지 않는다.
앱은 IP별 1분 창에서 API 전체 1,200회, 세션 생성 30회, 관리자 HTML/API 합계 60회를 허용한다. 초과하면 HTTP 429와 `Retry-After`를 반환하며 DB 처리 전에 차단한다. 기준값은 `constants/server.ts`에서 관리한다. 제한 상태는 메모리에만 보관하고 재시작 시 초기화한다. 관리자 제한도 공유 IP 기준이므로 관리자가 같은 네트워크를 사용하면 합산된다.
랭킹 개인 전적 캐시는 최대 1,000명·5분, 리더보드는 5초 동안 재사용하며 결과 저장 시 무효화한다. 동시 리더보드 요청은 하나의 계산을 공유한다. 캐시가 없거나 만료되면 저장된 경기 기록을 다시 읽으므로 대규모 장기 운영에서 영구 집계를 대신하는 구조는 아니다. 단일 프로세스만 같은 운영 DB에 쓰도록 유지한다.
## 업데이트
단일 프로세스이므로 배포 중 접속이 중단되며 진행 중 랭킹 경기는 복원되지 않는다. 운영 폴더를 실행 중에 빌드하지 않도록 중지 후 갱신한다.
```bash
pm2 stop dragon-ball
git pull --ff-only
npm ci
npm run typecheck
npm run build
pm2 restart ecosystem.config.cjs --update-env
curl --fail http://127.0.0.1:2100/ready
pm2 save
```
각 명령이 성공한 경우에만 다음 단계로 진행한다. 빌드 실패 시 재시작하지 말고 수정하거나 이전 커밋으로 복구한 후 다시 설치·빌드한다. `.config.json`는 Git에서 제외되어 유지된다. 데이터 백업은 MongoDB를 별도로 관리한다.
## 로그 관리 및 운영 확인
```bash
pm2 status
pm2 logs dragon-ball --lines 100
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
```
로그 회전은 디스크가 무한히 늘어나지 않도록 운영 서버에서 설정한다. 예시 nginx는 access log를 끄지만 nginx 자체 error log에는 URL·IP가 기록될 수 있으므로 서버의 로그 접근권한과 보관 정책도 설정한다.
배포 후 서로 다른 인터넷망에서 친구전 연결·재대전, 랭킹 매칭·승패 저장, 관리자 인증을 확인한다. 로컬 테스트 통과가 실제 TURN 경로 검증을 대신하지 않는다.
참고: [PM2 설정](https://pm2.keymetrics.io/docs/usage/application-declaration/), [PM2 종료 처리](https://pm2.keymetrics.io/docs/usage/signals-clean-restart/), [nginx WebSocket 프록시](https://nginx.org/en/docs/http/websocket.html).