This post shares a PowerShell script to automatically live migrate clustered Hyper-V virtual machines to the host that owns the CSV that the VM is stored on. The example below should work nicely with a 2-node cluster, such as a cluster-in-a-box.
For lots of reasons, you get the best performance for VMs on a Hyper-V cluster if:
- Host X owns CSV Y AND
- The VMs that are stored on CSV Y are running on Host X.
This continues into WS2016, as we’ve seen by analysing the performance enhancements of ReFS for VHDX operations. In summary, the ODX-like enhancements work best when the CSV and VM placement are identical as above.
I wrote a script, with little bits taken from several places (scripting is the art of copy & paste), to analyse a cluster and then move virtual machines to the best location. The method of the script is:
- Move CSV ownership to what you have architected.
- Locate the VMs that need to move.
- Order that list of VMs based on RAM. I want to move the smallest VMs first in case there is memory contention.
- Live migrate VMs based on that ordered list.
What’s missing? Error handling 🙂
What do you need to do?
- You need to add variables for your CSVs and hosts.
- Modify/add lines to move CSV ownership to the required hosts.
- Balance the deployment of your VMs across your CSVs.
Here’s the script. I doubt the code is optimal, but it works. Note that the Live Migration command (Move-ClusterVirtualMachineRole) has been commented out so you can see what the script will do without it actually doing anything to your VM placement. Feel free to use, modify, etc.
#List your CSVs $CSV1 = "CSV1" $CSV2 = "CSV2" #List your hosts $CSV1Node = "Host01" $CSV2Node = "Host02" function ListVMs () { Write-Host "`n`n`n`n`n`nAnalysing the cluster $Cluster ..." $Cluster = Get-Cluster $AllCSV = Get-ClusterSharedVolume -Cluster $Cluster | Sort-Object Name $VMMigrationList = @() ForEach ($CSV in $AllCSV) { $CSVVolumeInfo = $CSV | Select -Expand SharedVolumeInfo $CSVPath = ($CSVVolumeInfo).FriendlyVolumeName $FixedCSVPath = $CSVPath -replace '\\', '\\' #Get the VMs where VM placement doesn't match CSV ownership $VMsToMove = Get-ClusterGroup | ? {($_.GroupType –eq 'VirtualMachine') -and ( $_.OwnerNode -ne $CSV.OWnernode.Name)} | Get-VM | Where-object {($_.path -match $FixedCSVPath)} #Build up a list of VMs including their memory size ForEach ($VM in $VMsToMove) { $VMRAM = (Get-VM -ComputerName $VM.ComputerName -Name $VM.Name).MemoryAssigned $VMMigrationList += ,@($VM.Name, $CSV.OWnernode.Name, $VMRAM) } } #Order the VMs based on memory size, ascending $VMMigrationList = $VMMigrationList | sort-object @{Expression={$_[2]}; Ascending=$true} Return $VMMigrationList } function MoveVM ($TheVMs) { foreach ($VM in $TheVMs) { $VMName = $VM[0] $VMDestination = $VM[1] Write-Host "`nMove $VMName to $VMDestination" #Move-ClusterVirtualMachineRole -Name $VMName -Node $VMDestination -MigrationType Live } } cls #Configure which node will own wich CSV Move-ClusterSharedVolume -Name $CSV1 -Node $CSV1Node | Out-Null Move-ClusterSharedVolume -Name $CSV2 -Node $CSV2Node | Out-Null $SortedVMs = @{} #Get a sorted list of VMs, ordered by assign memory $SortedVMs = ListVMs #Live Migrate the VMs, so that their host is also their CSV owner MoveVM $SortedVMs
Possible improvements:
- My ListVMs algorithm probably can be improved.
- The Live Migration piece also can be improved. It only does 1 VM at a time, but you could implement parallelism using jobs.
- Quick Migration should be used for non-running VMs. I haven’t handles that situation.
- You could opt to use Quick Migration for low priority VMs – if that’s your policy.
- The script could be modified to start using parameters, e.g. Analyse (not move), QuickMigrateLow, QuickMigrate (instead of Live Migrate), etc.
Thanks Aidan, very useful indeed!
Does the same performance benefit apply for NTFS CSVs?
Yes, there are reasons to align VMs and NTFS-based CSVs.
Does not work for me if only 1 VM is on the wrong host. If more than one VMs are effected then the script works fine!