IP-based rules in Puppet Manifests
When it comes to infrastructure configuration management, almost everyone has to deal with IP addressing. It could be that an engineer configures it, validates it, or references it. One of the popular tools for infrastructure configuration management is Puppet. This post is about working with IP addressing objects in Puppet manifests. An IP addressing object is a subnet, a single IP address, or a collection of them. A Puppet manifest is a set of rules defining what a configuration of a particular system should be. Let’s start by defining a goal. Here, the goal is to configure the servers based on the following rules:
- The servers in subnet 10.1.1.0/24 should have variable
datacenter
set toNY
- The servers in subnet 10.2.2.0/24 should have variable
datacenter
set toCH
- The servers that are neither in
10.1.1.0/24
nor in10.2.2.0/24
should have variabledatacenter
set toNA
datacenter
is necessary to further configure the system.
For example, contact information for technical support staff for a server could be different
based on its datacenter location. For example, NY
could be Amazon-based,
while CH
could be Microsoft Azure-based.
Additionally, by default all of our servers:
- use
eth0
interface for network connectivity - the interface is configured with a single IP address
Interface Discovery
First step is to install and configure Puppet standard library.puppet module install puppetlabs-stdlib
Next, reference it in init.pp
of the module:
class example () inherits example::params {
include stdlib
...
Second step is to understand what is the IP address of a particular interface.
The below Puppet manifest discovers the IP address, network address,
and network mask of eth0
interface.
# params.pp
class example::params {
$ifname = 'eth0'
if $networking == "" {
fail("Failed to find networking stack")
}
unless has_key($networking, 'interfaces') {
fail("Failed to find 'interfaces' key in networking stack")
}
unless has_key($networking['interfaces'], $ifname) {
fail("Failed to find interface '$ifname' in host networking stack")
}
unless has_key($networking['interfaces'][$ifname], 'bindings') {
fail("The interface '$ifname' has no IP address bindings")
}
The class
defined module example
and this particular file is params.pp
.
Next, $ifname
defines the interface for network communications. The first if
conditional statement checks whether networking
key is in Puppet Facter facts. The unless
conditional statement make this module fail when networking
key in the facts does not have a sub-key interfaces
. The next unless
makes sure that eth0
is in the list of the interfaces. Then, it check for bindings
key.
networking -> interfaces -> eth0 -> bindingsIf the module reached this point without a failure, the server has
eth0
interface.
The bindings
contains a list of IP addresses associated with an interface.
networking:
interfaces:
eth0:
bindings:
- address: 10.1.1.15
netmask: 255.255.255.0
network: 10.1.1.0
Here, eth0
must have a single IP address associated with the interface.
Let’s continue with the module rules:
# params.pp
...
unless ($networking['interfaces'][$ifname]['bindings'].length == 1) {
fail("The interface '$ifname' must have a single IP address associated with it")
}
$ifbinding = $networking['interfaces'][$ifname]['bindings'][0]
['address', 'network', 'netmask'].each |String $binding_attr| {
unless has_key($ifbinding, $binding_attr) {
fail("The interface '$ifname' does not have '$binding_attr'")
}
unless ($ifbinding[$binding_attr] != "") {
fail("The interface '$ifname' has empty $binding_attr")
}
}
$ifipaddr = $ifbinding['address']
$ifipnetwork = $ifbinding['network']
$ifipnetmask = $ifbinding['netmask']
debug("The interface '$ifname' belongs to $ifipnetwork/$ifipnetmask network and has IP address $ifipaddress")
The above snippet checks whether the interface has subnet information and
whether the information is not empty. Upon success, it causes the module
to create additional variables and output a message when debug is enabled.
Importantly, the three variables are strings. The IP address based rules compute
information using special objects, typically integers.
IP Objects and Network Standard Library
Our next task is to create these special objects for the interface IP address and interface subnet. For that we would need to use Yelp'syelp-netstdlib
library available from Puppet Forge.
The below command installs the library.
puppet module install yelp-netstdlib --version 0.0.1
Next, add the reference to the library to init.pp
of your own class, e.g.:
class example () inherits example::params {
include stdlib
include netstdlib
...
As the result, the Yelp’s network standard library functions become available in params.pp
.
The Yelp’s library has a good interface to deal with IP addresses, MAC
addresses, and DNS operations. This post focuses on IP addressing
operations.
Let's continue with the module rules. Here, we will create if else
conditional
statement and use ip_in_cidr()
function. In this example, the structure
is inside the module. However, the best practice is to keep that data in hiera
or facts
and write a custom function for your module.
# params.pp
...
if ip_in_cidr($ifipaddr, "10.1.1.0/24") {
$datacenter = "NY"
} elsif ip_in_cidr($ifipaddr, "10.2.2.0/24") {
$datacenter = "CH"
} else {
$datacenter = "NA"
}
Labels: puppet