Automating DRS groups with PowerCLI

In vCenter we have lot's of DRS functionalities. I won't go into all of them here, you'll probably know about most of them already.

This blog post will talk about the VM/Host affinity functionality, i.e. rules to keep VMs running one or more specific host(s).

There is multiple use-cases for this. You might want to keep some VMs together on the same host to minimize latency, maybe you are doing some port mirroring and so on. In my scenario the use case is keeping some VMs on specific hosts for license compliance. Licensing is a huge topic and are always subject to change so this might not be a use-case in the future.

There are other ways to solve this as well, many organizations have specific clusters to comply with licensing (in some cases you'll have to), but for our specific use-case we be compliant if the VMs are running on their designated hosts.

So, the way to solve this with DRS is to create a DRS Host group with your licensed hosts, a DRS VM group with your specific VMs, and then tie them together with a DRS rule that says "The VMs in this VM group must run on the hosts in this Host group"

This is all fine, but what happens when you have a new VM that needs to be in the group, or a new host, or you want to remove a VM or host from their groups? Another caveat is that the DRS groups and rules are cluster objects which will say that you'll need groups and rules in all of your clusters. This could be a pain if you have lot's of clusters.

We need a way to automate this, and the place to go is of course PowerCLI.

So, we could maintain the list of Hosts and VMs through a file, a database or something, but vCenter has a nice functionality for tagging objects so why not use that.

What we'll do is:

  • Create a tag for VMs and a tag for Hosts
  • Assign the tags on the specific hosts / vms (and instruct people to use these tags going forward)
  • Create a script that checks for these tags and put the objects in to designated DRS groups
  • Create the rule to have the VM group run on the Host group
  • Schedule the script(s) to run on a suitable interval
  • Drink coffee

I will not go in to much detail on how you create Tags and how you assign Tags to specific VMs. This could be done in the GUI, but it's also really simple with PowerCLI.

To create a Tag you run

New-Tag -Name Tag001 -Category "VM Attributes"

Note that the Category needs to exist.

To tag a VM or a Host with a specific tag you'll just run

New-TagAssignment -Tag "Tag001" -Entity "vm-001"

The script

The script starts by assigning some variables for tag names etc. These could all be parameters to your script, but for now they are not

Our use-case in this example is for SQL VMs so our tags have been named accordingly

#Tag and Rule parameters
$vmgroup = "SQL-VM"
$hostgroup = "SQL-Host"
$vmtag = "SQL-Lic"
$hosttag = "SQL-Host"
$ruleName = "SQL-Lic"
$ruleEnable = $false

I've also included a $ruleEnable parameter which can be used to create the DRS rule with an Enabled status or not

The script connects to a vCenter which is passed as a parameter to the script. The other parameter is a $logfile parameter which is used to write output to a logfile. For now the logging isn't really sophisticated, but this is something that could be built on.

After connecting to vCenter we'll fetch all clusters. You could also specify which cluster(s) to check, but in my use case we'll run against them all.

We'll then do a foreach on all clusters. Inside the foreach we will initialize some variables

$sqlHosts = @()
$sqlVMs = @()

#Initiate existence variables
$tagExists = $false
$vmTagExists = $false
$hostGroupExists = $false
$vmGroupExists = $false

The first two are used to have a place to put the host/vm objects that we need to work with. The rest is variables set to false and will be set to true later on if some condition is met.

DRS Groups

First of we'll see if the DRS groups are already in place

$drsHostGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMHostGroup -Name $hosttag -ErrorAction SilentlyContinue
$drsVMGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMGroup -Name $vmtag -ErrorAction SilentlyContinue

if($drsHostGroup){
    Write-Verbose "DRS group for SQL hosts found"
    $hostGroupExists = $true
}
if($drsVMGroup){
    Write-Verbose "DRS group for SQL hosts found"
    $vmGroupExists = $true
}

If the groups exists the variables are set to $true

Hosts

Then we'll get all hosts in the cluster and put them in a $vmhosts variable, and then iterate through all. We'll use the Get-TagAssignment on the hosts to see if they are tagged with a tag with the name specified in the $hostTag variable

foreach($vmhost in $vmhosts){
    $hostTagAssigned = $vmhost | Get-TagAssignment -Category "Host Attributes"
    if($hostTagAssigned){
        foreach($hosttagA in $hostTagAssigned){
            if($hosttagA.Tag.Name -eq $hosttag){
               $sqlHosts += $vmhost
               $hostTagExists = $true
            }
        }
    }
}

If the host has a corresponding tag assigned it will be added to the $sqlHosts array, and the $hostTagExists variable will be set to $true

After iterating through all hosts in the cluster we will check if the $hostTagExists parameter is $true. If it is we know that we need a DRS group. We'll then check if the group is created or not. If not we will create it with the New-DrsClusterGroup cmdlet

New-DrsClusterGroup -Name $hosttag -Cluster $cluster -VMHost $sqlHosts

Note that you cannot create an empty DRS group, therefore we need to add the $sqlHosts array

Moving on we will now do another check if the DRS Host group exists and if we have hosts with the tag. The reason for doing this once more is that the first check will only be true if the group doesn't exist. But we will also need to check an existing group to see if all the designated hosts are a member of the group. This is done by iterating through all the $sqlHosts and check if the Member property of the DRS group contains the current host

if($hostGroupExists -and $hostTagExists){
    foreach($sqlHost in $sqlHosts){
        if(!$drsHostGroup.Member.Contains($sqlHost)){
            #Host is NOT a member, adding
            $drsHostGroup | Set-DrsClusterGroup -Add -VMHost $sqlhost
        }
    }
}

If the host is not a member it will be added to the group

We are now finished with the Hosts.

VMs

For VMs we do the same procedures. The only thing we've added to the VM logic is a check and potentially adding the tag name to the Notes field on the VM. This is done because in the vCenter GUI you can see the notes field in a VM list, but to see the tag you'll need to check the VM directly.

if($vm.Notes -notlike "*sql*"){
    if($vm.notes -eq $null -or $vm.notes -eq ""){
        Set-VM $vm -Notes $vmtag -Confirm:$false
    }
    else{
        $newnote = $vm.Notes + ";$vmtag"
            Set-VM -VM $vm -Notes $newnote -Confirm:$false
    }
}

DRS Rule

After processing all the VMs we'll check if we have a Host and a VM group in the cluster, and if so we will check if there is a corresponding DRS Rule. If not the rule will be created

if($hostGroupExists -and $vmGroupExists){
    $drsRules = Get-DrsVMHostRule -Cluster $cluster -Type ShouldRunOn -VMHostGroup $drsHostGroup -VMGroup $drsVMGroup
    if(!$drsRules){
        #Rule doesn't exist
        Write-Verbose "Creating DRS rule"
        New-DrsVMHostRule -Name $ruleName -Cluster $cluster -VMHostGroup $drsHostGroup -VMGroup $drsVMGroup -Type MustRunOn -Enabled:$ruleEnable
    }
}

Here we find the $ruleEnable variable. If this is set to False you will have to manually go in and enable it. This could be a good thing if you want to check things before DRS kicks off.

Note that you will have to have DRS turned ON and FullyAutomated to have DRS move VMs for you automatically. You could of course run DRS manually and get recommondations if you want.

Another thing to note is that we have only specified that the SQL tagged VMs have to run on the designated hosts, but this doesn't necessarily keep other VMs from running on the same hosts. If you want to ensure that there's ONLY tagged VMs running on your tagged hosts you'll need to create another VM group with the rest of the VMs in the cluster and then a DRS rule that ensure that those VMs "MustNotRunOn" the Host group

In our case we hope that DRS will be smart enough to balance out the non-tagged VMs on non-tagged Hosts :-)

The full script can be found on GitHub

This page was modified on April 1, 2019: Fixed categories and tags