Managing /etc/hosts With Puppet
Date: 28 August 2012
Updated: 03 September 2012
So, here’s the situation. I have a stack of VM servers running KVM and libvirt. The hosts need to connect to a SAN for ISO storage and, potentially, VM disks. The problem is that the VM running DNS may not be up yet when the host starts. That’s a problem since I’m referencing the san by it’s host name rather than the IP address. Yes, I could change all of my configs to use the IP instead but host names are a lot easier to deal with, most of the time.
Well, I could work around the lack of DNS by putting an entry for the server in =/etc/hosts= but then I’d have to update it on every server if I ever change the IP address. Fortunately, puppet makes it easy. Sort of.
Puppet is a wonderful tool for managing Linux (and other *nix) servers. It’s a little weak, though, when all you want to do is add a line to file. That’s exactly what I wanted to do with =/etc/hosts=.
The good news is that puppet has a hook into the tool augeus. That makes editing the config relatively easy but plugging that into puppet is still a little messy. To make it easier, I created a class and define to tidy that up a bit.
Update [2012-09-03 Mon 07:15]: Puppet actually makes this easier than I thought. There already exists a host data type that I totally missed before. Dominic Cleal also pointed me to a module that he wrote that adds augeus providers to some of the default data types, including host.
class hosts {
define entry(
$ipaddr,
$canonical,
$aliases = 'UNSET' # I want to make this an array
)
{
augeas { "create_$title":
context => '/files/etc/hosts',
changes => [
"ins 01 after 1",
"set 01/ipaddr $ipaddr",
"set 01/canonical $canonical"
],
onlyif => "match *[ipaddr = '$ipaddr'] size == 0"
}
augeas { "update_$title":
context => '/files/etc/hosts',
changes => [
"set *[ipaddr = '$ipaddr']/canonical $canonical"
],
}
Augeas["create_$title"] -> Augeas["update_$title"]
# It would be great if I could loop this
if ($aliases == 'UNSET') {
augeas { "alias_$title":
context => '/files/etc/hosts',
changes => [
"rm *[ipaddr = '$ipaddr']/alias[1]"
],
}
}
else {
augeas { "alias_$title":
context => '/files/etc/hosts',
changes => [
"set *[ipaddr = '$ipaddr']/alias[1] '$alias'"
],
}
}
Augeas["update_$title"] -> Augeas["alias_$title"]
}
}
There are a couple of known issues. First, you can’t unset an entry. If you add a host with this and then decide that you no longer want it there, you can’t take it out. I don’t think it would hard to add that feature but I haven’t.
Second, the define only lets you set the first alias. Augeas allows for multiple aliases and I could pass an array to the define but I don’t know how to loop through that list.
Anyway, here’s how you use the define within a node or class definition.
include hosts
hosts::entry { 'san':
ipaddr => '192.168.1.5',
canonical => 'san.example',
aliases => 'san'
}
You can use as many hosts::entry
blocks as you want.