PM2 ( node, bun )
## PM2는 Node 어플리케이션을 위한 프로세스 관리자이다.
Node.js 어플리케이션을 항상 살아있게 Keep Alive 만들어주는 역할을 한다.
개발 중에 우리는 node app.js와 같이 직접 파일을 실행하지만, 실제 서비스를 운영할 때 이렇게는 부족하다.
서버에 에러가 발생하면 프로그램이 그대로 종료되어 버리기 때문이다.
## PM2는 이러한 문제를 해결하기 위해서 다음과 같은 기능을 제공한다.
무중단 서비스
- 앱에 오류가 발생하여 종료되더라도 PM2가 즉시 자동으로 재시작해준다.
클러스터 모드
- 단일서버에서도 여러개의 프로세스를 동시에 실행하여 성능을 극대화한다.
모니터링
- 터미널에서 PM2 monit 명령어로 cpu, 메모리 사용량 등 앱의 상태를 실시간으로 확인한다.
로그관리
- 앱에서 발생하는 모든로그 console.log를 파일로 저장하고 관리해준다.
## PM2를 안정적으로 운영하기 위해서 몇가지 설정과 습관을 들이는 것이 좋다.
1. echosystem.config.js 파일 사용
- 여러 앱을 관리하거나 복잡한 설정이 필요할 때, 명령어 옵션으로 일일히 지정하는 것보다 설정파일을 사용하는 것이 훨씬 효율적이고 편리하다. 이 파일에 앱이름, 실행 스크립트, 클러스터모드, 환경 변수 등 모든 설정을 기록해두고 pm2 start ecosystem.config.js 명령어로 한번에 실행할 수 있다.
2. 재시작 전략 설정
- 앱이 비정상적으로 종료될 때 너무 자주 재시작되는 것을 방지하기 위해 재시작 딜레이 restart_delay나 최대 재시작 횟수 max_restarts를 설정할 수 있다.
3. Graceful Shutdown 활성화
- 앱이 재시작되거나 종료될 때, 현재 처리중이던 요청을 안전하게 마무리하고 종료되도록 설정하는 것이 중요하다. 이를 Graceful Shutdown 이라고 하며, Node.js 코드와 PM2 설정 양쪽에서 모두 처리해주어야 한다.
4. 서버 재부팅 시 자동 시작 설정
- pm2 startup 명령어를 실행해두면, 서버가 예기치 않게 재부팅되더라도 pm2가 자동으로 시작되고 관리하던 앱들도 다시 실행한다.
## Node.js와 Bun에서 PM2 사용법
- PM2는 기본적으로 Node.js를 위해서 만들어졌지만, Bun과 같은 다른 JS 런타임에서도 훌륭하게 작동한다.
기본시작(포크모드) : pm2 start app.js
클러스터모드로 시작 : pm2 start app.js -i 0 ( 0은 cpu 코어 수만큼 자동으로 인스턴스를 생성하라는 의미 )
--name 이름 지정
## PM2 클러스터 모드
PM2의 클러스터 모드는 단일 서버의 성능을 최대한으로 끌어올릴 수 있는 매우 강력하고 중요한 기능이다.
Node.js는 기본적으로 단일 스레드를 기반으로 동작한다. 하나의 프로세스는 하나의 cpu 코어만 사용한다.
최신 서버들이 대부분 멀티코어 cpu를 탑재하고 있다는 점을 감안하면, 이것은 상당한 자원의 낭비로 이어질 수 있다.
PM2의 클러스터 모드는 이러한 한계를 극복하기 위해서 Node.js의 내장 클러스터 모듈을 활용한다.
클러스터 모드로 어플리케이션을 실행하면, 사용가능한 cpu 코어수만큼 지정된 수만큼 프로세스(워커, Worker)를 복제하여 클러스터를 형성한다. 그리고 마스터 프로세스가 외부의 요청을 받아서 각 워커 프로세스에 라운드 로빈 Round-Robin 방식으로 분산하여 처리 ( 로드밸런싱 )한다.
이를 통해서 단일 프로세스로 모든 요청을 처리할 때 발생할 수 있는 병목 현상을 해소하고, 시스템의 전체적인 처리량을 크게 높일 수 있다.
## 클러스터 모드 시에는 다음 사항을 고려해야한다.
- 상태관리
클러스터 모드는 각 프로세스가 독립된 메모리 공간을 갖는 상태 비저장 Stateless 아키텍처를 전제로 한다.
즉, 한 워커 프로세스에서 메모리에 저장한 데이터 ( 예 : 세션, 웹 소켓 연결정보 )는 다른 워커 프로세스와 공유되지 않는다.
만약 어플리케이션이 사용자 세션과 같은 상태를 저장해야한다면, 각기 다른 워커로 요청이 분산될 때마다 상태 정보가 유실되는 문제가 발생할 수 있다.
이러한 문제를 해결하기 위해서는 모든 워커 프로세스가 접근할 수 있는 공유 데이터 저장소를 사용해야 한다.
1. Redis
인메모리 데이터 저장소인 레디스는 세션저장, 메시지큐 등 다양한 용도로 활용될 수 있어 PM2 클러스터 환경에서 가장 널리 사용되는 솔루션 중 하나이다. express-session 과 connect-redis와 같은 라이브러리를 함께 사용하면 세션을 효과적으로 관리할 수 있다.
2. 데이터베이스
상태정보를 영구적으로 저장하고 모든 워커에서 공유할 수 있다.
## 클러스터 모드는 다음과 같이 사용할 수 있다.
방법 1. pm2 start 명령어에 -i 또는 --instances 옵션을 추가하여 실행할 프로세스의 수를 지정한다.
방법 2. ecosystem.config.js 설정파일을 사용한다.
module.exports = {
app : [
{
name : 'my-app', // 어플리케이션 이름
script : 'app.js', // 실행할 스크립트 파일
instances : 'max', // max 또는 원하는 인스턴스 수 지정
exec_mode : 'cluster',
env:{
NODE_ENV : 'production',
},
}
]
}
이후에 설정 파일을 실행한다.
만약에 여러개의 서비스를 동시에 실행하고 싶다면 다음과 같이할 수 있다.
예를 들어서, 웹 서비스를 제공하는 api-server와 백그라운드에서 주기적인 작업을 처리하는 worker라는 두개의 서비스를 하나의 파일로 관리하고 싶다고 가정했을 때, 다음과 같이 ecosystem.config.js를 정의할 수 있다.
module.exports = {
apps: [
// 1. API 서버 서비스 설정
{
name: 'api-server', // 서비스 이름
script: './api/server.js', // 실행할 스크립트 파일 경로
exec_mode: 'cluster', // 클러스터 모드로 실행
instances: 'max', // CPU 코어 수만큼 인스턴스 생성
watch: true, // 파일 변경 시 자동 재시작
env_production: { // "production" 환경 변수
NODE_ENV: 'production',
PORT: 3000,
},
},
// 2. 백그라운드 워커 서비스 설정
{
name: 'worker', // 서비스 이름
script: './workers/job-processor.js', // 실행할 스크립트 파일 경로
exec_mode: 'fork', // 단일 프로세스인 포크 모드로 실행
instances: 1, // 하나의 인스턴스만 실행
watch: false, // 파일 변경 감지 비활성화
env: { // 기본 환경 변수
DB_HOST: 'localhost',
QUEUE_NAME: 'tasks',
},
},
],
};
이를 전체적으로 실행하려면
pm2 start ecosystem.config.js
개별적으로 제어하려면
pm2 restart api-server
pm2 stop worker
## 환경변수 사용
위에서처럼 지정하게 되면, 바로 bun의 hono에서 이렇게 사용할 수 있다.
import { Hono } from 'hono';
const app = new Hono();
// PM2 설정 파일에서 주입된 환경 변수를 사용
const port = parseInt(process.env.PORT || '3000', 10);
const message = process.env.MESSAGE || 'Hello from Hono!';
app.get('/', (c) => {
console.log('Request received!');
return c.text(message);
});
console.log(`Hono server running on port ${port}`);
export default {
port,
fetch: app.fetch,
};