티스토리 뷰

이번에는 노드를 더 우아하게 사용할 수 있도록 돕는 PM2 에 대해 이야기를 해본다. PM2Process Manager 의 약자로 이미 단어에서 모든게 설명 되는데 노드 프로세스를 관리해주는 역할을 한다. 아래에서 간단한 노드 프로그램을 기반으로 PM2 를 차근차근 이해하도록 하자.

보통 서버에 데몬 형태의 application 을 개발하게 되면 여러가지 것들을 신경써야 했다. 이를테면 application 에서 남기는 로그에 대한 처리( filesystem 을 이용해서 남기는 방법, 날짜 포함, rotate, etc ), 프로세스가 죽었을 때에 대한 처리( restart ), 부팅시에 자동 실행 등 무수히 많은 것들이 있다. 이런 귀찮은 것들을 몽땅 관리해주는 프로세스가 있다면 얼마나 편할까? (노드에만 국한된 이야기지만) 그래서 PM2 가 나왔다.

유사한 매니저 프로그램으로는 qmail 에서 사용하는 svscan 이 있겠지만 얘는 그냥 프로세스가 죽으면 살리는 역할만 할 수 있다. 반면 PM2 는 위에서 말한 모든 것+알파가 가능해진다. 자, 이제 그 PM2 를 본격적으로 살펴보자.

노드 프로젝트답게 npm 을 통해 아래와 같이 pm2 를 설치할 수 있다.

$ npm install pm2 -g

뒤에 -g 옵션을 붙이는 이유는 사용자가 pm2 명령어를 어떤 디렉터리에서건 사용할 수 있게 하기위함이다. 글로벌 세팅이라고 보면 된다. npm 이야기에서 만들어뒀던 API 서버 프로그램을 pm2 로 돌려보자.

$ pm2 start server.js

정말 이렇게 간단하게 pm2에 등록된다. PM2 에 등록된 관리 리스트를 볼 때는 list 옵션이 사용된다.

$ pm2 list

프로세스를 종료할 때는 pm2 list 에서 확인한 앱의 이름이나 애플리케이션의 ID (list 에서 확인 가능)를 stop 의 인자로 넘겨주면 된다.

$ pm2 stop 0

방금 위에서 애플리케이션의 ID 로 조정할 수 있다고 했는데 애초에 start 를 할 때 앱의 이름을 지정할 수 있다. 이렇게 하는 이유는 다양한 프로젝트를 pm2 에서 명확하게 관리하기 위해서다. 자, 그러면 기존에 등록되어 있던 server.js 를 우선 pm2 에서 delete 옵션을 통해 삭제하도록 하자.

$ pm2 delete 0

그리고 아래와 같이 ID 를 지정해서 다시 PM2 에 등록하도록 한다.

$ pm2 start server.js --name "API"

다시 pm2 list 를 살펴보면 ID 가 등록된 것을 볼 수 있다.

$ pm2 list

여기서 방금 올린 API 에 대한 상세 정보가 필요하면 show 옵션을 사용하도록 한다.

$ pm2 show API

server.js 내용이 수정되는 경우에는 pm2API 이름을 갖는 대상에 대해 재시작( restart )만 날려주면 되지만 pm2 의 인자를 변경하는 경우에는 delete 하고 새로 등록해줘야한다는 점을 잊지말자. 위에 내용중에 자세히 보면 out log path (console.log 가 출력되는 위치) 가 기본적으로 .pm2 밑에 저장되고 있는 것을 볼 수 있는데 이 위치를 우리가 원하는 곳으로 옮겨보자. 앞선 설명처럼 APIdelete 하고 옵션을 추가하도록 한다.

$ pm2 delete API && pm2 start server.js --name "API" -o ./api.log

뒤에 -o 옵션으로 api.log 를 지정해줬다. 이제 코드 내에서 console.log 로 출력한 모든 표준출력( stdout ) 이 api.log 로 기록 되게 된다. (짝짝) 노드가 아닌 다른 언어였면 로그를 남기기 위해 아래와 같은 함수를 등록해놓고 사용해야 했을 것이다.

// pseudo code
void systemLog(char *log)
{
    fopen(api.log)
    fwrite(log)
    fclose(api.log)
}

