Skip to content

PM2 Safe Restart Configuration

글 작성 사유

  • pm2 process를 재시작 할때, 모든 프로세스를 죽여버리면 프로세스가 다시 시작되기까지 서버 사용 불가.
  • 검색 결과 노드 앱이 실행 되었다는 신호를 pm2와 주고받아서 pm2 process를 재시작 할때 순차적으로 프로세스들을 재시작 하게 할 수 있는 기능 발견.

pm2 wait_ready option

  • 위 옵션은 기본값이 false로, 미설정 시 node server의 상황을 pm2가 몰라서 pm2 restart 시 모든 프로세스를 한번에 재시작을 해버림.
  • 서비스가 중단이 되면 안되기 때문에 wait_ready option을 true로 설정 후 node server가 실행 됬을 때 pm2에게 서버가 살았다는 신호를 전송. pm2는 서버가 살았다는 신호를 받으면 다음 프로세스 진행.

ecosystem.config.js 수정

javascript
//ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'app',
      script: 'server.js',
      instances: 4,
      exec_mode: 'cluster', // 실행 모드. cluster로 명시하지 않으면 기본 fork 모드가 된다.
      wait_ready: true, // Node.js 앱으로부터 앱이 실행되었다는 신호를 직접 받겠다는 의미
      listen_timeout: 50000, // 앱 실행 신호까지 기다릴 최대 시간. ms 단위.
      kill_timeout: 5000, // 새로운 프로세스 실행이 완료된 후 예전 프로세스를 교체하기까지 기다릴 시간
      max_memory_restart: '500M', //프로세스 메모리 사용량이 증가했을 경우 재시작 하는 옵션
      env: {
        NODE_ENV: 'development',
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: '8081',
      },
    },
  ],
};

app.ts 수정

  • app 실행 상태 전송
javascript
const server = express();

const listeningServer = server.listen(port, (err) => {
  console.log(`> Ready onhttp://localhost:${port}`);

  // PM2가 스크립트를 실행하지 않았다면 process.send 메소드가 undefined일 수 있다.
  if (process.send) {
    // PM2에게 앱 구동이 완료되었음을 전달한다
    process.send('ready');
  }
});
  • 서버가 시작되었으니 다른 프로세스를 재시작해도 된다는 신호 전송
javascript
let isAppGoingToBeClosed = false; // HTTP 연결을 종료시킬 미들웨어에서 사용할 변수

process.on('SIGINT', function () {
  // SIGINT 신호가 수신되었을 때  console.log('> received SIGNIT signal')

  isAppGoingToBeClosed = true;

  // pm2 재시작 신호가 들어오면 서버를 종료시킨다.
  // listeningServer: server.listen 메소드가 리턴하는 서버 인스턴스를 할당한 변수
  listeningServer.close(function (err) {
    console.log('server closed');
    process.exit(err ? 1 : 0);
  });
});
  • 서버가 죽었으니 다른 프로세스로 요청을 넘기기 위한 클라이언트와 커넥션 종료
javascript
server.use(function (req, res, next) {
  // 프로세스 종료 예정이라면 연결을 종료한다
  if (isAppGoingToBeClosed) {
    res.set('Connection', 'close');
  }

  next();
});

IronTrain Tech Blog