Try Chef Infra

See how Chef Infra can help you automate changes to your infrastructure.

1. Install Docker and Docker Compose

The installation is a set of containers orchestrated with Docker Compose, a tool for defining and running multi-container Docker applications.

You'll need a Windows, macOS, or 64-bit Linux system.

If you're new to Docker, you can read this overview to get a better understanding of how Docker works.

Terminal: ~

$
sudo apt install docker-compose

2. Set up your environment

In this module, you'll use a set of Docker images that we've created for you.

The setup includes four systems – one that acts as your workstation and a three that act as the targets, or the system you want to configure.

The Docker images are based on Ubuntu 16.04. The workstation image comes with Chef Workstation

Chef Workstation gives you everything you need to get started with the Chef tools. Ad-hoc remote execution, scans and configuration tasks, cookbook creation tools, as well as robust dependency and testing software all in one easy-to-install package.

Start by creating a working directory. We recommend ~/try-chef.

Terminal: ~

$
mkdir ~/try-chef

Next, move to your working directory.

Terminal: ~

$
cd ~/try-chef

Next, get the Docker Compose file. Run the command that matches your system to download a file named docker-compose.yml.

Windows:

Windows PowerShell: ~/try-chef

PS >
Invoke-WebRequest -useb -o docker-compose.yml https://raw.githubusercontent.com/learn-chef/chef/master/docker-compose.yml

macOS:

Terminal: ~/try-chef

$
curl -o docker-compose.yml https://raw.githubusercontent.com/learn-chef/chef/master/docker-compose.yml

Linux:

Terminal: ~/try-chef

$
wget https://raw.githubusercontent.com/learn-chef/chef/master/docker-compose.yml

Next, run the following docker-compose command to retrieve the latest workstation images. Docker must be running in order to use this command.

Terminal: ~/try-chef

$      
sudo docker-compose pullPulling target (learnchef/chef_target:latest)...latest: Pulling from learnchef/chef_target[...]Pulling workstation (learnchef/chef_workstation:latest)...latest: Pulling from learnchef/chef_workstation[...]

Next, run the following docker-compose command to start the containers. The -d argument starts the containers in the background.

Terminal: ~/try-chef

$
sudo docker-compose up -d

If you get an error like this -- Cannot create container for service workstation: Conflict. The container name "/workstation" is already in use by container "58bb327f4cbead2241399daaf9a6843e82...". You have to remove (or rename) that container to be able to reuse that name -- it may due to a previous Docker setup that was not cleaned up.

You can resolve such an error by running this command: sudo docker system prune -a. Then run sudo docker-compose pull and sudo docker-compose up -d again. You could also refer to this documentation for more details about removing Docker containers.

Now that your containers are running in the background, run this command to start an interactive Bash session on the workstation container.

Terminal: ~/try-chef

$  
sudo docker exec -it workstation bashAgent pid 25Identity added: .ssh/try-chef.key (.ssh/try-chef.key)

3. Basic ingredients

Chef Infra is driven by the concept of Resources, a single item that can be configured on your system. Normally you would collect these together in Chef recipes, but we are going to demonstrate them as simply as possible using the chef-run command to create a file on one of our target systems.

On this first invocation of the chef-run command you will be asked to accept the Chef EULA Note thisis only accepting it inside the context of the docker container, so even if you have already accepted it on your own computer, you will have to accept it again.

Terminal: /root

#    
chef-run web1 file hello.txt[✔] Generated local policyfile[✔] [web1] Connected.[✔] [web1] Successfully installed Chef client version 14.3.37[✔] [web1] Successfully converged target!

Let's break that down a little. First, the command is chef-run. This is a utility to run adhoc Chef Infra commands on a remote server. It provides a good way to get started with Chef Infra, without requiring any infrastructure beyond SSH.

Next is the host name of the machine we are going to be configuring--web1.

Finally we have file hello.txt. This refers to the Chef Infra file resource and passes it the name hello.txt. This has the effect of creating a file called hello.txt on the machine. We don't need to provide any credentials because we have provisioned these test machines with SSH keys.

