Join our Discord Server
Ajeet Raina Ajeet Singh Raina is a former Docker Captain, Community Leader and Arm Ambassador. He is a founder of Collabnix blogging site and has authored more than 570+ blogs on Docker, Kubernetes and Cloud-Native Technology. He runs a community Slack of 8900+ members and discord server close to 2200+ members. You can follow him on Twitter(@ajeetsraina).

Learn Puppet with Me – Day 3

9 min read

Puppet is an open source framework and toolset for managing the configuration of computer systems. Puppet can be used to manage configuration on UNIX (including OSX) and Linux platforms, and recently Microsoft Windows platforms as well. Puppet is often used to manage a host throughout its lifecycle: from initial build and installation, to upgrades, maintenance, and finally to end-of-life, whenyou move services elsewhere. Puppet is designed to continuously interact with your hosts, unlike provisioning tools which build your hosts and leave them unmanaged.

Puppet has a simple operating model that is easy to understand and implement. The model is made up of three components:-

• Deployment

• Configuration Language and Resource Abstraction Layer

• Transactional Layer

Puppet is usually deployed in a simple client-server model (Figure 1-2). The server is called a “Puppet master”, the Puppet client software is called an agent and the host itself is defined as a node.

The Puppet master runs as a daemon on a host and contains the configuration required for your environment. The Puppet agents connect to the Puppet master via an encrypted and authenticated connection using standard SSL, and retrieve or “pull” any configuration to be applied.

Importantly, if the Puppet agent has no configuration available or already has the required configuration then Puppet will do nothing. This means that Puppet will only make changes to your environment if they are required. The whole process is called a configuration run.

Each agent can run Puppet as a daemon via a mechanism such as cron, or the connection can be manually triggered. The usual practice is to run Puppet as a daemon and have it periodically check with the master to confirm that its configuration is up-to-date or to retrieve any new configuration. However,many people find being able to trigger Puppet via a mechanism such as cron, or manually, better suits their needs. By default, the Puppet agent will check the master for new or changed configuration once every 30 minutes. You can configure this period to suit your environment.

Configuration Language and Resource Abstraction Layer

Puppet uses a declarative language to define your configuration items, which Puppet calls “resources.” This declarative nature creates an important distinction between Puppet and many other configuration tools. A declarative language makes statements about the state of your configuration – for example, it declares that a package should be installed or a service should be started.

Most configuration tools, such as a shell or Perl script, are imperative or procedural. They describe HOW things should be done rather than the desired end state – for example, most custom scripts used to manage configuration would be considered imperative. This means Puppet users just declare what the state of their hosts should be: what packages should be installed, what services should be running, etc. With Puppet, the system administrator doesn’t care HOW this state is achieved – that’s Puppet’s problem. Instead, we abstract our host’s configuration into resources.

Configuration Language

What does this declarative language mean in real terms? Let’s look at a simple example. We have an environment with Red Hat Enterprise Linux, Ubuntu, and Solaris hosts and we want to install the vim application on all our hosts. To do this manually, we’d need to write a script that does the following:

• Connects to the required hosts (including handling passwords or keys)

• Checks to see if vim is installed

• If not, uses the appropriate command for each platform to install vim, for example on Red Hat the yum command and on Ubuntu the apt-get command

• Potentially reports the results of this action to ensure completion and success

Puppet approaches this process quite differently. In Puppet, we define a configuration resource for the vim package. Each resource is made up of a type (what sort of resource is being managed: packages, services, or cron jobs), a title (the name of the resource), and a series of attributes (values that specify the state of the resource – for example, whether a service is started or stopped).

Example:

A Puppet Resource

package { “vim”:

ensure => present,

}

Resource Abstraction Layer

With our resource created, Puppet takes care of the details of how to manage that resource when our agents connect. Puppet handles the “how” by knowing how different platforms and operating systems manage certain types of resources. Each type has a number of “providers.” A provider contains the “how” of managing packages using a particular package management tool. For the package type, forexample, for there are more than 20 providers covering a variety of tools including yum, aptitude, pkgadd, ports, and emerge.

When an agent connects, Puppet uses a tool called “Facter” to return information about that agent, including what operating system it is running. Puppet then chooses the appropriate package provider for that operating system and uses that provider to check if the vimpackage is installed. For example, on Red Hat it would execute yum, on Ubuntu it would execute aptitude, and on Solaris it would use the pkg command. If the package is not installed, then Puppet will install it. If the package is already installed, Puppet does nothing. Puppet will then report its success or failure in applying the resource back to the Puppet master.

INTRODUCING FACTER AND FACTS

Facter is a system inventory tool which returns “facts” about each agent, such as its hostname, IP address, operating system and version, and other configuration items. These facts are gathered when the agent runs. The facts are then sent to the Puppet master, and automatically created as variables available to Puppet.You can see the facts available on your clients by running the facter binary from the command line. Each fact is returned as a key => value pair. For example:

