Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Upstart와 Monit으로 node.js Application 서비스 하기

신기술들은 대개 휘양찬란한 특징들로 소개가 되고는 하고 매력적인 부분이 상당하지만 실제 적용을 하다보면 어려움을 겪는 경우가 꽤 많이 있습니다.(가장 큰 부분은 잘 몰라서 이기도 하겠지만요.) 이런 부분 중에 가장 큰 부분들이 리소스와 인프라에 대한 것들이 아닐까 생각합니다. 언어나 플랫폼의 장점을 명확하지만 실무에 적용하기에는 (신기술이기 때문에) 얻을 수 있는 관련 리소스들(대표적으로는 사용법이나 트러블슈팅같은 자료들...)이 상당히 부족하고 개발툴이나 디버거등의 개발환경이나 운영을 하기위한 보조적인 인프라가 많이 부족하기 때문에 도입에 가장 큰 장벽중 하나가 아닌가 생각합니다.

node.js는 그 바탕이 Javascript이기 때문에 그나마 괜찮다고 하더라도 이런 부분에서 예외는 아니라고 생각합니다. 얼마전에 Socket.IO로 간단한 예제를 만들고 호스팅받고 있는 서버에 올려야겠다고 생각하고 로컬에서 수없이 돌려봤기 때문에 금방 서버에 적용할꺼라고 생각했지만 다 개발된 것을 서버에 서비스로 적용하는 것만도 많은 시간을 소비하게 되었습니다. 이 내용은 Kevin van Zonneveld가 작성한 Run Node.js as a Service on Ubuntu Karmic라는 글과 이 포스트를 보고 Tim Smart가 How To NodeDeploying Node.js With Upstart and Monit를 보고 우분투 서버에 적용하면서 정리한 내용입니다.

처음에 node.js로 만든 것을 서버에 올려야겠다고 생각했을 때는 로컬에서 개발할 때처럼 node myapp.js로 실행하면 SSH등으로 터미널 원격접속한 상태에서는 터미널을 종료하는 순간 프로세스도 같이 죽어버리기 때문에 node myapp.js & 으로 백그라운드로 실행하였습니다. 이것은 잘 돌아가는 것처럼 보였지만 별로 적절한 방법은 아니었고 실제로 제가 올렸던 것은 딱 한번만 접속하면 무조건 프로세스가 죽어버리는 문제가 있었습니다.

제가 실제 서비스에 적용을 해보면서 느낀 문제점은 아래와 같습니다.

  • node.js개발을 하면 console.log()나 sys.puts()으로 콘솔에 로그 메시지를 출력하는데 SSH등으로 서버에 원격접속하였을때 node.js app를 실행하더라도 터미널을 종료하면 STDOUT이 없기 때문에 콘솔에 로그메시지를 출력하는 부분이 있으면 프로세스가 죽어버립니다.
  • 이러한 로깅은 소스에서 지워버려도 되겠지만 기본적으로 로그를 남기기 위해서 작성하는 것이므로 이런한 로그를 나중에 참조할 수 있도록 파일로 출력할 필요가 있었습니다.
  • 위 문제가 아니더라도 예상치 못한 문제로 프로세스가 죽어버렸을 때 다시 복구해 주어야 합니다.(저같은 경우 개인서비스이므로 약간의 장애는 큰 문제 안되긴 하지만 그렇다고 마냥 죽어있을수는 없는 노릇이니까요.)

위 문제들을 해결하기 위해서는 node로 돌린 프로세스가 진짜 데몬( Demon)처럼 동작하게 해야 하고 이벤트기반의 init데몬을 위한 툴인 upstart와 프로세스와 파일등을 모니터링하고 관리하는 Monit으로 해결합니다.





Upstart
node앱을 Demon처럼 실행하기 위해서 Kevin은 Upstart를 제시했습니다. Upstart는 /etc/init.d 스크립트를 대체하고 속도, 간결함 등의 추가적인 특징을 가지고 있습니다. 최신 우분투 배포판에는 Upstart가 포함되어 있으며 설치되어 있지 않다면 sudo apt-get install upstart 로 설치할 수 있습니다.

