Chef Tutorial: Understanding Structure of a Chef Cookbook

Sarath Pillai's picture
cookbooks and recipes in chef

Cookbook is the basic building block of chef configuration management tool. A cookbook contains the complete set of resources required to configure an application or certain thing on a node(a node is a server that has chef agent running, which will pull configurations applicable from chef server). 

For example, a cookbook for Apache or Nginx web server will have all the installation, configuration options to completely get it running on the node. 

If you have previously worked with Puppet configuration management tool, then you can compare chef cookbooks to puppet modules. 

 

Before proceeding further with this article, reading the below articles that gets you started with chef configuration management tool is necessary.

Read: An Introduction to Chef configuration Management

Read: Getting Chef Server Configured in Ubuntu 14.04

Read: Setting Up Chef Workstation

 

As mentioned earlier, cookbook contains each and every aspect for completely configuring an application on a node. It defines different resources that are required to be configured on the node in the correct order.

Unlike puppet, chef agent executes the resources on the node in the same order it is defined in the cookbook. Don't worry much right now, we will be creating cookbooks with multiple resources in them shortly.

 

Cookbook also contains templates for creating configuration files on the node.

Templates are used to create a configuration file on the node, by adding the relevent values that are applicable to that particular node during chef agent run.

 

Resources inside cookbooks are written using a chef specific DSL(Domain Specific Language). DSL is nothing but a small light weight language created for one specific field of operation. In this case, Chef DSL will help us create and define different resources inside the cookbook. For example, if you want to install a package on the node, you can easily achieve that using this DSL(which we will see shortly). You only need to specify the package name, and the required status(ie: whether it should be installed or uninstalled.)

Before we understand how resources are defined, let's get started by creating an example cookbook using knife command and then see the contents of that cookbook.

The below knife command will create a cookbook named "example". The command simply creates a directory in the cookbooks location, with several subdirectories and files that defines the skeleton structure of a cookbook. This helps a user to quickly get started with writing a cookbook.

Let me remind you the fact that this article is the continuation of the previous article that discusses installation and configuration of a chef workstation.

Hence am assuming that you have knife and chef-repo configured properly on your workstation.

 

root@workstation:/home/ubuntu/chef-repo# ls -la
total 32
drwxr-xr-x 5 root   root   4096 Jun 30 02:45 .
drwxr-xr-x 5 ubuntu ubuntu 4096 Jun 30 11:06 ..
drwxr-xr-x 4 root   root   4096 Jun 30 03:01 .chef
drwxr-xr-x 4 root   root   4096 Jun 30 02:59 cookbooks
-rw-r--r-- 1 root   root    495 Jun 30 02:30 .gitignore
-rw-r--r-- 1 root   root   2341 Jun 30 02:30 README.md
drwxr-xr-x 2 root   root   4096 Jun 30 02:30 roles

As shown above, am sitting inside the chef-repo directory in my workstation(where I also have .chef directory, which contains my knife.rb configuration file, and rsa private key file to connect and communicate with central chef server.)

 

root@workstation:/home/ubuntu/chef-repo# knife cookbook create example
** Creating cookbook example in /home/ubuntu/chef-repo/cookbooks
** Creating README for cookbook: example
** Creating CHANGELOG for cookbook: example
** Creating metadata for cookbook: example

 

The above command created a subdirectory inside the cookbooks directory inside our chef-repo. Let's get inside the newly created example cookbook directory and see what's inside.

 

root@workstation:/home/ubuntu/chef-repo/cookbooks/example# ll
total 52
drwxr-xr-x 10 root root 4096 Jul  1 08:03 ./
drwxr-xr-x  5 root root 4096 Jul  1 07:45 ../
drwxr-xr-x  2 root root 4096 Jul  1 07:45 attributes/
-rw-r--r--  1 root root  436 Jul  1 07:45 CHANGELOG.md
drwxr-xr-x  2 root root 4096 Jul  1 07:45 definitions/
drwxr-xr-x  3 root root 4096 Jul  1 07:45 files/
drwxr-xr-x  2 root root 4096 Jul  1 07:45 libraries/
-rw-r--r--  1 root root  278 Jul  1 07:45 metadata.rb
drwxr-xr-x  2 root root 4096 Jul  1 07:45 providers/
-rw-r--r--  1 root root 1462 Jul  1 07:45 README.md
drwxr-xr-x  2 root root 4096 Jul  1 07:45 recipes/
drwxr-xr-x  2 root root 4096 Jul  1 07:45 resources/
drwxr-xr-x  3 root root 4096 Jul  1 07:45 templates/

 

