AntiAffinity Groups in Failover Cluster. Part 4

AntiAffinity

  • Part 1 covers some theory and GUI configuration.
  • Part 2 focus on setting Preferred Owners with PowerShell.
  • Part 3 explained logic behind Possible Owners in PowerShell function.
  • Part 4 (this) will cover the last step – setting anti-affinity groups.
  • Part 5 will describe reporting of current configuration in the cluster.

    Today is a quick one – AntiAffinity groups.

    General concept

    There is no PowerShell cmdlet to set up Affinity Groups – like Set-ClusterOwnerNode. To achieve this, I must alter a class property AntiAffinityClassNames with an array of strings. Each object represents an affinity group a VM belongs to. If two VMs share the same group – cluster service will try to keep them on separate nodes.

    In short, this is all we need to check:

    $VMName = ‘Windows2016’

    (Get-ClusterGroup -Name $VMName).AntiAffinityClassNames

    And to set a simple assignment:

    (Get-ClusterGroup -Name $VMName).AntiAffinityClassNames = ‘Template’

    I’d like to be able to append new Groups if needed.

    Full Script

    Full script is available here:


    function Set-ClusterVMAntiAffinity {
    <#
    .SYNOPSIS
    Will configure Preferred Owners for Virtual Machine running on Failover Cluster
    .DESCRIPTION
    Uses Invoke-Command to allow for PSCredential
    If Preferred Owner is provided will verify if it matches cluster Owner Nodes.
    If yes – will set Preferred Owners for given VMs.
    If no – will abort.
    If $null, or '' is provided will reset Preferred Owners to defaults for given VM
    .PARAMETER Cluster
    Cluster Name
    .PARAMETER Credential
    Optional PSCredential used to connect to cluster
    .PARAMETER VMName
    Virtual Machine Name or array of names to process
    .PARAMETER AntiAffinityGroupName
    AntiAffinity Group name to configure VM with.
    .PARAMETER Append
    Switch option whether group should be added to current set.
    .EXAMPLE
    Set two VMs 'windows2016' and 'windows2016core' with AntiAffinity Group named 'templates'
    Set-ClusterVMAntiAffinity -Cluster 'cluster0' -VMName 'windows2016','windows2016core' -AntiAffinityGroupName 'Templates' -Verbose
    VERBOSE: Processing with default credentials of user {mczerniawski_admin}
    VERBOSE: Processing VM {windows2016}
    VERBOSE: VM {windows2016} current AntiAffinity Groups: {}
    VERBOSE: Setting Anti Affinity: {Templates} for VM {windows2016}
    VERBOSE: Processing VM {windows2016core}
    VERBOSE: VM {windows2016core} current AntiAffinity Groups: {Template}
    VERBOSE: Setting Anti Affinity: {Templates} for VM {windows2016core}
    Cluster VMName AntiAffinityGroupName
    ——- —— ———————
    cluster0 windows2016 Templates
    cluster0 windows2016core Templates
    .EXAMPLE
    Add AntiAffinity Group named 'templates2' to two VMs 'windows2016' and 'windows2016core'
    Set-ClusterVMAntiAffinity -Cluster 'cluster0' -VMName 'windows2016','windows2016core' -AntiAffinityGroupName 'Templates2' -Verbose -Append
    VERBOSE: Processing with default credentials of user {mczerniawski_admin}
    VERBOSE: Processing VM {windows2016}
    VERBOSE: VM {windows2016} current AntiAffinity Groups: {Templates}
    VERBOSE: Append selected. Preparing new object for AntiAffinity Group name for VM {windows2016}.
    VERBOSE: Setting Anti Affinity: {Templates2} for VM {windows2016}
    VERBOSE: Processing VM {windows2016core}
    VERBOSE: VM {windows2016core} current AntiAffinity Groups: {Templates}
    VERBOSE: Append selected. Preparing new object for AntiAffinity Group name for VM {windows2016core}.
    VERBOSE: Setting Anti Affinity: {Templates2} for VM {windows2016core}
    Cluster VMName AntiAffinityGroupName
    ——- —— ———————
    cluster0 windows2016 {Templates, Templates2}
    cluster0 windows2016core {Templates, Templates2}
    .EXAMPLE
    Clear any Anti Affinity Group name on 'windows2016' VM
    Set-ClusterVMAntiAffinity -Cluster 'cluster0' -VMName 'windows2016' -AntiAffinityGroupName $null -Verbose
    VERBOSE: Processing with default credentials of user {mczerniawski_admin}
    VERBOSE: Provided no AntiAffinity Group Name. Will reset to default setting
    VERBOSE: Processing VM {windows2016}
    VERBOSE: VM {windows2016} current AntiAffinity Groups: {}
    VERBOSE: Setting Anti Affinity: {} for VM {windows2016}
    Cluster VMName AntiAffinityGroupName
    ——- —— ———————
    cluster0 windows2016
    #>
    [CmdletBinding()]
    param(
    [Parameter(Mandatory, HelpMessage = 'Provide Cluster Name',
    ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $Cluster,
    [Parameter(Mandatory = $false, HelpMessage = 'Provide Credentials for Cluster',
    ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [System.Management.Automation.PSCredential]
    $Credential,
    [Parameter(Mandatory, HelpMessage = 'Provide VMName',
    ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [System.String[]]
    $VMName,
    [Parameter(Mandatory = $false, HelpMessage = 'Provide Preferred Owners Nodes to restrict VM to failover to',
    ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [System.String[]]
    $AntiAffinityGroupName,
    [Parameter(Mandatory = $false, HelpMessage = 'Append AntiAffinity group to current set',
    ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [switch]
    $Append
    )
    begin {
    #region PSSession parameters
    $connectionParams = @{
    ComputerName = $Cluster
    }
    if ($PSBoundParameters.ContainsKey('Credential')) {
    $connectionParams.Credential = $Credential
    Write-Verbose -Message "Processing with provided credentials {$($Credential.UserName)}"
    }
    else {
    Write-Verbose -Message "Processing with default credentials of user {$($env:USERNAME)}"
    }
    $ClusterSession = New-PSSession @connectionParams -ErrorAction Stop
    #endregion
    #null or empty string – reset to defaults
    if (-not $AntiAffinityGroupName) {
    Write-Verbose -Message "Provided no AntiAffinity Group Name. Will reset to default setting"
    $AntiAffinityGroupName = ''
    }
    }
    process {
    foreach ($VM in $VMName) {
    Write-Verbose -Message "Processing VM {$VM}"
    $ClusterVM = Invoke-Command -Session $ClusterSession -ScriptBlock {
    Get-ClusterGroup -Name $USING:VM -ErrorAction SilentlyContinue
    }
    if (-not $ClusterVM) {
    Write-Error -Message "VM {$VM} not found on cluster {$Cluster}"
    }
    else {
    Write-Verbose -Message "VM {$VM} current AntiAffinity Groups: {$($ClusterVM.AntiAffinityClassNames)}"
    #set to defaults
    if ($Append) {
    Write-Verbose -Message "Append selected. Preparing new object for AntiAffinity Group name for VM {$VM}."
    $finalAntiAffinityGroupName = @($ClusterVM.AntiAffinityClassNames) + @($AntiAffinityGroupName)
    }
    else {
    $finalAntiAffinityGroupName = @($AntiAffinityGroupName)
    }
    Write-Verbose -Message "Setting Anti Affinity: {$AntiAffinityGroupName} for VM {$VM} "
    $finalVMAntiAffinity = Invoke-Command -Session $ClusterSession -ScriptBlock {
    (Get-ClusterGroup -Name $USING:VM).AntiAffinityClassNames = @($USING:finalAntiAffinityGroupName)
    (Get-ClusterGroup -Name $USING:VM).AntiAffinityClassNames
    }
    [pscustomobject]@{
    Cluster = $Cluster
    VMName = $VM
    AntiAffinityGroupName = $finalVMAntiAffinity
    }
    }
    }
    }
    end {
    $ClusterSession | Remove-PSSession -ErrorAction SilentlyContinue
    }
    }

    Sample output

    Running PSCore 6.1 and targeting one vm on a cluster:

    Adding new AntiAffinityGroup to the same VM:

    Clearing all on ‘windows2016’:

    Setting two VMs into same AntiAffinity Group:

    Summary

    This one can help in keeping certain VMs on separate nodes (Domain Controllers, VM Guest cluster nodes, etc.)

    Next we’re going to get some reporting for every setting we’ve configured so far!

Leave a comment