Understanding DNS Changes in Nutanix DR Recovery Plans

Updated 10.31.24! I modified the script from using the vm_recovery.bat to do everything, to calling a powershell script since that gave some extra flexibility in validating the current IP and DNS settings.

One of the best features of the Nutanix DR Recovery Plan is the ability to automate IP address changes based on failover criteria. Whether using Async, Near Sync or Sync rep, you have the ability to create a recovery plan that will automate the failover of specific VM(s), or by using a category to capture a group of VMs. I always try to use a Category when possible, to remove any possibilities of missing VMs that I do want to failover. The recovery plan allows me to set a power on sequence to the VMs that are part of the recovery plan, as well as modifying the network association and IP address to match the recovery site.

One item that is missing from the Nutanix DR capabilities natively is the ability to change the DNS Servers associated with the VM. In the case where VMs that failover from site to site, it might be necessary to update the DNS addresses for that specific site. Zerto provides this capability natively alongside the updating of the IP Address, but this feature still does not exist in Nutanix DR recovery plans. Further down we'll talk about modifying the DNS, which currently requires either a script using the Nutanix Guest Tools (NGT), or other automation using something like Ansible. For the purpose of this post, we're using a custom batch script.

Let's briefly go over a few steps in the creation of a recovery plan. To create a recovery plan, we first must have created protection policies for protecting VMs between Availability Zones or clusters. This could be a single PC instance with multiple clusters, or multiple PC instances separated by sites.

To create the recovery plan, from within the PC instance that has the protection policy, create a new recovery plan, add the VM(s) or Categories to be part of the plan, and set any VM Power On Sequences by adding stages.

VM Recovery Script

With Nutanix DR, to automate in-guest script execution during a recovery plan activity, you will need to Enable custom scripts. To do this, once a VM is added to the recovery plan, click Manage Scripts and enable. To utilize this feature, the VM must have NGT installed, and the script must be installed in the proper path.

The script must be named vm_recovery, and depending on whether this is for Windows or Linux, the folder path is pretty specific. In many cases, you will find that the scripts folder does not exist, and will need to be manually created, as well as the production and test folders. I also found on windows that sometimes I needed to add .bat to the file name, even though I saved it as a batch file.

Windows VMs:

  • - Batch script file path for production failover:

    • - C:\Program Files\Nutanix\scripts\production\vm_recovery
  • - Batch script file path for test failover:

    • - C:\Program Files\Nutanix\scripts\test\vm_recovery

Linux VMs:

  • - Shell script file path for production failover:

    • - /usr/local/sbin/production_vm_recovery
  • - Shell script file path for test failover:

    • - /usr/local/sbin/test_vm_recovery

Network Mapping

Network Mapping allows you to map the different virtual networks that will be part of the recovery plan. This features allows for mapping both a Live and Test Failover network for the recovery plan. If the networs are stretched via Layer 2, VXLAN or another mechanism, this eliminates the need for IP changes in most cases.

Recovery Plan Configuration: Network Settings

One important feature of the Network Mapping is how Nutanix DR automates the IP changes. By default, the first 3 octets of the IP Address will change on failover, and the 4th octet of the VM will remain. For example in my lab, I have address space of 10.10.16.x/24 for the primary site, and 10.10.216.x/24 for the secondary site. A VM using 10.10.16.100 will be changed to 10.10.216.100 during the recovery plan failover process. This means that if you're failing over into an existing network that has IP's already in use, this could pose a problem. To get around this, the recovery plan has the ability for Custom IP Mapping, enabling the VM to have it's failover IP Address modified from the current 4th octet to ensure not overlaps.

DNS Modification

As mentioned there are many different ways to do this, but in my case I'm using the vm_recovery.bat script to update the DNS servers, based on the VMs detected IP range. In my lab, I have my VMs in the primary site on the 10.10.16.0/24 subnet, and upon failover they will go to 10.10.216.0/24. My DNS Servers in the primary site are 172.20.19.253 and .254, while the at the secondary site they are 172.20.20.253 and .254. While the VMs in both sites can talk to all 4 DNS servers, in the event VMs have failed over I want them to use the DNS servers at their local site.

The script below is what I use to do this. I set the variables for the DNS servers by the site (and VLAN), and then use powershell to get the IP Address of the VM, and based on the first 10 values of the IP address, assign the proper DNS servers. This will work for both failover to the secondary site, and back to the primary site.

Update!

Since the original posting I updated the script to make it a bit more flexible. Since we have to use the vm_recovery.bat batch file for guest customizations, I went ahead and had the script call a specific powershell script, which allowed me to do some better checks against the current IP and DNS settings, and make better decisions on those changes.

