로컬에서 node server.js나 uvicorn app:app으로 서버를 띄우는 건 쉽다. 그런데 이 방식으로 프로덕션 서버를 운영하면 어떻게 될까.
터미널을 닫는 순간 서버가 죽는다. 에러가 나도 자동으로 살아나지 않는다. 서버 재시작 후 자동으로 켜지지도 않는다. 이 문제들을 해결하는 것이 프로세스 관리자(Process Manager) 의 역할이다.
📌 왜 프로세스 관리자가 필요한가
단순 실행의 문제점
$ node server.js ← 터미널 닫으면 즉시 종료
$ uvicorn app:app ← 에러 나도 자동 재시작 없음
← 서버 재부팅 후 수동으로 다시 켜야 함
← 멀티 코어 활용 안 됨 (단일 프로세스)
← 로그 관리 없음
프로세스 관리자가 해결하는 것
기능 설명
| 프로세스 상시 유지 | 터미널 종료와 무관하게 백그라운드 실행 |
| 자동 재시작 | 크래시 발생 시 즉시 복구 |
| 부팅 시 자동 실행 | systemd 연동 |
| 멀티 프로세스 | CPU 코어 수만큼 워커 생성 |
| 로그 관리 | stdout/stderr 파일로 저장 |
📌 PM2 — Node.js / Next.js 프로세스 관리자
PM2(Process Manager 2)는 Node.js 생태계의 표준 프로세스 관리자다. Node.js 서버, Next.js 앱, 모든 Node 기반 프로세스를 관리할 수 있다. 데몬(Daemon) 방식으로 동작해서 터미널을 닫아도 프로세스가 유지된다.
기본 사용법
# 설치
npm install -g pm2
# 기본 실행
pm2 start server.js --name my-app
# Next.js 실행
pm2 start npm --name next-app -- start
# 상태 확인
pm2 list
pm2 status
# 로그 확인
pm2 logs
pm2 logs my-app
# 재시작 / 중지 / 삭제
pm2 restart my-app
pm2 stop my-app
pm2 delete my-app
# 시스템 재부팅 시 자동 실행 설정
pm2 startup
pm2 save
cluster 모드 — 멀티 코어 활용
Node.js는 기본적으로 싱글 스레드다. PM2의 cluster 모드를 사용하면 CPU 코어 수만큼 프로세스를 띄워 멀티 코어를 활용할 수 있다. 같은 포트를 여러 프로세스가 공유하며 PM2가 로드밸런싱을 처리한다.
# cluster 모드로 실행 (-i: 인스턴스 수, max: CPU 코어 수만큼)
pm2 start server.js -i max --name my-app
# 코어 수 직접 지정
pm2 start server.js -i 4 --name my-app
# 무중단 재시작 (트래픽 유지하면서 순차 재시작)
pm2 reload my-app
ecosystem.config.js — 설정 파일로 관리
실무에서는 CLI 대신 설정 파일로 관리하는 것이 훨씬 편리하다. 환경별(개발/스테이징/프로덕션) 설정을 하나의 파일에서 관리할 수 있다.
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'next-app',
script: 'npm',
args: 'start',
instances: 'max', // CPU 코어 수만큼
exec_mode: 'cluster', // 멀티 프로세스
watch: false, // 프로덕션에서는 false
max_memory_restart: '1G', // 메모리 초과 시 자동 재시작
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
},
],
};
# 실행
pm2 start ecosystem.config.js --env production
📌 Uvicorn + Gunicorn — Python 프로세스 관리
Python FastAPI 환경에서는 PM2 같은 별도 프로세스 관리자 대신, Gunicorn이 그 역할을 직접 담당한다. Gunicorn은 워커 프로세스를 관리하는 마스터 프로세스로 동작하고, 각 워커는 Uvicorn(ASGI 엔진)으로 실행된다.
[ Node.js 진영 ] [ Python 진영 ]
PM2 (프로세스 관리자) Gunicorn (프로세스 관리자)
├─ Node Worker 1 ├─ Uvicorn Worker 1
├─ Node Worker 2 ├─ Uvicorn Worker 2
└─ Node Worker N └─ Uvicorn Worker N
역할이 같다: 워커 생성 / 감시 / 복구 / 재시작
기본 실행 명령어
# 개발 환경: Uvicorn 단독 (--reload로 핫 리로드)
uvicorn app:app \
--host 0.0.0.0 \
--port 8000 \
--reload
# 프로덕션: Gunicorn + UvicornWorker
gunicorn app:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 120 \
--keep-alive 5 \
--access-logfile ./logs/access.log \
--error-logfile ./logs/error.log
# 워커 수 공식: (CPU 코어 수 × 2) + 1
# I/O 바운드 작업이 많은 경우 더 늘리기도 함
gunicorn.conf.py — 설정 파일로 관리
PM2의 ecosystem.config.js처럼 Gunicorn도 설정 파일로 관리하는 것이 편리하다.
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 120
keep_alive = 5
accesslog = "./logs/access.log"
errorlog = "./logs/error.log"
loglevel = "info"
# 워커 재시작 조건 (메모리 누수 방지)
max_requests = 1000
max_requests_jitter = 100 # 동시 재시작 방지용 랜덤 오프셋
# 실행
gunicorn app:app -c gunicorn.conf.py
systemd로 자동 실행 등록
PM2의 pm2 startup에 해당하는 Python 환경의 방법이다. systemd 서비스 파일을 만들면 서버 재부팅 후에도 자동으로 Gunicorn이 실행된다.
# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI Application
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/app
ExecStart=/usr/bin/gunicorn app:app -c gunicorn.conf.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
# 서비스 등록 및 시작
sudo systemctl daemon-reload
sudo systemctl enable fastapi # 부팅 시 자동 시작
sudo systemctl start fastapi
# 상태 확인
sudo systemctl status fastapi
🔍 PM2 vs Gunicorn 비교
항목 PM2 Gunicorn
| 언어 생태계 | Node.js / JavaScript | Python |
| 역할 | 프로세스 관리자 | 프로세스 관리자 + WSGI/ASGI 서버 |
| 워커 실행 엔진 | Node.js 자체 | UvicornWorker (ASGI) / sync worker (WSGI) |
| 멀티 프로세스 | cluster 모드 | --workers 옵션 |
| 자동 재시작 | 기본 지원 | 기본 지원 |
| 무중단 재시작 | pm2 reload | kill -HUP (graceful) |
| 부팅 시 자동 실행 | pm2 startup | systemd 직접 설정 |
| 로그 관리 | 내장 (pm2 logs) | 파일 경로 지정 필요 |
| 모니터링 대시보드 | pm2 monit | 없음 (외부 도구 필요) |
| 설정 파일 | ecosystem.config.js | gunicorn.conf.py |
핵심 차이 PM2는 순수한 프로세스 관리자로, 어떤 Node.js 프로세스든 감싸서 관리한다. Gunicorn은 프로세스 관리 기능에 더해 WSGI/ASGI 인터페이스 처리까지 담당한다. Python에서 PM2 역할을 하는 것이 Gunicorn이고, Node.js에서 Gunicorn 역할을 하는 것이 PM2 + Node HTTP 서버의 조합이라고 보면 된다.
📋 환경별 실행 방식 정리
환경 Node.js / Next.js Python / FastAPI
| 개발 | next dev 직접 실행, PM2 불필요, HMR 사용 | uvicorn app:app --reload, Gunicorn 불필요 |
| 프로덕션 | next build 후 PM2 실행, cluster 모드 + ecosystem.config.js | Gunicorn + UvicornWorker, gunicorn.conf.py + systemd |
| 공통 | Nginx 리버스 프록시 앞에 배치 | Nginx 리버스 프록시 앞에 배치 |
🏗️ 전체 프로덕션 아키텍처
Node.js와 Python API가 함께 운영되는 일반적인 구성이다.
클라이언트 (브라우저)
↓ HTTPS :443
Nginx ← SSL, 로드밸런싱, 정적 파일
├─ / → PM2 (Next.js) :3000
│ ├─ Node Worker 1
│ └─ Node Worker N
│
└─ /api → Gunicorn (FastAPI) :8000
├─ Uvicorn Worker 1
└─ Uvicorn Worker N
Docker 환경에서는 조금 다르다 컨테이너 환경(Docker, Kubernetes)에서는 PM2나 systemd 대신 컨테이너 오케스트레이터가 프로세스 관리를 담당한다. 컨테이너가 죽으면 오케스트레이터가 새 컨테이너를 띄우는 방식이라, 컨테이너 안에서는 PM2 없이 next start 단독 실행, Gunicorn 단독 실행으로 단순하게 구성하는 경우도 많다. 프로세스 관리의 책임이 컨테이너 레벨로 올라간 것이다.
📋 정리
항목 내용
| 프로세스 관리자의 역할 | 백그라운드 유지, 크래시 자동 복구, 멀티 프로세스, 부팅 시 자동 실행, 로그 관리 |
| PM2 | Node.js 표준. cluster 모드로 멀티 코어 활용. ecosystem.config.js로 설정. pm2 startup으로 부팅 자동 실행 |
| Uvicorn 단독 | 개발 환경 전용. --reload로 핫 리로드. 프로덕션 단독 사용 비권장 |
| Gunicorn + UvicornWorker | Python 프로덕션 표준. Gunicorn이 PM2 역할, Uvicorn이 ASGI 실행 엔진 |
| systemd | pm2 startup에 해당하는 Python 환경의 자동 실행 방법 |
| Docker 환경 | 오케스트레이터가 프로세스 관리 담당. 컨테이너 내부는 단순 실행으로 구성 가능 |