Stouter
Stouter

백엔드 개발자입니다.

PM2를 이용하여 무중단 배포를 해보자

Node 기반으로 서비스 환경을 구성했을 땐 무중단 배포를 위해 프로세스 관리 도구인 PM2를 많이 쓰게 된다. 해당 글에선 PM2가 어떤 동작을 하여 무중단 배포를 하는지, 또한 어떻게 구성하는 지를 정리해보자.


PM2 설치

Yarn 환경변수 설정

  • 환경 변수 설정을 하지 않으면 global 설치 시 해당 명령어를 불러오지 못할 수 있음
  • 다음과 같이 yarn global 설치를 위한 설치 폴더를 지정해보자.
1
2
3
4
5
// 예시
yarn config set prefix ~/.yarn-global

// 위치 확인
yarn config get prefix

pm2 global 설치

1
yarn global add pm2

yarn 환경 변수 설정

  • ubuntu라면 home 디렉토리에 있는 .bashrc 파일을 수정하자
1
export PATH="$HOME/.yarn-global/bin:$PATH"

[YARN] Yarn Global PATH 설정


PM2 명령어

ecosystem.config.js 설정

  • pm2 실행 설정에 관한 파일
  • cluster 모드를 활용해서 무중단 배포할 예정
1
2
3
4
5
6
7
8
9
10
module.exports = {
  apps: [
    {
      name: "moonjin-server", // 식별하기 위한 원하는 이름
      script: "dist/main.js", // 빌드된 실팽 코드 위치
      instances: 0, // 0 은 최대 코어만큼의 instance 생성
      exec_mode: `cluster`, // cluster 모드
    },
  ],
};

Clustering 실행

  • 입력
1
pm2 start ecosystem.config.js
  • 결과
1
2
3
4
 id  name                mode           status     cpu       memory   
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
 0   moonjin-server      cluster   0     online     0%        101.9mb  
 1   moonjin-server      cluster   0     online     0%        102.6mb  

pm2 재실행

  • 입력
1
pm2 reload moonjin-server
  • 결과
1
2
3
[PM2] Applying action reloadProcessId on app [moonjin-server](ids: [ 0, 1 ])
[PM2] [moonjin-server](0) 
[PM2] [moonjin-server](1) 

PM2 - Process Management


PM2 재시작 과정

  • 프로세스 10개 → pm2 reload
  1. 기존 process_0_old_0 으로 옮김
  2. 새로운 0번 프로세스는 준비가 다 될 시 ready 이벤트를 pm2에 전달
  3. ready 이벤트가 감지되면 pm2는 _old_0SIGINT 시그널을 보내고, 종료되기를 기다림.
  4. 1600ms 이상 종료되지 않을 시 SIGKILL 로 강제 종료


조심해야할 점

1. 구동이 오래걸리는 앱의 경우, Ready가 실제 완료 보다 먼저 될 수 있음

Untitled

  • app 단계에서 이를 구동 완료 시 ready를 보내는 식으로 해야함
  • config 파일 수정으로 해결 가능
1
2
3
4
5
6
7
8
9
10
11
module.exports = {
  apps: [
    {
      name: "moonjin-server",
      script: "./dist/main",
      instances: 0,
      exec_mode: "cluster",
      listen_timeout: 50000,
    },
  ],
};

2. 클라이언트 요청을 처리하는 도중에 old_process가 죽는 경우

Untitled

  • SIGINT 이벤트를 listen하다가 해당 시그널이 전달되면 app.close를 통해 종료될 프로세스가 새로운 요청을 받는 것을 거절해야함.
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
  apps: [
    {
      name: "moonjin-server",
      script: "./dist/main.js",
      instances: 0,
      exec_mode: "cluster",
      listen_timeout: 50000,
      kill_timeout: 5000,
      autorestart: true,
    },
  ],
};
  • main.ts (/src)
1
2
3
4
5
6
process.on("SIGINT", () => {
  app.close().then(() => {
    console.log("server closed");
    process.exit(0);
  });
});

PM2를 활용한 Node.js 무중단 서비스하기


무중단 배포

CD

  • github actions 에서 CD.yml 파일 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: cd

on:
  push:
    branches:
      - master

jobs:
  Auto-Deploy:
    name: Backend Deploy
    runs-on: ubuntu-latest
    steps:
      - name: SSH RemoteCommands
        uses: appleboy/ssh-action@v0.1.5
        with:
          host: $
          port: $
          username: $
          password: $
          script: |
            cd /home/ubuntu/server
            git pull origin master
            export NVM_DIR=~/.nvm
            source ~/.nvm/nvm.sh
            yarn install
            yarn build
            pm2 reload moonjin-server

EC2 비밀번호 접근 설정

1
2
sudo passwd ubuntu
// 비밀번호 생성
  • sshd_config 에서 password 접속 허용

EC2 비밀번호로 로그인하도록 변경하기