우리는 항상 서비스를 만들 때, 개발하고 배포를 진행합니다. 변경된 코드를 실행시키기 위해서 서버를 재실행하게 되는데, 아주 찰나에 사용자는 서비스가 죽은 화면을 볼 가능성이 높습니다. 이 문제점을 위해서 Node 프로세스 관리자인 PM2를 사용해서 이를 해결해볼까 합니다.
먼저 기본적인 PM2 사용법부터 살펴보도록 하겠습니다.
더 자세한 내용을 원하시는 분들은 PM2-Documentation을 보고 진행해도 됩니다.
PM2는 Node 패키지 매니저인 NPM을 통해서 간단하게 설치해줄 수 있습니다.
$ npm install -g pm2
프로세스 관리자이니, 데몬화해줄 어플리케이션이 있어야하겠죠? 아래와 같이 Express 샘플 코드가 있다고 할 때, 다음과 같이 사용할 수 있습니다.
// Sample App (app.js)
const express = require('express')
const app = express()
const port = 3000
app.get('/', function (req, res) {
res.send("Hi I'm NB")
})
app.listen(port, function () {
console.log(`Application is listening on port ${port}...`)
})
$ pm2 start app.js
위와 같이 실행했다면, 다음 명령어로 모니터링을 진행할 수 있습니다.
$ pm2 list
$ pm2 monit
list
는 현재 상태를 시각화해주며, monit
을 실시간 상태를 확인할 수 있도록 시각화를 해줍니다.
하지만, 위와 같이 pm2를 실행했다면, 기본적으로 fork
모드로 설정되어, 단일 인스턴스는 단일 스레드로 실행되는 Node의 특징을 가지고 실행되게 됩니다. 이때, PM2에서는 Node의 Cluster Module을 사용해서 Cluster
모드로 실행될 수 있도록 도와줍니다.
다음과 같이 ecosystem.config.json
파일을 만들어 실행해봅시다.
(.js
형태로도 작성할 수 있습니다.)
{
"apps": [
{
"name": "app-name",
"cwd": "./project-dir"
"script": "./app.js",
"instances": 0,
"exec_mode": "cluster",
"autorestart": true,
"max_memory_restart": "2G",
"output": "~/logs/pm2/console.log",
"error": "~/logs/pm2/onsoleError.log"
}
]
}
Info
name
: 어플리케이션 이름interpreter
: 인터프리터 경로 (기본값은 Node
입니다.)interpreter_args
: 인터프리터 옵션입니다.script
: pm2 시작에 대한 시작 파일 경로 (interpreter
으로 실행됩니다.)cwd
: 앱이 실행될 디렉토리args
: CLI로 실행되는 인자Advanced
instances
: 인스턴스 수를 결정할 수 있으며, 0으로 작성하면 현재 가능한 CPU 코어 수만큼 실행됩니다.exec_mode
: fork모드로 실행할지, cluster모드로 실행할지 선택watch
: 폴더 또는 특정 폴더를 감시하여 변경되면 Reload를 진행합니다.ignore_watch
: watch를 제외할 경로를 작성합니다.max_memory_restart
: 설정한 메모리를 초과할 시, 자동으로 재시작됩니다.source_map_support
: 소스맵 활성화 여부를 선택합니다.Logs
log_date_format
: 로그 날짜 형식 (기본값은 YYYY-MM-DD HH:mm Z 입니다.)output
: 로그 출력 경로를 설정합니다.error
: 에러 로그 출력 경로를 설정합니다.Control Flow (Reference)
min_uptime
: 어플리케이션이 가동되었다고 생각하는 최소 시간kill_timeout
: 최정 SIGKILL을 보내기까지의 시간wait_ready
: Reload 대기 이벤트 대신에 어플리케이션에서의 process.send('ready') 를 기다립니다.autorestart
: 프로세스를 1회만 실행시키고 싶을 때, false로 설정합니다.여기서 restart
와 reload
의 차이는 restart
는 프로세스를 종료하고 다시 시작하는 것과 다르게, reload
는 0초 다운타임 재로드가 됩니다.
$ pm2 start ecosystem.config.json
$ pm2 stop ecosystem.config.json
$ pm2 restart ecosystem.config.json
$ pm2 reload ecosystem.config.json
$ pm2 delete ecosystem.config.json
Ecosystem.config.json
에 맞춰서 어플리케이션을 실행하고난 뒤에도, 유동적으로 프로세스를 늘리거나, 줄이고 싶을 때가 있습니다. 그럴 때에는 아래처럼 해주도록 합시다.
# 프로세스 4개 늘리기
$ pm2 scale app +4
# 프로세스 4개 줄이기
$ pm2 scale app -4
# 프로세스 4개로 설정하기
$ pm2 scale app 4
어플리케이션이 종료되는 것은 PM2가 알아서 관리해주지만, 하지만 시스템이 재부팅되어 PM2 자체가 종료된다면 과연 누가 관리해줄까요?
이를 위해서 다음과 같은 방법이 존재합니다.
$ pm2 save
$ pm2 startup
[PM2] You have to run this command as root. Execute the following command:
sudo su -c "env PATH=$PATH:/home/unitech/.nvm/versions/node/v14.3/bin pm2 startup <distribution> -u <user> --hp <home-path>
위 처럼 출력된 명령어(sudo...
)를 복사/붙여넣기 해주면 됩니다.
추가적으로, Node 버전이 바뀐다던지, 하나의 서버에서 여러개의 사용자 계정이 존재하는 경우에는 다음 Documentation을 참고하여 설정해주면 됩니다.
위 Config 파일에서 설정한 로그 경로에 저장되는 로그는 우리가 삭제해주지 않는한 계속 쌓이게 됩니다. 이렇게 계속 쌓이게 된다면 서버 용량 부족으로 이어질 수도 있는데요. 이를 해결하기 위해서 로그 파일을 관리해주는 pm2-logrotate
라는 plugin을 사용해줍시다.
사용방법은 다음과 같습니다.
# npm으로 설치하는 것이 아닌 pm2로 설치해주어야 합니다!
$ pm2 install pm2-logrotate
# 옵션 설정은 다음과 같이 설정해줍니다.
$ pm2 set pm2-logrotate:<option> <value>
$ pm2 set pm2-logrotate:max_size 1K
$ pm2 set pm2-logrotate:retain 10
Options
max_size
: 로그 파일 사이즈 제한 크기 (기본값은 10M)retain
: 로그 파일을 최대 몇개까지 가지고 있을 것인지 설정 (기본값은 30개)compress
: 로그 파일을 gzip으로 압축할 것인지 여부 (기본값은 false)dateFormat
: 로그 파일 날짜 폼새 (기본값은 YYYY-MM-DD_HH-mm-ss)workerInterval
: 로그 파일 사이즈를 확인하는 1초마다의 회수 (기본값 초당 30회)rotateInterval
: cron job (기본값은 '0 0 * * *')우리는 이제까지 PM2의 환경을 세팅하고, 다루는 방법에 대해서 알아보았습니다. 도큐먼트를 읽어보면 배포 시스템을 구축해서, 좀 더 배포에 대해서 편하게 만들어주는 방법도 있더라구요. [Deployment System]
이 부분은 아직 제대로 적용해보지는 않았으니, 추후 다시 한 번 적용해서 글을 작성해보도록 하겠습니다.
저는 이 PM2를 템플릿서버를 위해서 사용하고 있었는데, Next.js와 함께 사용하는 상황이였습니다.
실제로 Next.js와 함께 PM2를 사용하는 사람들을 위해서 몇 가지 알려드릴 것이 이습니다만, ecosystem.config.json
파일에서 yarn
또는 npm
으로 시작하기보다 다음과 같은 방법으로 설정하는 것이 문제없이 돌아가더라구요.
{
"apps": [
{
"name": "app-name",
"cwd": "./project-dir"
"script": "node_modules/next/dist/bin/next",
"args": "start",
"instances": 0,
"exec_mode": "cluster",
"autorestart": true,
"max_memory_restart": "2G",
"output": "~/logs/pm2/console.log",
"error": "~/logs/pm2/onsoleError.log"
}
]
}