operatingsystem => Ubuntu

ipaddress => 10.0.0.10

Transactional Layer

Puppet’s transactional layer is its engine. A Puppet transaction encompasses the process of configuring each host including:

• Interpret and compile your configuration

• Communicate the compiled configuration to the agent

• Apply the configuration on the agent

• Report the results of that application to the master

The first step Puppet takes is to analyze your configuration and calculate how to apply it to your agent. To do this, Puppet creates a graph showing all resources, their relationships to each other and to each agent. This allows Puppet to work out in what order, based on relationships you create, to apply each resource to your host. This model is one of Puppet’s most powerful features. Puppet then takes the resources and compiles them into a “catalog” for each agent. The catalog is sent to the host and applied by the Puppet agent. The results of this application are then sent back to the master in the form of a report.

The transaction layer allows configurations to be created and applied repeatedly on the host.Puppet calls this idempotent, meaning multiple applications of the same operation will yield the same results. Puppet configuration can be safely run multiple times with the same outcome on your host and hence ensuring your configuration stays consistent.

3. Understanding Puppet Components

If you look at /etc/puppet directory , you will find various components of puppet underlying.

[root@puppet-server puppet]# ls -la

total 28

drwxr-xr-x. 4 puppet puppet 4096 Aug 23 11:41 .

drwxr-xr-x. 79 root root 4096 Aug 25 04:01 ..

-rwxr-xr-x. 1 puppet puppet 2552 Aug 21 17:49 auth.conf

-rwxr-xr-x. 1 puppet puppet 381 Jun 20 18:24 fileserver.conf

drwxr-xr-x. 4 puppet puppet 4096 Aug 25 06:50 manifests

drwxr-xr-x. 11 puppet puppet 4096 Aug 25 06:49 modules

-rwxr-xr-x. 1 puppet puppet 1059 Aug 23 11:41 puppet.conf

Let’s talk about what is manifest.

“Manifest” is Puppet’s term for files containing configuration information. Manifest files have a suffix of .pp. This directory and file is often already created when the Puppet packages are installed. If it hasn’t already been created, then create this directory and file now:

# mkdir /etc/puppet/manifests

Under manifests, there are important files such as nodes.pp, site.pp, template.pp and few classes and definitions. We are going to cover those too here.

Puppet manifests are made up of a number of major components:

• Resources – Individual configuration items

• Files – Physical files you can serve out to your agents

• Templates – Template files that you can use to populate files

• Nodes – Specifies the configuration of each agent

• Classes – Collections of resources

• Definitions – Composite collections of resources

These components are wrapped in a configuration language that includes variables, conditionals, arrays and other features. Later in this chapter we’ll introduce you to the basics of the Puppet language and its elements. In the next chapter, we’ll extend your knowledge of the language by taking you through an implementation of a multi-agent site managed with Puppet.

The site.pp file

The site.pp file tells Puppet where and what configuration to load for our clients. We’re going to store

this file in a directory called manifests under the /etc/puppet directory.

Please Note: Puppet will not start without the site.pp file being present.

Our first step in creating our first agent configuration is defining and extending the site.ppfile. See an example of this file in Listing 1-3.

The site.pp File

import ‘nodes.pp’

$puppetserver = ‘puppet.example.com’

The import directive tells Puppet to load a file called nodes.pp. This directive is used to include any Puppet configuration we want to load.

When Puppet starts, it will now load the nodes.pp file and process the contents. In this case, this file will contain the node definitions we create for each agent we connect. You can also import multiple files like so:

