Pro Puppet (16 page)

Read Pro Puppet Online

Authors: Jeffrey McCune James Turnbull

BOOK: Pro Puppet
4.7Mb size Format: txt, pdf, ePub

In
Listing 2-6
we can see what our template looks like.

Listing 2-6.
The Postfix main.cf template

soft_bounce = no
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
mail_owner = postfix
myhostname = <%= hostname %>
mydomain = <%= domain %>
myorigin = $mydomain
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
unknown_local_recipient_reject_code = 550
relay_domains = $mydestination
smtpd_reject_unlisted_recipient = yes
unverified_recipient_reject_code = 550
smtpd_banner = $myhostname ESMTP
setgid_group = postdrop  

You can see a fairly typical Postfix
main.cf
configuration file with the addition of two ERB variables that use Facter facts to correctly populate the file. Each variable is enclosed in
<%=  %>
and will be replaced with the fact values when Puppet runs. You can specify any variable in a template like this.

This is a very simple template and ERB has much of the same capabilities as Ruby, so you can build templates that take advantage of iteration, conditionals and other features. You can learn more about how to use templates further at
http://docs.puppetlabs.com/guides/templating.html
.

Tip
You can easily check the syntax of your ERB templates for correctness using the following command:
erb -x -T '-' mytemplate.erb | ruby –c
. Replace
mytemplate.erb
with the name of the template you want to check for syntax.

The postfix::service class

Next we have the
postfix::service
class, which manages our Postfix service:

class postfix::service {
  service { "postfix":
    ensure => running,
    hasstatus => true,
    hasrestart => true,
    enable => true,
    require => Class["postfix::config"],
  }
}

And finally, we have the core
postfix
class where we include all the other classes from our Postfix module:

class postfix {
  include postfix::install, postfix::config, postfix::service
}

We can then apply our
postfix
module to the
mail.example.com
node:

node "mail.example.com" {
  include base
  include postfix
}

Now when the
mail.example.com
node connects, Puppet will apply the configuration in both the
base
and
postfix
modules.

CLASS INHERITANCE

As with nodes, Puppet classes also have a simple inherit-and-override model. A subclass can inherit the values of a parent class and potentially override one or more of the values contained in the parent. This allows you to specify a generic class and override specific values in subclasses that are designed to suit some nodes, for example:

class bind::server {
        service {
              "bind":
              hasstatus => true,
              hasrestart => true,
              enable => true,
        }
}
class bind::server::enabled inherits bind::server {
       Service["bind"] { ensure => running, enable => true }
}
class bind::server::disabled inherits bind::server {
       Service["bind"] { ensure => stopped, enable => false }
}

Here, class
bind::server
is the parent class and defines a service that controls the
bind
service. It uses the service resource type to
enable
the
bind
service at boot time and specify the service must be
stopped
. We then specify two new subclasses, called
bind::server::enabled
and
bind::server::disabled,
which inherit the
bind::server
class. They override the
ensure
and
enable
attributes, and specify that the
bind
service must be running for all nodes with the
bind::server::enabled
subclass included. If we wish to disable
bind
on some nodes, then we need to simply include
bind::server::disabled
rather than
bind::server::enabled
. The use of class inheritance allows us to declare the bind service resource in one location, the bind::server class, and
achieve the desired behavior of enabling or disabling the service without completely re-declaring the bind service resource. This organization structure also ensures we avoid duplicate resource declarations, remembering that a resource can only be declared once.

You can also add values to attributes in subclasses, like so:

class bind {
       service { "bind": require => Package["bind"] }
}
class bind::server inherits bind {
       Service["bind"] { require +> Package["bind-libs"] }
}

Here we have defined the
proxy
class containing the
bind
service, which in turn requires the
bind
package to be installed. We have then created a subclass called
bind::server
that inherits the
bind
service but adds an additional package,
bind-libs
, to the
require
metaparameter. To do this, we use the
+>
operator. After this addition, the
bind
service would now functionally look like this:

service { "bind":
       require => [ Package["bind"], Package["bind-libs"] ]
}

We can also unset particular values in subclasses using the
undef
attribute value.

class bind {
       service { "bind": require => Package["bind"] }
}
class bind::client inherits bind {
       Service["bind"] { require => undef }
}

Here, we again have the
bind
class with the
bind
service, which requires the
bind
package. In the subclass, though, we have removed the
require
attribute using the
undef
attribute value.

It is important to remember that class inheritance suffers from the same issues as node inheritance:  variables are maintained in the scope they are defined in, and are not overridden. You can learn more at
http://projects.puppetlabs.com/projects/1/wiki/Frequently_Asked_Questions#Class+Inheritance+and+Variable+Scope
.

Managing MySQL with the mysql Module

Our next challenge is managing MySQL on our Solaris host,
db.example.com
. To do this we’re going to create a third module called
mysql
. We create our module structure as follows:

mysql
mysql/files/my.cnf
mysql/manifests/init.pp
mysql/manifests/install.pp
mysql/manifests/config.pp
mysql/manifests/service.pp
mysql/templates/
The mysql::install class

