Home Lab DNS Using dnsmasq and Puppet

It has been a long while since I’ve published anything to my kb site, perhaps just as long since I’ve started a new job nearly 2.5 years ago. In that time, my job responsibilities have shifted from the things I used to write on often. It’s my hope that I’ll get back into contributing to my kb site as I’ve enjoyed the collaboration with those of you who visit. So let’s get to it!

I’m going to try something new this time around. Here is a video overview on the installation and setup of DNS.

Followed by a write up on the overall approach I’ve taken to setup DNS for my home network.

First, let’s outline the article:

  • dnsmasq
    • installing and configuration dnsmasq for DNS services only
  • Putting it to use
    • Is the DNS service to be used by lab systems only?
    • Is the DNS service to be used for the entire home network?
    • Inputting and managing DNS entries
  • Puppet
    • How Puppet can be used to automate the management of DNS entries
    • How Puppet can be used to automate the build and configuration of dnsmasq
  • DNS with the help of Puppet, and without dnsmasq
    • How Puppet can be used to provide a poor mans DNS system without dnsmasq

And then the infrastructure:

  • Puppet Enterprise 2016.2.1
  • Red Hat Enterprise Linux 6 for the puppet master
  • Red Hat Enterprise Linux 7 for the dnsmasq server
  • DHCP enabled network

I am using Puppet Enterprise 2016.2.1 in my home lab, taking advantage of the free 10 node license. In order to setup DNS for your home network, you don’t need Puppet, but it comes in handy when dynamically deploying VMs that get an IP via DHCP and you want it automatically registered with DNS. With that, Puppet is not required to setup dnsmasq, nor is a DHCP enabled network should you assign static IPs to your lab systems. You can still follow along with the dnsmasq portion of this article and still come out with DNS for your home network / lab.

Red Hat Developer Network

For those who aren’t aware, you can now get Red Hat Enterprise Linux for free. Simply sign up at https://developers.redhat.com/ and you’ll have access to a no-cost Red Hat Enterprise Linux Developer Suite subscription. This article will reference the use of Red Hat, but of course CentOS or any distro will do as the dnsmasq configs will be the same.

dnsmasq

Let’s talk about dnsmasq first, and we’ll tie in the Puppet piece later. I’ve chosen to run my dnsmasq server in Red Hat 7. Our dnsmasq instance is only going to serve up DNS. We will NOT use the DHCP and TFTP capabilities of dnsmasq for this use case. I refer to my home lab as 3031.net. I do not own this domain, and I’m not really sure who does. I don’t care either, but I want to use it, and I can setup dnsmasq to resolve requests for 3031.net any way I’d like. You can do the same, but obviously, you wouldn’t want to use something that you use often, like google.com. Goes without saying I think… With that, chose your name with care, and start to set things up.

(For those intereted in the Puppet part, the things done manually here are puppetized further down in the article. At least continue reading from this point for the sake of context.)

  1. Deploy a Linux VM as the dnsmasq server
  2. Ensure that the dnsmasq package is installed
  3. We will configure dnsmasq to use the local /etc/hosts file to contain and serve up the the DNS records
  4. Leave /etc/dnsmasq.conf alone. Instead, place your dnsmasq configurations under /etc/dnsmasq.d. This is not a requirement, but just something I’ve opted to do.

    1. For example, I call my home lab 3031.net so I named my config file 3031.net
3031 vmwdnsmasq ~ # cat /etc/dnsmasq.d/3031.net  
domain-needed
bogus-priv
domain=3031.net
expand-hosts
local=/3031.net/
no-dhcp-interface=ens160
no-resolv
no-poll
server=8.8.8.8
server=8.8.4.4

Refer to what these various options mean by looking at /etc/dnsmasq.conf. In short:

  • domain-needed Block incomplete requests from leaving your network, such as google vs. google.com
  • bogus-priv Bogus private reverse lookups. All reverse lookups for private IP ranges (ie 192.168.x.x, etc) which are not found in /etc/hosts or the DHCP leases file are answered with “no such domain” rather than being forwarded upstream.
  • no-resolv Don’t read /etc/resolv.conf. Get upstream servers only from the command line or the dnsmasq configuration file
  • no-poll Don’t poll /etc/resolv.conf for changes
  • domain and expand-hosts play together. What this allows is for a DNS lookup to succeed if someone conducts a lookup on a fully qualified domain name, but only the short name exists in /etc/hosts. For instance, the ‘what’ entry below is only the short name:
