Tuesday 3 January 2017

Chapter 14. Roles

Roles are a way of classifying the different types of services in your infrastructure, as shown in Figure 14-1.
Roles overview
Figure 14-1. Roles overview
Roles can be used to represent the types of servers in your infrastructure:
  • Load balancer
  • Application server
  • Database cache
  • Database
  • Monitoring
Although you can add recipes directly to a node’s run list, that’s not how your infrastructure works. Think about how you normally refer to servers:
  • “It’s a web server.”
  • “It’s a database server.”
  • “It’s a monitoring server.”
Roles allow you to conveniently encapsulate the run lists and attributes required for a server to be what you already think it is. Roles make it easy to configure many nodes identically without repeating yourself each time.
In addition to obvious roles, such as a “web server,” it is common practice to group any functionality that goes together into a role. The most common example is a base role, where you include all the recipes that should be run on every node.

Create a Web Server Role

Roles can be created and managed in the same fashion as data bags—there is a directory under chef-playgroundin which they are organized. The directory name is roles by default.
Use the chef-playground directory you created in Chapter 11. Use the same dual command prompt setup you used there. Start the chef-zero server on an open port in one window. We will be using port 9501 in the examples in this chapter:
$ chef-zero --port 9501
Make sure the chef-playground directory is the current working directory:
$ cd chef-playground
Now run knife upload nodes to load up chef-zero with fake node data:
$ knife upload nodes
Created nodes/snowman.json
Created nodes/atwood.json
Created nodes/susu.json
Create a roles directory in chef-playground:
$ mkdir roles
We’re going to create a .json file representing the role data. A basic role has a name:description:, and run_list. The role can be used to encapsulate a long list of recipes into just one alias. Create file chef-playground/roles/webserver.json with the code in Example 14-1.
Example 14-1. chef-playground/roles/webserver.json
{
  "name": "webserver",
  "description": "Web Server",
  "json_class": "Chef::Role",
  "chef_type": "role",
  "run_list": [
    "recipe[motd]",
    "recipe[users]",
    "recipe[apache]"
  ]
}
Then run knife role from file passing in the webserver.json file. Similar to data bags, knife role from file assumes webserver.json is located in a subdirectory named roles, and not in the current directory.
$ knife role from file webserver.json
Updated Role webserver!
Run knife show role as follows to display the details about the webserver role:
$ knife role show webserver
chef_type:           role
default_attributes:
description:         Web Server
env_run_lists:
json_class:          Chef::Role
name:                webserver
override_attributes:
run_list:
  recipe[motd]
  recipe[users]
  recipe[apache]
You can reset a node’s run list with the knife node set command. Change the run list of the snowman node to use the webserver role you just created, using the following command on Linux/Mac OS X:
$ knife node run_list set snowman "role[webserver]"
snowman:
  run_list: role[webserver]
or, on Windows:
$ knife node run_list set snowman "'role[webserver]'"
snowman:
  run_list: role[webserver]
During the Chef run, the reference to the web server role will be expanded to the entries in the role’s run list:
  • recipe[motd]
  • recipe[users]
  • recipe[apache]
Roles are a powerful abstraction that let you think of your infrastructure as arrays of functionality. It is quite common for a role to contain dozens of recipes. Imagine needing to assign dozens of recipes to the run list of, say, hundreds of nodes. Roles make this process much easier.

Attributes and Roles

Roles can contain attributes as well.
Create a .json file to represent a base role. This role will include references to the chef-client::delete_validation and chef-client::default recipes, both of which we recommended running on every node in Chapter 10. In this case, we’ll also set an attribute to tell the chef-client::defaultrecipe to set the init_style to use runit instead of the default. Create the file chef-playground/roles/base.json with the code provided in Example 14-2.
Example 14-2. chef-playground/roles/base.json
{
  "name": "base",
  "description": "Common recipes for all nodes",
  "json_class": "Chef::Role",
  "chef_type": "role",
  "run_list": [
    "recipe[chef-client::delete_validation]",
    "recipe[chef-client]"
  ],
  "default_attributes": {
    "chef_client": {
      "init_style": "runit"
    }
  }
}
Then run knife role from file passing in the webserver.json file. Similar to data bags, knife role from file assumes webserver.json is located in a subdirectory named roles, and not in the current directory:
$ knife role from file base.json
Updated Role base!
When you run knife role show base as follows, notice that the role has attributes set as well as items in a run list:
$ knife role show base
chef_type:           role
default_attributes:
  chef_client:
    init_style: runit
description:         Common recipes for all nodes
env_run_lists:
json_class:          Chef::Role
name:                base
override_attributes:
run_list:
  recipe[chef-client::delete_validation]
  recipe[chef-client]
