DevOps/Process Management

서버 프로세스 관리 — PM2와 Gunicorn으로 Node.js, Python 서버 운영하기

포도쿵야 2026. 4. 6. 12:06

로컬에서 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 환경 오케스트레이터가 프로세스 관리 담당. 컨테이너 내부는 단순 실행으로 구성 가능