Protecting the production environment
Methuselah
Puppet is the Methuselah among solutions for configuration management, matured for a proud 15 years and currently at version 7. In contrast to Ansible, Puppet takes a declarative approach (i.e., it describes the state of a resource and not how to achieve it).
Listing 1 declares the kermit account, which must exist and must belong to the muppets primary group. A gonzo user must not exist at the same time. Puppet must therefore be able to determine the current state and independently change it to the declared, desired state.
Listing 1
Resource Declarations
user { 'kermit': ensure => present, gid => 'muppets', } user { 'gonzo': ensure => absent, } group { 'muppets': ensure => present, }
Resource Abstraction Layer
A major role in how Puppet accomplishes this task is played by the resource abstraction layer (RAL). This core element in Puppet is also responsible for platform independence. To do this, RAL distinguishes between types and providers. A type defines the properties of a resource like a user
. These properties include parameters such as gid
, home
, or shell
. Each type must have at least one provider that describes how the current state is determined and how the desired state can be achieved. The provider
type is a metaparameter, because it is always available with every resource.
Figure 1 also shows a package
type, which is used to take care of various software packages. If more than one provider is assigned to a type, there is always a default provider, which can differ depending on the platform. For example, on Red Hat-based systems, the Yum package manager is the default for package
, whereas Debian derivatives use Apt instead. If you also wants to manage GEM packages, you need to specify the provider explicitly.
Providers always use the standard system tools for their work. To manage a service on a RHEL version 7 or higher, Puppet uses systemctl
. The useradd
, usermod
, and userdel
tools are for users. Puppet's behavior always adapts internally to match the underlying system. If you are acting on a level that abstracts this complexity, you do not need to adjust, but you do need to check how a system that is new to you reacts to the various requirements.
Develop and Test
Once the Puppet agent is installed on the target workstation (preferably in a virtual machine), you need to test the code – the manifest in Puppet-speak – with the command:
$ sudo puppet apply ./test.pp
Vagrant [1] has proven its value as a management framework for virtual machines (VMs). In addition to fast provisioning of test VMs at the command line, it offers an uncomplicated option for mounting local directories as a filesystem on the VM, which means you can work on your own workstation with your favorite development tools and quickly test the manifest with puppet apply
.
Dependencies
In the context of resources, the order of processing is of particular importance in Puppet. A second look at Listing 1 raises the issue of why the muppets
group is at the end, although the resource declaration refers to this group at the kermit
section earlier on. Also, although I just talked about Puppet running useradd
on Linux, if you have ever tried to specify a group that does not exist on the system with -g
switch when creating a user, you will know that the attempt throws an error.
Puppet does not process resources according to their order in the manifest but determines the sequence itself. In doing so, dependencies between certain resource types are implicit. In this specific case, this means that the muppets group must be processed before the kermit user, which requires you to manage both the group and the user. Failure to do so will also cause Puppet to throw an error. Another example is the relationship between a file and the directory in which it resides. If both are in the manifest, Puppet always takes care of the directory first.
In addition to implicit dependencies, there are also dependencies that Puppet cannot detect. A typical example in the Unix environment is the sequence for setting up a service. You have to (1) install the package, (2) adjust its configuration file, and (3) start the associated service. If step 3 is done before step 2, the daemon will run, but not with the desired configuration. Starting with step 3 results in a fatal error. Both scenarios could be fixed with a second Puppet run, but this contradicts the paradigm of idempotency: The state after one run has to be the same as after any number of runs.
The Puppet before
and notify
metaparameters ensure that Puppet applies its own resource before the referenced resources. You can use notify
to cause the service to restart if changes are made to the configuration file, as shown in Listing 2. Conversely, require
and subscribe
have the opposite effect. The latter detects changes to the referenced resource and triggers a restart of its own.
Listing 2
Puppet apache Class
01 class apache( 02 String $package_name, 03 Stdlib::Absolutepath $config_file, 04 String $service_name, 05 Stdlib::Ensure::Service $ensure = 'running', 06 Boolean $enable = true, 07 ) { 08 package { $package_name: 09 ensure => installed, 10 before => File[$config_file], 11 } 12 file { $config_file: 13 ensure => file, 14 content => template('apache/httpd.conf'), 15 notify => Service[$service_name], 16 } 17 service { $service_name: 18 ensure => $ensure, 19 enable => $enable, 20 } 21 }
Not every resource type offers a restart feature. The resource types that do offer one include service
and exec
. The exec
type lets you run arbitrary commands or scripts and offers parameters that help to maintain idempotency.
Buy this article as PDF
(incl. VAT)