How to use the ordered steps pattern to create better maintainable Puppet code
Managing the order of execution for your Puppet classes is important to ensure that configuration is completed in the correct order. But there are so many ways how you can do this. What is a useful and maintainable way to do this? The ordered steps pattern allows you to manage the order of execution for classes. This article will discuss how to use the ordered steps pattern and provide some tips for creating maintainable Puppet code.
What are Puppet patterns, and why are they useful?
Puppet patterns help you design and organize your Puppet code. By using patterns, you can make your code more maintainable and easier to understand. The fact that a pattern has a name also facilitates discussion about it in your team and ensures familiarity to the team members when they see it. They know how it works. If you’d have to give a definition for a Puppet pattern, this would be one (based on the definition of a software design pattern):
It is not a finished design that can be transformed directly into Puppet code. Rather, it is a description or template for how to solve a problem that can be used in many different situations.
There are several different Puppet patterns, and each one is useful in different situations. One of the best-known Puppet pattern is the roles and profiles pattern. This is a way of structuring your code so that you can easily reuse parts of it in different places. The roles and profiles pattern is especially useful for large Puppet deployments, where you might have hundreds of different classes. The pattern allows you to make composable and reusable Puppet building blocks. These building blocks are called profile classes. You can then pick multiple profile classes together and build Puppet code that ensures a certain specific configuration. These classes are called role classes.
You can find more about the roles and profiles pattern hereand here.
What is the ordered steps pattern, and what are its benefits?
The ordered steps pattern is a Puppet pattern that allows you to manage the order of execution of classes. This can be useful for classes that need to be executed in a specific order or when you want to ensure that a particular class is always executed before another task.
There are several benefits to using the ordered steps pattern:
- It helps ensure that classes are executed in the correct order.
- It can help improve the maintainability of your Puppet code.
- It can help reduce errors caused by incorrect ordering of classes.
This case really presents itself when writing roles and profile classes.
Let’s look at an contrived example. You need to write Puppet code to ensure the installation and maintenance of your Oracle database on your Linux systems. When you look at the Oracle documentation, you see that these steps need to be executed before the Oracle software can be installed:
- Ensure correct sysctl settings are applied
- Ensure correct OS users and groups are created.
- Ensure correct limits are sets
- Ensure correct required packages are installed.
After the installation of the Oracle software, you want to ensure the database gets configured the correct way. You need to execute the next steps:
- Create the database
- Create the database users
- Create the database storage setup (tablespaces)
- etc.
Our first attempt.
This is how your Puppet code could look like:
class oracle_database {
include systl
include os_users_and_groups
include limits
include packages
include oracle_software
include database
include database_users
include database_storage
}
As you can see, all of the steps are available in the Puppet manifest and the code is pretty good readable. There are, however, some problems with this code. Although the order in which the code is written equals the requested order, Puppet does not enforce this order. It might go correct (Puppet manifest order), But it might also execute the classed in another order. This can change depending on other Puppet code included in your manifest.
Second attempt
Let’s fix that. To fix this, we include explicit ordering.
class oracle_database {
include systl
include os_users_and_groups
include limits
include packages
include oracle_software
include database
include database_users
include database_storage
Class['systl']
-> Class['os_users_and_groups']
-> Class['limits']
-> Class['packages']
-> Class['oracle_software']
-> Class['database']
-> Class['database_users']
-> Class['database_storage']
}
As you can see, we have used the ->
syntax to describe explicit ordering between the classes. Now everything should be ok….? Yes, it might. But you might also run into ordering issues when your classes include other classes. When using include
, Puppet enforces the ordering on the specified classes but not on any included classes. This might lead to Puppet code that executes some code included in the sysctl
class very late in the execution, resulting in an error.
Final version
A fix for that is easy. Change all the include
for contain
.
class oracle_database {
contain systl
contain os_users_and_groups
contain limits
contain packages
contain oracle_software
contain database
contain database_users
contain database_storage
Class['systl']
-> Class['os_users_and_groups']
-> Class['limits']
-> Class['packages']
-> Class['oracle_software']
-> Class['database']
-> Class['database_users']
-> Class['database_storage']
-> Class['database ']
}
See here for a full description on Puppet containment. There is also a good video explaining this here. Now the explicit ordering of all requirements is ensured.
Could this be done differently?
A disadvantage of this solution is that ordering is done at a fairly high level. If an error is raised in the sysctl
class, all other Puppet code in this class is skipped. You probably could safely apply some parts of some other classes.
However, doing this would mean applying ordering on a much lower level, for example, on the individual resource level. It is our experience that this leads to:
- More coupling between the classes, thus lesser reusability
- greater chance of circular dependencies
- More difficult to understand
These reasons are enough for us not to do this.
How to use the ordered steps pattern in Puppet code
As explained before, you can use this pattern to ensure ordering between some (high-level) classes. Let’s see how we can use the pattern to create a role class.
class role {
contain base_profile
contain database_profile
contain application_server_profile
contain application_profile
Class['base_profile']
-> Class['database_profile']
-> Class['application_server_profile']
-> Class['application_profile']
}
This class manages a server with an application that needs both a database and an application server. A best practice is to extract the configuration required for all servers- regardless of their role -to what we call base_profile
. So, base_profile
contains elements like:
- DNS configuration
- Network configuration
- Generic yum repositories
- NTP configuration
- timezone configuration
- Common OS users and groups
- etc.
We want to ensure our network configuration and DNS setup are correct before continuing. By ensuring the base_profile
class finishes successfully, we can then move on and apply the classes database_profile
, application_server_profile
followed by application_profile
. Doing things in this specific order guarantees a running application that all underlying components are working perfectly.
Examples of how to use the ordered steps pattern in practice
Most people use ordered steps in role and profile classes, but you can also find them in some component modules on the Puppet Forge. For example:
If you look around, you can probably find many more.
Summary
Puppet patterns are useful because they allow you to modularize your code, making it more maintainable. The ordered steps pattern is one of the used Puppet patterns and allows you to ensure that certain classes are always executed in a specific order. You can use this pattern when creating role or profile classes, or when creating a component class.
When used correctly, the ordered steps pattern can help you create more maintainable and understandable Puppet code. However, it is important to note that this pattern should only be used when necessary. Overusing this pattern can lead to more coupling between classes and So, use this pattern sparingly and only when it makes sense for your code.
We frequently utilize this Puppet pattern in our code for Oracle databases, WebLogic, IBM DB2 and IBM MQ software. So, we created some handy functions to make it simpler to write code with ordered steps. We cover that more in-depth in our next blog post.
If you could use a hand, we are here to help. Making good Puppet code is our bread and butter at Enterprise Modules. But besides developing our own modules, we are also helping customers build the best possible Puppet code. Do you think you could need some assistance? Don’t hesitate to contact us at info@enterprisemodules.com or by phone: +31 (0)30-601 6000 for some consultancy.
About us
Enterprise Modules is the leading developer of enterprise-ready puppet modules for Oracle databases,Oracle WebLogic, and IBM MQ or DB2 software. Our puppet modules help sysadmins and DBAs to automate the installation, configuration, and management of their databases and application server systems. These modules allow them to make managed, consistent, repeatable, and fast changes to their infrastructure and automatically enforce the consistency.
For more information, please visit our website: www.enterprisemodules.com or contact us at info@enterprisemodules.com.