A book for learning puppet by real example and by building code. Third chapter shows a basic use case of installing tomcat and creating a module to do the same.
Vishal BiyaniFounder & CTO Infracloud Technologies (We are hiring!) at Infracloud technologies Ltd
3. www.vishalbiyani.comLearning Puppet
What we will learn? Here is what we are going to do in this chapter
Write a very basic manifest which will install and configure tomcat &
run the manifest and see if it actually works?
Move manifest code into a class and package into module – thus
learning how to create module and basics of module
Make class configurable so that we can pass parameters for certain
things – for example port on which tomcat will run etc.
3|
Refactor code to separate configurable parameters, make it modular
in terms of class design & organization etc.
Learn what is containment (Sounds like enlightenment isn't it? ☺)
Finally use templates and ERB templatinging language to manage
configuration files of software being installed
That seems a lot?That’s going to be super easy though!
4. www.vishalbiyani.comLearning Puppet
First tomcat run! Now let’s log on to agent_0 and fire command:
In our first cut we want to do a few basic things:
install tomcat, start/run it and there should be a
way to test it in browser if it is working right. For
testing part we will install the “ROOT” app which
shows tomcat home page.Where to put all this
code? In the /etc/puppet/manifests/site.pp under
one of nodes.Our site.pp looks like:
1 node 'puppet.learn.com' {
2 package {'vim-enhanced.x86_64':
3 ensure => present,
4 }
5 }
6 node '0.pagent.vb.com' {
7 package { 'tomcat6':
So that will install & bring up tomcat.To check simply hit the
ip_address:8080* (Or find out the IP address that would get assigned from
Vagrantfile, in my case it is 192.168.17.10).You should see tomcat
homepage:
4|
7 package { 'tomcat6':
8 ensure => installed,
9 }
10
11 service { 'tomcat6':
12 ensure => running,
13 }
14 package { 'tomcat6-webapps':
15 ensure => installed,
16 require => Package['tomcat6'],
17 }
18 }
19 node '1.pagent.vb.com' {
20 }
In above snippet we have added lines from 7 – 17
to get done what we want. First we are declaring
that package tomcat6 should be installed,
secondly it should be running. In last block we are
installing the “ROOT” app which is named as
tomcat-webapps and also forming a relation on
installation of tomcat.
To get IP address of agent fire command “ifconfig” – here the IP address in
block eth1 would be accessible from your machine
So we have a basic tomcat server running but there are quite a few things
that need to be taken care of for example:
•Note that our current code will only work on certain operating systems – for
example if package name on a OS anything different than tomcat6 – then it
won’t work.
•Similarly adding code this way to node definition is not scalable for sure.
We need to move the code somewhere else.
But this is a good start for our first cut; we will refine this much more in
coming pages/slides!
5. www.vishalbiyani.comLearning Puppet
Creating a module! Let’s understand what all goes in module we created:
Gemfile is a where you define dependencies using the dependency
management tool in Ruby: Bundler
Manifests – is where most of the code we write will go.The special file here
is init.pp – this file has class with name same as module name (tomcat).
Note that all other classes should have same name for file in which class is
defined (Best practice) except for init.pp.
Metadata.json stores basic information about your module which you filled
in while generating module.
Rakefile is like a make file in C – which defined tasks for your codebase. And
you know what a README.md is for isn’t it?
Spec directory
Tests is where tests for our module live!
For this chapter we are only going to touch files in manifests directory!
Let’s get our house in order!We put bunch of lines
in node definition to getTomcat up and running
but we want to distribute our tomcat magic code
to others so that they can do the same. Let’s
wrap all of code in a module. For that let’s first
create a barebones module, on master node
navigate to /etc/puppet/modules and fire:
1 sudo puppet module generate learn-tomcat
sudo mv learn-tomcat tomcat
The name we gave is a combination of username
(learn) and module name (tomcat) which is
separated by dash and is the convention followed
5|
For this chapter we are only going to touch files in manifests directory!
If you look at the init.pp in manifests, you will see:
So let’s move the code we had defined inside node definition in this class.
That makes our module functional. If someone has to install tomcat, all one
has to do is in node definition (Try on your setup!):
The effect is same as previous slide.Our first ever module for tomcat is
ready.Our class still does not follow the best coding practices & design
patterns but we will fix that soon too!
* The name of module vs. the combo name of username-modulename is
a known bug and will be fixed in Puppet 4.0 I stumbled on this during
exercise so I added that command. Check
https://tickets.puppetlabs.com/browse/PUP-3124
separated by dash and is the convention followed
by puppet.You will be asked with bunch of
questions and you can fill them or leave blank!
And we need to rename the module name to
tomcat as the username-module name combo
needs to be only in metadata file*. And puppet
generates a basic structure of a module for us!
tomcat/
|── Gemfile
|── manifests
| |── init.pp
|── metadata.json
|── Rakefile
|── README.md
|── spec
| |── classes
| | └── init_spec.rb
| |── spec_helper.rb
|── tests
|── init.pp
1 class tomcat {
2 }
1 node '0.pagent.vb.com' {
2 include tomcat
3 }
6. www.vishalbiyani.comLearning Puppet
Making things parameterized!
Parameterization – first thing we want to do is
make some of hardcoded values to be passed as
parameters so that end user can customize the
module as per need. For this first of all we create
a class params.pp in manifests directory, that
way parameters will be managed in a different
class and can be referred from all over the
module. Also note that we have default values for
all parameters so even if end user does not
provide them when calling tomcat module, our
code won’t break!
1 class tomcat::params {
2 $package_name = 'tomcat6'
3 $package_ensure = 'installed'
4 $service_manage = true
5 $service_name = 'tomcat6'
6 $service_ensure = 'running'
7 $service_port = '8080'
8 $root_webapp_name = 'tomcat6-webapps‘
9 $root_webapp_ensure = 'installed'
10 }
6|
code won’t break!
Once we get introduced to Hiera in upcoming chapters, we will move the configuration parameters from params.pp to Hiera. We
are for now using loosely called “params class” pattern
Now we will inherit params class in our tomcat
class (init.pp) so that all parameters are
available. Moreover we will take parameters as
class arguments and in case they are not
provided, default values will do magic. You might
have noticed that our code for installing tomcat
etc. is missing – we will take care of that in a
moment. Notice the way we are all namespacing
all classes like “tomcat::params”?
1 class tomcat (
2 $package_name = $tomcat::params::service_name,
3 $package_ensure = $tomcat::params::package_ensure,
4 $service_manage = $tomcat::params::service_manage,
5 $service_name = $tomcat::params::service_name,
6 $service_ensure = $tomcat::params::service_ensure,
7 $service_port = $tomcat::params::service_port,
8 ) inherits ::tomcat::params {
9
10 }
7. www.vishalbiyani.comLearning Puppet
Code into logical classes
Next we want to break the code logic into
appropriate blocks.We are primarily doing a few
things – managing packages (installing them)
and once they are done then managing service
(Starting tomcat etc.). So it makes sense to add
the relevant chunk in relevant classes.
Our first step of getting packages and installing
them is grouped into install.pp which does same
thing but uses parameters instead of hardcoded
values of package names & states. Looks neat
isn’t it?
1 class tomcat::install inherits tomcat {
2 package { 'tomcat6':
3 name => $package_name,
4 ensure => $package_ensure,
5 }
6 package {'tomcat6-webapps':
7 name => $root_webapp_name,
8 ensure => $root_webapp_ensure,
9 require => Package['tomcat6'],
10 }
11 }
1 class tomcat::service inherits tomcat {
2 if ! ($service_ensure in ['stopped','running']){Next we manage services in service.pp. Here we
7|
2 if ! ($service_ensure in ['stopped','running']){
3 fail("Service status must be one of stopped or running")
4 }
5 if $service_manage == true {
6 service {'tomcat6':
7 ensure => $service_ensure,
8 name => $service_name,
9 }
10 }
11 }
Next we manage services in service.pp. Here we
add a small check to ensure user has not passed
invalid values for service status before we
actually start service.We are also letting user
decide weather service should be started or not
with $service_manage parameter. Rest all is old
code in new bottle (of beer or wine ;) )
1 class tomcat::config inherits tomcat {
2 }
We are adding one more class – config.pp and
leaving it blank, just for future you know!
So we defined classes but never invoked them?
How will this work? It will, in next slide ;)
8. www.vishalbiyani.comLearning Puppet
Get Set Go..Finally we add the meat to main block of our
original class – tomcat (init.pp).We are using a
magic word – contain and then declaring the
three classes we defined in a specific order.
Because we don’t want to start tomcat service
before it is installed isn’t it? BTWWTH is this
contain?We will understand that in next slide but
for now understand that we are scoping them in
current context so that even if someone declares
them elsewhere with include – we are not
affected! (Remember that irrespective of number
of includes – it is executes only once?)
1 class tomcat (
2 $package_name = $tomcat::params::service_name,
3 $package_ensure = $tomcat::params::package_ensure,
4 $service_manage = $tomcat::params::service_manage,
5 $service_name = $tomcat::params::service_name,
6 $service_ensure = $tomcat::params::service_ensure,
7 $service_port = $tomcat::params::service_port,
8 ) inherits ::tomcat::params {
9 contain tomcat::config
10 contain tomcat::install
11 contain tomcat::service
12
13 Class['tomcat::config'] ->
14 Class['tomcat::install'] ->
15 Class['tomcat::service']
16 }
8|There is an excellent document on Puppet website which talks about good module design, our design is loosely
based on that, it is a MUST read: https://docs.puppetlabs.com/guides/module_guides/bgtm.html
What changes in node classification declaration
from last modification? Not much. Doing a
puppet run on agent would have same effect but
now our code is much cleaner & well structured. If
you need to pass parameters then we will have to
use the resource declaration way as shown in
second code snippet for node declaration:
1 node '0.pagent.vb.com' {
2 include tomcat
3 }
1 node '0.pagent.vb.com' {
2 class {'tomcat':
3 service_ensure => 'stopped',
4 }
5 }
If you noticed something – if you try changing the
port of service to anything other than 8080 – that
does not work, because we have not yet coded for
that.We will do that in next chapter but before
we finish let’s go over what containment is ?
Why can not we pass parameters in include way of class declaration above?
Why we had to fall back to resource based declaration? If you use include
function to declare classes then configuration parameters should ideally
come from Hiera and we will see how in upcoming chapters. It is suggested
to use include way of declaring going forward as we already covered in
chapter 2
9. www.vishalbiyani.comLearning Puppet
Containment &
Anchor pattern
So how do you contain classes? It is very simple syntax – in include like as
well as resource like declaration. For resource like, you will need to use all 4
lines below and in case of include like only line number 4 is needed!
Containment can be a tricky topic to understand
if we ignore basics, so let’s recap them once more:
•In puppet sequence of execution is not
guaranteed unless you explicitly form that
relationship (Between two resources).
•“include” type of declaration can be done
multiple times although the execution will
happen only once
•Thirdly if you define a resource within a class –
1 class {'tomcat::service':
2 service_ensure => 'stopped',
3 }
4 contain 'tomcat::service'
But there is a small problem – contain function was introduced in Puppet
enterprise 3.2 and puppet open source 3.4. So if you are using versions prior
to those, you will need to use puppetlabs/stdlibs module along with anchor
resource type.This is named “anchor containment pattern”. Let’s look at
some code and then digest it, our above declaration with anchor pattern
now will look like:
1 anchor['tomcat_start:'] ->
9|
•Thirdly if you define a resource within a class –
then you can be sure that the resource code will
be executed after class starts and before class
ends – this is basically called containment of that
resource to class. Resources and defined types by
default are contained.
Now let’s imagine you defined a class A.You
included A in another class B and many other
places. But puppet does not guarantee that A will
be executed within B by default.This means
classes by default are not contained and need to
be contained explicitly.Why? See footnote!
Classes are not contained by default is intentional. Imagine you declared “include class_name” n number of places and
puppet had to contain the class everywhere? At the same time when we design a large module, we want to make sure
parent class can contain other classes. Creating classes to have logical division is a good practice as we already saw!
1 anchor['tomcat_start:'] ->
2 Class['tomcat::service':] ->
3 anchor['tomcat_end':]
4 # Almost same as 'contain tomcat::service'
So here is what is happening:
•The class to be contained should be between two anchors.These anchors
should be unique within containing class.
•You must form relationship between class to be contained and anchors
such that there is one anchor before and after the contained class (-> forms
this relationship)
•Anchor code does not affect the execution – it is only to facilitate the
containment!
10. www.vishalbiyani.comLearning Puppet
1
Node classification and how to add relevant
infrastructure code to a node
What did we learn?
2
How to create module, it’s basic structure and how
to it all forms together
3
Good practices for class design & parameterization
of classes for configurable values.
10|
4
Calling classes from a module for a given node &
configuring it as per need
5
Containment of classes & why anchor pattern is
needed for certain versions of puppet