As we discussed in Chapter 8, it is recommended that you restrict your use of attributes in roles to those of default priority, to make it easier to follow the composition of attributes when they come from multiple sources.
Because roles can have attributes, they have a place in the attribute hierarchy of precedence. Figure 14-2 is a modified version of the attribute precedence diagram we showed you in Figure 8-3 that includes roles. Roles can override attributes defined in recipes or attribute files, but they have a lower priority than the automatic attributes defined by ohai. Attribute settings in roles are intended to be global settings that override attributes set within cookbooks.
Roles overview
Figure 14-2. Roles overview
Roles can be search items as well. The following example shows how you can search for a recipe in the run list of a role. Note that you must use the \ character to escape the [ and ] characters in the query string:
$ knife search role "run_list:recipe\[apache\]"
1 items found

chef_type:           role
default_attributes:
description:         Web Server
env_run_lists:
json_class:          Chef::Role
name:                webserver
override_attributes:
run_list:
  recipe[motd]
  recipe[users]
  recipe[apache]
Because roles introduce the idea that a list of recipes in a run list can be expanded, there are two ways to search for recipes in a node’s run list:
knife search node "recipe:<recipe_name>"
When you do not want the search to include the expanded set of recipes within roles
knife search node "recipes:<recipe name>"
When you do want the search to expand role references
Recall that earlier in the chapter we assigned the snowman node to have the webserver role in its run list. Implicitly, when the role reference is expanded during a Chef run, the node will run the following recipes in the webserver role’s run_list:
  • recipe[motd]
  • recipe[users]
  • recipe[apache]
However, if you perform a recipe: search for, say, the recipe[apache::config], you might not get the results you intended:
$ knife search node "recipe:apache"
2 items found

Node Name:   atwood
...
Node Name:   susu
...
Notice when the reference to "role[webserver]" is expanded, snowman does have "recipe[apache]" in its run list. But it doesn’t have "recipe[apache]" directly in its run list if it is not expanded. So, snowmandoes not show up in the search results because the recipe search does not expand the node’s run list.

NOTE

Similar to the square bracket characters ([]), the colon (::) characters in a cookbook recipe reference must be individually escaped on a command line with the backslash (\) character: chef-client\:\:config. This is treated as if it were chef-client::config.
If you want to fully expand all the recipe references in a run list, perform a recipes: search instead. Then snowman shows up in the search results:
$ knife search node "recipes:apache"
3 items found

Node Name:  atwood
...
Node Name:  snowman
...
Node Name:  susu
...
There are similar search commands with expansion for roles as there are for recipes:
knife search node role:<role_name>
When you do not want the search to include the expanded set of role references
knife search node "roles:<role_name>"
When you do want the search to expand role references

Role Cookbook

Another issue related to the expansion of roles is that when a change gets made to a role, it gets reflected immediately across your entire infrastructure. Roles are not versioned in any way.
This usually has the most impact with run lists. Say, for example, one of your Chef infrastructure developers decided to remove the recipe[apache] role from the webserver role we have been using in this chapter. We’ll say this developer made the change because she didn’t want web servers to default to using the Apache web server, but instead wanted to offer cookbook developers the choice of using the Apache or Nginx web servers for their apps:
{
  "name": "webserver",
  "description": "Web Server",
  "json_class": "Chef::Role",
  "chef_type": "role",
  "run_list": [
    "recipe[motd]",
    "recipe[users]"
  ]
}
If the webserver role were used widely across your infrastructure, this could have unintended consequences for cookbooks that assumed the old behavior where the recipe[apache] was included in the role. Or conversely, if developers are careful not to make changes to the run lists of existing roles, it can result in a proliferation of differently named roles with similar functions. For example, that Chef developer might have instead chosen to create two new roles—webserver-apache and webserver-nginx—to make her intention to complement the existing webserver role more clear.
Because cookbooks are versioned, a pattern of using a role cookbook in lieu of using the run list feature of roles is one technique many Chef developers use. They still use roles for common attributes, but the role run list is moved to a cookbook. A recipe can emulate a role run list easily through the use of the include_recipecommand we introduced in Include_Recipe.
For example, in this case, we could create a webserver cookbook where the default recipe includes the apachecookbook:
#
# Cookbook Name:: webserver
# Recipe:: default
#
# Copyright (C) 2014
#
#
#

include_recipe "motd"
include_recipe "user"
include_recipe "apache"
Nodes could still include the webserver role as a classification mechanism and for any shared attributes. It can still be handy to run this command to find all the web servers on your network:
knife search node role:webserver

NOTE

chef-zero currently does not seem to index roles for searching, so the preceding command will not work with the test setup in this chapter. Instead, you’ll need to use a full Chef Server setup.
In this scenario, the run list of the webserver role would be blank, and instead nodes would add the cookbook recipe[webserver] to their run list. Cookbooks are versioned, and with environments, which we’ll introduce in the next chapter, you can ensure that a subset of nodes in your infrastructure is fixed to use specific versions of a cookbook. This is referred to as version pinning a node (or pinning a node).

Summary

We covered roles in this chapter. Roles provide a way to classify patterns of use in your infrastructure. Roles can contain attributes and a list of recipes and other roles as a run list. This allows you to package all the settings for a node configuration into a single role reference.
In the next and final chapter of this book, we’ll cover environments, which provide a different code of abstraction—a way to map your organization’s app deployment workflow to a set of server configurations and cookbook versions.

No comments:

Post a Comment