Handson3
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:
- Installing the apache package
- Configuring the web server
- 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!
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:
- Facter Documentation: https://docs.puppetlabs.com/facter
After testing your changes, don't forget to commit and push!
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!
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:
- Relationships and Ordering: http://docs.puppetlabs.com/puppet/latest/reference/lang_relationships.html
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.
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:
- Learning Puppet Templates: http://docs.puppetlabs.com/learning/templates.html
- Facter Documentation: https://docs.puppetlabs.com/facter
P.S. Yes, this is just an example... You should _not_ use this kind of template on any production hosts!!
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:
- Parameterized classes: http://docs.puppetlabs.com/learning/modules2.html
- Augeas: http://projects.puppetlabs.com/projects/1/wiki/puppet_augeas
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:
A full-featured apache puppet module can be downloaded from here [1]