Resource ordering

Quest objectives

  • Understand why some resources must be managed in a specific order.
  • Use the before, require, notify, and subscribe metaparameters to specify the order in which Puppet applies resource declarations.

Getting started

This quest will help you learn more about specifying the order in which Puppet should manage resources in a manifest. When you're ready to get started, type the following command:

quest begin resource_ordering

Resource order

So far, the modules you've written have been pretty simple. We walked you through minimal examples designed to demonstrate different features of Puppet and its language constructs. Because you've only handled a few resources at a time in these cases, we haven't been worried about dependencies among those resources.

When you start tackling more complex problems, it will quickly become clear that things have to happen in the right order. You can hardly configure a package before it has been installed, or give ownership of a file to a user you haven't yet created.

So how does Puppet manage these relationships?

Remember, in a declarative language like Puppet you're describing a desired state for a system, not listing the steps required to achieve that state. Because Puppet manifests describe a state, not a process, you don't get the implicit linear order of steps you would from an imperative language. Puppet needs another way to know how to order resources.

This is where resource relationships come in. Puppet's resource relationship syntax lets you explicitly define the dependency relationships among your resources.

Though there are a couple ways to define these relationships, the simplest is to use relationship metaparameters. A metaparameter is a kind of attribute value pair that tells Puppet how you want it to implement a resource, rather than the details of the resource itself. Relationship metaparameters are set in a resource declaration along with the rest of a resource's attribute value pairs.

If you're writing a module to manage SSH, for instance, you will need to ensure that the openssh-server package is installed before you try to manage the sshd service. To achieve this, you include a before metaparameter with the value Service['sshd']:

package { 'openssh-server':
  ensure => present,
  before => Service['sshd'],
}

You can also approach the problem from the other direction. The require metaparameter is the mirror image of before. require tells Puppet that the current resource requires the one specified by the metaparameter.

Using before in the openssh-server package resource is exactly equivalent to using require in the sshd service resource:

service { 'sshd':
  ensure   => running,
  enable   => true,
  require  => Package['openssh-server'],
}

In both of these cases, take note of the way you refer to the target resource. The target's type is capitalized, and followed by an array (denoted by the square brackets) of one or more resource titles:

Type['title']

We've already covered a couple of the resources you'll need, so why not make a simple SSH module to explore resource relationships?

Task 1:

To get started with your module, create an sshd directory with examples, manifests, and files subdirectories.

cd /etc/puppetlabs/code/environments/production/modules
mkdir -p sshd/{examples,manifests,files}

Task 2:

Create an sshd/manifests/init.pp manifest and fill in your sshd class with the openssh-server package resource and sshd service resource. Don't forget to include a require or before to specify the relationship between these two resources. (If you need a hint, feel free to refer back to the examples above!)

When you're done, use the puppet parser validate command to check your manifest.

Before we add the file resource to manage the the sshd configuration, let's take a look at the relationship between the package and service resources from another perspective: the graph.

When Puppet compiles a catalog, it generates a graph that represents the network of resource relationships in that catalog. Graph, in this context, refers to a method used in computer science and mathematics to model connections among a collection of objects. Puppet uses a graph internally to determine a workable order for applying resources, and you can access it yourself to visualize and better understand these resource relationships.

Task 3:

The quickest way to get Puppet to generate a graph for this kind of testing is to run a test manifest with the --noop and --graph flags. Go ahead and set up an sshd/examples/init.pp manifest. You don't have any parameters here, so you can use a simple:

include sshd

With this done, run a puppet apply on your test manifest with the --noop and --graph flags:

puppet apply sshd/examples/init.pp --noop --graph

Task 4:

Puppet outputs a .dot file to a location defined as the graphdir. You can find the graphdir location with the puppet config print command:

puppet config print graphdir

Use the dot command to convert the relationships.dot file in the graphdir into a .png image. Set the location of the output to the root of the Quest Guide's web directory so that it will be easily viewable from your browser.

dot -Tpng /opt/puppetlabs/puppet/cache/state/graphs/relationships.dot -o /var/www/quest/relationships.png

Take a look at <VM'S IP>/relationships.png. Notice that the openssh-server and sshd resources you defined are connected by an arrow to indicate the dependency relationship.

image

Task 5:

Now let's move on to the next step. We'll use a file resource to manage the sshd configuration. First, we'll need a source file. As you did for the vimrc file in the Modules quest, you can copy the existing configuration file into your module's files.

cp /etc/ssh/sshd_config sshd/files/sshd_config

You will also need to ensure that the pe-puppet user has permissions to read this file.

chown pe-puppet:pe-puppet sshd/files/sshd_config

Task 6:

