Rails god config
Apr 07, 2008
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
Previously I led the Terraform product team @ HashiCorp, where we launched Terraform Cloud and set the stage for a successful IPO. Prior to that I was part of the Startup Team @ AWS, and earlier still an early employee @ Heroku. I've also invested in a couple of dozen early stage startups.