Handson3

From Gridkaschool

A simplistic "real world example" of a puppet module

Introduction

The main purpose of this tutorial is to try to create a well structured and documented puppet feature.

The "real world example" in this case will be a puppet module called "myapache". It should demonstrate how to install and configure an apache web server in a very simplistic way!

This procedure is done in 3 steps:

  1. Installing the apache package
  2. Configuring the web server
  3. Starting the web server

All the system changes needed to install the apache web server will be defined using puppet and recorded with git. This allows us not only to document all the changes but also to *re-appy* them whenever needed! Git should already have been configured in the previous hands-on session.

Creating a new puppet module

As a first step we should change into our git working directory (under *modules*) and create a new puppet module called "myapache".

Use the following command to help you create a new puppet module:

cd /path/to/my/puppet/modules
puppet module generate <my_git_username>-myapache

Depending on the puppet version you might need to answer a few questions. Afterwards you should have a new directory containing the following contents:

.
├── manifests
│   └── init.pp
├── metadata.json
├── Rakefile
├── README.md
├── spec
│   ├── classes
│   │   └── init_spec.rb
│   └── spec_helper.rb
└── tests
    └── init.pp

The most important file in this directory is the *init.pp* which contains (for now) just an empty class definition.

After creating the module we need to rename the directory:

mv <my_git_username>-myapache myapache

Even though the module is still empty we can already run some simple tests:

puppet parser validate /path/to/my/puppet/modules/myapache/manifests/init.pp
puppet apply --modulepath=/path/to/my/puppet/modules -e "include myapache" --noop

(The --noop option causes puppet to only simulate changes. No changes will ever be applied when using this option!)

After testing we can already add the module into git and push it upstream into our personal development environment:

git add myapache
git commit -m"added myapache module"
git push

Filling your puppet module with life!

Let's start by editing the following file

./myapache/manifests/init.pp

and change it in order to install the "apache" package.

Hint: Each puppet resource has it's own "man-page"

puppet describe --help
puppet describe --list

In this case you should look more carefully at the "package" resource.

puppet describe package

After testing your changes, don't forget to commit and push!

Solution

Protecting your code

One should always make sure that changes are only applied where they should be applied on! This means that if someone else uses your puppet modules, they should not get their systems ruined because your forgot to add a simple check.

The first check should always ensure that your puppet module only runs on a list of supported operating systems, otherwise fail! This check can easily be done using the puppet facts "osfamily" and "operatingsystem".

To get a list of all puppet facts you can run the following command:

facter -p

References:

After testing your changes, don't forget to commit and push!

Solution

Cross-platform support

Of course it is much nicer if instead of just failing we can design our module to run on as many different platforms as possible... Just for fun try to re-write the previous check so that the module now runs on RedHat and on Debian systems. On Debian the "httpd" package name equivalent is "apache2".

After testing your changes, don't forget to commit and push!

Solution

Splitting the module into parts

It is good practice to split your code into a well defined set of manifests, whereas a single manifest should only manage a single task!

For now we should create three new manifests:

./myapache/manifests/params.pp
./myapache/manifests/install.pp
./myapache/manifests/service.pp

The *params.pp* manifest should only include a bunch of variables set to some reasonable "default" values depending on the list of supported operating systems. In this case we have for example the package name (httpd vs. apache2).

The contents from *init.pp* (installing of the apache package) should now be moved into *install.pp*. The apache package name in the *install.pp* manifest should however be set from the variable defined in *params.pp*.

The *service.pp* should start the apache web service. Also here, we should rather define the service name in *params.pp*. (On Debian systems the apache service is also called apache2)

After refactoring the code, the *init.pp* should be only including the classes defined in the other manifests + their dependencies!

After testing your changes, don't forget to commit and push!

As of now you should see a "default" html web page when opening a web browser with the URL from your test machine.

References:

Solution

Configurations

Typically a puppet module does not only install a package but also configures some settings related to that package.

For doing this we will now create a new manifest:

./myapache/manifests/config.pp

This manifest should change/overwrite the "default" web page installed by the apache package. You can find the "default" web page in the following location:

/var/www/error/noindex.html

The first step we need to do is to create a new "default" html web page and save it under:

./myapache/files/default-index.html

The second step we need to do is to use the puppet "file" resource in the *config.pp* manifest and tell it to deploy the new web page in the correct path.

-rw-r--r-- 1 root root 3822 May 14 15:24 /var/www/error/noindex.html

Don't forget to set the correct file permissions and that you can always get help for each puppet resource:

puppet describe file

After testing your changes, don't forget to commit and push!

You should now see _your_ "default" html web page when opening a web browser with the URL from your test machine.

Solution

Puppet templates

How about putting some dynamic information into the default html web page? For example something like:

Welcome!
Some infos about this host:
Hostname: foo@example.com
Number of CPU's:   2
Total memory:   7.73 GB
Free memory:   6.44 GB
Kernel:   Linux
Kernel version:   3.2.0
Operating system:   Ubuntu
OS family:   Debian
IP address:   131.169.171.143
MAC address:   00:1c:c0:b2:fa:d4

This information should be defined in a puppet template under:

./myapache/templates/default-index.html.erb

Note the .erb extension!

All the informations displayed above are available as "puppet facts". Remember, you can list all puppet facts by running the following command:

facter -p

The *config.pp* also needs to be modified for installing the template instead of the previously defined static file.

After testing your changes, don't forget to commit and push!

References:

P.S. Yes, this is just an example... You should _not_ use this kind of template on any production hosts!!

Solution

Parameterized classes

What if we want to change the default port on which the apache web server should listen to?

Well, this can be done by changing the "Listen" parameter in the following configuration file:

/etc/httpd/conf/httpd.conf

However, we don't want to change anything manually on the host! What we want is to add a new parameter "port" to our puppet module and tell it how to make the changes. We also want to keep 80 as the default port.

Examples:

   class{ myapache } # listen to port 80
   class{ myapache : port => 8080 } # listen to port 8080

After changing our class to accept the port parameter, we still need to pass this information into the configuration file /etc/httpd/conf/httpd.conf.

This can be basically be solved in two different ways:

  • One way is to copy /etc/httpd/conf/httpd.conf into our puppet module and make it a template which changes the 'Listen' parameter dynamically.
  • The other way is to use augeas to change the configuration file in-place.

The first way is easier since the augeas syntax tends to be somewhat cumbersome... Feel free to try out both ways and find which one you like best.

For testing the new parameter you need to call puppet as follows (leave out --noop to actually apply changes!):

puppet apply --modulepath=/path/to/my/puppet/modules -e "class{ 'myapache' : port => 8080}" --noop

After testing your changes, don't forget to commit and push!

References:

Solution

Class inheritance

Use inheritance to store the default value from the "port" class parameter in the *params.pp* manifest. The *init.pp* should declare the *myapache* class as follows:

class myapache( $port = $myapache::params::port ) inherits myapache::params {
   ...
}

After testing your changes, don't forget to commit and push!

References:

Solution


A full-featured apache puppet module can be downloaded from here [1]