Настройка отработки отказа виртуальной машины между виртуальными сетями

Важно!

Поддержка этой версии Virtual Machine Manager (VMM) завершена. Рекомендуется выполнить обновление до VMM 2022.

В этой статье описываются способы репликации и отработки отказа виртуальных машин в System Center Virtual Machine Manager (VMM) между виртуальными сетями, если вы не используете службу Azure Site Recovery для управления процессами аварийного восстановления.

  • Для репликации виртуальных машин мы рекомендуем использовать Azure Site Recovery. VMM не поддерживает управление репликами Hyper-V без Site Recovery, и для автоматизации операций c репликой Hyper-V вам нужно использовать командлеты PowerShell для реплики Hyper-V.
  • Для аварийного восстановления мы рекомендуем разделить основную и дополнительную виртуальные сети. Основные виртуальные машины следует подключить к основной сети, а виртуальные машины реплики — к дополнительной. Так вы обеспечите возможность одновременного подключения к сети двух типов виртуальных машин.
  • Если у вас одна виртуальная сеть, используйте Site Recovery для автоматизации управления сетью с помощью функции сетевого сопоставления. Если вы не используете Site Recovery, тщательно проверьте все предварительные требования и порядок подключения виртуальных машин к сети. В частности, виртуальная машина реплики и основная виртуальная машина не должны подключаться к одной виртуальной сети одновременно. В противном случае записи CA-PA могут быть удалены в VMM и привести к потере сетевого подключения.

Пример решения

В этом примере решения описана следующая среда.

  • Один сервер VMM управляет основным и дополнительным сайтами.
  • Основная виртуальная машина и виртуальная машина реплики размещаются в одной виртуальной сети Hyper-V.
  • Вам нужно выполнить плановую отработку отказа и сохранить IP-адрес виртуальной машины после отработки отказа.
  • Виртуальные машины имеют адреса IPv4.

Перед началом работы

  • Убедитесь, что параметры виртуального коммутатора и логического коммутатора являются допустимыми и совпадают в структуре VMM. В противном случае операция подключения к сети после отработки отказа может завершиться неудачей.

  • Основная виртуальная машина должна быть подключена к виртуальной сети.

  • Виртуальная машина реплики не должна быть подключена к сети.

  • Каждому сетевому адаптеру основной виртуальной машины должен быть назначен только один IP-адрес. Выполните следующую команду, чтобы проверить это. Если к виртуальной машине подключено больше одного сетевого адаптера, запустите команду для каждого адаптера, изменяя индекс массива.

    $VMOnPD = Get-SCVirtualMachine -Name "VM Name" | where {$_.IsPrimaryVM -eq $true}
    Get-SCIPAddress –GrantToObjectId $VMOnPD.VirtualNetworkAdapters[0].ID``
    
  • Убедитесь, что IP-адрес, назначенный виртуальной машине операционной системой, совпадает с IP-адресом, показанным выше. Войдите на виртуальную машину и запустите ipconfig, чтобы проверка это.

  • Убедитесь, что таблицы подстановки правильно заданы в основном и реплика. Для этого выполните следующую команду на каждом сервере и убедитесь, что есть запись, соответствующая возвращенному выше IP-адресу: Get-NetVirtualizationLookupRecord

  • Убедитесь, что IP-адрес имеет формат IPv4, а не IPv6.

  • Перед запуском скриптов убедитесь, что обе виртуальные машины отключены.

  • Убедитесь, что состояние репликации включено на обеих виртуальных машинах.

Запустите скрипт плановой отработки отказа.

Этот скрипт выполняет следующее.

  1. Сохраняет IP-адрес, сеть и пул IP-адресов для каждого сетевого адаптера на основной виртуальной машине.
  2. Отменяет все IP-адреса для каждого сетевого адаптера на основной и дополнительной виртуальных машинах.
  3. Отключает все сетевые адаптеры.
  4. Выполняет отработку отказа для основной и дополнительной виртуальных машин.
  5. При необходимости запускает обратную репликацию.
  6. Присваивает виртуальной машине реплики те же IP-адреса, которые использовались на каждом из сетевых адаптеров.
  7. Присоединяет каждый сетевой адаптер виртуальной машины реплики к тем сетям, которые были сохранены на шаге 1.

