Creating Your Complete Menu with Chef Cookbooks
The course is part of this learning path
In this lesson, we will drill down into the Workstation component even further to talk about the chef-repo and Cookbooks.
We will start off by discussing how code in Chef resides in a Cookbook.
Then, we will dig into chef-repo, and what types of cookbooks and directories are housed inside including: roles, data bags, and environments. We will also discuss the proper naming convention for sub-directories.
For the rest of this lesson we will explain the anatomy of a Cookbook. You will learn how to write recipes in Ruby, and that the ingredients are considered resources (package, service, and custom).
We will discuss definitions, attributes (default and automatic among others), files, templates, libraries, and metadata files as Chef uses the terms.
Finally, we will provide a quick wrap-up quiz to see how much you were able to retain about Cookbook components.
Welcome back! So far in the course I’ve kept things pretty high level. And while I’ve mentioned coding for Chef several times, I’ve been intentionally vague. And that’s because once you start diving into the coding, there’s a lot learn. And I didn’t to distract from setting up the foundational knowledge.
However, it’s now time to drill down into the Workstation even further to talk about the chef-repo and Cookbooks.
Since Chef is a tool that you can use to automate the creation, configuration and management of your infrastructure, in code, that means you’ll be writing code at some point. And that code is going to reside in what Chef calls a “Cookbook.” For now, I want you to consider a cookbook in the context of Chef to be a package for your automation code - and I’ll expand on this in just a bit.
So a Cookbook is a package for your automation code, and a chef-repo is just a directory on your workstation where cookbooks reside. The chef-repo also has directories for a couple other components besides cookbooks, such as roles, data bags, and environments, which I might of mentioned previously, however I won’t be going into detail on those right now. The only thing required to create a chef-repo is that the names of the sub-directories match what Chef expects. That means you could create the chef-repo manually. However, the easy way is to use the chef command with the “generate repo” sub command. I’ll demonstrate using the chef command to generate the repo later in the course.
Since the chef-repo is just a directory that contains all of your code, it should be treated like any other code, and that means you should be keeping it under source control. Being able to treat infrastructure as code means you get to have it versioned, and that makes things such as collaboration and rollbacks much easier.
Alright, for now, that’s about all there is to say about the chef-repo. It’s just a directory structure where your code will reside. And having shown you the directory structure for the chef-repo, I think it’d be worth switching gears back to the Cookbook.
So for the rest of this lesson I’ll be covering the anatomy of a Cookbook. I said earlier that a Cookbook is basically a package for your automation code. And while that’s true as a very generic definition, it doesn’t sufficiently cover all that Cookbooks entail.
Just like the chef-repo is a directory with a very specific sub-directory structure, so is a Cookbook. And each sub-directory represents a specific component of a Cookbook.
Let’s go through these different components, starting with recipes. After all, what good is a cookbook without recipes?
When it comes to Chef, a recipe is kind of the most important component; the reason being that recipes are where all of the code for configuring a node resides.
Recipes are written in Ruby, which is why understanding Ruby is a prerequisite for this course. A cookbook can contain multiple recipes. You can even create dependent recipes, by including an existing recipe inside another recipe. That will allow you to expand on an existing recipe.
The code that you write inside of a recipe is based on resources. A resource basically represents the desired configuration for a specific aspect of a node that you want to configure. That may sound a bit confusing, so here are some examples.
The package resource allows you to install, update, or remove a software package. So maybe you want to make sure nginx is installed on the node, and for that you’d use the package resource and you’d provide it with the name of the package to install.
Another example is the service resource, which allows you to specify the name of a service and the action you want Chef to take. You could tell Chef that you expect a service to be enabled, disabled, stopped, started, restarted or reloaded.
What’s great about these resources is that they don’t require you to instruct them on how to go from their current state to the desired state. That’s Chef’s problem to worry about. So all you need to do is set the desired state of the resource and Chef will be able to move from the current state, to whatever state you specified.
Here’s a list of the available resources that Chef provides out of the box. I’ll be demonstrating some of these different resources later in the course. However I recommend browsing through this list for yourself.
Okay, all of this talk about resources segues nicely into the next component, which is custom resources. You saw the list of resources that Chef provides, there are quite a few. However, you may need something custom, and starting with Chef version 12.5 you can create your own custom resources.
Custom resources allow you to create your own resources, and then use them in the same way that you use the built-in resources. An example of a custom resource might be a resource that makes sure the node is added to a load balancer. Another example might be a resource that applies some security configuration to the node. A custom resource could really be anything that you need to do, that isn’t doable with the built-in resources.
I mentioned that starting with Chef 12.5 you can create custom resources. Prior to that you could use what Chef calls “definitions.” Definitions aren’t going away anytime soon, however Chef as a company, recommends using resources instead of definitions.
Definitions are kind of like compile-time macros that you can use across multiple recipes. The reason why Chef is transitioning away from definitions is because while they look similar to resources, they’re not the same. Definitions don’t support some of the properties that resources do; properties such as notifies, subscribes, only_if and not_if. And while these are probably new to you, and may not make sense yet, for now the important thing to know is that definitions are similar to resources, however more limited.
The next component to cover is attributes. Attributes are details about a node related to the state of the node. I want you to think of them as variables, and some will be set by the developer of a Cookbook or Recipe, and others might be set automatically by Ohai. For example some of the automatically detected attributes are things such as the IP address, MAC address, hostname, platform, and other data that Ohai collects.
There are six types of attributes, and they each have a different precedence, which will allow you to potentially override them, as needed. I’m not going to go through them all. However feel free to pause and read up on them, or read more about them from the Chef documentation directly. The reason I’m not going to go into each of these is that for the most part, especially since you’re just starting out the two important ones are: default and automatic.
Chef can source attributes from 5 different places. The first being nodes, and that’s done automatically with Ohai when the chef-client is executed.
The next are attribute files, that are stored in the attributes directory inside of a Cookbook.
The next is inside of a recipe. And the final two are inside environments and roles.
So attributes are basically variables that hold information about the state of a node. And they’re set in one of the five different sources that I just mentioned.
Here’s an example of some defaults from an attributes file.
default['database']['username'] = 'database_user'
default['database']['dbname'] = 'awesome_database'
In this example I’m setting two default attributes. One is for the username of the database, and the other is for the database name. Don’t worry if this doesn’t completely make sense yet. The goal for now is to introduce to these concepts so that when I start actually creating some Cookbooks, you’ll have some context.
The next component to cover is files. Files in Chef are just files that you want to be copied to a node. Chef allows you to copy a file from a Cookbook, inside the directory named files to whatever location on the node you specify.
For example if you have an HTML file that will be the index.html file for the Apache web server, you could use the file resource to copy a file from the files directory, to /var/www/index.html.
There’s a similar component to files called templates. And templates are like files except that you can apply programming logic to dynamically change the contents of the file. As an example if you need to copy a configuration file out to a node, and it needs to know the location of the database server, then you could have a bit of code in the template that could populate that. If you’re already a Ruby developer then the template engine will be familiar to you, since it uses ERB templating based on the Erubis template engine. If you’re not a Ruby developer, then the important thing to know is that ERB templates allow you to embed ruby code into the template, and it will be processed before the file is written to disk.
Templates are stored in the templates directory inside a Cookbook, and while it isn’t required they usually end with a dot erb extension.
The next component of a Cookbook that I want to cover are libraries. A library file is a Ruby files that resides in the libraries directory of a Cookbook. Libraries are used to add new functionality to your recipes. For example, you could create a library to fetch some data dynamically from a database, and store the results inside of a file.
Since library files are just Ruby code, anything you can do with Ruby could be implemented as a library.
The final component of a Cookbook that I want to cover is the metadata file. This is a ruby file that resides at the root of the Cookbook directory and it stores information related to the Cookbook itself.
It contains things such as the name, description and version of the Cookbook. Which are important to the Chef Server. However it also stores things such as any dependent Cookbooks, and their allowed versions; as well as any gems that should be installed; it stores the allowed versions of the chef-client; as well as other metadata that describes the Cookbook.
Okay, that’s going to wrap up the review of the cookbook components, so let’s summarize what was covered in this lesson.
The chef-repo is a directory on your workstation where your Cookbooks reside.
And a Cookbook is a directory structure containing the different components of a Cookbook; such as recipes, attributes, resources, files, templates, metadata and others.
Let’s see how well you recall the different components of a cookbook. I’ll ask a few questions and you can shout out the answer. If you’re around other people at the moment, and you don’t want them to think you’re strange, you can just answer in your head. I’ll give just a few seconds between the question and answer, so feel free to pause if you need to.
The first question is: What’s the difference between a file and a template?
There are a lot of potential answers here, and if your answer doesn’t match mine, it doesn’t necessarily mean that it’s wrong.
The key difference as I see it is that you can dynamically populate the contents of a template using Ruby code. And files are just copied over to a node as is.
The next question is: If you’re going to develop a Cookbook that sets a default variable and allows other people to override it, where would you set that?
The location I’d set it is in an attributes file, using the default attribute type. And the reason is that the default has the lowest precedence, allowing it to be easily changed by someone using my cookbook.
The next question is: What is a resource?
The basic answer is that a resource defines the desired state for some aspect of a node. The aspect could be a service, for example, and the desired state might be to have that service disabled.
The last question is: Which component would you consider the most important part of a Cookbook?
Now this is admittedly a subjective question. And you could perhaps make a compelling argument for just about any of the components. However the answer I’d give is that recipes are the most important component. And the reason is that recipes are where the configuration code lives. So recipes are where you define the desired state of your different resources.
And that’s kind of what the next lesson is all about. If you’re going to write recipes to configure resources, you’ll not only need to understand what resources exist, but how to get the most out of a recipe.
So if you’re ready to dive deeper into the rabbit hole and learn more about Recipes, then let’s get started in the next lesson!
About the Author
Ben Lambert is a software engineer and was previously the lead author for DevOps and Microsoft Azure training content at Cloud Academy. His courses and learning paths covered Cloud Ecosystem technologies such as DC/OS, configuration management tools, and containers. As a software engineer, Ben’s experience includes building highly available web and mobile apps. When he’s not building software, he’s hiking, camping, or creating video games.