PM2를 이용한 nextJS 무중단 배포

2023-11-01 10:26 | nextJS

Docker + PM2를 이용한 nextJS 무중단 배포


PM2 작동 방식

Node.js 애플리케이션은 단일 CPU 코어에서 실행되기 때문에 CPU의 멀티코어 시스템은 사용할 수 없다. 만약 서버의 사양이 8코어이며 하이퍼스레딩을 지원한다면 최대 16개 코어를 사용 할 수 있는데 모든 코어를 사용해 최대 성능을 내지 못하고 오직 한 개의 코어만 사용해야 한다면 주어진 자원을 제대로 활용하지 못하게 된다. Node.js는 이런 문제를 해결하기 위해 클러스터(Cluster) 모듈을 통해 단일 프로세스를 멀티 프로세스(Worker)로 늘릴 수 있는 방법을 제공한다. 이 클러스터 모듈을 사용하여 마스터 프로세스에서 워커 프로세스를 생성하고 멀티스레드로 프로젝트를 관리 할 수 있는 PM2를 사용한다.

이 과정에서 서버와 pm2가 서로 유기적으로 작동시키기 위해 순수 nextjs 보다는 express로 next를 감싸서 설정하는것이 좋다.

PM2는 멀티스레드로 진행 중에 프로젝트의 변경점이 생기면 프로세스를 죽이지 않고 다시 시작시켜준다.

  • pm2가 reload를 통해 프로젝트를 재시작 함
  • pm2는 자동으로 기존에 있던 0번 프로세스를 _old_0 프로세스로 옮겨두고 새로운 0번 프로세스를 생성한다
  • 여기서 새로운 0번 프로세스는 앱이 켜진 후 뒤에 보낸 "ready"라는 요청을 받게되면
  • _old_0 프로세스를 죽인뒤에 해당 프로세스가 켜고있는 앱에 "sigint" 시그널을 보내어 해당 앱을 끄게 만든다. 하지만 여기서 일정시간이 지난후 (1600ms) 종료되지 않으면 SIGKILL 시그널을 보내 프로세스를 강제 종료시킨다.
  • 이 과정이 0번 프로세스가 재시작되는 과정이며 이과정을 현재 실행중인 프로세스 만큼 진행함

이 과정 속에서 정확한 타이밍에 pm2에게 ready를 보내고, pm2는 정확한 타이밍에 Sigint 시그널을 보내야한다.

PM2를 위한 Dockerfile 작성

# 부모 이미지 지정
FROM node:17.9.0
 
# 루트 디렉터리의 파일을 기본 디렉터리에 복사한다.
ADD . /app
 
# 기본 이미지 내에 디렉터리를 생성하고 기본 디렉터리로 정의한다.
WORKDIR /app
 
ADD package.json .
 
# 프로젝트 종속 요소 설치.
RUN npm install
 
# nextjs build
RUN npm run build
 
# pm2 설치
RUN npm install pm2 -g
 
#Dockerfile 이 존재 하는 디렉토리 -> Docker workdir 디렉토리에 복사한다.
 
COPY . .
 
# pm2: 일반적인 용도로 애플리케이션을 백그라운드에 보내고 실행함.
# pm2-runtime: 도커 컨테이너에서 사용하는 용도로, 애플리케이션을 foreground에 유지하고 컨테이너를 계속 실행하게 함.
 
CMD ["pm2-runtime", "start", "ecosystem.config.js", "--env", "production"]
 

ecosystem.config.js 작성

module.exports = {
 
    /* apps 항목은 pm2에 사용할 옵션을 기재 */
    apps: [
       {
          name: 'web-nextjs-server', // app이름
          script: 'server.js', // 실행할 스크립트 파일
          instances: 3, // cpu 코어수 만큼 프로세스 생성 (instance 항목값을 ‘0’으로 설정하면 CPU 코어  만큼 프로세스를 생성)
          exec_mode: 'cluster', // 클러스터 모드
          wait_ready: true, // 마스터 프로세스에게 ready 이벤트를 기다려라
          listen_timeout: 50000, // ready 이벤트를 기다릴 시간값(ms)
          kill_timeout: 5000, // SIGINT  SIGKILL 대기시간을 5초로 설정
          max_memory_restart: '300M', // 프로세스의 메모리가 300MB에 도달하면 reload 실행
          merge_logs: true, // 클러스터 모드 사용   클러스터에서 생성되는 로그를  파일로 합쳐준다.
          autorestart: true, // 프로세스 실패  자동으로 재시작할지 선택
          watch: true, //bin폴더, routes폴더를 감시해서 변경사항 실행
          
          
          env: {
             // 환경변수 지정
             NODE_ENV: 'development',
             Redis_HOST: 'localhost',
             Redis_PORT: 6379,
          },
          env_production: {
            // 운영 환경설정 (--env production 옵션으로 지정할  있다.)
            NODE_ENV: 'production',
            HTTP_PORT: 8888,
            PG_USER:'test',
            PG_HOST:'localhost',
            PG_DB:'testdb',
            PG_PW:'pwtest',
            PG_PORT:5433
          }
 
       },
    ],
 
 };