Actions: | Security

AllGoodBits.org

Navigation: Home | Services | Tools | Articles | Other

Configuration Management with Puppet - a nearly minimal example

There are many valid ways to implement configuration management using Puppet. I have designed this approach with simplicity and ease of use in mind, because I believe that complexity should only be introduced when necessary.

I consider the following features to be necessary complexity:

The Minimal Example

In the spirit of explaining by example, here's the simplest example I can contrive that conforms to the patterns I'm using:

[dhutty@puppet puppet]$ tree
.
|-- auth.conf
|-- fileserver.conf
|-- manifests
    |--site.pp
|-- modules
    |-- common
        |--manifests
           |--init.pp
    |-- ntp
        |-- manifests
            |-- init.pp
        |-- templates
            |-- client-ntp.conf.erb
|-- puppet.conf

The interesting sections of the tree are under manifests/ and modules/. The rest is, at least for a minimal example, exactly as created at installation (at least as installed by the EPEL rpm). Note that while the directory structure is only a convention, because the Ruby/Puppet community is so strong on convention, it has become meaningful. If you want to do it another way, you'll have to teach puppet where to look for everything; in other words, don't change the directory structure unless you have a really good reason and know what you're doing - and probably not even then.

manifests/site.pp:

node default {
  include common
    class {'ntp':
      ntpServerList => ['ntp1.example.com']
    }
}

modules/common/manifests/init.pp:

class common {
  include common::data
}

class common::data {
  $ntpServerList = [ 'ntp1.example.com','ntp2.example.com','ntp3.example.com' ]
}

modules/ntp/manifests/init.pp:

class ntp( $ntpServerList = $common::data::ntpServerList) {
  package { 'ntp':
  ensure => 'present',
  } #package

  file { '/etc/ntp.conf':
    mode    => "644",
    content => template("ntp/client-ntp.conf.erb"),
    notify  => Service["ntpd"],
    require => Package["ntp"],
  } # file

  service { 'ntpd':
    ensure  => running,
    enable  => true,
    require => Package["ntp"],
  } # service
}

modules/ntp/manifests/server.pp:

class ntp::server ( $ntp_sources = '', $ntp_peers = $common::data::ntpServerList ) inherits ntp {

  File ['/etc/ntp.conf'] {
    content => template("ntp/server-ntp.conf.erb"),
  }
} # class ntp::server

modules/ntp/templates/client-ntp.conf.erb:

# This file is being maintained by Puppet.
# DO NOT EDIT

# Permit time synchronization with our time source, but do not
# permit the source to query or modify the service on this system.
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery

# Permit all access over the loopback interface.  This could
# be tightened as well, but to do so would effect some of
# the administrative functions.
restrict 127.0.0.1
restrict -6 ::1

<% ntpServerList.each do |ntpServer| -%>
server <%= ntpServer %> iburst
<% end -%>

# Drift file.  Put this in a directory which the daemon can write to.
# No symbolic links allowed, either, since the daemon updates the file
# by creating a temporary in the same directory and then rename()'ing
# it to the file.
driftfile /var/lib/ntp/drift

Explanation

manifests/site.pp

This is where everything starts, the file that puppet reads first. The node that has the name default is special in puppet; its config is applied to any node that is not otherwise specified. Other nodes should probably be specified by fully qualified domain name: node 'myhost.example.com' { ... }

This file shows us how to use parameterized classes, passing a parameter into a class from a node definition.

modules/common/manifests/init.pp
This file shows a pattern for separating code and data. Use of a common::data class gives a single place to provide default values without having to mix these values into the class specifications in our modules.
modules/ntp/manifests/init.pp

This file is where the {package,file,service} resources are declared, with a couple of exceptions, this is just like every other puppet manifest you have seen.

The line class ntp( $ntpServerList = $common::data::ntpServerList) { is saying that the ntp class takes a parameter $ntpServerList which takes its default from $common::data::ntpServerList if the calling code does not pass in a parameter value.

The only other line of interest is content => template("ntp/client-ntp.conf.erb"), which specifies the erb template to use; it should live in ntp/templates/.

modules/ntp/manifests/server.pp
This file shows class inheritance, and how to override both parameters and resources. Note that the capitalization of the resource keyword (File) means that it is a reference to an existing declaration not a new declaration.
modules/ntp/templates/client-ntp.conf.erb
This is erb, the embedded ruby templating system; much like template toolkit, genshi, cheetah and many others, erb parses the file, executes codeblocks (as indicated by <% %>), and renders the template to an output file based on the variables, etc. passed to it and the code executed.

Execution

Having created an almost minimal puppet configuration, it's time to apply it. Since we have a definition for node default that declares that the default node is of class ntp, we can merely apply the site manifest in standalone mode, which means we don't have to worry about setting up a server, fiddling with and troubleshooting certificate issues, or anything else. I always run in the the dry-run, --noop, mode first:

puppet apply -v --noop /etc/puppet/manifests/site.pp