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.