Embed just a cookbook in your Ruby project, not a whole kitchen
Published 2014-01-13
TL;DR
To embed a Chef cookbook in your Ruby web project, run berks cookbook cookbook
from your project's top-level directory. Setup Vagrant so everyone can easily deploy. But for production, point to the cookbook from a Berkfile
in a kitchen outside your project, with the git
or path
option.
If you're a Ruby web developer and you handle deployment yourself (instead of having a sysadmin or Heroku do it for you), you can imagine the benefit of automating deployment. This post assumes that you've decided on Chef because of its popularity with the Ruby community, and now you're planning to write a Chef cookbook and distribute it with your Ruby project, so that future maintainers can easily deploy with your cookbook.
Unfortunately, the Chef documentation assumes you have a single "chef-repo" in version control, which contains cookbooks for all your projects, as well as info you wouldn't want to make public, such as passwords. So it's not obvious how your chef-repo and your project's repo should relate to each other. Given the enormous flexibility of Chef and Ruby and community tool contributions, there must be a way to supply a cookbook along with your project -- but how?
(For the rest of this post, I'll assume you're using chef-solo with a kitchen directory created by knife solo init
, instead of chef-server with a chef-repo, but the two situations are similar.)
Solution #1 (bad): embed your project inside your cookbook
Recommended by Mischa Taylor's post, but it's probably intended as a toy example.
Approach
Start with an empty Berks cookbook: berks cookbook myface
.
Later, add your source code to templates
(e.g. templates/default/index.php.erb
for a PHP app) and refer to it with template
from your recipe.
Advantage
It's straightforward to see how your Chef recipe finds your project's source code files; they're right under templates
or files
.
Disadvantage
Requires turning your project into a Chef cookbook, burying its source files under templates
and files
. A developer may not be able to run your app without deploying it with Chef.
Solution #2 (okay): store your project's cookbook in a totally separate repo
Recommended by Jamie Winsor's post (we don't see where the dropbox.com artifacts come from, so I assume it's from another repo)
Approach Create a separate Git repository for your Chef cookbook. For example, if your project were named ort
you'd have a separate repo named ort-cookbook
. You and every developer will then use librarian-chef or Berksfile to refer to that cookbook from their kitchen.
Advantage This option is the easiest to understand, and corresponds best to the Berkshelf and librarian-chef docs.
Disadvantage Requires two Git repos. An outside developer could be completely unaware of the second Git repo or neglect to keep the two in sync.
Solution #3 (better): embed an entire kitchen inside your project
Recommended by Frank Hoffsümmer's blog post and Dan Ivovich's slides
Approach
Run kitchen solo init
to create a "my_kitchen" directory inside your project.
Now "my_kitchen" will contain a cookbooks
and a site_cookbooks
directory.
According to the Knife solo docs, your custom cookbook(s) should go in site_cookbooks
since the cookbooks
dir will be managed by Berkshelf or librarian-chef.
Advantage
Developers unfamiliar with Chef are given a working example, just by running vagrant up
.
Disadvantages
- Developers may confuse the
cookbooks
and thesite_cookbooks
directory. - Non-custom cookbooks will be mixed together with your custom cookbook in the
cookbooks
directory. - Developers may be tempted to deploy to production from this directory, so they'll either commit deployment info (IP addresses, node info, users, passwords, etc.) to Git, or keep them out of Git altogether by using
.gitignore
. - Commits to this info might trigger unwanted continuous integration runs.
- It's easy to overlook the hidden
.chef
directory.
Solution #4 (best): embed cookbook inside your project; point to it from kitchen outside project
Recommended by me :-)
Approach
- Install Berkshelf and relevant Vagrant plugins:
gem install berkshelf --no-ri --no-rdoc vagrant plugin install vagrant-berkshelf vagrant plugin install vagrant-omnibus
- Create a cookbook directory named
cookbook
containing aVagrantfile
andBerksfile
:berks cookbook cookbook cd cookbook bundle install
- Fill out
metadata.rb
with information for your cookbook. Cookbook dependencies will go here too, for example:depends 'apt', '~> 2.3.4'
- Tweak the
Vagrantfile
as needed, for example:- Set the
config.vm.hostname
- Change the
config.vm.box
to"precise32"
andconfig.vm.box_url
to"http://files.vagrantup.com/precise32.box"
or whatever your prefer, to avoid being dependent on the Berkshelf box. - Add
config.omnibus.chef_version = :latest
since theprecise32
box won't have chef auto-installed. - Delete
config.ssh.max_tries
andconfig.ssh.timeout
, which are deprecated settings. - Add
config.vm.boot_timeout = 120
- Add port forwarding if you prefer
- Change the recipe in the
runlist
fromcookbook
to whatever you named the recipe inmetadata.rb
- Set the
- Start the VM with
vagrant up
. It will automatically provision itself usingchef-solo
using the recipe atrecipes/default.rb
- Test your app at http://33.33.33.10/ or http://localhost:8080/ or whatever you have set up.
- Iterate to improve your cookbook by editing
recipes/default.rb
and re-runningvagrant provision
. Repeat as needed. - When you're ready to deploy to staging or production,
cd
to your kitchen in another directory outside this project, and in your kitchen'sBerksfile
, refer to your project's cookbook by adding one of the following lines:cookbook 'myproject', git: 'https://github.com/myname/myproject.git', rel: 'cookbook'
cookbook 'myproject', path: '/Users/myname/myproject/cookbook'
Advantages
- Developers unfamiliar with Chef are given a working example, just by running
vagrant up
. - Non-custom cookbooks are hidden at
~/.berkshelf/cookbooks
, where developers are unlikely to edit them. - The custom cookbook's directories (e.g.
recipes
,files
, etc.) are directly visible undercookbook
. - The cookbook is stored in the same repo as the project, so the two can stay synchronized.
Disadvantage
Well, it is a little confusing to have two Berksfile
s (one in your project, one in your kitchen) and three Gemfile
s (project dir, cookbook dir, kitchen).
Agree? Disagree? I'm new to Chef and would love to hear your insight in the comments below.