A Simple Chef Cookbook - Reliably Deploying Rails Applications: Hassle free provisioning, reliable deployment (2014)

Reliably Deploying Rails Applications: Hassle free provisioning, reliable deployment (2014)

5.1 - A Simple Chef Cookbook

Overview

As we’ve covered, a chef cookbook is made up of a collection of recipes generally relating to a specific package. In this example we’ll assemble a chef cookbook containing a single recipe which installs and configures Redis.

Cloning the Redis recipe

Begin by cloning the below repository into the site_cookbooks folder of the chef repository we created in the previous chapter.

https://github.com/TalkingQuickly/redis-tlq

information

Site Cookbooks v Cookbooks

As we saw in the previous chapter, if we’re writing custom cookbooks, e.g. ones which are not being installed automatically by Berkshelf (covered in 6.1), these go in site-cookbooks not cookbooks. This is because the cookbooks directly is wiped and replaced with the correct versions by Berkself every time we provision.

Structure of the Redis Recipe

metadata.rb

The metadata file contains details about what the cookbook can do, who it is written by, its dependencies and the version.

metadata.rb for our redis cookbook looks like this:

1 name "redis-tlq"

2 maintainer "Ben Dixon"

3 maintainer_email "ben@talkingquickly.co.uk"

4 description "Installs redis from rwky's ppa"

5 version "0.0.1"

6

7 recipe "redis-tlq", "Installs redis"

8

9 supports "ubuntu"

The supports line specifies the operating system the cookbook has been created to work with. This is particularly important as our simple cookbook will only be designed for Ubuntu and so will use some Ubuntu specific commands. More complex cookbooks which may be used across multiple OS’s will include detection to ensure suitable commands are used depending on the OS.

The version line and the recipe line specify the name of the recipe and the version (suprising, I know). These are key when working with Berkshelf as it will look at metadata.rb to confirm whether the cookbook it’s looking at contains the correct version of a recipe with a matching name.

The one line we haven’t covered here but will encounter a lot in future is depends. This allows us to indicate that our cookbook is dependent on other external cookbooks.

For example if we were writing a cookbook which installs ruby by building it from source, we’d need access to the standard build toolchain. There is a very common cookbook called “build-essential” written by Opscode which handles installing the build toolchain across most platforms. In order to indicate a dependency on this cookbook we would include the following line:

1 depends "build-essential"

default.rb

A chef cookbook should always include a recipe called “default.” This is the default recipe which will be installed if the cookbook is specified in a node or role definition without specifying a particular recipe.

cookbooks/redis-tlq/recipes/default.rb begins with:

1 # add the stable redis ppa as the version included in

2 # the Ubuntu repositories is generally quite out of date

3 bash 'adding stable redis ppa' do

4 user 'root'

5 code <<-EOC

6 add-apt-repository ppa:rwky/redis

7 apt-get update

8 EOC

9 end

This is a good example of what makes chef so powerful, you’ll recognise the commands to add a repository as the standard shell commands you’d use if you’d ssh’d in directly.

The line beginning “bash” tells chef to execute commands in the bash shell, the text afterwards is displayed when the recipe is run to give the user (us) an indication of what the recipe is currently doing.

This then accepts a ruby block. The first line specifies that the user the commands should be run as is “root.”

warning

User Gotcha

When setting the user like this, although commands are executed as that user, it’s not quite the same as actually logging into a shell for that user. Environment variables (such as HOME and PATH) will not be automatically setup. Therefore if any of the commands rely on environment variables, these should be set explicitly.

The next line, code <<-EOC means that everything after that will be executed in the bash shell, as the user provided, until EOC is reached.

This is pure Ruby although something which is often missed, feel free to move to the next paragraph if you’re already familiar with the <<- syntax for constructing multi-line strings. There is nothing special about the EOC sequence, <<-SOMETHING in Ruby means that all input following should be treated as a single string literal, until SOMETHING is reached.