upstart 스크립트는 아래와 같이 작성합니다.

description "socket.io-slide server"
author      "Outsider"

start on startup
stop on shutdown

script
    chdir /path/to/socketio-slide/socket.io
    exec sudo -u USERNAME sh -c "/usr/local/bin/node socketio-slide.js >> /var/log/node/socketio-slide.log 2>&1"
end script

9번 라인의 USERNAME부분은 명령어를 실행할 유저명을 적어주면 됩니다. 일반적으론 PATHS에 node.js가 잡혀있어서 그냥 node 명령어를 사용할 수 있지만 스크립트 내에서는 위처럼 절대경로를 사용해야했습니다.  이 파일을 /etc/init/myapp.conf로 저장을 하고 chomd u+x를 주어 실행가능하게 되도록 합니다. 이렇게 하면 node 앱을 데몬처럼 실행할 수 있고 node 앱의 시작과 중지를 위한 간단한 명령어를 통해서 사용할 수 있게 됩니다.

start myapp
stop myapp

을 이용해서 node 앱을 실행하고 중지할 수 있으며 부팅시에 앱을 실행하고 로그는 /var/log/node/socketio-slide.log 파일에 남게 됩니다.





Monit
아직 문제점이 남아 있는데 Upstart를 이용해서 Demon으로 실행했다고 하더라도 node 앱은 죽어버릴 가능성이 있기 때문에 이에 대한 대처가 필요한데 일정한 인터벌을 두고 테스트를 수행하고 그 결과에 따라 액션을 취하는 모니터링 툴인 Monit으로 이 문제를 해결할 수 있습니다. 우분투에서는 sudo apt-get install monit 를 통해서 설치할 수 있습니다.

set logfile /var/log/monit.log

check host socketio-slide with address 127.0.0.1
    start program = "/sbin/start myapp"
    stop program = "/sbin/stop myapp"
    if failed port 8001 protocol HTTP
        request /
        with timeout 10 seconds
        then restart

위 파일이 Monit의 설정파일이고 이것을 /etc/monit/monitrc의 하단에 추가하면 됩니다.
1번 라인은 Monit의 로그파일을 지정한 부분이며 3번라인이 노드 인스턴스의 이름(로그에 이 이름이 사용됩니다.)과 위치를 지정해 주고 테스트 요청을 날릴 ip지정합니다.(여기서는 같은 서버에서 사용되므로 127.0.0.1을 사용합니다.) 4,5번 라인에서 node앱을 어떻게 시작하고 중지하는지 적어주는데 위에서 만든 upstart로 만든 명령어를 사용하며 이때는 절대주소로 적어주어야 합니다. 6번라인이 핵심적인 부분인 테스트부분인데 HTTP 8001포트로 루트(/)에 요청을 보내서 10초동안 결과가 오지 않는다면 node 앱을 재시작하도록 지정하였습니다.

이제 아래 명령어를 통해서 node앱을 실행하고 모니터링을 위해서 monit을 시작하면 됩니다.

sudo start myapp
sudo monit -d 60 -c /etc/monit/monitrc

 -d 60 플래그는 monit이 60초마다 테스트를 수행하도록 하는 것이고 이렇게 실행해 놓으면 노드앱이 죽어도 자동으로 60초 이내에 재시작되게 됩니다.

여기서 myapp이 죽게 되면 재시작이 되며 monit의 로그에는 아래와 같이 로깅이 됩니다.

[KST Oct 29 22:59:40] error    : 'socketio-slide' failed, cannot open a connection to INET[127.0.0.1:8001] via TCP
[KST Oct 29 22:59:40] info     : 'socketio-slide' trying to restart
[KST Oct 29 22:59:40] info     : 'socketio-slide' stop: /sbin/stop
[KST Oct 29 22:59:40] info     : 'socketio-slide' start: /sbin/start
[KST Oct 29 23:00:40] info     : 'socketio-slide' connection succeeded to INET[127.0.0.1:8001] via TCP

2010/11/04 01:08 2010/11/04 01:08