learning puppet chapter 3

10
LEARNING PUPPET - 03 SlideBook(Book on slides) Inspired by Slidedoc- http://www.duarte.com -Vishal Biyani www.vishalbiyani.com

Upload: vishal-biyani

Post on 08-Aug-2015

1.227 views

Category:

Software


0 download

TRANSCRIPT

LEARNINGPUPPET - 03

SlideBook (Book on slides)

Inspired by Slidedoc -

http://www.duarte.com

- Vishal Biyani

www.vishalbiyani.com

Configuring a a server with puppet

www.vishalbiyani.com Learning 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!

www.vishalbiyani.com Learning 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!

www.vishalbiyani.com Learning 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 get Tomcat 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 }

www.vishalbiyani.com Learning 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 }

www.vishalbiyani.com Learning 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 ;)

www.vishalbiyani.com Learning 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? BTW WTH 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

www.vishalbiyani.com Learning 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!

www.vishalbiyani.com Learning Puppet

1 Node classification and how to add relevant

infrastructure code to a nodeWhat 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