Let's understand the use cases of each of these directories and files that got created while creating the cookbook.

 

Recipes

This is the directory where most of you will be working in the beginning. This directory holds all the steps that needs to be executed on the nodes to configure it the required way. By default there will be a default.rb file inside this directory, which can be used to define our resources.

Recipe is a collection of resources. Resources are nothing but Ruby code blocks that defines a particular step that needs to happen on the node. Resources are written in Ruby DSL. still thinking what a resource is? Let's dive into an example to understand it further.

 

below shown is a directory resource(this will create "/opt/test" directory on the node.)

directory "/opt/test" do
        owner "root",
        group "root",
        mode "0755",
        action :create
end

The syntax of a resource looks like the below..

type "name" do
    attribute "value"
    action :type_of_action
end

 

Attributes

Automating something requires several details. Let's say for example, I want to install something on a server.  Installing and running a service on a server might require a user, a directory where configuration files will be created/or installed, a directory for logging, some other URL or endpoint where the application that you are installing might need to connect to(for example a database endpoint) etc.

 

One approach is to take care of all these above mentioned components by hard cording values inside the automation script. Although this approach does get the job done, its not quite useful. If you want to use the same automation script for a separate environment of yours, the hard corded values might be different(basically you need to modify the automation scripts to reflect values that are applicable to that specific environment.).

Yet another problem is that you will have to maintain separate versions of your automation scripts that are applicable for different environments. This is because everything is hard corded inside the scripts themselves, hence you cannot use the same script for every environment of yours(you will have to maintain different copies for different environments).

 

Same rules applies to a chef cookbook. So you need a method, where you can use the same cookbook in all of your environments, irrespective of what values are applicable. The best method is to separate these values from the actual automation script and keep it segregated. Along with this, you also need a method to specify the values as variables depending upon the environment where its being executed.

Chef has this capability where you can define a value that is only applicable to one specific environment, and another value for another one. You can also define values depending upon the server name.  This way you are only modifying or adding environment specific variable data without touching the actual functionality of the automation script(or chef cookbook in our case.)

 

Attributes directory holds all these variable values, that are applicable to different environments.

Inside the attributes directory, there will be an attribute file, which will hold these values. The syntax will look like the below.

 

default['user']['location'] = '/opt/user'
default['user']['username'] = 'testuser'

 

Resources

This directory will contain all your custom defined resources. In the Recipes section, we saw a basic resource called directory. Chef supports a lot of default resources. Like file, package, service, user, cron, exec etc..The entire list can be found here: Chef Resources List

All of those resources will let you achieve 90 percent of the things that you want to automate using chef. However, if you have a bit more complex requirement, where you need your own custom resource which is not available in the default list of resources, you will have to create it.

You can create it by defining it inside this directory of the cookbook. Custom resources are defined in chef using ruby .rb files. A simple example of custom resource definition looks like the below.

basically am creating a file called testresource.rb inside the resources directory of the cookbook.

resource_name :testresource
property :data, kind_of: String, name_property: true

action :create do
  file '/tmp/testresource' do
    content "#{data}\n"
    mode '0600'
  end
end

 

 

Once you have the above defined inside resources directory, you can then use this newly created resource inside your recipe as shown below.

testresource 'some string data'

 

The above will create the file /tmp/testresource with the content "some string data". Most of the cases you actually do not use this custom resources, as you can get 90 percent of the jobs done using readily available resources in chef.

 

Providers

This location will contain your custom actions and the actual code that will execute a particular action. Let's consider you have created a custom resource of your own, with few custom actions of your own. You then need to actually define the things that needs to happen during that action.

You define your actions in this directory.

The structure looks something like the below. The file name should end with .rb extention here as well.

 

action :check do
#your ruby code for this action goes here.
end

action :setmaster do
#your ruby code for this action goes here.
end

 

Definition

Definition is nothing but simply named collection of resources. As mentioned earlier, you can find the complete list of resources supported by chef out of the box here: Chef List of Resources

 

In simple words, you can actually create a newly named resource, by using the existing list of resources. A good use case of definition is when you have a pattern of resources repeating over and over again in your cookbook.

Definition in itself is not a resource as such, but its a collection of resources. Also please note the fact that chef officially recommends using custom resources instead of definitions starting from chef version 12.5.

Once you have a definition, you can then use it inside your cookbook as any other resource. Also please keep the fact in mind that unlike resources, definitions are processed during compile time.