Выполнение скрипта

Скрипт принимает два параметра.

  • $VMName — это имя виртуальной машины.
  • $ReverseRep — логический аргумент, указывающий, следует ли выполнять обратную репликацию.
    • Если $true передается, обратная репликация запускается немедленно, и вы не сможете отменить отработку отказа позже.
    • После успешного выполнение скрипта со значением $true для параметра $ReverseRep:
      • основная виртуальная машина будет иметь статус репликации Подготовлено к плановой отработке отказа;
      • виртуальная машина реплики будет иметь статус репликации Отработка отказа завершена.
    • Если передается значение $false, обратная репликация не выполняется. ReverseRepORCancelFO.ps1 можно использовать для выполнения обратной репликации или отмены отработки отказа.
    • После успешного выполнение скрипта со значением $false для параметра $ReverseRep:
      • основная виртуальная машина будет иметь статус репликации Подготовлено к плановой отработке отказа;
      • виртуальная машина реплики будет иметь статус репликации Отработка отказа завершена.

Если скрипт не выполняет ни одно из этих действий, вам нужно выполнить это действие вручную, а затем вернуться в окно PowerShell. Шаги сценария включают отработку отказа основной виртуальной машины, отработку отказа виртуальной машины реплика и при необходимости обратную репликацию.

Выполните скрипт:

 Param(
 [Parameter(Mandatory=$True)]
   [string]$VMName,
 [Parameter(Mandatory=$true)]
   [boolean]$ReverseRep
)

# the script running on system with SCVMM Console/PowerShell installed. Also, requires Hyper-V powershell module.``

Import-Module hyper-v

## Refresh VM configuration and initialize
Write-Host -ForegroundColor Green (Get-Date) ".....Refreshing the VMs..."
Get-SCVirtualMachine -Name $VMName | Read-SCVirtualMachine

$VMOnPD = Get-SCVirtualMachine -Name $VMName | where {$_.IsPrimaryVM -eq $true}
$VMOnDR = Get-SCVirtualMachine -Name $VMName | where {$_.IsPrimaryVM -eq $false}

if ($VMOnPD.StatusString -ne "Stopped")
{
    write-host -ForegroundColor Red (Get-Date) "....VM is not in stopped state. Actual State " $VMOnPD.StatusString
    write-host -ForegroundColor Red (Get-Date) "....Exiting"
    exit 1
}

$error.Clear()
$VMRepConfig = Get-VMReplication -ComputerName $VMOnPD.HostName -VMName $VMOnPD.Name
$VMRepConfig = Get-VMReplication -ComputerName $VMOnDR.HostName -VMName $VMOnPD.Name

if ($error -ne 0)
{
    $temp = $VMOnPD.HostName.Split(".")
    $primaryHostName = $temp[0]

    $temp = $VMOnDR.HostName.Split(".")
    $recoveryHostName = $temp[0]

    write-host -ForegroundColor Red (Get-Date) "....Error in getting VM Replication state using FQDN, switching to Hostname"
    write-host -ForegroundColor Yellow (Get-Date) "....Primary Hostname: " $primaryHostName " Replica Hostname: " $recoveryHostName

    $error.Clear()
    $VMRepConfig = Get-VMReplication -ComputerName $primaryHostName -VMName $VMOnPD.Name
    $VMRepConfig = Get-VMReplication -ComputerName $recoveryHostName -VMName $VMOnPD.Name

    if ($error -ne 0)
    {
        write-host -ForegroundColor Red (Get-Date) "....Error in getting VM Replication state using Hostname"
        write-host -ForegroundColor Red (Get-Date) "....Exiting"
        exit 1
    }

    write-host -ForegroundColor Green (Get-Date) "....Successful in getting VM Replication state using Hostname"
}
else
{
    $primaryHostName = $VMOnPD.HostName
    $recoveryHostName = $VMOnDR.HostName
}

$VMOnPDAdapter = Get-SCVirtualNetworkAdapter -VM $VMonPD
$VMOnDRAdapter = Get-SCVirtualNetworkAdapter -VM $VMonDR

$fileName = $VMName + (Get-Date).ToString() + ".txt"
$fileName = $fileName.Replace("/","_")
$fileName = $fileName.Replace(":","_")