3031 vmwdnsmasq ~ # cat /etc/hosts  
127.0.0.1    localhost    localhost.localdomain localhost4 localhost4.localdomain4
::1          localhost    localhost.localdomain localhost6 localhost6.localdomain6
192.168.1.3    vmwdnsmasq.3031.net    vmwdnsmasq
192.168.1.254  what

3031 vmwdnsmasq ~ #
3031 vmwdnsmasq ~ # nslookup what.3031.net 192.168.1.3
Server:        192.168.1.3
Address:       192.168.1.3#53

Name:    what.3031.net
Address: 192.168.1.254

If those two options were not set, the lookup would only work against the short name, and fail on the FQDN:

3031 vmwdnsmasq ~ # nslookup what          
Server:     192.168.1.1
Address:    192.168.1.1#53

Name:    what
Address: 192.168.1.254

vs.

3031 vmwdnsmasq ~ # nslookup what.3031.net 192.168.1.3  
Server:     192.168.1.3
Address:    192.168.1.3#53

** server can't find what.3031.net: NXDOMAIN
  • local ensures that queries for your private domain are only answered by dnsmasq, from /etc/hosts. In the 3031.net example, I can set local=/3031.net/ to tell dnsmasq never resolve this domain outside of what I’ve setup in /etc/hosts. That way, I will never get the real Internet facing IP address to anything 3031.net.
  • no-dhcp-interface turns off DHCP and TFTP, and provides DNS service only.
  • The server lines inform dnsmasq where for forward Internet DNS requests. The upstream nameservers have been set to Google DNS.

That’s it for the dnsmasq setup! Add whatever you want resolved to the local /etc/hosts file on the dnsmasq server, restart the dnsmasq service, and resolve away! At this point, implement dnsmasq into your lab however you see fit. Further reading below outlines my implementation.

Putting it to use

Is the DNS service to be used by lab systems only?

If you’re simply in need of a DNS system for your lab systems only, you could call it done at this point, and manually manage your dnsmasq config from this point going foward. If you’re manually deploying lab VMs and assigning static IPs, you can simply point DNS for those interfaces to your new dnsmasq server, update /etc/hosts on the dnsmasq server, bounce the dnsmasq service, and allow for name resolution within your lab systems only. Manual management of the config may be okay if you are only deploying a handful of machines, and just need to get something setup and functional.

Is the DNS service to be used for the entire home network?

For me, I wanted to take it a step further. In my lab, I auto deploy VMs to my vCenter instance directly from my laptop using vagrant (see this article yet to be linked). Each VM is assigned an IP via DHCP via my router, and I want this IP to auto-register itself with the dnsmasq server and become resolvable automatically, without having to manually inject the information into the dnsmasq servers /etc/hosts file. I also want to be able to resolve my lab systems from any system on the home network, including my laptop. To accomplish the latter, I pointed my routers DNS servers to my dnsmasq server (which is staically IP’d), instead of my ISPs DNS servers. This means less configuration on the side of dnsmasq to take over DHCP handouts, and setup of dhcp-options to ensure proper router configurations are being handed out.

Changing the DNS servers of the router banks heavily on the stability and uptime of the dnsmasq server of course. Obviously, if it went down, I will have no DNS resolution until the server is back up. For me, this is acceptable. My Lenovo TS140 that runs vCenter is on a battery backup with an uptime of 540+ days, where my router gets rebooted periodically and is not on a battery backup. So far, this setup has been extremely reliable.

Cool – now that we’ve got DNS resolution for lab systems and Internet requests, where does Puppet come into play? Exported resources!!

Puppet

How Puppet can be used to automate the management of DNS entries

Stay tuned for more indepth coverage on this via the video session!

Using exported resources, we can publish the resources of individual nodes for use by other nodes. Think about it… Most things in puppet are a resource; package, file, service, cron, mount, host.

Using Puppet, we can export the host resource of each VM, and collect the exported resources across other nodes, including our newly created dnsmasq server. Note that in order to use exported resources, you must have a PuppetDB deployment. This is where exported resources are stored.

To accomplish this, here is a simple host module that exports AND collects the exported host resource.

