Chef 101: An Introduction to Chef

Find this code on GitHub
Here
Related Articles:
Vagrant 101: Simple Linux VMs

I've been using Chef for awhile at work, and seeing how complicated parts of it can be, I wanted to take the time to write a blog post about it, and give an introduction on how to be up and running with Chef. Hopefully it will save others some the aggravation I dealt with early on. Also, I tested these recipes out on an Ubuntu 12.04 box. (If you aren't running Ubuntu, no problem! Just install Vagrant)

Different Parts of Chef

There are a few different parts of chef:

  • Chef-server - The software that runs on a server and holds "cookbooks", "recipes", and "data bags". We won't be covering that here.
  • Chef-client - The software that runs on machines managed by Chef. It talks to the machine running Chef-server, downloads cookbooks from it, and runs the recipes in those cookbooks locally. We won't be covering that here, either.
  • Knife - A tool used to manage machines with chef-client on remotely. We definitely won't be covering that here.
  • Chef-solo - A tool used to run recipes out of cookbooks in the absence of a server. That will be the focus of this article.

Chef Configuration

If we try and run chef-solo right away, it will freak out because it doesn't have a configuration. So the very first thing we need to is create a configuration file, which we'll call config.rb:

base_dir Dir.pwd + "/"
file_cache_path base_dir + "cache/"
cookbook_path base_dir + "cookbooks/"

This tells Chef what directory we're running out of, where it can store cached files (and the output of Chef runs), and where the cookbooks are found.

Now let's create a directory for our cookbooks along with a sample cookbook and an empty recipe, which we will call "hello":

mkdir -p cookbooks/hello/recipes
touch cookbooks/hello/recipes/default.rb

At this point, we're ready to run Chef. Let's give it a try:

$ sudo chef-solo -c ./config.rb
Starting with base directory: /vagrant/chef-101-introduction-to-chef/
[2012-12-16T22:09:06-05:00] INFO: *** Chef 10.14.2 ***
[2012-12-16T22:09:07-05:00] INFO: Run List is []
[2012-12-16T22:09:07-05:00] INFO: Run List expands to []
[2012-12-16T22:12:26-05:00] INFO: Starting Chef Run for precise64
[2012-12-16T22:12:26-05:00] INFO: Running start handlers
[2012-12-16T22:12:26-05:00] INFO: Start handlers complete.
[2012-12-16T22:12:26-05:00] INFO: Chef Run complete in 0.021557 seconds
[2012-12-16T22:12:26-05:00] INFO: Running report handlers
[2012-12-16T22:12:26-05:00] INFO: Report handlers complete

Two things worth noting from the previous run:

1) I ran chef-solo with sudo. This is necessary, since you will be (eventually) be installing packages and tweaking system files.

2) The concept of a "run list". A run list tells chef which recipes you will be running during this invocation. Right now it's empty, but we're about to fix that.

Attributes in Chef

The next thing we're going to is create a file called attributes.json, and drop this in:

{
        "run_list": [ 
                "recipe[hello::default]"
        ]
}

What we're doing here is telling Chef that the run list will contain a single recipe, our default one. (which is still empty at this point)

