Friday, August 3, 2018

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 to NY
  • The servers in subnet 10.2.2.0/24 should have variable datacenter set to CH
  • The servers that are neither in 10.1.1.0/24 nor in 10.2.2.0/24 should have variable datacenter set to NA
The variable 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 -> bindings
If 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's yelp-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:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home