The vm_recovery.bat file, with logging enabled.

 1@echo off
 2:: -------------------------------------------------------------------------
 3:: vm_recovery.bat for Nutanix Disaster Recovery
 4:: -------------------------------------------------------------------------
 5:: Purpose:
 6::     This batch file initiates a PowerShell script to check and update 
 7::     the DNS configuration based on the current IP address. It logs all 
 8::     activities to a specified log file, creating a new log file with 
 9::     the current date if the previous log exists.
10::
11:: Description:
12::     - Checks if an existing log file (vm_recovery.log) is present.
13::       If it exists, renames it with the current date.
14::     - Calls the PowerShell script (check_dns.ps1) in the same directory
15::       to perform DNS checks and updates as required.
16::
17:: Requirements:
18::     - Must run with administrative privileges.
19::     - Requires PowerShell to be installed.
20::
21:: Version:
22::     1.0
23::
24:: Author:
25::     Mike Dent ([email protected])
26:: -------------------------------------------------------------------------
27
28set "scriptDir=%~dp0"
29set "logDir=C:\Program Files\Nutanix\scripts"
30set "logFile=%logDir%\vm_recovery.log"
31
32REM Check if log file exists and rename it with the current date if it does
33if exist "%logFile%" (
34    set "dateStr=%date:/=-%"
35    ren "%logFile%" "vm_recovery_%dateStr%.log"
36)
37
38REM Call the PowerShell script and pass the log directory
39powershell -ExecutionPolicy Bypass -File "%scriptDir%vm_recovery.ps1" -LogDir "%logDir%"

The vm_recovery.bat file calls the vm_recovery.ps1 PowerShell script, which does the heavy lifting. The PowerShell sscript uses variables for the Subnets and DNS servers to check, and ultimately prints out the current and updated DNS entries.

  1<#
  2.SYNOPSIS
  3    Nutanix Disaster Recovery DNS and IP Configuration Script
  4
  5.DESCRIPTION
  6    This script checks the current IP address against predefined subnets for Site A and Site B. If the IP address
  7    matches a subnet and the DNS servers are different from the defined configuration, it updates the DNS servers
  8    accordingly.
  9
 10.VERSION
 11    1.4
 12
 13.AUTHOR
 14    Mike Dent ([email protected])
 15
 16.NOTES
 17    - Called from vm_recovery.bat
 18    - Logs all actions to "C:\Program Files\Nutanix\scripts\vm_recovery.log"
 19    - Maintains a summary table in the log file of previous and updated DNS configurations
 20
 21.PARAMETER LogDir
 22    Directory to save the log file.
 23#>
 24
 25param (
 26    [string]$LogDir
 27)
 28
 29# Define the path for the log file
 30$LogFile = Join-Path -Path $LogDir -ChildPath "vm_recovery.log"
 31
 32# Function to write log entries
 33function Write-Log {
 34    param ([string]$Message)
 35    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
 36    $entry = "$timestamp - $Message"
 37    Add-Content -Path $LogFile -Value $entry
 38}
 39
 40# Start logging
 41Write-Log "-------------------------------------------------------------"
 42Write-Log "DNS and IP Configuration Checker - Version 1.4"
 43Write-Log "Script initiated to verify and update DNS settings if needed"
 44Write-Log "-------------------------------------------------------------"
 45
 46# Define subnet and DNS information for Site A and Site B
 47$sites = @{
 48    "SiteA" = @{
 49        "Subnets" = @("172.x.x.0/24", "172.x.x0/24")
 50        "DNSServers" = @("172.x.x.250", "172.x.x.251")
 51    }
 52    "SiteB" = @{
 53        "Subnets" = @("172.x.x.0/24", "172.x.x0/24")
 54        "DNSServers" = @("172.x.x.250", "172.x.x.251")
 55    }
 56}
 57
 58# Get the current IP and DNS information
 59$currentIP = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.IPAddress -ne "127.0.0.1"}).IPAddress
 60$currentDNS = (Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -ExpandProperty ServerAddresses)
 61
 62Write-Log "Current IP Address: $currentIP"
 63Write-Log "Current DNS Servers Detected: $($currentDNS -join ', ')"
 64
 65# Store previous and new DNS values for logging
 66$previousDNS = $currentDNS
 67$newDNS = $null
 68
 69# Function to check if an IP belongs to a subnet
 70function Test-Subnet {
 71    param (
 72        [string]$IP,
 73        [string]$Subnet
 74    )
 75    $ipAddress = [System.Net.IPAddress]::Parse($IP)
 76    $subnetAddress, $prefixLength = $Subnet -split '/'
 77    $subnetMask = [System.Net.IPAddress]::Parse($subnetAddress)
 78    $networkBits = [int]$prefixLength
 79    $subnetBytes = $subnetMask.GetAddressBytes()
 80    $ipBytes = $ipAddress.GetAddressBytes()
 81
 82    for ($i = 0; $i -lt $networkBits / 8; $i++) {
 83        if ($ipBytes[$i] -ne $subnetBytes[$i]) {
 84            return $false
 85        }
 86    }
 87    return $true
 88}
 89
 90# Get the active network adapter’s InterfaceAlias
 91$activeAdapter = Get-NetAdapter | Where-Object {$_.Status -eq "Up"}
 92if (-not $activeAdapter) {
 93    Write-Log "Error: No active network adapter found. Exiting."
 94    exit
 95}
 96$interfaceAlias = $activeAdapter.InterfaceAlias
 97
 98Write-Log "Active Network Adapter InterfaceAlias: $interfaceAlias"
 99
