Glenn Gillen

Rails god config

If you're anything like me, you're not a big one for shelling out needless money to host the various development sites your working on. That inevitably means you'll end up with a whole host of small applications running on the one server, each needing a mongrel or two, and of course you want to make sure they are up and running. Thanks to the wonder that is ruby (and god) we can easily set it up to automatically become aware of any new apps we deploy via capistrano.

Enter app.god

Don't stress if you've never heard of app.god, it's a convention we've taken on where I'm currently working so chances are it doesn't exist outside my own little world. Here's what it looks like

class Rubypond
  attr_reader :ports, :server_names

  def initialize
    @ports = [5000, 5001]
    @server_names = "www.rubypond.com rubypond.com" 
  end
end

@apps 

What we do is define a new class named after whatever we want to call our application, and then create a new instance off it in an array names @apps. Next step, making god aware of these config files.

Setting up god.conf

If we are going to be running multiple rails apps on the one server, there is going to be a lot of duplication with the config files. Life will be much easy if we extract the common bits into some methods that we can call at the appropriate time to create our god watch:

def configure_mongrel(w, rails_root,  app_name, port)
  w.uid = "mongrel" 
  w.gid = "webserver" 
  w.name = "#{app_name}-#{port}" 
  w.group = "#{app_name}" 
  w.interval = 30.seconds
  w.start = "mongrel_rails start -c #{rails_root} -p #{port} -P #{rails_root}/log/mongrel.#{port}.pid -e production -d" 
  w.stop = "mongrel_rails stop -P #{rails_root}/log/mongrel.#{port}.pid" 
  w.restart = "mongrel_rails restart -P #{rails_root}/log/mongrel.#{port}.pid" 
  w.pid_file = "#{rails_root}/log/mongrel.#{port}.pid" 

  w.behavior(:clean_pid_file)
end

def start_if_not_running(w)
  w.start_if do |start|
    start.condition(:process_running) do |c|
      c.interval = 5.seconds
      c.running = false
    end
  end
end

def restart_if_resource_hog(w)
  w.restart_if do |restart|
    restart.condition(:memory_usage) do |c|
      c.notify = "glenn" 
      c.above = 150.megabytes
      c.times = [3,5]
    end

    restart.condition(:cpu_usage) do |c|
      c.notify = "glenn" 
      c.above = 50.percent
      c.times = 5
    end
  end
end

def monitor_lifecycle(w)
  w.lifecycle do |on|
    on.condition(:flapping) do |c|
      c.notify = "glenn" 
      c.to_state = [:start, :restart]
      c.times = 5
      c.within = 5.minutes
      c.transition = :unmonitored
      c.retry_in = 10.minutes
      c.retry_times = 5
      c.retry_within = 2.hours
    end
  end
end

def monitor_rails_app(w)
  start_if_not_running(w)
  restart_if_resource_hog(w)
  monitor_lifecycle(w)
end

Letting god know about our rails apps

Next thing we want god to know about all our rails apps, we can use a standard ruby trick to traverse the appropriate directory tree for find our app.god configs, and then load them in using the ruby load command:

@apps = []
Dir["/var/www/apps/*/current/config/*.god"].each { |config| load config}

And because of the way we've instantiated our classes in each config, @apps will now be an array of applications we want to configure. So we can iterate over each, and plug in the appropriate values:

@apps.each do |app|
  app_name = app.class.name.downcase
  rails_root = "/var/www/apps/#{app_name}/current" 

  app.ports.each do |port|
    God.watch do |w|
      configure_mongrel(w, rails_root, app_name, port)
      monitor_rails_app(w)
    end
  end
end

Now all you need to do when you deploy a new application is make sure you have the config/app.god file and restart your god instance, and it will automatically monitor your new application. Sweet! Next up I'll run through a capistrano task to complete the deal by using the same config to setup nginx. So, we may as well seal the deal by ensuring that happens in capistrano (in your deploy.rb file):

after "deploy:setup", "deploy:god:restart" 

namespace :deploy do

  namespace :god do

    task :restart, :roles=>:app do
      sudo "/etc/init.d/god restart" 
    end

  end
end

Glenn Gillen

I'm an advisor to, and investor in, early-stage tech startups. Beyond that I'm an incredibly fortunate husband and father. Working on a developer-facing tool or service? Thinking about starting one? Email me and let me know or come to one of our days to help make it a reality.