The Puppet Labs Issue Tracker has Moved: https://tickets.puppetlabs.com

« Previous - Version 6/17 (diff) - Next » - Current version
James Turnbull, 03/14/2010 06:04 am


A More Advanced Puppet Recipe

When we left off in the Simplest Puppet Install Recipe , we had two files, our site manifest and a class configuring the sudo tool.

Our initial site manifest

The site manifest, /etc/puppet/manifests/site.pp, contained:

# /etc/puppet/manifests/site.pp

import "classes/*"

node default {
    include sudo
}

This file imports all the .pp files in the classes directory and then specifies a special node, called default, whose configuration will be applied to all nodes. In this case the default node only contains a single class: sudo.

Our first class

The sudo class contained:

# /etc/puppet/manifests/classes/sudo.pp

class sudo {
    file { "/etc/sudoers":
        owner => "root",
        group => "root",
        mode  => 440,
    }
}

This simple class performs only one function: managing the /etc/sudoers file.

This is a good start but Puppet is capable of a whole lot more so let’s expand on this initial example.

Revision Control

Before we get too far along though, you’ll notice we’ve got a couple of configuration, .pp, files. As you configure more and more resources you’ll find yourself adding to this collection of files. This collection of files also needs to be managed and we strongly recommend you implement a revision control system, such as Subversion or Git. You should place all your manifests and potentially other aspects of your Puppet configuration under revision control and preferably host your repository on another system. The repository should be regularly backed up. This will allow you to make changes to your manifests and configuration and know you can safely roll them back or recreate an earlier state without needing to re-write or edit a large number of files.

Our first module

We’ve seen our first class, sudo, but a better (and recommended!) way of bundling configuration resources exists. This is called a module. Puppet best practice is to put as much configuration into modules as possible. A module is a portable collection of classes, configuration resources, templates and files that configures a particular application or function, for example a module might be created to manage Apache or MySQL.

In this case we’re going to create a module to manage our sudo configuration. But our earlier work on the sudo class is not all lost and we can make use of our existing class to create our first module.

First, let’s create a module environment. Create a directory to hold your modules (this should match the directory set in the modulepath configuration option which usually defaults to /etc/puppet/modules):

# mkdir -p /etc/puppet/modules

Now make a directory to hold our new module, sudo:

# mkdir -p /etc/puppet/modules/sudo/manifests

Each module has a specific directory structure that allows Puppet to find all elements of the module and auto-load them. The heart of this module is the init.pp file, located in the module/manifests/ directory. We’re going to use our existing sudo class as the basis of our new init.pp file:

# cp /etc/puppet/manifests/classes/sudo.pp /etc/puppet/modules/sudo/manifests/init.pp

Expanding The Sudo Module

Now we’ve got the core of our new sudo module let’s expand on the functions it performs for us. First, we’re going to have it handle the installation of the sudo package and then we’re going to use Puppet’s built-in file server to distribute a /etc/sudoers file for us. This file will contain all a central sudo configuration that all our nodes will use. Let’s look at our updated sudo class now re-invented as a module.

# /etc/puppet/modules/sudo/manifests/init.pp

class sudo {

    package { sudo: ensure => latest }

    file { "/etc/sudoers":
        owner   => root,
        group   => root,
        mode    => 440,
        source  => "puppet:///sudo/sudoers",
        require => Package["sudo"],
    }
}

First, we’ve added a new resource type, package, and told it to install the sudo package and always ensure the package installed is the latest version.

Next, we’ve updated our original file type resource to include a few more attributes. Instead of now just managing a local file we’ve added the source attribute that tells Puppet to look for our /etc/sudoers file on Puppet’s built-in file server. To make this work we need to refer back to our module and add a new location to hold this file and potentially other files we might want to distribute with this module. First we make a directory to hold the file:

# mkdir /etc/puppet/modules/sudo/files

Then we copy in our new sudoers file:

# cp /etc/sudoers /etc/puppet/modules/sudo/files/sudoers

We’ve just copied in an arbitrary sudoers file but you could create your own and modify it to suit your environment. Now, when we look at the source attribute, we can see it is constructed like this puppet:///sudo/sudoers. The puppet tells Puppet that we’re using the internal file server. This works much like a normal protocol prefix, like http:// or ftp:// (although Puppet only supports file serving from its internal file server so far, in later releases support for other sources will be supported). But notice we’ve got an extra /. This isn’t a mistake. Normally, the source attribute would look like puppet://server_name/module/file. Instead of specifying the server name explicitly using the / tells Puppet to look for the file on the Puppet master currently managing the node. This makes our configuration a bit more portable. We’ve then specified the module we want Puppet to look into the find the file, sudo, and the name of the file to be sourced. Puppet knows from this attribute that the sudoers file contained in the /etc/puppet/modules/sudo/files/ directory is the file it needs to source and send to the client.

Lastly, we’ve added a meta-parameter called require which allows us to build a dependency. It says to Puppet that before it should do anything with the /etc/sudoers file then it should ensure the resource, Package[“sudo”], has been processed. In this case this means that the sudo package will always be installed before the /etc/sudoers file is sourced and managed.

Importing Modules

Modules need to be imported into Puppet to make use of them. However, generally everything under the modulepath, in our case /etc/puppet/modules/, is automatically imported into Puppet and is available to be used. There are cases where you will need to explicitly import modules (such as pretty much every module from David Schmitt’s Complete Configuration set). Since it never hurts to import them explicitly anyway, let’s go ahead and make a file for that purpose:

# /etc/puppet/manifests/modules.pp

import "sudo"

Node Definitions

Next, let’s create some nodes that we’ll apply our new module too. We’ll want to put node definitions into their own file, nodes.pp, like so:

# /etc/puppet/manifests/nodes.pp

node basenode {
  include sudo
}

node 'web.example.com' inherits basenode {
}

Here in our new nodes.pp file we’ve created two nodes: basenode and web.example.com. The basenode we can use to store configuration we intend to apply to all nodes (you don’t have to do this – this is just one style). We also created our first client node, web.example.com, which inherits our first node, basenode, and hence includes the sudo module.

In addition to individually specifying nodes in your manifests you can also make use of External Nodes or LDAPNodes nodes to store your node configurations.

Updating site.pp

All this leads to some changes to the site.pp file.

# /etc/puppet/manifests/site.pp

import "modules"
import "nodes"

# The filebucket option allows for file backups to the server
filebucket { main: server => 'my.server.name' }

# Set global defaults - including backing up all files to the main filebucket and adds a global path
File { backup => main }
Exec { path => "/usr/bin:/usr/sbin/:/bin:/sbin" }

What’s Next?

At this point you should have a decent grasp on the basics. You’ll want to read the Puppet Best Practice , Style Guide , and Language Tutorial , probably in that order.

To learn from other people’s work, or simply incorporate it in your own, you can look at Recipes and Puppet Modules .

It is also important to remember that the default Puppet server uses an internal webrick web server. The webrick web server does not scale very well and is not recommended for production use beyond 10 to 20 nodes. It is recommend that you move your Puppet server to Mongrel or Passenger. You can find instructions at Using Mongrel and Using Passenger respectively.

For reference information, see Reference Index .