Definitions does not support only_if & not_if parameters available in resources. It also does not support notifies, subscribes etc

 

define :test_definition do
  directory '/tmp/test' do
    owner root,
    group root,
    mode 0755,
    action: create
  end
  file '/tmp/test/test_definition_file' do
    content 'testing'
  end
end

 

Files

This directory contains all the files that you need to copy to the servers as part of your chef cookbook. Let's take an example. Imaging you are trying to deploy apache or nginx on the nodes using Chef.

After installing apache using standard chef resources(like package, file, directory, service etc.), you will have to deploy web content as well in there. The static files that you want to serve using the webserver that is.

You can actually use this directory of chef cookbook to place your required files, and then copy it down to the nodes using the resource called cookbook_file as shown below.

 

Let's imagine that you have the file index.html inside chef-repo/cookbooks/{cookbook name}/files/default/ location of your cookbook. You are now willing to copy that index.html file to the nodes. Note: Please replace {cookbook name} with the actual name of the cookbook.

 

cookbook_file '/var/www/html/index.html' do
  source 'index.html'
  owner 'www-data'
  group 'www-data'
  mode '0755'
  action :create
end

The above will copy index.html file located at chef-repo/cookbooks/{cookbook_name}/files/default/ down to the node.

 

Templates

Templates are very much similar to files in a chef cookbook. Templates lets you create static files with dynamically created content inside.

Consider having a configuration file for a web server like Apache or Nginx. Using templates, you can create a skeleton of the configuration file with all required contents that will remain the same in all nodes. But several places in that configuration skeleton, might have details like hostname or ip address of the server, which you can never hard code in there, as these values will be different on different servers. Hence chef client should place the correct value during runtime at the correct place in the skeleton.

 

Inside the template file, we can have ruby language statements which will take care of placing correct values dynamically during run time. You can simply create a template file inside the directory cookbooks/{cookbook name}/templates/default/. Template file looks like the below. Let's create a template file named httpd.conf.erb (inside the directory mentioned.)

 

<VirtualHost <%= @ip %>>
    DocumentRoot "/var/www/example"
    ServerName www.example.com
</VirtualHost>

 

 

In the above shown template snippet of httpd.conf.erb, <%= @ip %> will get replaced by the actual ip address of the server. Server details like fqdn, memory, cpu, hostname, ip address, interfaces and many other can be used just like this example. You can also use ruby code snippets in your templates.

 

Once you have defined your template file(which ends with a .erb extention inside the previously mentioned cookbook directory), you can use the template using the template resource as shown below.

 

template '/etc/httpd/httpd.conf' do
  source 'httpd.conf.erb'
  mode '0644''
  owner 'root'
  group 'root'
end

 

Libraries

In simple words, libraries in a chef cookbook are nothing but a way to add more features and functionality to your cookbooks. If you know ruby, you can then write a ruby class, and then include it in your chef recipe.

Once you define a library inside the libraries directory, you can then use it anywhere inside the cookbook. In a way you can say that library files create custom resources for your cookbook, that you cannot find in any of the standard resources available in chef by default.

For example, let's create a small library and then use it inside a recipe.

 

class Chef
 class Recipe
  def serverconfiglocation(name)
   node[:server_config_location][name]
  end
 end
end

We can now use the above library as shown below inside our recipe. 

 

directory serverconfiglocation(:test)[:testlocation] do
 owner test
 group test
 action: create
end

 

metadata.rb file

Metadata.rb file in a cookbook is used for storing details about a cookbook. The name itself suggests the same. It contains details like the below.

  • Name of the cookbook
  • Maintainer of the cookbook
  • Email address of the maintainer
  • License details
  • Description about the cookbook.
  • Version of the cookbook
  • dependancy.

 

An example metadata.rb file is shown below.

maintainer      "Example, Inc."
maintainer_email        "contact@example.com"
license "GPL"
description     "This installs and configures some app on the server"
version "1.2.0"
depends "yum"
depends "mysql"

 

readme.md file is used for elaborate description about the cookbook(which a user can read and understand). changelog.md is used for specifying changes in each version of the chef cookbook. Its a good practice to document changes of a chef cookbook, although chef cookbook functionality is not impacted as such by either of these files. Example of changelog.md file can be found here: https://github.com/chef-cookbooks/openssh/blob/master/CHANGELOG.md & Example of readme.md file can be found here: https://github.com/chef-cookbooks/openssh/blob/master/README.md

Rate this article: 
No votes yet

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Type the characters you see in this picture. (verify using audio)
Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive.