Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Learning puppet chapter 3


Published on

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.

Published in: Software
  • Login to see the comments

Learning puppet chapter 3

  1. 1. LEARNING PUPPET - 03 SlideBook (Book on slides) Inspired by Slidedoc - -Vishal Biyani
  2. 2. Configuring aa server with puppet
  3. 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. 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 '' { 2 package {'vim-enhanced.x86_64': 3 ensure => present, 4 } 5 } 6 node '' { 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 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 '' { 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. 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 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 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 |── |── spec | |── classes | | └── init_spec.rb | |── spec_helper.rb |── tests |── init.pp 1 class tomcat { 2 } 1 node '' { 2 include tomcat 3 }
  6. 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. 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. 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: 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 '' { 2 include tomcat 3 } 1 node '' { 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. 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. 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