Resource ordering
Quest objectives
- Understand why some resources must be managed in a specific order.
- Use the
before
,require
,notify
, andsubscribe
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.
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.
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.
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.