High Availability Recipe
Problem:
Sometimes, when configuration management is a crucial part of a bigger picture, e.g. server management, you just can't rely on one puppet master that stores your resources (fileservers) and configuration data. The easy solution would be having two puppet masters running which stores the same resources and configuration data. When doing this, different puppet masters will have issues with trusting certificates signed by puppetmasters different from themselfves, and clients will store their signed certificates which probably aren't trusted by other puppetmasters. This is not a bug or error, it's just what these certificates guarantee, having a trust relationship between a puppetmaster and a client.
This recipe will handle a - possible, but certainly not the only one - way to set up high availability with Puppet.
Solution
The tools which I will use to have HA are:
- heartbeat: provides death-of-node detection and communication.
- mon: provides death-of-service detection (in our situation: local on the machine). When a death-of-service is detected, it will stop the heartbeat process on the current machine so the heartbeat on the other server will detect a death-of-node.
- a redundant IP: this is specified as a heartbeat resource. When a death-of-node is detected, it will assign this resource to another active node (in our case: the second puppet master).
The certificates have a following way of living:
- When a puppet client pulls configuration from a puppet master, it will check for a self created certificate.
- When this certificate isn't available, it will create one and send it to the master. The master will sign it automatically and send it back to the puppet client, who will store it in his certificate repository. When this handshake is done, the client knows that the master is trusted, and the master will permit the client to pull configuration from him.
To ensure that the problem (as described above) will not occur in the future, follow this (chronological) way to start this high availability service:
- Set up the puppet master (including the CA service) on the first server.
firstpuppetmasterhost# /etc/init.d/puppetmaster start
- Start a client on the second server (which will generate certificates and let them get signed by the first puppet master.
secondpuppetmasterhost# puppetd --test --server firstpuppetmasterhost
- Copy the CRL certificate (/var/lib/puppet/ssl/ca/ca_crl.pem) from the first server to the second server (same directory). Start the puppet master (with no CA service) on the second server.
firstpuppetmasterhost# scp /var/lib/puppet/ssl/ca/ca_crl.pem $user@secondpuppetmasterhost:/var/lib/puppet/ssl/ca/ secondpuppetmasterhost# puppetmasterd --noca
- All clients can now pull configuration from both servers, certificates will still need to be validated by the first server.
puppetclient# puppetd --test --serve firstpuppetmasterhost --server [firstpuppetmasterhost|secondpuppetmasterhost]
You probably want autosigning enabled, you can do this by adding the following lines to the puppetd.conf:
[ca] autosign = true
The CRL certificate is shared by the two puppet masters, which will grant all certificates signed by the first puppet master.
To ensure that both puppetmasters share the same puppet configuration data, you can let puppet handle the consistency of the configuration data itself. This can be done by running a puppet client on both two puppetmasterservers. Below is a bash script which can help in converting the configuration data files into a puppet configuration file:
#!/bin/bash
# 07/12/2006 v0.1
# koen.vereeken at gmail dot com
TMPFILE=/tmp/$RANDOM.pp
PUPPETDIR=/etc/puppet/
HOSTNAME=firstpuppetmasterhost
OTHERPUPPETMASTER=secondpuppetmasterhost
echo "class puppetcf {" > $TMPFILE
for directory in `find $PUPPETDIR -type d` ; do
echo " file { \"$directory\":
owner => root,
group => root,
ensure => directory,
mode => 644
}" >> $TMPFILE
done
for file in `find $PUPPETDIR -type f` ; do
filestripped=`echo ${file}|sed -e 's/\/etc\/puppet//'`
echo " file { \"$file\":
owner => root,
group => root,
mode => 644,
source => \"puppet://$secondpuppetmasterhost/puppetcf${filestripped}\"
}" >> $TMPFILE
done
echo "}" >> $TMPFILE
mv $TMPFILE $PUPPETDIR/manifests/projects/puppetcf.pp
Everything is set up now to use HA. Now we have to configure the HA tools. I'm assuming you know how to use these tools, I'm only going to describe and list some scripts and configuration that I've used to let it work with Puppet.
- mon: put this in the /etc/mon/mon.cf:
hostgroup puppetmaster localhost watch puppetmaster service puppetmasterd interval 30s monitor puppet.monitor period wd {Mon-Sun} alert stop-heartbeat.alert
and /usr/lib/mon/alert.d/stop-heartbeat.alert:
/usr/lib/heartbeat/hb_standby
and /usr/lib/mon/mon.d/puppet.monitor
#!/bin/bash
# koen.vereeken at gmail dot com
# this can probably be far more fine grained
# but it does the thrick now
exitstatus=0
processid=`ps -u puppet|grep puppetmasterd|awk '{print $1}'`
if [ "$processid" == "" ] || test `netstat -apn|grep "${processid}/ruby"|wc -l` -eq 0 ; then
exitstatus=1
fi
exit $exitstatus
- heartbeat: add a redundant IP into the /etc/ha.d/haresources file:
firstpuppetmasterhost 172.16.32.40/21/eth0:0
Just start up the daemons and let the HA do its work
firstpuppetmasterhost# /etc/init.d/heartbeat start && /etc/init.d/mon start secondpuppetmasterhost# /etc/init.d/heartbeat start && /etc/init.d/mon start
Of course you can use other HA tools, as discussed in the puppet list (round robin DNS, UCARP, ...) but this is what I've been using and it works great. Thanks to Luke for helping me out with the certificate problem.