Ruby Background Tasks with Starling - Part 3

In my previous post, I went over the changes I made to Workling to add threading and allow it to run for long periods. Now, we need to deploy and monitor everything. In the Linux word, there are several options, but monit seems to be the most popular. However, I wanted to give god.rb a shot. God.rb is basically a clone of monit written in Ruby. What it’s written in doesn’t really matter to me, but setting up my config files in Ruby was interesting. That’s one more set of script commands I don’t have know.

Installing god.rb is as simple as:

sudo gem install god

The god.rb web site has some decent documentation to help you understand how to build a config file. I tend to build my config files for as much as I can using erb templates rather than manually updating the config files for each environment.  I build my apache config file this way as well.

1. Download my god config to /lib/templates/god.conf. This is an example of an actual god.conf file. You will want to update it for paths and to setup the mongrels and the listeners to not run as root — not a good idea to run those as root. My apache and mysql scripts already run as a different user.

2. Now, add the following to your config/deploy.rb file:


namespace :god do

  desc <<-DESC
  Generate the GOD configuration. We will create the appropriate GOD
  configuration stanza for the application and copy it to:

  /shared/path/god.conf

  Once it's there, somebody with the required permission to manage the
  GOD configuration should somehow incorporate that file and restart
  the GOD server. Each time the runtime configuration (ie number of
  mongrels running) changes, the configuration will have to be manually
  updated to match.
  DESC
  task :config, :roles => :app do
    rails_env = fetch(:rails_env, 'production')
    dispatcher_starting_port = fetch(:dispatcher_starting_port, 8000)
    dispatcher_instances = fetch(:dispatcher_instances, 3)
    dispatcher_ending_port = dispatcher_starting_port + dispatcher_instances - 1
    dispatcher_ports = dispatcher_starting_port..dispatcher_ending_port
    god_conf_path = "#{shared_path}/god.conf"
    god_conf_template = File.join(File.dirname(__FILE__), '..', 'lib', 'templates', 'god.conf.erb')
    begin
      god_conf = ERB.new(File.read(god_conf_template)).result(binding)
      put god_conf, god_conf_path, :mode => 0644
    rescue Exception => e
      abort "An error occurred in the GOD config generation: #{e}"
    end
  end
end

3. Execute cap god:config with the desired environment, and a new god.conf file will be copied to the shared directory on your server.  I’m assuming you have capistrano 2.x here.

4. On your server, execute: sudo god -c /shared/path/god.conf

That’s it! Now, all your processes will be monitored by god.rb and restarted whenever there is a problem. Hold on though, we’re not completely done. You want god.rb to start up when your server boots, right? We also have to update our deploy process to use god.rb to start/stop our services now. Try to use you existing spin or spawn tasks, and god.rb will fight with you.

First, let’s get god.rb setup as a service. I found this script in the god.rb sources, and I tweaked it a bit for my system.


#!/bin/bash
# god       startup script for god (like monit, only Ruby)
# Author: Dave Dupre

# Comments to support chkconfig on CentOS
# chkconfig: - 85 15
# description: god - monitor all my processes

CONF_DIR=/shared/path
LOG_DIR=/var/log
PID_DIR=/var/run/god
BIN_DIR=/usr/local/bin

RETVAL=0

# Go no further if config directory is missing.
[ -d "$CONF_DIR" ] || exit 0

case "$1" in
  start)
    $BIN_DIR/god -P $PID_DIR/god.pid -l $LOG_DIR/god.log -c $CONF_DIR/god.conf
    RETVAL=$?
  ;;
  stop)
    $BIN_DIR/god terminate
    RETVAL=$?
  ;;
  restart)
    $BIN_DIR/god terminate
    $BIN_DIR/god -P $PID_DIR/god.pid -l $LOG_DIR/god.log -c $CONF_DIR/god.conf
    RETVAL=$?
  ;;
  status)
    $BIN_DIR/god status
    RETVAL=$?
  ;;
  *)
  echo "Usage: god {start|stop|restartstatus}"
  exit 1
  ;;
esac

exit $RETVAL

Put the above contents into /etc/init.d/god and make it executable (sudo chmod +x /etc/init.d/god). Lastly, tell your system there is a new service in town. I use CentOS 5, so I run:

sudo chkconfig /etc/init.d/god on

Now, god.rb will start up whenever your server boots, and you can start/stop/restart it using standard service calls.

