Puppet notes

Creating a new service

A walk through the creation of a new service to explain some of my design decisions.

I will use memcached as an example.

Let's start with a simple class to ensure the required packages are installed:

  • /etc/puppet/modules/memcached/manifests/packages.pp
class memcached::packages {

    include memcached

    package { $memcached::package:

        ensure => installed,

    }

}

This could have been written more simply like this:

class memcached::packages {

    package { 'memcached':

        ensure => installed,

    }

}

However, I have found that it is useful for the config files to "require" the application package to be installed first, so I define the packages in a variable which can be used in another class something like:

require => Package[$memcached::package],

OK, so now let's create the base class and define the variable we use in the packages class:

  • /etc/puppet/modules/memcached/manifests/init.pp:
class memcached {

    $package = 'memcached'

}



define memcached::config (

    port,

    user = 'memcache',

    maxconn = 1024,

    cachesize = 64,

    options = ''

) {

    include site::config

    file { "${site::config::sysconfdir}/$name":

        content => template('memcached/sysconfig'),

        ensure  => present,

        owner   => 'root',

        group   => 'root',

        mode    => '0644',

    }

}

Whoah there - what's all that?

Well, in addition to the basic application class, I've created a define for the application configuration. This creates the required configuration file using a template:

  • /etc/puppet/modules/memcached/templates/sysconfig:
PORT="<%= port %>"

USER="<%= user %>"

MAXCONN="<%= maxconn %>"

CACHESIZE="<%= cachesize %>"

OPTIONS="<%= options %>"

The values used in the template are the variables specified at the top of the define, ie. $port, $user, etc. These are set when the define is used, or the default values are used. Note that port has no default value and is therefore a mandatory parameter to this define.

site::config is where I store various fragments of configuration information; in this case, the location of sysconfig directory.

So, we now need to create a class that uses the config define:

  • /etc/puppet/modules/site/manifests/foo/app/memcached/config01.pp
class site::foo::app::memcached::config01 {

    include memcached

    $port = 11211

    memcached::config{'memcached01':

        port => $port

    }

}

This is pretty straight-forward. Note that port is defined as a variable, again so that it can be used elsewhere.

Here's where we re-use the $port variable, in the firewall definition:

  • /etc/puppet/modules/site/manifests/foo/firewall/memcached01.pp
class site::foo::firewall::memcached01 {

    include site::foo::firewall::allowed_ips

    include site::foo::app::memcached::config01

    iptables::fragment{ '1100-memcached':

        dport    => "$site::foo::app::memcached::config01::port",

        state    => 'new',

        protocol => 'tcp',

        jump     => 'foo_INTERNAL',

    }

}

site::foo::firewall::allowed_ips contains code to define the foo_INTERNAL iptables chain.

site::foo::app::memcached::config01 is included so the $port variable can be used.

iptables::fragment is a custom define that creates an iptables fragment.

Finally, we can pull together all these elements into a service that may be included in a profile:

  • /etc/puppet/modules/site/manifests/foo/service/memcached01.pp
class site::foo::service::memcached01 {



    include memcached::packages

    include site::foo::app::memcached::config01

    include site::foo::firewall::memcached01



    $svname = 'memcached01'

    runit::service { $svname:

        runit_package   => $::memcached::package,

        run_script      => 'puppet:///site/foo/service/memcached/run',

        sv_config       => Class['site::foo::app::memcached::config01'],

        sysv_service    => 'memcached',

        sysv_daemon     => '/usr/bin/memcached',

        log             => true,

    }

}

This class includes the packages, the configuration, and the firewall.

It then defines a runit service named "memcache01" which will replace any existing SysV init memcached service. The service will refresh/restart if the configuration changes.

The final piece in the jigsaw is the run script for the service:

  • /etc/puppet/modules/site/files/foo/service/memcached/run
#!/bin/sh

exec 2>&1



DAEMON=${DAEMON:-memcached}



if [ -f /etc/sysconfig/${DAEMON} ]; then

        . /etc/sysconfig/${DAEMON}

fi



# Get the name of the service this log belongs to

SVNAME=$(basename $(readlink -f .))



# Where to look for configuration settings

CFGBASE=/etc/sysconfig/sv



# Get settings for default service and this service

[ -r "${CFGBASE}/default/settings" ] && \

  . "${CFGBASE}/default/settings"

[ -r "${CFGBASE}/${SVNAME}/settings" ] && \

  . "${CFGBASE}/${SVNAME}/settings"



# Set vars used to run service

SVOPENFILES=${SVOPENFILES:-250}

SVDATALIMIT=${SVDATALIMIT:-3000000}



ARGS="-p ${PORT} -m ${CACHESIZE} -c ${MAXCONN} -u ${USER}"



exec chpst \

    -o ${SVOPENFILES} \

    -d ${SVDATALIMIT} \

    ${DAEMON} \

        ${ARGS} \

        ${OPTIONS}