How to make Puppet and Facter work on Docker enabled hosts

Docker provides a lightweight virtual environment by using Linux containers (LXC). We are establishing Docker in one of our projects to implement continuous delivery. For host management we use Puppet, which itself relies on some facts provided by their tool Facter.

Our Puppet modules make use of the ipaddress fact, determined by a built-in Facter script. We regard the ipaddress as the public IP address of the host. As described at gesellix.net, Facter doesn’t always collect the public IP address of the eth0 interface, but uses the IP address of docker0 on Docker hosts.

Finding the public IP address isn’t trivial, because it is a very environment specific datum so that Facter cannot always provide the best result. Daniel Pittman of Puppet Labs describes the problem at a forum post. We’ll show you two code examples how to find the best IP address for your specific demands. Other ideas are mentioned in a Puppet Labs forum answer.

Custom Facts

With Facter you can define custom facts, implemented in your preferred language. Custom facts help you define completely new facts or use existing facts. Since Docker adds a completely new network interface, we added three custom facts ipaddress_primary, macaddress_primary, and netmask_primary.

All of our dockerized hosts had the public interface named eth0, so we only had to get the fact named ipaddress_eth0 as our primary IP address. As fallback we used the original ipaddress:

 Facter.add("ipaddress_primary") do  
   setcode do  
     if Facter.value(‚ipaddress_eth0‘)  
       Facter.value(‚ipaddress_eth0‘)  
     else  
       Facter.value(‚ipaddress‘)  
     end  
   end  
 end

The same logic has been used for the netmask and macaddress facts. In order to distribute the new facts on our hosts, we added the files in our Puppet sources at /modules/module_name/lib/facter/ipaddress_primary.rb. We could now use the new facts in our Puppet modules, just like the original ipaddress fact.

For consistency, we should have changed all existing Puppet modules to use the new ..._primary facts. Since we only wanted to update the dockerized hosts and their modules, we tried to override the original fact. Some posts describe how to implement fact overrides by only using the same fact name, but that didn’t work for us. So we tried another way of overriding existing facts by using environment variables.

Environment variables

The Puppet CookBook describes how to override existing facts. You simply add a variable with the prefix FACTER_ and append the fact name you’d like to override. In our example, the result looks like this:

 FACTER_ipaddress="192.168.42.42"  
 FACTER_netmask="255.255.255.0"  

Adding the environment variables on our Docker hosts through our Puppet modules looks like follows:

 augeas { "environments":  
   context => "/files/etc/environment",  
   changes => [  
     "set FACTER_ipaddress ‚192.168.42.42‘",  
     "set FACTER_netmask ‚255.255.255.0‘",  
   ],  
   onlyif => ‚match /files/etc/environment/FACTER_ipaddress/* size == 0‘,  
 }

The overrides through environment variables have been added only to our dockerized modules, so we didn’t have to update all other hosts.

Another way to make Facter work together with Docker would have been to change the docker0 interface name. Well, as mentioned above, you have to keep in mind that Docker wasn’t the main issue, but the generic way of Facter to set the ipaddress fact. Facter cannot know what you expect in your environment, so you have to describe your specific needs in explicit facts.

If you have found another way of overriding facts or if this post was helpful to you, we’d like to know. Just leave a comment or get in contact @gesellix!