Class Containment in Puppet
Class containment is an advanced topic in Puppet, and something that new users often find confusing. In the past, elaborate systems of anchor
resources needed to be created to order classes effectively, and this sometimes led to difficult-to-diagnose dependency cycles.
The purpose of containment, in general, is to let you control where and when certain parts of your Puppet code are executed. Containment offers granular control by operating on the level of individual resources, and it also offers tremendous power by operating at the class level.
But let’s step back for a minute.
What Is Containment?
Containment is the policy of Puppet Labs to prevent the spread of “ssh-and-a-for-loop” in IT shops throughout the world by rogue sysadmins.
Just kidding.
Containment, put simply, is the relationships that resources have to the classes and defined types that "contain" them.
As per the docs:
Classes and defined type instances contain the resources they declare. Any contained resources will not be applied before the container is begun, and will be finished before the container is finished.
So for example, if we run puppet apply
with the following manifest:
we receive the following output:
You'll notice that the begin
notify resource fires before all the notify resources in the class myklass
, just as the end
notify resource is applied after all of the notify resources in the class myklass
. This is because the relationships that the begin
and end
notify resources have with the class myklass
apply to the resources within myklass
as well. This is "containment".
If you're using a version of Puppet Enterprise prior to 3.2.0, or Open Source Puppet earlier than 3.4.0, please refer to the section lower down in this post with the subhead, "Class Containment Prior to Puppet Enterprise 3.2.0 (or Open Source Puppet 3.4.0)."
What is Class Containment?
Where this gets more complicated, and often leads to confusion is with class declaration.
Given the following example, what would you expect to happen?
It looks like the notify { 'foobarbaz': }
resource will be applied before notify { 'bazbarfoo': }
due to the class ordering, but this turns out to not be the case:
This is because classes never contain the classes they include. So even though classa
and classb
are ordered, the classes they include are not.
It can be a little confusing, because the words "contain" and "include" are so similar, but they have different meanings in Puppet syntax.
Why is this the case? Well, for one thing, classes can be included multiple times. Imagine for example the following scenario:
If classes were contained each time they were included, Puppet would have no way of knowing what order to apply the resources within the ntp
class, in the above example.
Therefore, in order to allow classes to be included an unlimited number of times per node, classes never contain the classes they include.
Because classes don't contain other classes, Puppet will happily apply the catalog generated from the manifest above, including the ntp
class.
When Should I Care About Class Containment?
Whenever you care about forming ordering relationships against a module that contains subclasses, you likely care about class containment. For example, if you are building an Apache module that you plan to reuse multiple times, and it contains a top level apache
class that can be declared directly, as well as several subclasses (think apache::package
, apache::file
, apache::service
, but it can often be much more complex than that). If you want those subclasses to be affected by ordering against the main class, you'll need to deal with class containment.
This comes into play especially when you're writing modules you'll want to re-use, like a generic MySQL module. For example, if you look at the puppetlabs/mysql module on GitHub, you'll see that the class that manages the installation of the server components uses the "anchor pattern" (more on that below), to contain its subclasses. This allows you to write other modules that can reliably depend on MySQL being configured in a certain order during the Puppet run, either before or after other resources and classes.
Dealing with Class Containment
Let's imagine that we're spinning up a web stack on a single node, that includes a database like MySQL and a web server like Apache.
We've written manifests to manage all this, but it's critical in our infrastructure that the database be operational before the web server is brought online, so that the web application can actually function.
To that end, we've written the following puppet code:
In the above example, we've not successfully ordered the MySQL and Apache classes, even though we've ordered the classes that include them.
There are two ways to achieve succesful ordering, depending on which version of Puppet you're running.
Class Containment in Puppet Enterprise 3.2.0 (Puppet 3.4.0) and later
If you're using Puppet Enterprise 3.2.0 (or POSS 3.4.0) or later, you're in luck! As of 3.2.0, we've implemented the contain
function.
If we replaced include mysql
and include apache
, with contain mysql
and contain apache
, the ordering in the roles::ecommerce_app
class from our previous example would extend to the resources in the "contained" classes:
It's important to be extremely careful with the contain
function. For example, the following code:
Will generate a catalog successfully:
But once modified to include ordering:
Catalog compilation will fail:
Because we've "contained" class a
and class b
in multiple locations, and then set order between those locations (classes) containing them, Puppet isn't sure what we're trying to do, and we've got a dependency cycle.
Class Containment Prior to Puppet Enterprise 3.2.0 (or POSS 3.4.0)
If you're using a version of Puppet Enterprise prior to 3.2.0 (or POSS 3.4.0), you'll need to use the anchor pattern.
Basically this is the same as contain, but a bit more verbose, in that it requires you to essentially "bookend" classes you'd like to contain using dummy anchor
resources.
That's about it for class containment, including both the anchor pattern and the contain function.
Zee Alexander is a support engineer at Puppet Labs.
Learn More
- If you've got additional questions about this topic, check out ask.puppetlabs.com.
- Pop into the #puppet channel on Freenode.
- Pose your questions in the Puppet Users Google group.
- If you're a Puppet Enterprise customer, you can always contact us directly via the Support Portal.
- Not using Puppet Enterprise yet? Try it out on 10 nodes for free.