Prepare game updates and single-process PM2 deployment

This commit is contained in:
2026-10-14 19:13:36 +09:00
parent 4f24129e29
commit 22792bc2b4
50 changed files with 1546 additions and 197 deletions
+93
View File
@@ -0,0 +1,93 @@
# 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 .env.example .env
chmod 600 .env
```
`.env`에서 다음을 설정한다.
- `MONGODB_URI`: 서버에서 연결 가능한 MongoDB 주소와 인증 정보. DB 포트를 인터넷에 공개하지 않는다.
- `MONGODB_DATABASE`: 초기화하려면 기존에 사용하지 않은 DB 이름을 지정한다. 예시의 `dragon_ball_production`도 이미 사용했다면 다른 이름을 지정한다. 기존 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 설정은 실행 위치와 무관하게 저장소 루트를 사용하며 `.env`가 없으면 시작하지 않는다. 운영 환경에 이미 설정된 환경변수는 Node의 `.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
```
각 명령이 성공한 경우에만 다음 단계로 진행한다. 빌드 실패 시 재시작하지 말고 수정하거나 이전 커밋으로 복구한 후 다시 설치·빌드한다. `.env`는 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).