There’s certainly no wrong way to run chef, however, a reasonable set of defaults are usually helpful as a starting place. I’m building an example of a simple deploy with chef, with the goals of ending up with something that’s easy to understand, copy and modify. Eventually, I hope this allows for discussion of and iteration on what a solid ruby app stack should include.
The first stumbling block with chef for most people is not usually building am ami. It’s usually getting chef and their recipes runningon a box. At Pivotal we’re firm believers in doing the simplest thing that could possibly work, and so what follows is the just least I could do to cleanly run chef. Essentially, you want to install the ruby prerequisites, install a known Ruby (let Wayne figure out how), install chef, get some cookbooks on the box and run chef. This boils down to a script written in Bash to get you to the point you can run Ruby, and some capistrano tasks to install gems and run chef.
I’ve written Soloist, a gem I use for invoking chef solo easily. The goal is for it to be a thin layer over chef-solo to make it more friendly to use. I’ll use it here, but all it’s doing is generating a solo.rb and node.json files – it’s not magic.
Here are the files I ended up with:
├── Capfile <- How we tell cap what to do
├── Gemfile <- The three gems we need
├── bootstrap.sh <- The bash script to bootstrap an instance
│ └── cookbooks <- Chef cookbooks go here
│ └── joy_of_cooking
│ └── recipes
│ └── default.rb
│ └── deploy
│ └── staging.rb <- What servers are in your environment
└── soloistrc <- How you'll tell chef what to run
First off we’l start with a bash script. You can see the full thing on github, but the psuedocode is:
Create an app user account
allow password authentication # adding an Authorized key would be fine too
add epel as an RPM repository # for an easy git instal
install the rvm prereqs # readline, zlib, openssl and more
enable passwordless sudo # it's the only way to live
add rvm to bashrc
set rvm to trust all rvmrcs, install rubys on use and install gemsets on use
That’s it. My example is 41 lines of bash (including 11 blank and 6 comment lines).
Capistrano is a reasonable multi server SSH tool. Parts of it are fundamentally flawed, but the “ssh out to some servers and run some commands” works just fine. So, we’ll use it to upload and run the bootstrap script:
task :bootstrap do
set :user, "root"
set :default_shell, "/bin/bash"
upload "bootstrap.sh", "/root/bootstrap.sh"
run "chmod a+x /root/bootstrap.sh"
The boostrap task sets the user to root because everywhere else the user is set to the app user. We set the shell to /bin/bash to override the use of rvm-shell. We then upload the bootstrap script and run it.
The deploy task consists of:
task :deploy do
To see the details of each task, see the Capfile. Be aware the first time you deploy, rvm will compile the requested ruby – this will take a few minutes.
This leaves us at 4 lines in the terminal to spin up as many ec2 instances as amazon will let you and run chef on them:
ec2-run-instances ami-e67e8d8f -k mine2 -t m1.large
ec2-describe-instances # you'll need to add the IP(s) of your
# new instance(s) to the staging.rb file
cap staging bootstrap
cap staging deploy
Currently, the chef run consists of a recipe which touches a file in the root directory. For my next trick, I hope to actually run a web app. Stay tuned.
All the source code is available on github.
- Rubyforge will be down when you’d like to to be up.
- Learning the ec2 shell commands is well worth the time over using the web interface.