Building a Rails server with Puppet
A short write up of my experience learning and configuring Puppet to build and manage servers to serve Rails applications.
On a regular basis at pebble.code where I work we have a requirement to provision servers for clients, mostly for Rails apps. In the past we have used a combination of Cloud PaaS providers like Heroku or Engine Yard, we have built servers manually (gasp) or used bash scripts.
Puppet is a tool for building and managing servers and looked like a great fit for our requirements.
Puppet has a learning curve but there are lots of excellent resources available on the web. I found Pulling Strings With Puppet to be an excellent resource and there is also some good documentation on the Puppet site. I've written before on how to set up Puppet on Ubuntu 10.04 and also how to connect clients to a Puppet master.
Once you learn about resources and modules you are pretty much ready to get going.
Setting up Puppet Dashboard
Puppet comes with a dashboard that can be run on the Puppet Master to give a graphic view into what your nodes are doing, providing reports of updates and letting you know if there are any problems. There's some good documentation offered by PuppetLabs on this to get you started.
I had some problems getting this going getting the error
can't activate rack (~> 1.0.1), already activated rack-1.2.2
This thread on the puppet-users mailing list gives a fix
cd <path-to-dashboard> git clone git://github.com/puppetlabs/puppet-dashboard.git rm -r vendor/gems/rack-1.0.1 sed -i -e 's,~> 1.0.1,~> 1.2.2,' vendor/rails/actionpack/lib/action_controller.rb
After that I was able to see the nodes and updates.
Rails stack choices
Then came the hard part - creating Puppet modules and using third party ones to install software on the server. I ended up writing my own for some but used existing modules where possible.
The ones I found most useful in building a Rails stack were
One issue is where to put the responsibility for some items in the stack. The web server is a good example. In my case I chose to use Unicorn and manage the installation of this via bundler and the Gemfile of the application. I could have chosen to use Puppet for this with something like Passenger. For automation Passenger is difficult to upgrade as it needs to be recompiled against Apache or Nginx. By chosing Unicorn I can upgrade it easily using bundler. To manage Unicorn I use an init script and hook this up to Monit and capistrano recipes. I'm still not sure about this approach but it works at least.
Switching the Puppet Master to Unicorn
As the number of nodes grew, and particularly where a new node was added I found that the default WEBrick server that ships with Puppet was not good enough and I started to hit some errors. Thankfully there is an excellent wiki article on the Puppet Labs site walking through how to switch Puppet to using Unicorn. Unicorn is a great web server and handles load balancing itself - perfect for my requirements.
I chose to deploy using Capistrano and a git based workflow. I have an init script for the Unicorn Master powering Puppet so I can take advantage of the rolling restarts offered by Unicorn.
Still to do...
I still need to create a good framework for testing. Currently I run things against a blank VM. There are a few patterns for testing which I would like to become a bit more familiar with and use. I'd like to extend a few existing modules and write a few new ones, particularly for user management.
The good bit
The good bit is that we can now create a secure, bootstrapped server that can host any rack application with any cloud provider within about 20 minutes. Moreover we can manage SSH keys with Puppet so it is easy to grant and revoke developer access.
There's more to learn but in my opinion the investment of time was well worth it.
After reading this article Matt Tanase contacted me about a great project called Blueprint. If you are porting existing infrastructure to Puppet have a look.
Have an update or suggestion for this article? You can edit it here and send me a pull request.