Of course, SSH is already reasonably configured on the Learning VM, but for the sake of example, let's make a change so you can see how Puppet handles it. We're not using GSS API Authentication, so you can improve connection performance by setting the GSSAPIAuthentication setting to no. Open the sshd/files/sshd_config file and find the GSSAPIAuthentication line. Change the setting to no, then save the file and exit your editor.

Task 7:

With the source file prepared, go back to your sshd/manifests/init.pp manifest and add a file resource to manage the sshd_config file. You want to ensure that this file resource is applied after the openssh-server package, so include a require metaparameter targeting that resource.

class sshd {

  ...

  file { '/etc/ssh/sshd_config':
    ensure     => present,
    source     => 'puppet:///modules/sshd/sshd_config',
    require    => Package['openssh-server'],
  }

}

Task 8:

Apply your test manifest again with the --graph and --noop flags, then use the dot tool again to regenerate your graph image.

dot -Tpng /opt/puppetlabs/puppet/cache/state/graphs/relationships.dot -o /var/www/quest/relationships.png

Check <VM'S IP>/relationships.png again to see how your new file resource fits in.

image

You can easily see from the graph diagram that both the file and service resources require the package resource. What's missing from the picture so far? If you want your configuration changes to have an effect, you will have to either make those changes before the service is started, or restart the service after you've made your changes.

Puppet uses another pair of metaparameters to manage this special relationship between a service and its configuration file: notify and subscribe. The notify and subscribe metaparameters establish the same dependency relationships as before and require, respectively, and also trigger a refresh whenever Puppet makes a change to the dependency.

While any resource can be the dependency that triggers a refresh, there are only a couple of resource types that can respond to one. In the following task, we'll look at service which should already be familiar to you. (The second is called exec, and the details of how it works are beyond the scope of this quest.)

Like before and require, notify and subscribe are mirror images of each other. Including a notify in your file resource has exactly the same result as including subscribe in your service resource.

Task 9:

Edit your sshd/manifests/init.pp manifest to add a subscribe metaparameter to the the sshd resource.

class sshd {

  ...

  service { 'sshd':
    ...
    subscribe => File['/etc/ssh/sshd_config'],
  }

  ...

}

Validate your syntax with the puppet parser tool. When your syntax looks good, apply your test manifest with the --graph and --noop flags, then use the dot tool again to regenerate your graph image again.

Check <VM'S IP>/relationships.png one more time. Notice that the sshd resource now depends on the /etc/ssh/sshd_config file.

image

Finally, drop the --noop flag to actually apply your changes. You'll see a notice that the content of the config file has changed, followed by a notice for the 'refresh' for the sshd service.

Chaining arrows

Chaining arrows provide another means for creating relationships between resources or groups of resources. The appropriate occasions for using chaining arrows involve concepts beyond the scope of this quest, but for the sake of completeness, we'll give a brief overview.

The -> (ordering arrow) operator causes the resource to the left to be applied before the resource to the right.

The ~> (notification arrow) operator causes the resource on the left to be applied before the resource on the right, and sends a refresh event to the resource on the right if the left resource changes.

Though you may see chaining arrows used between resource declarations themselves, this generally isn't good practice. It is easy to overlook chaining arrows, especially if you're refactoring a large manifest with many resources and resource relationships.

So what are chaining arrows good for? Unlike metaparameters, chaining arrows aren't embedded in a specific resource declaration. This means that you can place chaining arrows between resource references, arrays of resource references, and resource collectors to concisely and dynamically create one-to-many or many-to-many dependency relationships among groups of resources.

Autorequires

Autorequires are relationships between resources that Puppet can figure out for itself. For instance, Puppet knows that a file resource should always come after a parent directory that contains it, and that a user resource should always be managed after the primary group it belongs to has been created. You can find these relationships in the type reference section of the Puppet Docs page, as well as the output of the puppet describe tool.

For example,

puppet describe user | less

Will include the following:

**Autorequires:** If Puppet is managing the user's primary group (as
provided in the `gid` attribute), the user resource will autorequire
that group. If Puppet is managing any role accounts corresponding to the
user's roles, the user resource will autorequire those role accounts.

This means that if your catalog contains a resource declaration for a user and its primary group, Puppet will know to manage that group first, before moving on to the user. Note that these relationships between resource types are only documented in the type reference for the requiring resource type (e.g. user), not the required resource type (e.g. group).

Review

In this Quest, you learned how to specify relationships between resources. These relationships let you specify aspects of the order Puppet follows as it applies resources. You learned how to use the --graph flag and dot tool to visualize resource relationships, and how to use notify and subscribe to refresh a service when a related configuration file changes. Finally, you learned about chaining arrows, an alternate syntax for specifying resource relationships, and autorequires, Puppet's built-in knowledge about how some resource types should be ordered.

results matching ""

    No results matching ""