Write-Host -ForegroundColor Yellow (Get-Date) "....Dumping network information for $VMName to file $fileName"
Write-Host -ForegroundColor Yellow (Get-Date) "....Number of Network adapters found: " $VMOnPDAdapter.count

$VMNetwork = @()
$VMSubnet = @()
$Pools = @()

$counter = 0
foreach($vmAdapter in $VMOnPDAdapter)
{
    if ($vmAdapter.VMNetwork -eq $null)
    {
        $VMNetwork = $VMNetwork + $null
        $VMSubnet = $VMSubnet + $null
        $Pools = $Pools + $null
        $counter = $counter + 1
        continue
    }

    $VMNetwork = $VMNetwork + (Get-SCVMNetwork -Name $vmAdapter.VMNetwork.Name -ID $vmAdapter.VMNetwork.ID)
    $VMSubnet = $VMSubnet + (Get-SCVMSubnet -Name $vmAdapter.VMSubnet.Name | where {$_.VMNetwork.ID -eq $vmAdapter.VMNetwork.ID})
    #$PortClassification = Get-SCPortClassification | where {$_.Name -eq "Guest Dynamic IP"}
    $Pools = $Pools + (Get-SCStaticIPAddressPool -IPv4 | where {$_.VMsubnet.name -eq $vmAdapter.VMSubnet.Name})

    Out-File -FilePath $fileName -InputObject $VMNetwork[$counter] -Append
    Out-File -FilePath $fileName -InputObject $VMSubnet[$counter] -Append
    Out-File -FilePath $fileName -InputObject $Pools[$counter] -Append

    $counter = $counter + 1
}

if ($error.Count -ne 0)
{
    write-host -ForegroundColor Red (Get-Date) "....Error is gathering information for $VMName. No changes made"
    write-host -ForegroundColor Red (Get-Date) "....Exiting"
    exit 1
}

$IP = @()
$counter = 0
foreach($vmAdapter in $VMOnPDAdapter)
{

    if ($VMNetwork[$counter] -eq $null)
    {
        Write-Host -ForegroundColor Yellow (Get-Date) ".....Network Adapter '" $counter "' not connected"
        $IP = $IP + $null
        $counter = $counter + 1
        continue
    }

    ## Revoke IP
    $error.Clear()
    $IP = $IP +(Get-SCIPAddress –GrantToObjectId $VMOnPD.VirtualNetworkAdapters[$counter].ID)
    Write-Host -ForegroundColor Yellow (Get-Date) "....Revoking IP " $IP[$counter] "from Primary VM"
    Revoke-SCIPAddress $IP[$counter]
    if ($error.count -eq 0)
    {
        Write-Host -ForegroundColor Green (Get-Date) "....." $IP[$counter] "revoke completed"
    }

    ## Disconnect Primary VM
    Write-Host -ForegroundColor Yellow (Get-Date) "....Disconnecting Primary VM from Network " $VMNetwork[$counter]
    Set-SCVirtualNetworkAdapter -VirtualNetworkAdapter $VMOnPD.VirtualNetworkAdapters[$counter] -NoLogicalNetwork -NoConnection -NoPortClassification
    Write-Host -ForegroundColor Green (Get-Date) "....Network Adapter '" $counter "' of Primary VM Disconnected"

    $counter = $counter + 1
}

## Start failover
Write-Host -ForegroundColor Yellow (Get-Date) ".....We are going to Failover " $VMName " from " $primaryHostName " to " $recoveryHostName

$error.Clear()
Start-VMFailover -ComputerName $primaryHostName -VMName $VMOnPD.Name -Prepare -Confirm:$false

start-sleep 5

Write-Host -ForegroundColor Yellow (Get-Date) ".....Completing Failover on Replica site..."
Start-VMFailover -ComputerName $recoveryHostName -VMName $VMOnDR.Name -Confirm:$false
if ($ReverseRep)
{
    write-host -ForegroundColor Green (Get-Date) ".....Starting Reverse Replication..."
    Set-VMReplication -ComputerName $recoveryHostName -reverse -VMName $VMOnDR.Name
}

