I first started working with Opscode Chef in early 2011 while on a consulting project (native environments' support was still a beta feature back then). Since late 2012, I've been working extensively with Chef once again for Bitium.

I've been looking to level up my Chef skills, specifically on the latest best practices. Opscode seems to have an aversion to promulgating current standards and leaving it up to the community to come up with whatever works best for them.

These blog posts and presentations shed light on some good current practices:


Here's a summary of some of the patterns, practices & terminology that I have come across that weren't prevalent back when I first started working with Chef.

Single Repo Per Cookbook

A single-chef-repo with all the cookbooks used to be the norm. There are still benefits to this all in one repo approach - everything is centralized in a single view and it's easy to search across the cookbooks.

The single-repo-per-cookbook approach on the other hand makes it easier to keep up-to-date with upstream community cookbooks, to open source cookbooks and to a certain extent it just makes more sense for cookbooks to be versioned and managed independently.

A common convention seems to be to have a separate GitHub Org account to host these individual cookbooks, e.g. github.com/opscode-cookbooks. I'm more in favour of just sticking with a primary Github Org account and naming the repos in the form <name>-cookbook.

Data bags, roles, environments and such should remain in a private chef-repo.

Cookbook Bundlers

Berkshelf and Librarian-Chef are cookbook bundlers (in the spirit of Bundler) but that are philosophically very different from each other in how they work.

I think the rule of thumb when deciding on which to adopt depends on how you prefer to view/manage your cookbook infrastructure:

If you're using the single-repo-per-cookbook approach then Berkshelf is likely a great fit. Make sure to checkout the nifty built-in Vagrant integration that makes the local build test/inspect cycle really easy.

If you're using the single-chef-repo approach then Librarian-Chef could improve your workflow, just move your existing privately maintained cookbooks into a site-cookbooks directory within your chef-repo. Berkshelf could still work with this approach but it's definitely not the recommended way.

Cookbook Types

There are a bunch of terms being bandied around to describe the type/flavor of a cookbook. Since there's no official description for these terms, I thought I'd take a stab at trying to restate some of the descriptions I've come across:

Application Cookbook - just what you would imagine it to be, something like a mycompany-app cookbook that installs and configures a complete application (e.g. a web app). It could depend on all other types of cookbooks. Postgresql & Nginx should be considered Application cookbooks too! These cookbooks should have their versions locked at the Environment level.

Library Cookbook - contains Definitions, LWRPs, Libraries that are used by other cookbooks. They may or may not include recipes. The database cookbook is a good example of a Library cookbook. These cookbooks shouldn't be directly assigned to nodes. Cookbooks that depend on Library cookbooks should lock their required Library versions in their metadata.rb.

Wrapper Cookbook - a more specific type of Application cookbook that depends directly on a single other Application cookbook and potentially Library cookbooks. For example, a hypothetical bitium-phantomjs cookbook could wrap around the phantomjs community cookbook and contain attribute overrides and recipes that orchestrates phantomjs to our company's specific needs.

Roles Cookbook

I've been using roles extensively to maintain run lists. Roles aren't versioned unlike cookbooks so there's always the chance a bad change could end up in a production environment by accident.

I'm planning on switching to using a roles cookbook that contains recipes like base.rb & web_server.rb that use include_recipe to define a run list. I'll still use the native roles but they would just have a call to the corresponding role recipe within its run list. Here's an example:

# roles-cookbook/metadata.rb
name    'roles'
version '0.1.0'

recipe 'base', 'Base Server Role'
recipe 'web_server', 'Web Server Role'

depends 'hostname'
depends 'nginx'
depends 'ntp'
# roles-cookbook/recipes/base.rb
include_recipe 'hostname::default'
include_recipe 'ntp::default'
# roles-cookbook/recipes/web_server.rb
include_recipe 'roles::base'
include_recipe 'nginx::default'
# roles/base.rb
name 'base'
description 'Base role for all servers'

run_list(
  'recipe[roles::base]'
)
# roles/web-server.rb
name 'web-server'
description 'Web server role'

run_list(
  'recipe[roles::web_server]'
)

Remember to bump up the version of the roles cookbook when making changes and freeze it when uploading to the Chef Server.

The roles cookbook can now be versioned in an environment file:

# environments/production.rb
name 'production'
description 'Production Environment'

cookbook_versions({
  'roles' => '= 0.1.0'
  # ... and other cookbooks
})

You could probably do away completely with the native roles and just use the roles' recipes directly in a node's run list. I'm going to keep them for now but using them only as described in the examples above.

Private Recipes

This is just a simple convention to use an underscore to prefix a recipe name to mark it as private, e.g. recipes/_common_setup.rb. Some guidelines for when to make a recipe private:

  • meant for logical code separation

  • included only in recipes from the same cookbook

  • not to be included directly in the run list of a node

Cookbook Testing

The testing landscape for Chef has definitely gone through a revolution. There's now a good selection of tools for every category of tests.

Syntax Checking

Linting

Unit Tests

  • ChefSpec. Built on top of RSpec. It performs assertions without actually converging a node so it should be fast. See also Fauxhai for mocking out Ohai attributes in specs.

Integration Tests

  • Minitest Chef Handler. Built on top of minitest and works with Vagrant. It does its assertions post convergence, i.e after a Vagrant up/provision run.

  • Test Kitchen. Cross platform testing with Vagrant. The 1.0 version (a complete rewrite) is still alpha level and lacking documentation. It uses Vagrant to run Minitest Chef Handler across various platforms, similar to how Travis CI runs tests across different language runtimes. See this talk announcing the 1.0 version.

  • Vagrant. Plain old manual testing using the Chef Solo Provisioner.

You can use Strainer to manage your various tests for a cookbook, similar to how Foreman manages application processes.

See here & here for more in-depth coverage on testing.

Cookbook Build System / Pipeline

This just means having an automated system for building (testing, versioning & deploying) your cookbooks.

You could use a CI system like Jenkins with a flow similar to the following:

  • Make changes to a cookbook locally.

  • Push changes to a remote repo.

  • A Jenkins Job runs the cookbook's tests (possibly using Strainer).

  • If all the tests pass, use Knife Spork to bump up the cookbook version, upload & freeze the cookbook on the Chef Server and potentially promote the cookbook into another environment by updating the environment's cookbook version constraint.

  • Potentially invoke a chef-client run on the relevant nodes in a specific environment.

Special mention here for Ridley, a well written Ruby based Chef API client. It's a useful tool for building out a custom workflow or supporting tools outside of knife. I've been using it in Capistrano recipes to help with application deployments and will likely use it in the build system.

Update (June 19): Seth Vargo's Test Driven Infrastructure with Chef presentation is a good how-to for getting started with a simple build system.