Leveling Up on Chef Best Practices
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:
-
Beginning Chef Antipatterns (video & slides)
-
The Berkshelf Way (video & slides)
-
Chef: Patterns and Anti-Patterns for Cookbooks, Environments, Roles
-
Cookbook Refactoring & Abstracting Logic to RubyGems (video & slides)
-
Getting More Chefs in the Kitchen – Developing Infrastructure as a Team (video & slides)
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:
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:
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
- Built-in knife cookbook test.
Linting
- Foodcritic. See also the additional rules by Etsy and CustomInk.
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 usingStrainer
). -
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.