if ($error -ne 0)
{
    write-host -ForegroundColor Red (Get-Date) ".....Error occured during Planned Failover for VM $VMName"
    write-host -ForegroundColor Red (Get-Date) ".....Please manually complete Failover before continuing"
    Write-Host -ForegroundColor Red (Get-Date) ".....Press any key to continue..."
    $ignoreKey = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}

Write-Host -ForegroundColor Green (Get-Date) ".....Connecting Network(s) to Failed-over VM"

$counter = 0
foreach($vmAdapter in $VMOnPDAdapter)
{

    if ($VMNetwork[$counter] -eq $null)
    {
        Write-Host -ForegroundColor Yellow (Get-Date) ".....Network Adapter '" $counter "' not connected"
        $counter = $counter + 1
        continue
    }

    Write-Host -ForegroundColor Yellow (Get-Date) "Granting " $IP[$counter] "to Failed-over VM"
    Grant-SCIPAddress -GrantToObjectType "VirtualNetworkAdapter" -GrantToObjectID $VMOnDRAdapter[$counter].ID -StaticIPAddressPool $Pools[$counter] –IPAddress $IP[$counter]
    Write-Host -ForegroundColor Green (Get-Date) "Granting IP completed"

    Write-Host -ForegroundColor Yellow (Get-Date) "Connecting Replica VM to " $VMNetwork[$counter]
    Set-SCVirtualNetworkAdapter -VirtualNetworkAdapter $VMOnDRAdapter[$counter] -IPv4AddressType static -VMNetwork $VMNetwork[$counter] -VMSubnet $VMSubnet[$counter]
    Write-Host -ForegroundColor Green (Get-Date) "Network Adapter '" $counter "' of Failed-over VM connected to " $VMNetwork[$counter]

    $counter = $counter + 1
}

Запуск скрипта для обратной репликации или отмены

Этот скрипт выполняет следующее.

  1. Если скрипт перехода на другой ресурс выполнялся без применения обратной репликации, вы можете использовать этот скрипт для выполнения обратной репликации или отмены отработки отказа.
  2. При отмене сценарий отменяет действия сети и восстанавливает подключения к основной виртуальной машине после отключения реплика сетей виртуальных машин.

Выполнение скрипта

Этот скрипт должен выполняться, если скрипт отработки отказа запущен со значением $false для параметра $ReverseRep. Этот скрипт принимает три аргумента:

  • $VMName — имя виртуальной машины.
  • $ReverseRep — логический аргумент, который указывает, нужно ли выполнять обратную репликацию. Значение $true обозначает, что обратная репликация выполняется.
  • $CancelFO — логический аргумент, который указывает, нужно ли отменять отработку отказа. Значение $true обозначает, что выполняется отмена на основном и резервном сайтах.

Параметры $ReverseRep и $CancelFO не могут одновременно иметь значения $true. После успешного выполнения скрипта на обеих виртуальных машинах должно быть установлено состояние Репликация включена.

Выполните скрипт:

Param(
 [Parameter(Mandatory=$True)]
   [string]$VMName,
 [Parameter(Mandatory=$true)]
   [boolean]$ReverseRep,
 [Parameter(Mandatory=$true)]
   [boolean]$CancelFO
)

# the script running on system with SCVMM Console/PowerShell installed. Also, requires Hyper-V powershell module.

Import-Module hyper-v

if ($ReverseRep -eq $CancelFO)
{
    write-host -ForegroundColor Red (Get-Date) "....Please ensure that one and only one of the parameters -ReverseRep and -CancelFO is passed as $True"
    write-host -ForegroundColor Red (Get-Date) "....Exiting"
    exit 1
}

## Refresh VM configuration and initialize
Write-Host -ForegroundColor Green (Get-Date) ".....Refreshing the VMs..."
Get-SCVirtualMachine -Name $VMName | Read-SCVirtualMachine

$VMOnPD = Get-SCVirtualMachine -Name $VMName | where {$_.IsPrimaryVM -eq $true}
$VMOnDR = Get-SCVirtualMachine -Name $VMName | where {$_.IsPrimaryVM -eq $false}

$error.Clear()
$VMRepConfig = Get-VMReplication -ComputerName $VMOnPD.HostName -VMName $VMOnPD.Name
$VMRepConfig = Get-VMReplication -ComputerName $VMOnDR.HostName -VMName $VMOnPD.Name