We can see that chef-run connected to our target machine, ensured Chef Infra Client is installed, and then ran it with our resource. We are able to test what has happened by running a command over ssh. Note we have specified a path here of the root (/) directory. This is the default working directory for chef if you don't specify anything else.

Terminal: /root

# 
ssh web1 cat /hello.txtWarning: Permanently added 'web1,172.20.0.4' (ECDSA) to the list of known hosts.

Well, that wasn't very interesting, we didn't get any output. Although there weren't any errors, let's run a different command to ensure the file really is there.

Terminal: /root

#  
ssh web1 ls -l /hello.txtWarning: Permanently added 'web1,172.20.0.4' (ECDSA) to the list of known hosts.-rw-r--r-- 1 root root 0 Oct 25 12:43 /hello.txt

Ah, definitely a file, so it clearly doesn't have any content. What has happened is we have asked Chef to create a file, but not actually specified anything else. Let's try again, but this time with some content.

Terminal: /root

#    
chef-run web1 file hello.txt content='Hello World!'[✔] Generated local policyfile[✔] [web1] Connected.[✔] [web1] Chef client version 14.3.37 already installed on target[✔] [web1] Successfully converged target!

Here we have added to the chef-run command, by providing a property to the file resource. This property is content and specifies the content of the file. This is a primitive way to create a file, and we shall see more flexible ways later. Let's prove it has worked by looking at the file again.

Terminal: /root

#  
ssh web1 cat /hello.txtWarning: Permanently added 'web1,172.20.0.4' (ECDSA) to the list of known hosts.Hello World!root@33543c57c361:~#

Note that we didn't add a new line, so our prompt is appended to the end of the file output, but we can see the file has been created with content.

We can also ask Chef Infra to remove the file by defining an action, overriding the default of create.

Terminal: /root

#    
chef-run web1 file hello.txt action=delete[✔] Generated local policyfile[✔] [web1] Connected.[✔] [web1] Chef client version 14.3.37 already installed on target.[✔] [web1] Successfully converged target!

If we try and look at the file again we can see it has been removed:

Terminal: /root

# 
ssh web1 cat /hello.txtcat: /hello.txt: No such file or directory

4. Recipes for success

If you want to configure more than a single item on your system, you will need to collect your resources together in a recipe. With a recipe, Chef Infra will process the resources in the order they appear.

We have included a basic recipe for you to try. It will install a utility, FIGlet, and use it to create a file with a hello world message. Let's take a look at the file and talk through it.

Terminal: /root

#          
cat recipe.rbapt_update package 'figlet' directory '/tmp' execute 'write_hello_world' do    command 'figlet Hello World! > /tmp/hello.txt'    not_if { File.exist?('/tmp/hello.txt') }end

There is a lot going on here, but we will work through it in stages.

The first line, apt_update, invokes a Chef Infra resource designed for Debian and Ubuntu systems which ensures that the package repository is up-to-date and that the packages we install will be the latest version. The documentation gives more details if you wish to understand further.

The next line, package 'figlet', will install the package. We don't have to tell Chef how to install the package, we just said that we wanted it installed. This is part of the power of Chef: it allows us to work cross-platform. We simply state how we wish a system to look, and when Chef Infra converges, it uses the correct commands for the system it is running on to bring it into the state we desire.

Next, we ensure our temporary directory exists with directory '/tmp'. One of the great powers of Chef Infra is that we can safely include this line, even if this directory already exists. The Chef Infra Client will only take action if it needs to. This is known as test & repair. By including it we ensure that the next lines will not fail if the directory does not exist, but not cause problems if it does.

The next four lines are more complex: they are all related to one resource, the execute resource. This time, however, we need to give it more information. Unlike the previous commands, the name does not get used by the resource to direct the action, so instead we just provide a descriptive name.

We finish the line off with the keyword do. This indicates to Chef Infra that we want to give more arguments to the resource.

The next two lines (indented above for clarity) are again parameters. This is how we give resources extra information like in the file example above.

  • First we have command. This is the shell command that is to be executed by the resource. Here we are going to run figlet and direct the output to a file.
  • Next we have not_if. This parameter is known as a guard. We use guards to provide the test part of test & repair for more generic resources. In this case, we test for the existence of the file we want to create, and if it exists, Chef Infra will not run the resource.

