Skip to main content

DNS for VMs

Previously we talked about using Vagrant at Fictive Kin and how we typically have many Virtual Machines (VMs) on the go at once.

Addressing each of these VMs with a real hostname was proving to be difficult. We couldn’t just use the IP addresses of the machines because they’re unreasonably hard to remember, and other problems like browser cookies don’t work properly.

In the past, I’ve managed this by editing my local /etc/hosts file (or the Windows equivalent, whatever that’s called now). Turns out this wasn’t ideal. If my hosts don’t sync up with my colleagues’ hosts, stuff (usually cookies) can go wrong, for example. Plus, I strongly believe in setting up an environment that can be managed remotely (when possible) so less-technical members of our team don’t find themselves toiling under the burden of managing an obscurely-formatted text file deep within the parts of their operating systems that they — in all fairness — shouldn’t touch. Oh, and you also can’t do wildcards there.

As I mentioned in a previous post, we have the great fortune of having all of our VM users conveniently on one operating system platform (Mac OS X), so this post will also focus there, but a similar strategy to this one could be used on Windows or Linux, without the shiny resolver bits — you’d just have to run all of your host’s DNS traffic through a VM-managed name resolver; and these other operating systems might have something similar to resolver that I simply haven’t been enlightened to, and surely someone will point out my error on Twitter or email (please).

The short version (which I just hinted at) is that we run a DNS server on our gateway VM (all of our users have one of these), and we instruct the workstation’s operating system to resolve certain TLDs via this VM’s IP address.

We set up the VM side of this with configuration management, in our Salt sates. Our specific implementation is a little too hacky to share (we have a custom Python script that loads hostname configuration from disk, running within systemd), but I’ve recently been tinkering with Dnsmasq, and we might roll that out in the non-distant future.

Let’s say you want to manage the .sean TLD. Let’s additionally say that you have an app called saxophone (on a VM at 192.168.222.16) and another called trombone (on 192.168.222.17), and you’d like to address these via URLs like https://saxophone.sean/ and https://trombone.sean/, respectively. Let’s also say that you might want to make sure that http://www.trombone.sean/ redirects to https on trombone.sean (without the www). Finally, let’s say that the saxophone app has many subdomains like blog.saxophone.sean, admin.saxophone.sean, cdn.saxophone.sean, etc. As you can see, we’re now out of one-liner territory in /etc/hosts. (Well, maybe a couple long lines.)

To configure the DNS-resolving VM (“gateway” for us), with Dnsmasq, the configuration lines would look something like this:

address=/.saxophone.sean/192.168.222.16
address=/.trombone.sean/192.168.222.17

You can test with:

$ dig +short @gateway.sean admin.saxophone.sean
192.168.222.16
$ dig +short @gateway.sean www.trombone.sean
192.168.222.17
$ dig +short @gateway.sean trombone.sean
192.168.222.17

Now we’ve got the VM side set up. How do we best instruct the OS to resolve the new (fake) sean TLD “properly”?

Mac OS X has a mechanism called resolver that allows us to choose specific DNS servers for specific TLDs, which is very convenient.

Again, the short version of this is that you’d add the following line to /etc/resolver/sean (assuming the gateway is on 192.168.222.2) on your workstation (not the VM):

nameserver 192.168.222.2

Once complete (and mDNSResponder has been reloaded), your computer will use the specified name server to resolve the .sean TLD.

The longer version is that I don’t want to burden my VM users (especially those who get nervous touching anything in /etc — and with good reason), with this additional bit of configuration, so we manage this in our Vagrantfile, directly. Here’s an excerpt (we use something other than sean, but this has been altered to be consistent with our examples):

# set up custom resolver
if !File.exist? '/etc/resolver/sean'
  puts "Need to add the .sean resolver. We'll need sudo for this."
  puts "This should only happen once."
  print "\a"
  puts `sudo sh -c 'if [ ! -d /etc/resolver ]; then mkdir /etc/resolver; fi; echo "nameserver 192.168.222.2" > /etc/resolver/san; killall -HUP mDNSResponder;'`
end

Then, when the day comes that we want to add a new app — call it trumpet — we can do all of it through configuration management from the ops side. We create the new VM in Salt, and the next time the user’s gateway is highstated (that is: the configuration management is applied), the Vagrantfile is altered, and the DNS resolver configuration on the gateway VM is changed. Once the user has done vagrant up trumpet, they should be good to point their browsers at https://trumpet.sean/. We don’t (specifically Vagrant doesn’t) even need sudo on the workstation after the initial setup.