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}