100# Determine site based on current IP and update DNS if necessary
101foreach ($site in $sites.Keys) {
102    $siteData = $sites[$site]
103    $matchingSubnet = $siteData["Subnets"] | Where-Object { Test-Subnet -IP $currentIP -Subnet $_ }
104    
105    if ($matchingSubnet) {
106        Write-Log "Matched $currentIP to subnet $matchingSubnet in $site"
107        
108        # Strictly compare DNS server settings
109        $desiredDNS = $siteData["DNSServers"]
110        $dnsMismatch = ($currentDNS.Count -ne $desiredDNS.Count) -or ($currentDNS | Where-Object { $_ -notin $desiredDNS })
111
112        if (-not $dnsMismatch) {
113            Write-Log "DNS servers are already correctly configured for $site. No changes needed."
114            $newDNS = $currentDNS  # New DNS remains the same
115        }
116        else {
117            Write-Log "DNS servers mismatch detected. Expected: $($desiredDNS -join ', '), Current: $($currentDNS -join ', ')"
118            Write-Log "Attempting to update DNS servers to $($desiredDNS -join ', ') for $site"
119            
120            # Clear existing DNS servers
121            try {
122                Set-DnsClientServerAddress -InterfaceAlias $interfaceAlias -ServerAddresses @()
123                Write-Log "Cleared existing DNS servers on $interfaceAlias."
124                
125                # Set new DNS servers
126                Set-DnsClientServerAddress -InterfaceAlias $interfaceAlias -ServerAddresses $desiredDNS -ErrorAction Stop
127                Write-Log "DNS servers updated successfully to $($desiredDNS -join ', ') on $interfaceAlias."
128                $newDNS = $desiredDNS  # New DNS after update
129            }
130            catch {
131                Write-Log "Error: Failed to update DNS servers. $_"
132            }
133
134            # Verify the DNS settings after update
135            $updatedDNS = (Get-DnsClientServerAddress -InterfaceAlias $interfaceAlias -AddressFamily IPv4).ServerAddresses
136            if ($updatedDNS -join ',' -eq $desiredDNS -join ',') {
137                Write-Log "DNS update verification passed: $($updatedDNS -join ', ')"
138            } else {
139                Write-Log "Warning: DNS update verification failed. Expected: $($desiredDNS -join ', ') | Actual: $($updatedDNS -join ', ')"
140            }
141        }
142        break
143    }
144}
145
146# Summary table in log file
147Write-Log "-------------------------------------------------------------"
148Write-Log "Summary of DNS Configuration Changes"
149Write-Log "-------------------------------------------------------------"
150Write-Log "| Parameter       | Previous Value             | Current Value           |"
151Write-Log "|-----------------|---------------------------|--------------------------|"
152Write-Log "| IP Address      | $currentIP                 | $currentIP               |"
153Write-Log "| DNS Servers     | $($previousDNS -join ', ') | $($newDNS -join ', ')   |"
154Write-Log "-------------------------------------------------------------"
155
156Write-Log "DNS and IP check completed."

Recap

Nutanix DR provides a very robust method for protecting VMs between sites and/or clusters, with automated failover and ip address modification. To ensure we have full resiliency, adding in a custom script to modify the DNS servers based on the site also eliminates any manual intervention during failover or failback activities. The beauty of the vm_recovery scripts are that it's not limited to just DNS changes, but anything you can might need to script post failover can be added.

I'd love to know how you're handling any custom modifications to VMs during failovers, and what you're' using for that, so let me know!