The last thing we need to do is update our deployment process to use god.rb to stop/start our processes. Add this to your deploy.rb file:


namespace :deploy do
  [ :stop, :start, :restart ].each do |t|
    desc "#{t.to_s.capitalize} mongrels using god"
    task t, :roles => :app do
      sudo "god #{t.to_s} listeners"
      sudo "god #{t.to_s} mongrels"
    end
  end
end

namespace :starling do
  [ :stop, :start, :restart ].each do |t|
    desc "#{t.to_s.capitalize} starling using god"
    task t, :roles => :app do
      sudo "god #{t.to_s} starlings"
    end
  end
end

namespace :workling do
  [ :stop, :start, :restart ].each do |t|
    desc "#{t.to_s.capitalize} workling using god"
    task t, :roles => :app do
      sudo "god #{t.to_s} listeners"
    end
  end
end

OK! Now, we’re done. You deploy as you normally would, and I have full control over Starling and the Workling listener. Notice that there is no mongrel cluster either. God.rb started up all the instances of mongrel I needed, and it will monitor everything so there is no need for mongrel cluster anymore.

That’s it for now. You have a system that will process all your background tasks and stay running. The only thing I didn’t setup here is notifications from god.rb when there is a problem. The god.rb config settings have lots of schemes for email notifications. Take a peak at the docs and make sure god can talk to you.

11 Responses to “Ruby Background Tasks with Starling - Part 3”

  1. sebastian Says:

    Hi Dave,

    great series! I am searching for a solution for my background-processes at the moment and also tried backgroundrb. But your lightweight solution seems to be a big win regarding memory. The only thing that’s missing is the possibility to get the status of a background job back to the user.

    Do you have any idear how to accomplish this?

  2. Dave Says:

    I haven’t had a need to do it yet, so I can’t comment other than to look at the code. Check out StarlingReturnStore in the Workling code. It looks like a worker sets a value into a starling queue, and the app does a get on the queue to retrieve it. It sure seems like a simple enough system. Just make sure you empty the queue from your app so you get the latest progress.

    If you have memcached already integrated into your app, then that is another way to share data between a worker and your application. Simple generate a key that both the worker and the app know, and then have the worker set values that the app can retrieve. This is very similar to the StarlingReturnStore except the value is stored in memory instead of a queue, and it will always be the latest.

  3. sebastian Says:

    Okay, I’ll give it a try. Thanks!

  4. Harm Says:

    Just wanted to say what an excellent series this was! I’m glad you took us through the entire process including God.

  5. Dave Says:

    My pleasure. I hope to keep more like it coming.

  6. Julien Says:

    Hi Dave,

    You mention that we should change the user… instead of root. Can you detail this?

    Thanks a lot for your great help!

  7. Dave Says:

    If you run your application as root, then it has full access to your server. Generally, you should have any process available to outside users running as root. It’s also good protection for you as well. For instance, I usually setup grants for mysql such that my application can do drop or add tables, truncate tables, and sometimes even do a delete. This way if I happen to miss a escape someplace, a bad user can’t trash my database. With the process not running as root, you ensure that the user can’t get to areas on your server they don’t belong.

  8. Julien Says:

    Thanks Dave for this, but my question was more: “how do you have god launching mongrel and the listener with a different user?” sudo -u app … ?

  9. Dave Says:

    Sorry, I misunderstood. God.rb allows you to specify the user and group to execute a watch with. For example, you could add the following to your watch:

    w.uid = ‘tom’
    w.gid = ‘devs’

    Unfortunately, I had some issues with getting this to work in all cases with mongrel and workling. I really need to figure out why, but for now I use:

    w.start = “su - tom -c \”[insert your start command here\”"

    Please note that I’m starting to wonder about god.rb. It’s leaking memory (10-20Mb per week), so I end up restaring it periodically. I’m tempted to switch to monit because I know that guy will run forever. Apparently, there is a bug in ruby 1.8 that is causing the leak. See http://groups.google.com/group/god-rb/browse_thread/thread/1cca2b7c4a581c2 for more info.

  10. Julien Says:

    I had the same memory problem with god… and I plan to stop using it to switch to monit…
    Have you switched yet? If so, could you please help us with a few hints on the config files to monitor both workling and starling with Monit?

  11. Dave Says:

    I was going to switch because god was leaking memory, but when I upgraded god to 0.7.6, my memory leak went away. As a result, I stopped investigating making a switch to monit.

Leave a Reply