Let’s quickly walk through the classes to create, starting with
mysql::install
.

class mysql::install {
  package { [ "mysql5", "mysql5client", "mysql5rt", "mysql5test", "mysql5devel" ]:
    ensure => present,
    require => User["mysql"],  
}
  user { "mysql":
    ensure => present,
    comment => "MySQL user",
    gid => "mysql",
    shell => "/bin/false",
    require => Group["mysql"],
}
  group { "mysql":
    ensure => present,
  }
}

You can see that we’ve used two new resource types in our
mysql::install
class, User and Group. We also created a
mysql
group and then a user and added that user, using the
gid
attribute, to the group we created. We then added the appropriate
require
metaparameters to ensure they get created in the right order.

The mysql::config class

Next, we add our
mysql::config
class:

class mysql::config {
  file { "/opt/csw/mysql5/my.cnf":
    ensure = > present,
    source => "puppet:///modules/mysql/my.cnf",
    owner => "mysql",
    group => "mysql",
    require => Class["mysql::install"],
    notify => Class["mysql::service"],
  }
  file { "/opt/csw/mysql5/var":
    group => "mysql",
    owner => "mysql",
    recurse => true,
    require => File["/opt/csw/mysql5/my.cnf"],
  }
}

You can see we’ve added a File resource to manage our
/opt/csw/mysql5
directory. By specifying the directory as the title of the resource and setting the
recurse
attribute to
true,
we are asking Puppet to recurse through this directory and all directories underneath it and change the owner and group of all objects found inside them to
mysql
.

The mysql::service class

Then we add our
mysql::service
class:

class mysql::service {
  service { "cswmysql5":
    ensure => running,
    hasstatus => true,
    hasrestart => true,
    enabled => true,
    require => Class["mysql::config"],
  }
}

Our last class is our
mysql
class, contained in the
init.pp
file where we load all the required classes for this module:

class mysql {
  include mysql::install, mysql::config, mysql::service
}

Lastly, we can apply our
mysql
module to the
db.example.com
node.

node "db.example.com" {
  include base
  include mysql
}

Now, when the db.example.com node connects, Puppet will apply the configuration in both the
base
and
mysql
modules.

AUDITING

In addition to the normal mode of changing configuration (and the
--noop
mode of modelling the proposed configuration), Puppet has a new audit mode that was introduced in version 2.6.0. A normal Puppet resource controls the state you’d like a configuration item to be in, like this for example:

file { '/etc/hosts':
   owner => 'root',
   group => 'root',
   mode => 0660,
}

This file resource specifies that the
/etc/hosts
file should be owned by the
root
user and group and have permissions set to
0660
. Every time Puppet runs, it will check that this file’s settings are correct and make changes if they are not. In audit mode, however, Puppet merely checks the state of the resource and reports differences back. It is configured using the
audit
metaparameter.

Using this new metaparameter we can specify our resource like this:

file { '/etc/hosts':
   audit => [ owner, group, mode ],
 }

Now, instead of changing each value (though you can also add and mix attributes to change it, if you wish), Puppet will generate auditing log messages, which are available in Puppet reports (see
Chapter 9
):

audit change: previously recorded value owner root has been changed to owner daemon

This allows you to track any changes that occur on resources under management on your hosts. You can specify this
audit
metaparameter for any resource and all their attributes, and track users, groups, files, services and the myriad of other resources Puppet can manage.

You can specify the special value of
all
to have Puppet audit every attribute of a resource rather than needing to list all possible attributes, like so:

file { '/etc/hosts':
   audit => all,
 }

You can also combine the audited resources with managed resources, allowing you to manage some configuration items and simply track others. It is important to remember though, unlike many file integrity systems, that your audit state is not protected by a checksum or the like and is stored on the client. Future releases plan to protect and centralise this state data.

Managing Apache and Websites

As you’re starting to see a much more complete picture of our Puppet configuration, we come to managing Apache, Apache virtual hosts and their websites. We start with our module layout:

apache
apache/files/
apache/manifests/init.pp
apache/manifests/install.pp
apache/manifests/service.pp
apache/manifests/vhost.pp
apache/templates/vhost.conf.erb
The apache::install class

Firstly, we install Apache via the
apache::install
class:

class apache::install {
  package { [ "apache2" ]:
    ensure => present,
  }
}

This class currently just installs Apache on an Ubuntu host; we could easily add an
apache::params
class in the style of our SSH module to support multiple platforms.

The apache::service class

For this module we’re going to skip a configuration class, because we can just use the default Apache configuration. Let’s move right to an
apache::service
class to manage the Apache service itself.

Other books

Shadowplay by Laura Lam
Wings of Tavea by Devri Walls
The Days of Abandonment by Elena Ferrante
Murder Team by Chris Ryan
Revenge by Delamar, Dana
Jago by Kim Newman
Capitol Betrayal by William Bernhardt
Trouble with Kings by Smith, Sherwood