Class Containment in Puppet

Breadcrumb

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.

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.

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 nevercontain 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.

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:

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.

It may be worth pointing out that include means "include in catalog" - this helps set the concepts class containment and inclusion apart. Even though we say "class x is included in class y", what we really mean is "class y added x to the catalog (if it was not already there)"

Also note that currently there is a bug in the contain function, which prevents you from using it with fully qualified classes, which is a major showstopper in many use cases. More info here: https://jira.puppetlabs.com/browse/PUP-1597

Levi, according to the language docs linked in the post, using the resource-like declaration still doesn't contain that class, so you have to follow your "class { 'mco' : ... }" with another line to say "contain mco".