Getting Started with Chef Solo. Part 2
Hello my dear friends. Today we will continue talk about Chef Solo. All example code you can find here: github.com/le0pard/chef-solo-example/tree/2.0.
In the previous article we discussed how to use Chef Solo, learned about knife, librarian and vagrant tools, which help us to use and testing Chef Solo kitchen. In this article we will learn cookbook structure and will write own cookbook.
Cookbook
Cookbook is a collection of Chef recipes. All cookbooks like a Chef written on Ruby. You already have seen how we get nginx cookbook and use “source” recipe from it to install nginx on our server. Let’s look on the structure of this cookbook:
$ ls -la cookbooks/nginx
total 112
drwxr-xr-x 16 leo staff 544 Jan 4 19:24 .
drwxr-xr-x 6 leo staff 204 Jan 4 19:24 ..
drwxr-xr-x 15 leo staff 510 Jan 4 19:24 .git
-rw-r--r-- 1 leo staff 28 Jan 4 19:24 .gitignore
-rw-r--r-- 1 leo staff 3526 Jan 4 19:24 CHANGELOG.md
-rw-r--r-- 1 leo staff 10811 Jan 4 19:24 CONTRIBUTING.md
-rw-r--r-- 1 leo staff 37 Jan 4 19:24 Gemfile
-rw-r--r-- 1 leo staff 10850 Jan 4 19:24 LICENSE
-rw-r--r-- 1 leo staff 14633 Jan 4 19:24 README.md
drwxr-xr-x 8 leo staff 272 Jan 4 19:24 attributes
drwxr-xr-x 3 leo staff 102 Jan 4 19:24 definitions
drwxr-xr-x 3 leo staff 102 Jan 4 19:24 files
-rw-r--r--@ 1 leo staff 3283 Jan 4 19:24 metadata.rb
drwxr-xr-x 20 leo staff 680 Jan 4 19:24 recipes
drwxr-xr-x 5 leo staff 170 Jan 4 19:24 templates
drwxr-xr-x 3 leo staff 102 Jan 4 19:24 test
Cookbook can have:
- metadata.rb - the file, which contain all information about cookbook (name, dependencies).
name "nginx"
maintainer "Opscode, Inc."
maintainer_email "cookbooks@opscode.com"
license "Apache 2.0"
description "Installs and configures nginx"
version "1.1.2"
recipe "nginx", "Installs nginx package and sets up configuration with Debian apache style with sites-enabled/sites-available"
recipe "nginx::source", "Installs nginx from source and sets up configuration with Debian apache style with sites-enabled/sites-available"
%w{ ubuntu debian centos redhat amazon scientific oracle fedora }.each do |os|
supports os
end
%w{ build-essential }.each do |cb|
depends cb
end
depends 'ohai', '>= 1.1.2'
%w{ runit bluepill yum }.each do |cb|
recommends cb
end
This is an important file, if you want distribute your cookbook.
- attributes - folder, which contain files with default attributes for recipes. In nginx cookbook you can find such default attributes:
default['nginx']['version'] = "1.2.3"
default['nginx']['dir'] = "/etc/nginx"
default['nginx']['log_dir'] = "/var/log/nginx"
default['nginx']['binary'] = "/usr/sbin/nginx"
As you remember we can redefine all this attributes in node file.
- definitions - folder, which contain helpers from this cookbook. In nginx cookbook you can find this helper:
define :nginx_site, :enable => true do
if params[:enable]
execute "nxensite #{params[:name]}" do
command "/usr/sbin/nxensite #{params[:name]}"
notifies :reload, "service[nginx]"
not_if do ::File.symlink?("#{node['nginx']['dir']}/sites-enabled/#{params[:name]}") end
end
else
execute "nxdissite #{params[:name]}" do
command "/usr/sbin/nxdissite #{params[:name]}"
notifies :reload, "service[nginx]"
only_if do ::File.symlink?("#{node['nginx']['dir']}/sites-enabled/#{params[:name]}") end
end
end
end
The helper “nginx_site” can enable/disable configuration from folder “site-available” and reload nginx. I will show how to use this helper.
- files - folder, which contain files and this files just need to copy on server in the right place (it can be ssl keys, static configs, etc.)
- recipes - folder, which contain all recipes of this cookbook. Each recipe is in a separate Ruby file:
$ ls -la cookbooks/nginx/recipes
total 152
drwxr-xr-x 20 leo staff 680 Jan 4 19:24 .
drwxr-xr-x 16 leo staff 544 Jan 4 19:24 ..
-rw-r--r-- 1 leo staff 1123 Jan 4 19:24 authorized_ips.rb
-rw-r--r-- 1 leo staff 792 Jan 4 19:24 commons.rb
-rw-r--r-- 1 leo staff 1114 Jan 4 19:24 commons_conf.rb
-rw-r--r-- 1 leo staff 1070 Jan 4 19:24 commons_dir.rb
-rw-r--r-- 1 leo staff 854 Jan 4 19:24 commons_script.rb
-rw-r--r-- 1 leo staff 1201 Jan 4 19:24 default.rb
-rw-r--r-- 1 leo staff 1551 Jan 4 19:24 http_echo_module.rb
-rw-r--r-- 1 leo staff 3412 Jan 4 19:24 http_geoip_module.rb
-rw-r--r-- 1 leo staff 814 Jan 4 19:24 http_gzip_static_module.rb
-rw-r--r-- 1 leo staff 1352 Jan 4 19:24 http_realip_module.rb
-rw-r--r-- 1 leo staff 797 Jan 4 19:24 http_ssl_module.rb
-rw-r--r-- 1 leo staff 1091 Jan 4 19:24 http_stub_status_module.rb
-rw-r--r-- 1 leo staff 738 Jan 4 19:24 ipv6.rb
-rw-r--r-- 1 leo staff 1704 Jan 4 19:24 naxsi_module.rb
-rw-r--r-- 1 leo staff 1059 Jan 4 19:24 ohai_plugin.rb
-rw-r--r-- 1 leo staff 2994 Jan 4 19:24 passenger.rb
-rw-r--r-- 1 leo staff 5218 Jan 4 19:24 source.rb
-rw-r--r-- 1 leo staff 1571 Jan 4 19:24 upload_progress_module.rb
As you remember we added to run_list:
"run_list": [
"recipe[nginx::source]"
]
This is run source.rb recipe from nginx cookbook. If you change by this:
"run_list": [
"recipe[nginx]"
]
This is run default recipe from nginx cookbook (file default.rb in recipes folder).
- templates - folder, which contain Erb templates of this cookbook (this is nginx configs)
- test - folder, which contain tests for this cookbook
First cookbook
Let’s create our first cookbook. Our custom cookbooks should be in folder “site-cookbooks” (folder “cookbooks” using for vendor cookbooks and managed by librarian, so we add this folder in gitignore). If you look in solo.rb, you can see such settings:
file_cache_path "/tmp/chef-solo"
cookbook_path [ "/tmp/chef-solo/site-cookbooks",
"/tmp/chef-solo/cookbooks" ]
This mean what Chef will search needed cookbook first in folder site-cookbooks and if no found will try to search in folder cookbooks. So if you create in site-cookbooks nginx cookbook, Chef will try use it first.
Let’s create a cookbook with name “tomatoes”:
$ mkdir site-cookbooks/tomatoes
$ mkdir site-cookbooks/tomatoes/recipes site-cookbooks/tomatoes/templates
$ mkdir site-cookbooks/tomatoes/templates/default
$ ls -la site-cookbooks/tomatoes
drwxr-xr-x 4 leo staff 136 Jan 5 14:50 .
drwxr-xr-x 4 leo staff 136 Jan 5 14:49 ..
drwxr-xr-x 2 leo staff 68 Jan 5 14:50 recipes
drwxr-xr-x 3 leo staff 102 Jan 5 14:50 templates
And create file default.rb in recipes folder with content:
package "git"
Command “package” is used to manage packages in the server. This command will install on server git package. More info about this command you can read here: docs.opscode.com/chef/resources.html#package. Next, add to our vagrant.json node file in run list new recipe:
"run_list": [
"recipe[nginx::source]",
"recipe[tomatoes]"
]
And test our kitchen again:
$ vagrant provision
[default] Running provisioner: Vagrant::Provisioners::ChefSolo...
[default] Generating chef JSON and uploading...
[default] Running chef-solo...
stdin: is not a tty
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: *** Chef 0.10.10 ***
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: Setting the run_list to ["recipe[nginx::source]", "recipe[tomatoes]"] from JSON
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: Run List is [recipe[nginx::source], recipe[tomatoes]]
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: Run List expands to [nginx::source, tomatoes]
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: Starting Chef Run for precise64
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: Running start handlers
[Sat, 05 Jan 2013 13:07:34 +0000] INFO: Start handlers complete.
...
[Sat, 05 Jan 2013 13:07:35 +0000] INFO: Processing package[git] action install (tomatoes::default line 1)
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: package[git] installed version 1:1.7.9.5-1
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: execute[nxensite default] sending reload action to service[nginx] (delayed)
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: Processing service[nginx] action reload (nginx::source line 82)
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: service[nginx] reloaded
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: Chef Run complete in 16.410976 seconds
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: Running report handlers
[Sat, 05 Jan 2013 13:07:50 +0000] INFO: Report handlers complete
As you can see git package installed on the server. Let’s check this:
$ vagrant ssh
Welcome to Ubuntu 12.04.1 LTS (GNU/Linux 3.2.0-23-generic x86_64)
* Documentation: https://help.ubuntu.com/
Welcome to your Vagrant-built virtual machine.
Last login: Sat Jan 5 13:09:24 2013 from 10.0.2.2
vagrant@precise64:~$ git --version
git version 1.7.9.5
vagrant@precise64:~$ exit
logout
Connection to 127.0.0.1 closed.
All works fine.
Сonfigure nginx through our cookbook
Let’s configure nginx for our application. First of all add new attributes in vagrant node (file “nodes/vagrant.json”):
{
"app": {
"name": "tomatoes",
"web_dir": "/var/data/www/apps/tomatoes"
},
"user":{
"name": "vagrant"
},
"nginx": {
"version": "1.2.3",
"default_site_enabled": true,
"source": {
"modules": ["http_gzip_static_module", "http_ssl_module"]
}
},
"run_list": [
"recipe[nginx::source]",
"recipe[tomatoes]"
]
}
Next, create nginx template (“tomatoes/templates/default/nginx.conf.erb”):
server {
listen 80 default;
access_log <%= node.app.web_dir %>/logs/nginx_access.log;
error_log <%= node.app.web_dir %>/logs/nginx_error.log;
keepalive_timeout 10;
root <%= node.app.web_dir %>/public;
}
And create file index.html in directory “tomatoes/files/default” (create this directory before) with content:
<h1>Hello from Chef Solo</h1>
This we will use to check what nginx will show after setup of settings.
At last add this content to “tomatoes/recipes/default.rb”:
directory node.app.web_dir do
owner node.user.name
mode "0755"
recursive true
end
directory "#{node.app.web_dir}/public" do
owner node.user.name
mode "0755"
recursive true
end
directory "#{node.app.web_dir}/logs" do
owner node.user.name
mode "0755"
recursive true
end
template "#{node.nginx.dir}/sites-available/#{node.app.name}.conf" do
source "nginx.conf.erb"
mode "0644"
end
nginx_site "#{node.app.name}.conf"
cookbook_file "#{node.app.web_dir}/public/index.html" do
source "index.html"
mode 0755
owner node.user.name
end
As you can see in recipe node attributes available for us in “node” variable. You can get this attributes in several ways:
node.app.web_dir
node['app']['web_dir']
node[:app][:web_dir]
This all ways will give you the same value from app.web_dir attribute.
As you can see in recipe code we created 3 directories, created new config for nginx, enabled this config by “nginx_site” helper (this helper automatically reload nginx) and put “index.html” into server directory. After launch command “vagrant provision” you should see this in your browser by url “http://localhost:8085/”:
Ruby Power!
As you can see in our recipe we created 3 directories by 3 command. Better DRY this code. But how to do this? Simple! This is all Ruby code, so you can use it to do your recipe more powerful (and beautiful, of course):
package "git"
%w(public logs).each do |dir|
directory "#{node.app.web_dir}/#{dir}" do
owner node.user.name
mode "0755"
recursive true
end
end
template "#{node.nginx.dir}/sites-available/#{node.app.name}.conf" do
source "nginx.conf.erb"
mode "0644"
end
nginx_site "#{node.app.name}.conf"
cookbook_file "#{node.app.web_dir}/public/index.html" do
source "index.html"
mode 0755
owner node.user.name
end
We collect all subfolders in Ruby array and create its in one cycle.
Summary
In the current article we have learn the Chef cookbook structure and write simple Chef cookbook. In the next article we will look at the usage of roles in your Chef kitchen.
All example code you can find here: github.com/le0pard/chef-solo-example/tree/2.0.
That’s all folks! Thank you for reading till the end.