This is not a perfect test, as it doesn't check that the contents of the file is correct, but will serve our purposes for now.

Finally the keyword end indicates we are finished giving the resource parameters.

Let's run it on our test machine again, and see the results.

Terminal: /root

#   
chef-run web1 recipe.rb[✔] [web1] Connected.[✔] [web1] Chef client version 14.3.37 already installed on target[✔] [web1] Successfully converged target!

This command is similar to the previous command, but instead of providing the resource and a parameter (package cowsay) we just provide the name of the recipe recipe.rb

We can test what happened in a similar manner to the last time:

Terminal: /root

#      
ssh web1 cat /tmp/hello.txtWarning: Permanently added 'web1,172.21.0.2' (ECDSA) to the list of known hosts. _   _      _ _        __        __         _     _ _| | | | ___| | | ___   \ \      / /__  _ __| | __| | || |_| |/ _ \ | |/ _ \   \ \ /\ / / _ \| '__| |/ _` | ||  _  |  __/ | | (_) |   \ V  V / (_) | |  | | (_| |_||_| |_|\___|_|_|\___/     \_/\_/ \___/|_|  |_|\__,_(_)

5. Cooking up Awesome

As we saw before, recipes are great for gathering together a selection of resources but there are still some limitations. One in particular is highlighted by the file. You can use the file resource to create a text file on disk, and use the content property to add the text, as follows

Editor: Untitled

1
2
3
4
5
6
file '/foo/bar' do
  content 'Hello World!'
  mode '0755'
  owner 'myuser'
  group 'mygroup'
end

This can quickly become complex and messy as we add more text over several lines, especially if that text contains special characters that would need to be escaped, like '\'.

Instead, we should be able to keep the content in separate files from the Chef Infra code. To do this, we need a way to package all the pieces of our recipe together. We call this package a cookbook.

We have included for you two cookbooks - one that will setup a webserver and one that will setup a load balancer. The load balancer will balance traffic to the two web server machines we have.

We are going to demonstrate an additional feature of chef-run that allows you to quickly apply a cookbook to more than one server simultaneously.

First however, we should take a look at our webserver cookbook.

Terminal: /root

