Authors: Jeffrey McCune James Turnbull
class apache::service {
service { "apache2":
ensure => running,
hasstatus => true,
hasrestart => true,
enable => true,
require => Class["apache::install"],
}
}
This has allowed us to manage Apache, but how are we going to configure individual websites? To do this we’re going to use a new syntax, the definition.
Definitions are also collections of resources like classes, but unlike classes they can be specified and are evaluated multiple times on a host. They also accept parameters.
Note
Remember that classes are singletons. They can be included multiple times on a node, but they will only be evaluated ONCE. A definition, because it takes parameters, can be declared multiple times and each new declaration will be evaluated.
We create a definition using thedefine
syntax, as shown in
Listing 2-7
.
Listing 2-7.
The First Definition
define apache::vhost( $port, $docroot, $ssl=true, $template='apache/vhost.conf.erb', $priority, $serveraliases = '' ) {
include apache
file {"/etc/apache2/sites-enabled/${priority}-${name}":
content => template($template),
owner => 'root',
group => 'root',
mode => '777',
require => Class["apache::install"],
notify => Class["apache::service"],
}
}
We gave a definition a title (apache::vhost
) and then specified a list of potential variables. Variables can be specified as a list, and any default values specified, for example$ssl=true
. Defaults will be overridden if the parameter is specified when the definition is used.
Inside the definition we can specify additional resources or classes, for example here we’ve included theapache
class that ensures all required Apache configuration will be performed prior to our definition being evaluated. This is because it doesn’t make sense to create an Apache VirtualHost if we don’t have Apache installed and ready to serve content.
In addition to theapache
class, we’ve added a basic file resource which manages Apache site files contained in the/etc/apache2/sites-enabled
directory. The title of each file is constructed using thepriority
parameter, and the title of our definition is specified using the$name
variable.
Tip
The$name
variable contains the name, also known as the title, of a declared defined resource. This is the value of the string before the colon when declaring the defined resource.
This file resource’scontent
attribute is specified by a template, the specific template being the value of the$template
parameter. Let’s look at a fairly simple ERB template for an Apache VirtualHost in
Listing 2-8
.
Listing 2-8.
VirtualHost Template
NameVirtualHost *:<%= port %>
>
ServerName <%= name %>
<%if serveraliases.is_a? Array -%>
<% serveraliases.each do |name| -%><%= " ServerAlias #{name}\n" %><% end -%>
<% elsif serveraliases != '' -%>
<%= " ServerAlias #{serveraliases}" -%>
<% end -%>
DocumentRoot <%= docroot %>
>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
ErrorLog /var/log/apache2/<%= name %>_error.log
LogLevel warn
CustomLog /var/log/apache2/<%= name %>_access.log combined
ServerSignature On
Each parameter specified in the definition is used, including the$name
variable to name the virtual host we’re creating.
You can also see some embedded Ruby in our ERB template:
<%if serveraliases.is_a? Array -%>
<% serveraliases.each do |name| -%><%= " ServerAlias #{name}\n" %><% end -%>
<% elsif serveraliases != '' -%>
<%= " ServerAlias #{serveraliases}" -%>
<% end -%>
Here we’ve added some logic to theserveraliases
parameter. If that parameter is an array of values, then create each value as a new server alias; if it’s a single value, then create only one alias.
Let’s now see how we would use this definition and combine our definition and template:
apache::vhost { 'www.example.com':
port => 80,
docroot => '/var/www/www.example.com',
ssl => false,
priority => 10,
serveraliases => 'home.example.com',
}
Here we have used our definition much the same way we would specify a resource by declaring the apache::vhost definition and passing it a name,
www.example.com
(which is also the value of the$name
variable). We’ve also specified values for the required parameters. Unless a default is already specified for a parameter, you need to specify a value for every parameter of a definition otherwise Puppet will return an error. We could also override parameters, for example by specifying a different template:
template => 'apache/another_vhost_template.erb',
So in our current example, the template would result in a VirtualHost definition that looks like
Listing 2-9
.
Listing 2-9.
The VirtualHost Configuration File
NameVirtualHost *:80
ServerName www.example.com
ServerAlias home.example.com
DocumentRoot /var/www/www.example.com
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
ErrorLog /var/log/apache2/www.example.com_error.log
LogLevel warn
CustomLog /var/log/apache2/www.example.com_access.log combined
ServerSignature On
The final class in our module is theapache
class in theinit.pp
file, which includes our Apache classes:
class apache {
include apache::install, apache::service
}
You can see we’ve included our three classes but not the definition,apache::vhost
. This is because of some module magic called “autoloading.” You learned how everything in modules is automatically imported into Puppet, so you don’t need to use the import directive. Puppet scans your module and loads any .pp file in the manifests directory that is named after the class it contains, for example theinstall.pp
file contains theapache::install
class and so is autoloaded.
The same thing happens with definitions: Thevhost.pp
file contains the definitionapache::vhost,
and Puppet autoloads it. However, as we declare definitions, for example callingapache::vhost
where we need it, we don’t need to do aninclude apache::vhost
because calling it implies inclusion.
Next, we include our classes into ourwww.example.com
node and call theapache::vhost
definition to create thewww.example.com
website.
node "www.example.com" {
include base
include apache
apache::vhost { 'www.example.com':
port => 80,
docroot => '/var/www/www.example.com',
ssl => false,
priority => 10,
serveraliases => 'home.example.com',
}
}
We could now add additional web servers easily and create additional Apache VirtualHosts by calling theapache::vhost
definition again, for example:
apache::vhost { 'another.example.com':
port => 80,
docroot => '/var/www/another.example.com',
ssl => false,
priority => 10,
}
In our very last module we’re going to show you Puppet being self-referential, so you can manage Puppet with Puppet itself. To do this we create another module, one calledpuppet
, with a structure as follows:
puppet
puppet/files/
puppet/manifests/init.pp
puppet/manifests/install.pp
puppet/manifests/config.pp
puppet/manifests/params.pp
puppet/manifests/service.pp
puppet/templates/puppet.conf.erb
Our first class will be thepuppet::install
class which installs the Puppet client package.
class puppet::install {
package { "puppet" :
ensure => present,
}
}
All of the operating systems we’re installing on call the Puppet packagepuppet,
so we’re not going to use a variable here.
We do, however, need a couple of variables for our Puppet module, so we add apuppet::params
class.
class puppet::params {
$puppetserver = "puppet.example.com"
}
For the moment, this class only contains a Puppet server variable that specifies the fully-qualified domain name (FQDN) of our Puppet master.
Now we create ourpuppet::config
class:
class puppet::config {
include puppet::params
file { "/etc/puppet/puppet.conf":
ensure = > present,
content => template("puppet/puppet.conf.erb"),
owner => "puppet",
group => "puppet",
require => Class["puppet::install"],
notify => Class["puppet::service"],
}
}
This class contains a single file resource that loads thepuppet.conf.erb
template. It also includes thepuppet::params
class so as to make available the variables defined in that class. Let’s take a look at the contents of our template too:
[main]
user = puppet
group = puppet
report = true
reports = log,store
[master]
certname = <%= puppetserver %>
[agent]
pluginsync = false
report = true
server = <%= puppetserver %>
This is a very simple template, which we can then expand upon, or you can easily modify to add additional options or customize for your own purposes. You’ll notice we’ve included configuration for both our master and the client. We’re going to manage onepuppet.conf
file rather than a separate one for master and client. This is mostly because it’s easy and because it doesn’t add much overhead to our template.
We can then add thepuppet::service
class to manage the Puppet client daemon.
class puppet::service {
service { "puppet":
ensure => running,
hasstatus => true,
hasrestart => true,
enable => true,
require => Class["puppet::install"],
}
}
We can then create aninit.pp
that includes thepuppet
class and the sub-classes we’ve just created:
class puppet {
include puppet::install, puppet::config, puppet::service
}
Just stopping here would create a module that manages Puppet on all our clients. All we need to do, then, is to include this module on all of our client nodes, and Puppet will be able to manage itself. But we’re also going to extend our module to manage the Puppet master as well. To do this, we’re going to deviate slightly from our current design and put all the resources required to manage the Puppet master into a single class, calledpuppet::master
:
class puppet::master {
include puppet
include puppet::params
package { "puppet-server":
ensure => installed,
}
service { "puppetmasterd":
ensure => running,
hasstatus => true,
hasrestart => true,
enable => true,
require => File["/etc/puppet/puppet.conf"],
}
}
You can see that our classpuppet::master
includes the classespuppet
andpuppet::params
. This will mean all the preceding Puppet configuration will be applied, in addition to the new package and service resources we’ve defined in this class.