I use Vagrant when testing new machines and experimenting locally with clusters, and since moving (mostly) to Linux, I have been using the LibVirt Plugin to create the virtual machines. Not only is it significantly faster than Hyper-V was on windows, but it also means I don’t need to use Oracle products, so it’s win-win really.

The only configuration challenge I have had with it is setting up VM hostname resolution, and as I forget how to do it each time, I figured I should write about it.

Setup

First I install the plugin so Vagrant can talk to Libvirt.

vagrant plugin install vagrant-libvirt

I also created a single vagrantfile with two virtual machines defined in it, so that I can check that the machines can resolve each other, as well as the host being able to resolve the guests.

Vagrant.configure("2") do |config|
 config.vm.box = "elastic/ubuntu-16.04-x86_64"

 config.vm.define "one" do |n1|
 n1.vm.hostname = "one"
 end

 config.vm.define "two" do |n1|
 n1.vm.hostname = "two"
 end
end

Once running vagrant up has finished (either with --provider libvirt or setting VAGRANT_DEFAULT_PROVIDER=libvirt), connect to one of the machines, and try to ping the other:

andy@karhu$ vagrant ssh one
vagrant@one$ ping two
ping: unknown host two
vagrant@one$ exit

Now that we can see they can’t resolve each other let’s move on to fixing it.

Custom Domain

The solution is to configure the libvirt network to have a domain name, and then to set the host machine to send requests for that domain to the virtual network.

First, I picked a domain. It doesn’t matter what it is, but I gather using .local will cause problems with other services, so instead, I picked $HOSTNAME.xyz, which is karhu.xyz in this case.

Vagrant-libvirt by default creates a network called vagrant-libvirt, so we can edit it to include the domain name configuration by running the following command:

virsh net-edit --network vagrant-libvirt

And adding the ` line to the xml which is displayed:

<network ipv6='yes'>
 <name>vagrant-libvirt</name>
 <uuid>d265a837-96fd-41fc-b114-d9e076462051</uuid>
 <forward mode='nat'/>
 <bridge name='virbr1' stp='on' delay='0'/>
 <mac address='52:54:00:a0:ae:fd'/>
+ <domain name='karhu.xyz' localOnly='yes'/>
 <ip address='192.168.121.1' netmask='255.255.255.0'>
 <dhcp>
 <range start='192.168.121.1' end='192.168.121.254'/>
 </dhcp>
 </ip>
</network>

To make the changes take effect, we need to destroy and re-create the network, so first I destroy the vagrant machines, then destroy and restart the network:

vagrant destroy -f
virsh net-destroy --network vagrant-libvirt
virsh net-start --network vagrant-libvirt

Finally, we can re-create the machines, and log in to one to check that they can resolve each other:

andy@karhu$ vagrant up
andy@karhu$ vagrant ssh one
vagrant@one$ ping two
PING two.karhu.xyz (192.168.121.243) 56(84) bytes of data.
vagrant@one$ exit

You can also check that the host can resolve the machine names when querying the virtual network’s DNS server:

andy@karhu$ dig @192.168.121.1 +short one
> 192.168.121.50

Host DNS Forwarding

The host cant talk to the machines by name still, so we need to tweak the host’s DNS, which means fighting with SystemD. Luckily, we only need to forward requests to a DNS server running on port 53 - if it was on another port then replacing systemd-resolved like my post on Consul DNS forwarding would be necessary.

Edit /etc/systemd/resolved.conf on the host, to add two lines which instruct it to send DNS requests for the domain picked earlier to the DNS server run by libvirt (dnsmasq):

[Resolve]
-#DNS=
+DNS=192.168.121.1
#FallbackDNS=
-#Domains=
+Domains=~karhu.xyz
#LLMNR=no
#MulticastDNS=no
#DNSSEC=no
#DNSOverTLS=no
#Cache=yes
#DNSStubListener=yes
#ReadEtcHosts=yes

Lastly, restart systemd-resolved for the changes to take effect:

systemctl restart systemd-resolved

Now we can resolve the guest machines by hostname at the domain we picked earlier:

andy@karhu$ ping one.karhu.xyz
PING one.karhu.xyz (192.168.121.50) 56(84) bytes of data.

Done!