Thursday, January 31, 2008

Monitoring Swiftiply & Merb with Monit

Swiftiply combined with Merb is a pretty nice way to serve up your application. And while we hope that both are stable, it would be nice to know that just in case anything went wrong they would restart. It would also be nice for the application to automatically start upon reboot.

That is what we are going to attempt to setup using Monit.

Prerequisites:
We assume you already have Swiftiply and Merb setup and functioning properly. We are also assuming that you have Monit installed and functioning properly.

Versions:

  • Ubuntu 7.10 (Gutsy)
  • Ruby: 1.8.6
  • Merb: 0.5.2
  • Swiftiply: 0.6.1.1
  • Monit 4.8.1
  • Assumes your Merb app is "frozen" (rake merb:freeze)
Those are the version I was using at the time of the article.  Different versions may lead to varying results.

Setup Monit To Auto-Start
> sudo vim /etc/default/monit
This is a pre-existing file.  Edit as follows:
startup=1
Create init script for Swiftiply
The big problem with trying to monitor Swiftiply was that it does not write a pidfile for Monit to track.  Fortunately someone solved the problem, and Google found it.  Big credit for the script: http://pastie.textmate.org/107044.txt
> sudo vim /etc/init.d/swiftiply
This will be a new blank file.  Edit as follows:
#!/bin/bash

function generatepid(){
  current_pid=`pgrep -f 'swiftiply -c /etc/swiftiply.conf'`
  pidfile_pid=`cat /var/run/swiftiply.pid`

  if [[ $current_pid = '' ]]; then
    rm /var/run/swiftiply.pid
  elif [[ $pidfile_pid != $current_pid ]]; then
    echo $current_pid > /var/run/swiftiply.pid
  fi
}


case "$1" in

  start)
    echo -n "Starting swiftiply:"
    swiftiply -c /etc/swiftiply.conf
    generatepid
    echo
    ;;

  stop)
   echo -n "Stopping swiftiply:"
   killall -9 swiftiply
   generatepid
   echo
   ;;

  restart)
    $0 stop
    $0 start
    ;;

  pid)
    generatepid
    ;;

  *)
  echo "usage: $0 [start|stop|restart|pid]"
esac

exit 0
This does assume that you Swiftiply configuration file is location at /etc/swiftiply.conf, if not change to the correct location.

Create init Script For Your Merb App
I will use "app1" but you can replace that with whatever name you would like as an identifier for this specific Merb application.
> sudo vim /etc/init.d/merb_app1
This will be a new blank file.  Edit as follows:
#!/bin/bash

case "$1" in

  start)
    echo -n "Starting Merb for App1:"
    export SWIFT=1
    export HOME="/home/admin" #Assuming you have a user admin
    cd /full/path/to/your/app/current/
    sudo ./script/merb -d -h 127.0.0.1 -p 30000 -e production
    echo
    ;;

  stop)
    echo -n "Stopping Merb for App1:"
    cd /full/path/to/your/app/current/
    sudo ./script/stop_merb
    echo
    ;;

  restart)
    $0 stop
    $0 start
    ;;

  *)
  echo "usage: $0 [start|stop|restart]"
esac

exit 0
The -p directive should point to the same port that you have configured Swiftiply to listen to for this specific app. Also, this is assuming that you have Merb frozen. If not, then you will need to change "./script/merb" and "./script/stop_merb" to their unfrozen counterparts.

Set Permissions
Without these permissions set to execute the init scripts will not work.
> sudo chmod 755 /etc/init.d/swiftiply
> sudo chmod 755 /etc/init.d/merb_app1
Configure Your Main Monit File
> sudo vim /etc/monit/monitrc
Add the following in to the pre-existing configuration:
check process swiftiply with pidfile /var/run/swiftiply.pid
  group root
  start program = "/etc/init.d/swiftiply start"
  stop program = "/etc/init.d/swiftiply stop"
  if 5 restarts within 5 cycles then timeout

include /etc/monit/app1
Configure App1 Monit File
You could have just added the following in the main file, but I prefer to use the include statement and keep them in separate files.
> sudo vim /etc/monit/app1
This will be a new blank file. Edit as follows:
check process merb_app1  
  with pidfile /full/path/to/your/app/current/log/merb.30000.pid  
  start program = "/etc/init.d/merb_app1 start"
  stop program = "/etc/init.d/merb_app1 stop"
  if 5 restarts within 5 cycles then timeout
depends on swiftiply
30000 should be replaced with the specific port that you have Swiftiply listening on for this application. The "depends on swiftiply" will make sure that swiftiply is started first.

Does it work?
It'd take a miracle...
> sudo monit -t
> sudo /etc/init.d/monit force-reload
> sudo monit validate
> sudo monit summary
Caveats
This setup only checks to make sure the processes are up and running.
Right now Merb overwrites log/merb.port#.pid with the last mongrel process started.  

Unfortunately that means if you start 5 mongrels, you will only have the pid of the last one, and thus will only be able to monitor that one process with Monit.

On the up side, it still means you can guarantee at least one process to be up and responding to requests - which is better than none.

Update
Merb 0.9 is supposed to add the ability to specify a pidfile which will allow us to monitor multiple Merb processes even on the same port.

Useful?

2 comments:

  1. Great article! However, I think the way you monitor Swiftiply is wrong. The problem is that if the Swiftiply process dies, the pid file will remain, which means that Monit won't be able to restart it.

    The solution would be to add these 2 lines to your "check process monit" section:

    if failed host 127.0.0.1 port 80 then restart
    if failed host 127.0.0.1 port 30000 then restart

    I haven't tested but it should work. Thoughts?

    ReplyDelete
  2. Thanks for leaving a comment.

    Yeah, I agree with your thoughts. Haven't tried the port testing, but that might work better.

    If you get a chance to test, please post your results back.

    ReplyDelete