Authors: Jeffrey McCune James Turnbull
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 Postfixmain.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 athttp://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
. Replacemytemplate.erb
with the name of the template you want to check for syntax.
Next we have thepostfix::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 corepostfix
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 ourpostfix
module to themail.example.com
node:
node "mail.example.com" {
include base
include postfix
}
Now when themail.example.com
node connects, Puppet will apply the configuration in both thebase
andpostfix
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, classbind::server
is the parent class and defines a service that controls thebind
service. It uses the service resource type toenable
thebind
service at boot time and specify the service must bestopped
. We then specify two new subclasses, calledbind::server::enabled
andbind::server::disabled,
which inherit thebind::server
class. They override theensure
andenable
attributes, and specify that thebind
service must be running for all nodes with thebind::server::enabled
subclass included. If we wish to disablebind
on some nodes, then we need to simply includebind::server::disabled
rather thanbind::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 theproxy
class containing thebind
service, which in turn requires thebind
package to be installed. We have then created a subclass calledbind::server
that inherits thebind
service but adds an additional package,bind-libs
, to therequire
metaparameter. To do this, we use the+>
operator. After this addition, thebind
service would now functionally look like this:
service { "bind":
require => [ Package["bind"], Package["bind-libs"] ]
}
We can also unset particular values in subclasses using theundef
attribute value.
class bind {
service { "bind": require => Package["bind"] }
}
class bind::client inherits bind {
Service["bind"] { require => undef }
}
Here, we again have thebind
class with thebind
service, which requires thebind
package. In the subclass, though, we have removed therequire
attribute using theundef
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 athttp://projects.puppetlabs.com/projects/1/wiki/Frequently_Asked_Questions#Class+Inheritance+and+Variable+Scope
.
Our next challenge is managing MySQL on our Solaris host,db.example.com
. To do this we’re going to create a third module calledmysql
. 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/
Let’s quickly walk through the classes to create, starting withmysql::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 ourmysql::install
class, User and Group. We also created amysql
group and then a user and added that user, using thegid
attribute, to the group we created. We then added the appropriaterequire
metaparameters to ensure they get created in the right order.
Next, we add ourmysql::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 therecurse
attribute totrue,
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 tomysql
.
Then we add ourmysql::service
class:
class mysql::service {
service { "cswmysql5":
ensure => running,
hasstatus => true,
hasrestart => true,
enabled => true,
require => Class["mysql::config"],
}
}
Our last class is ourmysql
class, contained in theinit.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 ourmysql
module to thedb.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 thebase
andmysql
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 theroot
user and group and have permissions set to0660
. 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 theaudit
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 thisaudit
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 ofall
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.
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
Firstly, we install Apache via theapache::install
class:
class apache::install {
package { [ "apache2" ]:
ensure => present,
}
}
This class currently just installs Apache on an Ubuntu host; we could easily add anapache::params
class in the style of our SSH module to support multiple platforms.
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 anapache::service
class to manage the Apache service itself.