#         
tree webserverwebserver/|-- README.md|-- metadata.rb|-- recipes|   `-- default.rb`-- templates    `-- index.html.erb 2 directories, 4 files

This represents a basic cookbook with just the minimal of files.

  • The README.md file gives description of the cookbook and how it should be used. While not mandatory, it is highly recommended that all cookbooks include a README.
  • The metadata.rb file is required for all cookbooks. It contains the name and version number of the cookbook and information about dependencies. You can lean more about the metadata.rb here.

Next we have two folders--recipes and templates.

  • recipes contain the recipes. You may have as many as necessary including the default recipe,default.rb, which is the recipe that is run if another is not specified.
  • templates directory contains templates for text files that are required by a recipe, for example for configuration files that need populated with realtime data

The recipes are similar to above, but you have a couple more resources available. If we look at the recipe we can see:

Terminal: /root

#                 
cat webserver/recipes/default.rb ## Cookbook:: webserver# Recipe:: default## Copyright:: 2018, The Authors, All Rights Reserved.apt_update package 'apache2' template '/var/www/html/index.html' do  source 'index.html.erb'end service 'apache2' do  action [:enable, :start]end

This is very similar to our previous recipe. The part to highlight is the template resource. As you can see we pass it a parameter of source. This refers to a template in the templates directory we saw above. Let's take a look at it.

Terminal: /root

#         
cat webserver/templates/index.html.erb<html>  <head>    <title>Learn Chef Demo</title>  </head>  <body>    <h1>Hello Learn Chef</h1>    <p>This is < % =node['hostname']% ></p>  </body></html>

For the most part, this is just some HTML. However there is one part that is unusual < %=node['hostname']% > This is syntax in the templating language that will be replaced by the hostname of the machine. This is explained in more detail in the Make your recipe more manageable module.

Let's run that on our test machines, this time using a slightly different chef-run syntax to apply it to two machines at once, web1 and web2:

Terminal: /root

#   
chef-run web[1:2] webserver┌ [✔] Converging targets:├── ✔ [web1] Successfully converged target!└── ✔ [web2] Successfully converged target!

We can test these by connecting to our new web servers:

Terminal: /root

#         
curl web1<html>  <head>    <title>Learn Chef Demo</title>  </head>  <body>    <h1>Hello Learn Chef</h1>    <p>This is ae6574960e01</p>  </body></html>

Terminal: /root

#         
curl web2<html>  <head>    <title>Learn Chef Demo</title>  </head>  <body>    <h1>Hello Learn Chef</h1>    <p>This is f00120a691c4</p>  </body></html>

Here you can see the power of Chef Infra. Very quickly we have provisioned two webservers, each configured the same, including the required local changes.

To wrap up, we'll setup a load balancer in front of those web servers. You can look at the recipe and template in the same way as before.

Terminal: /root

#                 
cat loadbalancer/recipes/default.rb## Cookbook:: loadbalancer# Recipe:: default## Copyright:: 2018, The Authors, All Rights Reserved.apt_updatepackage 'haproxy' directory '/etc/haproxy' template '/etc/haproxy/haproxy.cfg' do    source 'haproxy.cfg.erb'end service 'haproxy' do    action [:enable, :start]end

The directory resource isn't strictly necessary as the package will create it, but it's good practice to manage any directories that you place files into.

Terminal: /root

#                                              
cat loadbalancer/templates/haproxy.cfg.erbglobal    log         127.0.0.1 local2     chroot      /var/lib/haproxy    pidfile     /var/run/haproxy.pid    maxconn     4000    user        haproxy    group       haproxy    daemon     stats socket /var/lib/haproxy/stats defaults    mode                    http    log                     global    option                  httplog    option                  dontlognull    option http-server-close    option forwardfor       except 127.0.0.0/8    option                  redispatch    retries                 3    timeout http-request    10s    timeout queue           1m    timeout connect         10s    timeout client          1m    timeout server          1m    timeout http-keep-alive 10s    timeout check           10s    maxconn                 3000 frontend  main    bind                 *:80    acl url_static       path_beg       -i /static /images /javascript /stylesheets    acl url_static       path_end       -i .jpg .gif .png .css .js     use_backend static          if url_static    default_backend             app backend static    balance     roundrobin    server      static 127.0.0.1:4331 check backend app    balance     roundrobin    server web1 web1:80 weight 1 maxconn 100 check    server web2 web2:80 weight 1 maxconn 100 check

The recipe is almost identical but using the haproxy package and service in place of apache2. This is a common pattern of cookbooks, to install an application, create a config file, and then start the service, so you will see it a lot.

The haproxy configuration file looks daunting, but in this case it has nothing particular to Chef Infra in it, we have just hard coded the webservers to connect to at the bottom of the file.

Using Chef Infra server gives you the opportunity to create this configuration dynamically. You can learn more about Chef Infra Server here.

Let's apply this configuration and see it all working.

Terminal: /root

#   
chef-run lb loadbalancer[✔] [lb] Connected.[✔] [lb] Successfully installed Chef client version 14.3.37[✔] [lb] Successfully converged target!

As we did previously, we just have to provide the name of the server lb and the cookbook loadbalancer.

Congratulations, you have setup a three node system with Chef Infra!

Terminal: /root

#         
curl lb<html>  <head>    <title>Learn Chef Demo</title>  </head>  <body>    <h1>Hello Learn Chef</h1>    <p>This is ae6574960e01</p>  </body></html>

Clean up

You can experiment more with your setup. When you're done experimenting, run exit to leave your workstation container.

Terminal: /root

#
exit

You can run the following docker-compose down command to destroy your setup.

Keep in mind that this command will destroy your setup and delete the Docker images from disk. Omit the --rmi all argument to delete the containers but keep the images on disk.

Terminal: ~/try-chef

$
sudo docker-compose down --rmi all

To bring up a fresh installation, run the docker-compose up -d command as you did in step 2.