Environments are a feature of Chef Server used to model the server configurations required during each phase of your software development lifecycle, as shown in Figure 15-1.
Environments reflect your patterns and workflow, and can be used to model the life stages of your application, such as:
- Development
- Testing
- Staging
- Production
Every Chef Server starts out with a single environment, the
_default
environment.
Environments might include attributes necessary for configuring your infrastructure, such as:
- The URL of a payment service API
- The location of a package repository
- The version of Chef configuration files that should be used
Environments allow for isolating resources on a Chef Server because environments can contain version constraints, unlike with roles. Environments still have a use even when you have Test Kitchen at your disposal, because you’ll probably want to do some testing against servers in your production environment.
Create a Dev Environment
Environments can be created and managed in the same fashion as data bags and roles, organized in a directory under chef-playground. The directory name is environments 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:
Make sure that the chef-playground directory is the current working directory:
Create an
environments
directory in chef-playground:
We’re going to create a .json file representing the new environment. A basic environment has a
name:
and description:
. Environments can have one or more cookbook constraints as well. The ability to “pin” cookbooks to particular versions is the most useful feature of environments. Create the file chef-playground/roles/dev.json containing the code provided in Example 15-1.NOTE
There are other options for a version constraint besides “equal to” (=). Equality is the recommended practice. To learn more about the other options, refer to http://bit.ly/abt_cookbook_versions.
Run
knife environment from file
passing in the dev.json file. knife environment from file
assumes dev.json is located in a subdirectory called environments, and not in the current directory:
Run
knife environment show dev
as follows to display the details about the dev
environment:Attributes and Environments
Environments can contain attributes. Let’s experiment with this by creating a .json file to represent a
production
environment. This environment will pin production to the older version of the apache
cookbook, the 0.1.0
that is not currently under development. We’ll also make sure that in the production environment, the message of the day is set to a suitable message of the day for production. Create the file chef-playground/environments/production.json with the code provided in Example 15-2.
Then run
knife environment from file
passing in the production.json file, as follows:knife show environment
displays more detailed information about an environment:
Things get a little more complicated with attribute precedence when environments come into the picture.Figure 15-2 shows that an environment has a priority less than a role, but greater than a cookbook recipe or attribute file.
Because an environment can override and pin specific cookbook versions, it seems more reasonable that environments should have a higher priority than the default. So in this particular instance with environments, it does make sense to use
override_attributes
instead of default_attributes
for any environment attributes.Putting All the Pieces Together
Let’s go through a complete example using the
apache
cookbook from Chapter 7, making use of environments and roles as you would in a production Chef setup.SIMULATE A PRODUCTION ENVIRONMENT
Create a directory called chef-zero. This will be structured similar to chef-repo in Chapter 9 and chef-playground, with cookbooks, environments, and roles as subdirectories. Once you create the directory, make it the current working directory as follows:
Create a chef-zero/environments subdirectory to contain our environment definitions in the JSON file format, and make it the current working directory:
Let’s say our
apache
cookbook is ready to go and we are making use of attributes, environments, and roles in our production environment. First, let’s simulate the production environment with Test Kitchen.
Create an environment definition in chef-zero/environments as shown in Example 15-3. This will represent a
production
environment, like we covered earlier in this chapter. There is also an attribute set for node['motd']['message']
as an attribute with override precedence.
Our production environment uses roles. Create a directory parallel to
environments
called roles
, and make it the current working directory, as follows:
In our production environment, we use a
webserver
role just like we covered in Chapter 14 to denote nodes that are web servers. Create the file chef-zero/roles/webserver.json as shown in Example 15-4. It contains the apachedirectory in its run list and the attribute node['apache']['port']
set at the default attribute precedence. We’ll be using this attribute to show how we can change the behavior of the cookbook depending on whether the node is in production.
Create a directory called cookbooks, parallel to the others you have created so far in this chapter, and make it the current working directory:
So far, your chef-zero directory should resemble the following structure:
We’ll be recreating a version of the
apache
cookbook for this chapter, with a few additions. Create an apache
cookbook in the cookbooks
subdirectory by using chef generate cookbook
or knife cookbook create
, as per your Chef development tool setup.
Chef Development Kit:
Chef Client:
Create a .kitchen.yml file as shown in Example 15-5. There’s a lot more going on in this version than we have seen in previous chapters.
For this example, we must use the
chef_zero
provisioner because we are making use of Chef Server features, so make sure that is being set in the provisioner:
stanza of the .kitchen.yml. We need Test Kitchen to spin up a Chef Zero instance for us.
Also in the
provisioner:
stanza, we tell Test Kitchen where the roles and environments directories are relative to the location of the .kitchen.yml:
Don’t miss that we are setting the Test Kitchen suite name to be
prod
in the suites:
stanza. We’re using a special suite name in this chapter because eventually there are going to be two suites, one for each environment, and we need a way of telling them apart. The suite for production will have the suite name prod
.
We’re also introducing some new syntax in the
suites:
stanza. We set the environment for our sandbox node in its /etc/chef/client.rb, like so:
Nodes can be a member of only one environment at a time. The environment is a setting in the /etc/chef/client.rbfile. If this is not set, a node uses the default environment named
_default
.
Outside of this simulated setup, you would use the
chef-client::config
recipe to change the value of the environment
setting in /etc/chef/client.rb, using the following node attribute, similar to how we set ssl_verify_mode
in Chapter 10:
We also show how a
private_network
IP address can be set in the suites:
stanza instead of the provisioner:
stanza:
When a value is set in the
provisioner:
stanza in Test Kitchen, the values are inherited by all the items in the suites:
stanza. In this case, we’re going to want our production and dev sandbox environments to have different IP addresses, so we move the private_network
setting to be under suites
.
Check the syntax of your .kitchen.yml with
kitchen list
. The output should resemble the following:
Edit apache/metadata.rb, filling in the
maintainer
, maintainer_email
, and license
. We filled in ours like in Example 15-6.
Because we will be using attributes in this version of the
apache
cookbook, create a default.rb attributes file.
Chef Development Kit:
Chef Client:
Provide default settings for all the attributes we’re going to be using in our cookbook by creating attributes/default.rb as shown in Example 15-7. In order to test attribute precedence, we’re going to set the default values for
node['apache']['port']
and node['motd']['message']
to something different than what is being set in the role and in the environment we are using. We also moved the root location for our index.html file to an attribute.
Create the recipe file recipes/default.rb as shown in Example 15-8. Most of this recipe code should look familiar.
We are adding a new
template
resource to create a custom.conf file on the sandbox node, along with an accompanying directory
resource to create the required directory on the node. custom.conf is an optional file used to configure apache
web server settings. In this file we’re going to set the default listening port and the document root.
We are introducing an alternative
template
resource syntax:
We covered the use of
notifies
in Chapter 9.
You can pass a hash of variables to be used when the template file is evaluated using the
variables()
attribute. This is a way to pass local instance variables in a recipe to a template, or to use shorter, more memorable variable names in the template file.
Generate the template file templates/default/index.html.erb, using the appropriate command line for your Chef development setup.
Chef Development Kit:
Chef Client - Linux/Mac OS X:
Chef Client - Windows:
Create the file templates/default/index.html.erb as shown in Example 15-9. We are using the short variable instance forms we defined in the
variables()
attribute of the template resource. Also, for some variety, we left one of them as the standard form: node["ipaddress"]
. You can mix and match these forms as you like.
Generate one more template file, templates/default/custom.erb, which will be used as an
apache
configuration file.
Chef Development Kit:
Chef Client - Linux/Mac OS X:
Chef Client - Windows:
Create templates/default/custom.erb as shown in Example 15-10. We’re using this optional
apache
configuration file to set the port the server is listening on via the Listen
setting and the DocumentRoot
.
We will explain the
if
syntax in the template in more detail in Chapter 16. In short for now, you can place conditional logic in templates when it is enclosed by <% %>
(vs. <%= %>
when you want to evaluate a string). Also, if the closing tag has a minus sign in it, such as -%>
, the line is removed from the resultant template output when it is evaluated. Therefore, in Example 15-10, these three lines are processed when the template file is evaluated: <% if @port != 80 -%>
, Listen <%= @port %>
, and <% end -%>
. When the evaluated output is written to the resultant template file, it becomes just one line, because there are -%>
symbols on the first and the third lines: Listen <%= @port %>
. Further, the single line with Listen <%= @port %>
is only written to the resultant template file if the conditional logic evaluates to a port number besides 80.
We need this conditional logic in the template because the
Listen
line is required in the .conf file when any port besides 80
is used. If we left out the conditional, we’d get an error configuring the website if it evaluates to port 80
.
Run
kitchen
converge to deploy your cookbook to the sandbox node using the production
environment:
If all goes well, you should be able to view the production website on the sandbox node at http://192.168.33.15on the default web port 80—it should resemble Figure 15-3. The port 80 setting in the role overrides the default attribute set in the
apache
cookbook. Also, the message attribute set in the environment takes precedence.SIMULATE A DEVELOPMENT ENVIRONMENT
Let’s say we want to start a new development cycle for our
apache
cookbook, adding some new requested functionality. For the purposes of this chapter, we don’t care what the enhancements are, we just want our new cookbook development not to interfere with the stable 0.1.0
version we already have in production. We will perform our development on a node allocated to an environment called dev
.
First, before doing anything else, increment the current cookbook version
0.1.0
to the next minor version number 0.2.0
. We recommend that you follow semantic versioning guidelines when you version your cookbooks, incrementing the second digit when there are new changes that won’t break existing functionality. This is the intent with these hypothetical changes we might make to the apache
cookbook.
If you try to deploy this new cookbook to the production node, you should get an error saying could not satisfy version constraints. Now we know that our production environment is enforcing the policy we set to pin the
apache
cookbook to version 0.1.0
. When we tried to deploy version 0.2.0
, we got an error:
Add a new environment definition to
chef-zero/environments
as shown in Example 15-12. We’ll pin the environment to use the latest cookbook version 0.2.0
. Also, set the node['apache']['port']
and node['motd']['message']
to use developer-specific overrides.
Add a new
dev
suite to chef-zero/cookbooks/apache/.kitchen.yml as shown in Example 15-13. It’s in the same format as our prod
instance—it just uses the dev
environment and the IP address 192.168.33.16
.
Run
kitchen list
to check your .kitchen.yml syntax. Now you should see two instances, like so:
Run
kitchen converge
against the dev-centos65
instance, as follows:
If all goes well, you should be able to view the development website on the sandbox node at http://192.168.33.16:8080—it should resemble Figure 15-4. The port 8080 setting in the environment overrides the default attribute set in the
apache
cookbook and in the role. Also, the message attribute set in the environment takes precedence.Summary
In this chapter we have covered environments and how they provide the ability to fix settings to match the different stages of your deployment workflow. We also walked you through a realistic example that made use of both environments and roles to change cookbook behavior. You can use environments to match the promotion model you use as your Chef code travels from development to production.
In the next, and final, chapter of this book, we’re going to show you how to test your automation code.
No comments:
Post a Comment