How to Implement Self-Hosted Dynamic DNS with BIND9 for KVM Virtual Machines in a Private Network


4 views

When managing a private KVM-based virtualization environment, manually updating DNS records every time you spin up a new VM becomes tedious. Traditional BIND9 configurations expect static zone files, but we need a solution where:

  • VMs auto-register their hostnames on creation
  • DNS updates happen without service restarts
  • The solution works entirely within a private network

Modern BIND9 actually supports dynamic updates through RFC 2136. Here's how to configure it:

// named.conf.local
zone "internal.lan" {
    type master;
    file "/etc/bind/zones/internal.lan.zone";
    allow-update { key "rndc-key"; };
    update-policy {
        grant rndc-key zonesub ANY;
    };
};

Create a TSIG key for secure updates:

dnssec-keygen -a HMAC-SHA256 -b 256 -n HOST rndc-key

Add the key to /etc/bind/rndc.key:

key "rndc-key" {
    algorithm hmac-sha256;
    secret "generated-key-here";
};

Here's a Python script for VMs to self-register:

#!/usr/bin/env python3
import dns.update
import dns.query
import dns.tsigkeyring

keyring = dns.tsigkeyring.from_text({
    'rndc-key': 'your-secret-key'
})

update = dns.update.Update(
    'internal.lan',
    keyring=keyring,
    keyalgorithm='hmac-sha256'
)
update.add('vmhost01.internal.lan', 300, 'A', '192.168.1.50')

response = dns.query.tcp(update, 'dns.internal.lan', timeout=10)

For KVM VMs, add this to your cloud-init config:

#cloud-config
runcmd:
  - curl -s http://dns.internal.lan/get_script.py | python3 - register_vm

For more API-driven approaches, consider PowerDNS:

# pdnsutil create-zone internal.lan
# pdnsutil set-meta internal.lan ALLOW-DNSUPDATE-FROM 192.168.1.0/24
  • Always use TSIG keys for updates
  • Restrict update permissions by subnet
  • Consider client certificates for additional security
  • Implement DNS update logging

Add this to your BIND9 config for audit logging:

logging {
    channel update_debug {
        file "/var/log/named/update.log";
        severity debug 3;
        print-time yes;
    };
    category update { update_debug; };
};

When managing a private virtualization environment with KVM-based Ubuntu VMs, traditional DNS solutions like BIND9 often feel cumbersome for dynamic environments. The manual process of updating zone files and restarting services creates operational friction during VM provisioning.

For automated DNS registration of KVM VMs, I recommend a three-component solution:


1. DNS Server: dnsmasq (lightweight + DHCP integration)
2. Configuration Management: Ansible 
3. Hook Scripts: libvirt hooks + cloud-init

1. Setting Up dnsmasq

Install and configure dnsmasq on your DNS server:


sudo apt install dnsmasq
sudo nano /etc/dnsmasq.conf

# Add these configurations:
domain-needed
bogus-priv
expand-hosts
domain=yourdomain.local
dhcp-range=192.168.1.50,192.168.1.150,12h
dhcp-option=option:router,192.168.1.1
dhcp-leasefile=/var/lib/misc/dnsmasq.leases

2. Automating VM Registration

Create a libvirt hook script to trigger on VM creation (place in /etc/libvirt/hooks/qemu):


#!/bin/bash

if [ "$1" = "yourdomain" ] && [ "$2" = "start" ] && [ "$3" = "begin" ]; then
    VM_IP=$(virsh domifaddr "$1" | awk '/ipv4/ {print $4}' | cut -d'/' -f1)
    VM_HOSTNAME=$(virsh dumpxml "$1" | xmllint --xpath 'string(/domain/metadata/yourdomain:metadata/yourdomain:hostname)' -)
    
    # Update dnsmasq leases
    echo "$(date +%s) $VM_MAC $VM_IP $VM_HOSTNAME" >> /var/lib/misc/dnsmasq.leases
    systemctl restart dnsmasq
fi

3. Cloud-init Integration

Configure your VM templates with this cloud-init configuration:


#cloud-config
hostname: ${var.hostname}
fqdn: ${var.hostname}.yourdomain.local
manage_etc_hosts: true

If you prefer BIND9, configure it for dynamic updates:


sudo nano /etc/bind/named.conf.local

zone "yourdomain.local" {
    type master;
    file "/var/lib/bind/db.yourdomain.local";
    allow-update { key rndc-key; };
    update-policy {
        grant rndc-key name *.yourdomain.local. A;
    };
};

Then use nsupdate in your provisioning scripts:


nsupdate -k /etc/bind/rndc.key << EOF
server dns.yourdomain.local
zone yourdomain.local
update add ${HOSTNAME}.yourdomain.local. 3600 A ${IP}
send
EOF
  • Restrict DNS updates to specific subnets or using TSIG keys
  • Implement proper logging for all DNS updates
  • Consider using separate views for internal/external DNS

Key diagnostic commands:


# Check dnsmasq leases
cat /var/lib/misc/dnsmasq.leases

# Verify BIND9 updates
sudo named-checkconf
sudo rndc reload