class hosts {
resources {'host':
purge => true,
}

host { 'localhost':
ensure => present,
host_aliases => ['localhost.localdomain', 'localhost4', 'localhost4.localdomain4'],
ip => '127.0.0.1',
}

@@host { $facts['fqdn']:
ensure => present,
host_aliases => $facts['hostname'],
ip => $facts['ipaddress'],
tag => '3031',
}
}

Simply classify all of your lab systems with the host class, and on subsequent puppet runs, it’ll export the systems hostname and ipaddress on all nodes classified with the host class. In addition, any /etc/hosts entries not managed via the hosts class will be purged. A quick note on purging… Puppet will purge anything that it doesn’t know about when setting purge => true. Be careful when using this if there are local /etc/host entries that need to be kept and haven’t been declared as a host resource within the host class. In addition, if a node is destroyed, the exported resources won’t be removed from PuppetDB until they too, have been purged from PuppetDB, meaning purging the node on the puppet master via the ‘puppet node purge <puppet node cert>‘ command.

You’ll notice that the resource collector is not included in the hosts class. If you want to populate each systems local /etc/hosts file, a collector may be added here. Otherwise, I’ve personally opted NOT to do that, and instead, want to collect the exported resource on the dnsmasq server only, as shown below. As part of the resource collector, the dnsmasq service will be notified each time an exported resource tagged as 3031 is collected. That way, newly added DNS records input into /etc/hosts will be available for name resolution.

Puppetizing dnsmasq

How Puppet can be used to automate the build and configuration of dnsmasq

ktreese puppet-control (classify with the role::dnsmasq class contained here)

ktreese/dnsmasq

class dnsmasq (
$network_ip = $::dnsmasq::params::network_ip,
$network_netmask = $::dnsmasq::params::network_netmask,
$network_gateway = $::dnsmasq::params::network_gateway,
$network_bootproto = $::dnsmasq::params::network_bootproto,
$network_onboot = $::dnsmasq::params::network_onboot,
$network_dns1 = $::dnsmasq::params::network_dns1,
$network_defroute = $::dnsmasq::params::network_defroute,
$network_iface = $::dnsmasq::params::network_iface,
$network_domain = $::dnsmasq::params::network_domain,
) inherits ::dnsmasq::params {

package { 'dnsmasq':
ensure => present,
}

file { "/etc/dnsmasq.d/${network_domain}":
ensure => present,
content => template("dnsmasq/${network_domain}.erb"),
owner => 'root',
group => 'root',
mode => '0644',
notify => Service['dnsmasq'],
require => Package['dnsmasq'],
}

service { 'dnsmasq':
ensure => running,
enable => true,
require => Package['dnsmasq'],
}

network::interface { $network_iface:
ipaddress => $network_ip,
netmask => $network_netmask,
gateway => $network_gateway,
bootproto => $network_bootproto,
onboot => $network_onboot,
dns1 => $network_dns1,
defroute => $network_defroute,
}

Host <<| tag == '3031' |>> ~> Service['dnsmasq']

}
➜ dnsmasq git:(master) cat templates/dnsmasq/3031.net  
domain-needed
bogus-priv
domain=<%= @network_domain %>
expand-hosts
local=/<%= @network_domain %>/
no-dhcp-interface=<%= @network_iface %>
no-resolv
no-poll
server=8.8.8.8
server=8.8.4.4

DNS with the help of Puppet, and without dnsmasq

Another approach would be to bypass the use of dnsmasq altogether, and instead, add a resource collector to the host module, and use it to populate the /etc/hosts file on every system, thus relying on local /etc/hosts entries for name resolution. You may consider installing the puppet agent on your laptop as well, and classify it with the host class so that it too, will get the exported host resource of your lab gear, therefore allowing your laptop to resolve against /etc/hosts. Lots of ways to approach this… Let me know what you think!

Limitations

Exported resources should be immediately available upon new system deployments and their first puppet run, assuming newly deployed systems match the Node Group configured with the host class.

However, DNS Record updates will become available as frequent as the runinterval of the puppet agent on the dnsmasq server. Provided this is a home lab, either perform a puppet run on the dnsmasq server via command line, from the Puppet Console, via mco, or set the runinterval to run every n seconds, whatever works for your setup.

For me, I’m not deploying VMs frequently enough to warrant the dnsmasq server checking in with the puppet master every 30 seconds, for example, so I left mine set to default, and opt’d to force a puppet run in one of the ways noted above.

Share