때때로 open/write 에 대한 atomic 보장을 위해 함수 내부에 lock 이 필요한 언어도 있다. 아무튼, 중요한건 이런 불편을 pm2 가 어느정도 다 감수해준다는거다. 하물며 개발단계에서 확인하던 console.log 를 그대로 사용할 수 있다는 메리트가 크다. (사실 처음부터 pm2 로 관리하면 여러모로 편합니다)

하지만 아직 아쉬운 것이 있다. 위에서 언급한 것 처럼 -o 옵션은 stdout 을 처리하기 때문에 프로세스에서 발생하는 표준에러( stderr ) 에 대해서는 처리하지 못한다. 이를 처리하기 위한 옵션도 있으니 걱정하지마시라. 우선 API 를 삭제하고 -e 옵션으로 stderr 에 대한 처리도 추가해보자.

$ pm2 delete API && pm2 start server.js --name "API" -o ./api.log -e ./api.log --merge-logs

이제 노드 모듈의 예외발생 같은 크래시 메시지를 api.log 에서 확인할 수 있을 것이다. 자, 이렇게 잘 운영을 하고 있었는데 우리 서비스가 무척 잘 되서 트래픽이 급증하였다. 이런 일이 발생하면 단일 노드(js)에서 모든 것을 처리하기 버거워진다. 트래픽을 받아서 async 하게 처리한다고 해도 내부로직이 복잡해지면 어느순간 치솟는 CPU 를 어찌하지 못하는 상황이 온다. 이때 필요한 것이 클러스터이다. PM2 의 클러스터는 분산컴퓨팅의 그 용어와 비슷한 역할을 하는데 server.js 한 개 노드파일을 지정한 개수만큼 PM2 에 등록하고 Load Balancing (LB) 를 통해 부하를 분산시켜준다. PM2LBround robin (RR) 방식으로 동작하며 모든것은 옵션 하나면 충분하다.

$ pm2 delete API && pm2 start server.js --name "API" -i 3 -o ./test.log

위 예제에서는 -i 옵션을 통해 클러스터를 3 개 등록했다. 실제 패킷을 발생시켜보면 완벽하게 RR 으로 LB 되는 것을 확인할 수 있다. 아래처럼 패킷을 발생시켜보자.

$ curl localhost:6955

로그도 확인해보면 test.log 에 클러스터 번호가 붙어서 각각 test-0.log, test-1.log, test-2.log 로 나뉘어서 기록되게 된다. 저장되는 모든 로그를 아래처럼 확인해보자. RR 으로 LB 됨이 확인된다. (코드에 각 프로세스를 구분할 수 있도록 process.pid 를 추가해줬다)

$ tail -f test-*

==> test-0.log <== pid: 1736 > get request

==> test-1.log <== pid: 1737 > get request

==> test-2.log <== pid: 1750 > get request

==> test-0.log <== pid: 1736 > get request

==> test-1.log <== pid: 1737 > get request

==> test-2.log <== pid: 1750 > get request

운영도중에 클러스터 개수를 조정할 수도 있다. 즉 scale in/out 이 가능하다는 이야기다. scale in/out 은 아래와 같이 설정할 수 있다.

$ pm2 scale API 5

클러스터 3개로 돌고 있던 API 를 클러스터 5로 지정했다. 이에따라 2개의 클러스터가 추가된 것을 확인할 수 있겠다. 혹시 API 를 급하게 중단해야한다? all 을 인자로 넘겨서 아래와 같이 관리되고 있는 전체를 중단하거나 재시작할 수 있다.

$ pm2 stop all

monit 옵션으로 프로세스를 모니터링하는 것도 지원하는데 이것도 참 물건이다.

$ pm2 monit

아래와 같이 PM2 로 관리되는 프로세스별 상태를 볼 수 있고 실시간 로그까지 확인 가능하다. 빠져나올때는 키보드의 q 를 눌러주면 되겠다.


Node.js 로 작성된 프로그램을 서버에서 운영하기 위해 필요한 기초적인 몇 가지를 살펴봤다. 사실 이외에도 살펴보면 엄청나게 많은 옵션이 제공되고 있는데 공식홈페이지를 참고하면 되겠다. 여러가지 옵션을 잘 살펴보고 적절한 상황에서 활용해주면 된다.

댓글
댓글쓰기 폼