We could include apt-get install redis-server in that list of shell commands to install the redis-server package but chef includes a better way of doing this. Further down default.rb we see the following:

1 # install the redis server package

2 package 'redis-server'

This tells chef to check whether the package redis-server is installed using the distributions package manager and if not, install it.

This covers installing the Redis package but we still need to add any custom configuration we need and an init script to start and stop it. This is where templates come in.

Templates

Further on, default.rb contains:

1 # Copy our custom redis configuration file

2 template "/etc/redis/redis.conf" do

3 owner "root"

4 group "root"

5 mode "0644"

6 source "redis.conf.erb"

7 end

This will:

· Look for the file redis.conf.erb in redis-tlq/templates/default/ (where redis-tlq is the name of the cookbook, see below re directory structure)

· parse any erb in this file and then copy it to /etc/redis/redis.conf on our node. We’ll cover why erb is so useful here in 5.3 (Node Definitions).

· Make this file owned by root with group root with permissions “0644”

A note on template directories

The directory structure of templates can be confusing because of the use of the default name. It’s easy to assume that the folder default within templates, refers to the recipe default. This is not the case.

The subdirectories within templates refer to the distribution the remote system is running. Therefore if we’re installing on an Ubuntu 12.04 system, the above template block for redis.conf.erb which search in the following locations in the order shown:

1 templates/ubuntu-12.04/redis.conf.erb

2 templates/ubuntu/redis.conf.erb

3 templates/default/redis.conf.erb

If you move to writing more complex cookbooks which are designed to work across multiple systems, this can be extremely powerful. For now though we’ll work exclusively in the default directory.

Attributes

A recipe can be customised with attributes. There a multiple possible sources of these attributes including:

· node definitions (see 5.2)

· role definitions (see 5.3)

· cookbook defaults (see below)

Generally a cookbook will specify a default for an attribute and you then have the option to override these defaults if required.

Where our the node variable referenced below comes from is covered in 5.2, for now it’s enough to understand that node will contain attributes which we’ve chosen to define custom values for as a ruby hash.

In the template referenced above, redis.config.erb there is a section which looks like this:

1 <% unless node[:redis] && node[:redis][:dont_bind] %>

2 bind 127.0.0.1

3 <% end %>

In our redis config file, we first check whether the node has defined a sub element “redis.” This ensures that if the node definition does not specify a value for the redis key, our recipe will not fail with an “undefined method [] for nil class” exception when looking for the dont_bind key.

Unless that key exists and is a true value (and remember this is just ruby so anything other than 0 or false is considered truthy) then the config file will include the line bind 127.0.0.1

The reason we’re careful to default to including the line is that without this line, redis will accept unauthenticated connections from any external host. Without a suitable firewall in place this is a serious security risk.

Cookbook Attribute Defaults

As mentioned above, a cookbook can define default values for attributes. It is generally considered best practice to construct a cookbook so that it can be used with minimal custom attributes by a majority of users.

The default attributes for a cookbook are defined in the file attributes/default.rb. As you’ll notice in the following section, the format for defining default attributes here is different to that of defining custom attributes in node or role definition files.

Our Redis cookbook only has one attribute so its default file is extremely simple:

1 #

2 # Author:: Ben Dixon

3 # Cookbook Name:: redis-tlq

4 # Attributes:: default

5 #

6

7 # whether to prevent redis from binding to 127.0.0.1

8 default[:redis][:dont_bind] = false

So in the above scenario, if it is not subsequently overwritten, the line:

1 default[:redis][:dont_bind] = x

will make x available to the cookbook as

1 node[:redis][:dont_bind]

Unless the value is subsequently overridden. For more on attribute precedence it’s well documented by OpsCode here:

http://docs.opscode.com/essentials_cookbook_attribute_files.html

Although I’d recommend getting comfortable with the following few chapters before delving into this.

Next will look at what a node definition is for and how to construct one.