This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
Doug McIlroy, then head of the Bell Labs CSRC and contributor to Unix pipes, From Wikipedia on 6/16/2014
I’ve been working with Chef for about a year and a half now. Our team has used a number of community cookbooks as well as written our own. There are a few things we’ve learned along the ways, but the biggest of them is to build modular cookbooks. In order to aid modularity and agility, here are a few of my best tips:
Write Wrapper cookbooks, and integration cookbooks
What this means is that it is best if you don’t make one cookbook to rule them all(TM). Instead, write cookbooks that wrap your technologies and adapt them to chef. Use “integration” cookbooks to coordinate the pieces. Basically, each cookbook is a computer program of its own and should do one thing, and do it well. The wrapper should make chef and the specific program work well together. Then, you should use a cookbook to coordinate all the pieces of your infrastructure.
Get your data from variables and lightweight resource providers
This rule helps out a lot when people (or you) are going to use your cookbooks in ways you didn’t expect. It is really tempting to just make a cookbook do a knife search all on it’s own, mid-recipe, but using a variable will help a lot when more complex ways of defining data come into play in a short time period (think “Continuous attention to technical excellence and good design enhances agility.” from the principles agile manifesto). In this specific case, it’s fine to have the default attribute for a cookbook come from a chef search. For instance:
Instead of (from opscode example)
recipes/somerecipe.rb:
search(:node, "keys_ssh:* NOT name:#{node.name}").each do |h|
...
end
Try this one:
attributes/default.rb
default[:myrecipe][:keys]=search(:node,"keys_ssh:* NOT name:#{node.name}")
recipes/somerecipe.rb
node[:myrecipe][:keys].each do |h|
...
end
This one specifically bit our team when trying to integrate the nagios cookbook. The cookbook expects a chef search to define machines which are going to be monitored. This is all well and good, but chef-solo doesn’t have chef search. It’s true that there is a chef-solo search for use with vagrant, but it’s short sighted to think search is the only way that this information could be derived. In our case, we often run only a slice of our machines because of the number necessary. In fact, depending on the specific set of requirements, it would be nice to create development profiles (such as all of our different tomcat apps, ldap, and databases on one or two machines). In order to facilitate this, the best way would be for vagrant to build the profile and set the roles. Since the data only exists inside of vagrant (and we’re not using chef servers with vagrant), it would be nice to just have the chef-json specify the search information.
Similar situations can be imagined using real chef information (such as conjunctive/disjunctive searching).
Always use the lowest Overrides you can when writing recipes.
This one helps make sure that as you integrate more and more cookbooks, you can override the variables in more and more useful ways without as much refactoring. This should be self explanatory. Opscode has a good write up about precedence.
Use an infrastructure cookbook
Write one cookbook for each customer or group of infrastructures you manage. The Unix Analogy is that each is similar to a shell script. These combine the atomic pieces of other cookbooks together in meaningful ways. It is okay to build these up over time to have a hierarchy of cookbooks – such as a common cookbook and individual customer cookbooks for their unique requirements. This topic actually is rich enough for a post all of its own, so stay tuned.
That ought to be a good start for anyone looking at starting chef work. Please add any other best practices you know of in the comments!
You must log in to post a comment.