import ‘nodes/*’

import ‘classes/*’

The import statement will load all files with a suffix of .pp in the directories nodes andclasses.

The $puppetserver statement sets a variable. In Puppet, configuration statements starting with a dollar sign are variables used to specify values that you can use in Puppet configuration.

In Listing 1-3, we’ve created a variable that contains the fully qualified domain name of our Puppet master, enclosed in double quotes.

Agent Configuration

Let’s add our first agent definition to the nodes.pp file we’ve just asked Puppet to import. In Puppet manifests, agents are defined using node statements.

# touch /etc/puppet/manifests/nodes.pp.

You can see the node definition we’re going to add in Listing 1-4.

Listing 1-4. Our Node Configuration

node ‘puppet-client.test.com’ {

include sudo

}

Next, we specify an include directive in our node definition. The include directive specifies a collection of configuration that we want to apply to our host. There are two types of collections we can

include in a node:

• Classes – a basic collection of resources

• Modules – an advanced, portable collection of resources that can include classes, definitions, and other supporting configuration.You can include multiple collections by using multipleinclude directives or separating each

collection with commas.

include sudo

include sshd

include vim, syslog-ng

Creating our first module

The next step is to create the sudo module. A module is a collection of manifests, resources, files, templates, classes, and definitions. A single module would contain everything required to configure a particular application. For example, it could contain all the resources (specified in manifest files), files and associated configuration to configure Apache or the sudo command on a host.

Each module needs a specific directory structure and a file called init.pp. This structure allows Puppet to automatically load modules. To perform this automatic loading, Puppet checks a series of directories called the module path. The module path is configured with themodulepath configuration

option in the [main] section of the puppet.conf file. By default, Puppet looks for modules in the /etc/puppet/modules and /var/lib/puppet/modules directories, but you can add additional locations if

required:

[main]

moduledir = /etc/puppet/modules:/var/lib/puppet/modules:/opt/modules

The automatic loading of modules means, unlike our nodes.pp file, modules don’t need to be loaded into Puppet using the import directive.

Module Structure

Let’s start by creating a module directory and file structure in Listing 1-5. We’re going to create this structure under the directory /etc/puppet/modules. We will name the modulesudo. Modules (and classes) must be normal words containing only letters, numbers, underscores and dashes.

Listing 1-5. Module Structure

# mkdir –p /etc/puppet/modules/sudo/{files,templates,manifests}

# touch /etc/puppet/modules/sudo/manifests/init.pp

The manifests directory will hold our init.pp file and any other configuration. The init.pp file is the core of your module and every module must have one. The files directory will hold any files we wish to serve as part of our module. The templates directory will contain any templates that our module might use.

The init.pp file

Now let’s look inside our sudo module, starting with the init.pp file, which we can see in Listing 1-6.

Listing 1-6. The sudo module’s init.pp file

class sudo {

package { sudo:

ensure => present,

}

if $operatingsystem == “Ubuntu” {

package { “sudo-ldap”:

ensure => present,

require => Package[“sudo”],

}

}

file { “/etc/sudoers”:

owner => “root”,

group => “root”,

mode => 0440,

source => “puppet://$puppetserver/modules/sudo/etc/sudoers”,

require => Package[“sudo”],

}

}

Our sudo module’s init.pp file contains a single class, also called sudo. There are three resources in the class, two packages and a file resource.The first package resource ensures that the sudo package is installed, ensure => present. The second package resource uses Puppet’s if/else syntax to set a condition on the installation of the sudoldap package.

The next portion of our source value tells Puppet where to look for the file. This is the equivalent of the path to a network file share. The first portion of this share is modules,which tells us that the file is stored in a module. Next we specify the name of the module the file is contained in, in this case sudo.

Finally, we specify the path inside that module to find the file.

All files in modules are stored under the files directory; this is considered the “root” of the module’s file “share.” In our case, we would create the directory etc under the files directory and create the sudoers file in this directory.

puppet$ mkdir –p /etc/puppet/modules/sudo/files/etc

puppet$ cp /etc/sudoers /etc/puppet/manifests/files/etc/sudoers

Applying Our First Configuration

We’ve created our first Puppet module! Let’s step through what will happen when we connect an agent that includes this module.

1. It will install the sudo package.

2. If it’s an Ubuntu host, then it will also install the sudo-ldap package

3. Lastly, it will download the sudoers file and install it into /etc/sudoers.

Now let’s see this in action and include our new module on the agent we’ve created, Nod1.example.com.

Remember we created a node statement for our host in Listing 1.4:

node ‘node1.example.com’ {

include sudo

}

When the agent connects it will now include the sudo module. To do this we run the Puppet agent again, as shown in Listing 1-7.

Listing 1-7. Applying Our First Configuration

puppet# puppet agent –server=puppet.example.com –no-daemonize –verbose –onetime

notice: Starting Puppet client version 2.6.1

info: Caching catalog for node1.example.com

info: Applying configuration version ‘1272631279’

notice: //sudo/Package[sudo]/ensure: created

notice: //sudo/File[/etc/sudoers]/checksum: checksum changed

‘{md5}9f95a522f5265b7e7945ff65369acdd2’ to ‘{md5}d657d8d55ecdf88a2d11da73ac5662a4’

info: Filebucket[/var/lib/puppet/clientbucket]: Adding

/etc/sudoers(d657d8d55ecdf88a2d11da73ac5662a4)

info: //sudo/File[/etc/sudoers]: Filebucketed /etc/sudoers to puppet with sum

d657d8d55ecdf88a2d11da73ac5662a4

notice: //sudo/File[/etc/sudoers]/content: content changed

‘{md5}d657d8d55ecdf88a2d11da73ac5662a4’

In our next section, we are going to talk more on Puppet Modules.

Have Queries? Join https://launchpass.com/collabnix

Ajeet Raina Ajeet Singh Raina is a former Docker Captain, Community Leader and Arm Ambassador. He is a founder of Collabnix blogging site and has authored more than 570+ blogs on Docker, Kubernetes and Cloud-Native Technology. He runs a community Slack of 8900+ members and discord server close to 2200+ members. You can follow him on Twitter(@ajeetsraina).
Join our Discord Server