Attributes in Chef can be best thought of "variables" that can be set. Attributes can also be used to contain things like usernames, Amazon Web Services (AWS) Credentials, and the like. (though once you graduate to running chef-client and chef-server, you'll learn all about "encrypted data bags" for sensitive data Smiling )

Let's run chef-solo again, specifying the -j switch to tell chef to read attributes from a file:

$ sudo chef-solo -c ./config.rb -j ./attributes.json
[2012-12-16T22:14:52-05:00] INFO: *** Chef 10.14.2 ***
[2012-12-16T22:14:54-05:00] INFO: Setting the run_list to ["recipe[hello::default]"] from JSON
[2012-12-16T22:14:54-05:00] INFO: Run List is [recipe[hello::default]]
[2012-12-16T22:14:54-05:00] INFO: Run List expands to [hello::default]
[2012-12-16T22:14:54-05:00] INFO: Starting Chef Run for precise64
[2012-12-16T22:14:54-05:00] INFO: Running start handlers
[2012-12-16T22:14:54-05:00] INFO: Start handlers complete.
[2012-12-16T22:14:54-05:00] INFO: Chef Run complete in 0.025812 seconds
[2012-12-16T22:14:54-05:00] INFO: Running report handlers
[2012-12-16T22:14:54-05:00] INFO: Report handlers complete

After getting chef-solo to run successfully for the first time, you'll probably feel like this:

Awww yeeeeaahhh

Chef Resources and Templates

Now, let's work on our "hello" recipe so that it actually does something. Smiling

To do this, we're going to add some Chef resources into our recipe. Chef resources are the heart and soul of Chef. With resources you can add or remove packages, users, configuration files, crontabs, etc. What's nice about recipe files, BTW, is that they are all written in Ruby. This means that if you have any Ruby knowledge, you can add in your own Ruby code. And if you're not a Ruby person, recipes are simply enough that not knowing Ruby will not be a handicap.

So let's add the following into cookbooks/hello/recipes/default.rb:

#
# Create a user.
# manage_home is set to true so that his home directory will be created.
#
user "hello" do
	comment "Hello User"
	home "/home/hello"
	shell "/bin/bash"
	supports  :manage_home => true
end

#
# When executing a script, it should create a file specified by 
# "creates" upon completion. This ensures that the command will 
# only run once throughout the life of the system.
#
execute "a sample command" do
	command "touch /home/hello/sample.txt"
	creates "/home/hello/sample.txt"
end

#
# Create a directory with specified ownership and permissions.
#
directory "/home/hello/hello-app" do
	owner "hello"
	group "hello"
	mode 0755
	#
	# This weird syntax looks scary, but it's quite harmless. :-)
	# In Ruby, if you have a string preceeded by a colon, it is 
	# similar to having a string with single quotes around it.  
	# There is a slight additional difference, but that is beyond 
	# the scope of this blog post about Chef.
	#
	action :create
end


#
# Create a configuration file based on a template.
# This will ONLY run if the date of the template file is newer than the date 
# of the deployed file. Chef is fairly efficient about things like that.  
# It never does more work than is necessary.
#
template "/home/hello/hello-app/config.json" do
	source "config.json.erb"
	variables(
		:home_dir => "/home/hello/hello-app"
	)
	user "hello"
	group "hello"
	mode 0600
end


#
# Install the "toilet" package. Because every system needs a toilet.
#
package "toilet" do
	action :install
end

But we're not quite ready to run this yet. We need to create a template file like this:

mkdir -p cookbooks/hello/templates/default
touch cookbooks/hello/templates/default/config.json.erb

Again, the .erb extension may look scary, but it's nothing to be worried about. It's from a template system called "Erubis" that is used in Ruby. (Additional reading is here!) We're going to barely scratch the surface of what can be done with Erubis. Case in point, please put the below into your config.json.erb file:

{
        "home_dir": "<%= @home_dir %>"
}

You now know as much about Erubis as you will need to know to get started with chef. Smiling

Final Chef Run

Let's try running Chef one final time, with the new recipe in place. Your output should be something like this:

$ sudo chef-solo -c ./config.rb -j ./attributes.json
[2012-12-16T23:09:04-05:00] INFO: *** Chef 10.14.2 ***
[2012-12-16T23:09:06-05:00] INFO: Setting the run_list to ["recipe[hello::default]"] from JSON
[2012-12-16T23:09:06-05:00] INFO: Run List is [recipe[hello::default]]
[2012-12-16T23:09:06-05:00] INFO: Run List expands to [hello::default]
[2012-12-16T23:09:06-05:00] INFO: Starting Chef Run for precise64
[2012-12-16T23:09:06-05:00] INFO: Running start handlers
[2012-12-16T23:09:06-05:00] INFO: Start handlers complete.
[2012-12-16T23:09:06-05:00] INFO: Processing user[hello] action create (hello::default line 7)
[2012-12-16T23:09:06-05:00] INFO: user[hello] created
[2012-12-16T23:09:06-05:00] INFO: Processing execute[a sample command] action run (hello::default line 19)
[2012-12-16T23:09:06-05:00] INFO: execute[a sample command] ran successfully
[2012-12-16T23:09:06-05:00] INFO: Processing directory[/home/hello/hello-app] action create (hello::default line 27)
[2012-12-16T23:09:06-05:00] INFO: directory[/home/hello/hello-app] created directory /home/hello/hello-app
[2012-12-16T23:09:06-05:00] INFO: directory[/home/hello/hello-app] owner changed to 1002
[2012-12-16T23:09:06-05:00] INFO: directory[/home/hello/hello-app] group changed to 1004
[2012-12-16T23:09:06-05:00] INFO: directory[/home/hello/hello-app] mode changed to 755
[2012-12-16T23:09:06-05:00] INFO: Processing template[/home/hello/hello-app/config.json] action create (hello::default line 48)
[2012-12-16T23:09:06-05:00] INFO: template[/home/hello/hello-app/config.json] updated content
[2012-12-16T23:09:06-05:00] INFO: template[/home/hello/hello-app/config.json] owner changed to 1002
[2012-12-16T23:09:06-05:00] INFO: template[/home/hello/hello-app/config.json] group changed to 1004
[2012-12-16T23:09:06-05:00] INFO: template[/home/hello/hello-app/config.json] mode changed to 600
[2012-12-16T23:09:06-05:00] INFO: Processing package[toilet] action install (hello::default line 62)
[2012-12-16T23:09:13-05:00] INFO: Chef Run complete in 7.144552 seconds
[2012-12-16T23:09:13-05:00] INFO: Running report handlers
[2012-12-16T23:09:13-05:00] INFO: Report handlers complete

As is clearly visible above, all of the resources of our recipe were run. The user and his directory was created, the configuration file was placed, and toilet was installed. (And I got to use the word "toilet" in a blog post!)

Running Chef Multiple Times

Feel free to run chef-solo again. No, go right ahead. I encourage it! If you do, you'll notice that none of the above actions happen again, and the chef run is in fact much shorter. Idempotence is a core feature of Chef, which means that it can be run many times, and the same resources will never be run twice--unless the part of the system differs from what is in the recipe. That can be anything from file content to file/directory permissions to usernames.

Ultimately, this means that once a working set of recipes is in place, chef-solo (or chef-client) can be run many times, and only new changes will get applied. This drastically simplifies running an environment that could have thousands of servers.

If you'd like a copy of this cookbook to play around with, all of the code samples on this post are up on Git. The repository is here.

I hope you have fun with Chef, but if you run into any problems, feel free to reach out to me or leave a comment below.

3.625
Average: 3.6 (8 votes)
Your rating: None