if ($error -ne 0)
{
    $temp = $VMOnPD.HostName.Split(".")
    $primaryHostName = $temp[0]

    $temp = $VMOnDR.HostName.Split(".")
    $recoveryHostName = $temp[0]

    write-host -ForegroundColor Red (Get-Date) "....Error in getting VM Replication state using FQDN, switching to Hostname"
    write-host -ForegroundColor Yellow (Get-Date) "....Primary Hostname: " $primaryHostName " Replica Hostname: " $recoveryHostName

    $error.Clear()
    $VMRepConfig = Get-VMReplication -ComputerName $primaryHostName -VMName $VMOnPD.Name
    $VMRepConfig = Get-VMReplication -ComputerName $recoveryHostName -VMName $VMOnPD.Name

    if ($error -ne 0)
    {
        write-host -ForegroundColor Red (Get-Date) "....Error in getting VM Replication state using Hostname"
        write-host -ForegroundColor Red (Get-Date) "....Exiting"
        exit 1
    }

    write-host -ForegroundColor Green (Get-Date) "....Successful in getting VM Replication state using Hostname"
}
else
{
    $primaryHostName = $VMOnPD.HostName
    $recoveryHostName = $VMOnDR.HostName
}

if ($VMOnDR.ReplicationStatus.ReplicationState -ne "Recovered")
{
    write-host -ForegroundColor Red (Get-Date) "....Replica VM is not in Failed over state. Actual State " $VMOnDR.ReplicationStatus.ReplicationState
    write-host -ForegroundColor Red (Get-Date) "....Exiting"
    exit 1
}

$error.Clear()

if ($ReverseRep -eq $true)
{
    write-host -ForegroundColor Green (Get-Date) ".....Starting Reverse Replication..."
    Set-VMReplication -ComputerName $recoveryHostName -reverse -VMName $VMOnDR.Name

    if ($error -ne 0)
    {
        write-host -ForegroundColor Red (Get-Date) ".....Error occured during Reverse Replication for VM $VMName"
        write-host -ForegroundColor Red (Get-Date) ".....Please manually complete Reverse replication"
        exit 1
    }

    write-host -ForegroundColor Green (Get-Date) ".....Reverse Replication completed..."
    exit 0
}

if ($VMOnDR.StatusString -ne "Stopped")
{
    write-host -ForegroundColor Red (Get-Date) "....VM is not in stopped state. Actual State " $VMOnDR.StatusString
    write-host -ForegroundColor Red (Get-Date) "....Exiting"
    exit 1
}

$VMOnPDAdapter = Get-SCVirtualNetworkAdapter -VM $VMonPD
$VMOnDRAdapter = Get-SCVirtualNetworkAdapter -VM $VMonDR

$fileName = $VMName + (Get-Date).ToString() + ".txt"
$fileName = $fileName.Replace("/","_")
$fileName = $fileName.Replace(":","_")

Write-Host -ForegroundColor Yellow (Get-Date) "....Dumping network information for $VMName to file $fileName"
Write-Host -ForegroundColor Yellow (Get-Date) "....Number of Network adapters found on Failed-over VM: " $VMOnDRAdapter.count

$VMNetwork = @()
$VMSubnet = @()
$Pools = @()

$counter = 0
foreach($vmAdapter in $VMOnDRAdapter)
{
    if ($vmAdapter.VMNetwork -eq $null)
    {
        $VMNetwork = $VMNetwork + $null
        $VMSubnet = $VMSubnet + $null
        $Pools = $Pools + $null
        $counter = $counter + 1
        continue
    }

    $VMNetwork = $VMNetwork + (Get-SCVMNetwork -Name $vmAdapter.VMNetwork.Name -ID $vmAdapter.VMNetwork.ID)
    $VMSubnet = $VMSubnet + (Get-SCVMSubnet -Name $vmAdapter.VMSubnet.Name | where {$_.VMNetwork.ID -eq $vmAdapter.VMNetwork.ID})
    #$PortClassification = Get-SCPortClassification | where {$_.Name -eq "Guest Dynamic IP"}
    $Pools = $Pools + (Get-SCStaticIPAddressPool -IPv4 | where {$_.VMsubnet.name -eq $vmAdapter.VMSubnet.Name})

    Out-File -FilePath $fileName -InputObject $VMNetwork[$counter] -Append
    Out-File -FilePath $fileName -InputObject $VMSubnet[$counter] -Append
    Out-File -FilePath $fileName -InputObject $Pools[$counter] -Append

    $counter = $counter + 1
}

if ($error.Count -ne 0)
{
    write-host -ForegroundColor Red (Get-Date) "....Error is gathering information for $VMName. No changes made"
    write-host -ForegroundColor Red (Get-Date) "....Exiting"
    exit 1
}

$IP = @()
$counter = 0
foreach($vmAdapter in $VMOnDRAdapter)
{

    if ($VMNetwork[$counter] -eq $null)
    {
        Write-Host -ForegroundColor Yellow (Get-Date) ".....Network Adapter '" $counter "' not connected"
        $IP = $IP + $null
        $counter = $counter + 1
        continue
    }

    ## Revoke IP
    $error.Clear()
    $IP = $IP +(Get-SCIPAddress –GrantToObjectId $VMOnDR.VirtualNetworkAdapters[$counter].ID)
    Write-Host -ForegroundColor Yellow (Get-Date) "....Revoking IP " $IP[$counter] "from Replica VM"
    Revoke-SCIPAddress $IP[$counter]
    if ($error.count -eq 0)
    {
        Write-Host -ForegroundColor Green (Get-Date) "....." $IP[$counter] "revoke completed"
    }

    ## Disconnect Replica VM
    Write-Host -ForegroundColor Yellow (Get-Date) "....Disconnecting Replica VM from Network " $VMNetwork[$counter]
    Set-SCVirtualNetworkAdapter -VirtualNetworkAdapter $VMOnDR.VirtualNetworkAdapters[$counter] -NoLogicalNetwork -NoConnection -NoPortClassification
    Write-Host -ForegroundColor Green (Get-Date) "....Network Adapter '" $counter "' of Replica VM Disconnected"

    $counter = $counter + 1
}

## Cancel failover
Write-Host -ForegroundColor Yellow (Get-Date) ".....We are going to Cancel Failover " $VMName " on both " $primaryHostName " and " $recoveryHostName

$error.Clear()
Stop-VMFailover -ComputerName $recoveryHostName -VMName $VMName
Start-Sleep -Seconds 10
Stop-VMFailover -ComputerName $primaryHostName -VMName $VMName

if ($error -ne 0)
{
    write-host -ForegroundColor Red (Get-Date) ".....Error occured during Cancel Failover for VM $VMName"
    write-host -ForegroundColor Red (Get-Date) ".....Please manually Cancel Failover on both Primary and Recovery Server"
    Write-Host -ForegroundColor Red (Get-Date) ".....Press any key to continue..."
    $ignoreKey = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}

Write-Host -ForegroundColor Yellow (Get-Date) ".....Connecting Network(s) back to the Primary VM"

$counter = 0
foreach($vmAdapter in $VMOnDRAdapter)
{

    if ($VMNetwork[$counter] -eq $null)
    {
        Write-Host -ForegroundColor Yellow (Get-Date) ".....Network Adapter '" $counter "' not connected"
        $counter = $counter + 1
        continue
    }

    Write-Host -ForegroundColor Yellow (Get-Date) "Granting " $IP[$counter] "to Primary VM"
    Grant-SCIPAddress -GrantToObjectType "VirtualNetworkAdapter" -GrantToObjectID $VMOnPDAdapter[$counter].ID -StaticIPAddressPool $Pools[$counter] –IPAddress $IP[$counter]
    Write-Host -ForegroundColor Green (Get-Date) "Granting IP completed"

    Write-Host -ForegroundColor Yellow (Get-Date) "Connecting Primary VM to " $VMNetwork[$counter]
    Set-SCVirtualNetworkAdapter -VirtualNetworkAdapter $VMOnPDAdapter[$counter] -IPv4AddressType static -VMNetwork $VMNetwork[$counter] -VMSubnet $VMSubnet[$counter]
    Write-Host -ForegroundColor Green (Get-Date) "Network Adapter '" $counter "' of Primary VM connected to " $VMNetwork[$counter]

    $counter = $counter + 1
}