配置虚拟网络之间的 VM 故障转移

本文介绍如何在不使用 Azure Site Recovery 服务管理灾难恢复的情况下,在 System Center Virtual Machine Manager (VMM) 中处理 VM 的复制和故障转移。

  • 使用 Azure Site Recovery 复制 VM。 VMM 不能管理没有 Site Recovery 的 Hyper-V 副本,您需要使用 Hyper-V 副本 PowerShell cmdlet 来自动化 Hyper-V 副本操作。
  • 对于灾难恢复,建议使用单独的主虚拟网络和辅助虚拟网络。 主 VM 连接到主网络,并将 VM 复制到辅助网络。 这可确保这两个 VM 可以同时连接到网络。
  • 如果有单个虚拟网络,请使用 Site Recovery 通过网络映射功能自动执行网络管理。 如果您不使用 Site Recovery,则需要仔细检查先决条件,以及虚拟机连接到网络的顺序。 具体而言,副本 VM 和主 VM 不能同时连接到单个虚拟网络。 否则,CA-PA 记录可能会在 VMM 中删除并导致网络连接丢失。

示例解决方案

此示例解决方案描述了以下环境:

  • 单个 VMM 服务器同时管理主站点和辅助站点。
  • 主 VM 和副本 VM 托管在单个 Hyper-V 虚拟网络上。
  • 想要运行计划中的故障转移,以便在故障转移后保留 VM 的 IP 地址。
  • VM 具有 IPv4 地址。

开始之前

在开始在虚拟网络之间配置 VM 故障转移之前,需要注意以下事项:

  • 确保虚拟交换机和逻辑交换机设置在 VMM 构造中有效且匹配。 如果未这样做,网络附加操作可能会在故障转移后失败。

  • 主 VM 必须连接到虚拟网络

  • 副本 VM 不得连接到网络

  • 必须将一个 IP 地址分配给主 VM 的每个网络适配器。 运行以下命令以确保这样做。 如果 VM 上有多个连接的网络适配器,请更改阵列索引,以对适配器运行此项。

    $VMOnPD = Get-SCVirtualMachine -Name "VM Name" | where {$_.IsPrimaryVM -eq $true}
    Get-SCIPAddress –GrantToObjectId $VMOnPD.VirtualNetworkAdapters[0].ID``
    
  • 确保操作系统分配给 VM 的 IP 地址与上面显示的 IP 地址相同。 登录到 VM 并运行 ipconfig 以检查此情况。

  • 确保在主服务器和副本服务器上正确设置查找表。 为此,请在每台服务器上运行以下命令,并确保有一个条目对应于上面返回的 IP 地址: Get-NetVirtualizationLookupRecord

  • 检查 IP 地址是否为 IPv4,而不是 IPv6

  • 在运行脚本之前,请确保两个 VM 都已关闭。

  • 确保两个 VM 上都启用了复制状态。

下面是此脚本的作用:

  1. 对于主 VM 上的每个网络适配器,它将存储 IP 地址、VM 网络和 IP 池。
  2. 撤销主 VM 和辅助 VM 上每个网络适配器的所有 IP 地址。
  3. 断开所有网络适配器的连接。
  4. 进行主要 VM 和辅助 VM 故障转移。
  5. (可选)启动反向复制。
  6. 为副本 VM 中的每个网络适配器提供相同的 IP 地址。
  7. 将复制 VM 上的每个网络适配器附加到步骤 1 中存储的 VM 网络。

运行脚本

脚本将采用两个参数:

  • $VMName – 虚拟机的名称
  • $ReverseRep – 用于指定是否必须执行反向复制的布尔参数
    • 如果传递 $true,则立即启动反向复制,并且后面将无法取消故障转移。
    • 脚本成功完成且 $ReverseRep 为 $true 时:
      • 主要 VM 必须处于“已为计划的故障转移做好准备”复制状态。
      • 副本 VM 必须处于“故障转移完成”复制状态。
    • 如果传递$false,则不会执行反向复制。 可以使用 ReverseRepORCancelFO.ps1 执行反向复制或取消故障转移。
    • 在将 $ReverseRep 设为 $false 的情况下成功完成脚本后:
      • 主要 VM 必须处于“已为计划的故障转移做好准备”复制状态。
      • 副本 VM 必须处于“故障转移完成”复制状态。

如果脚本没有完成任何步骤,则需手动完成失败的步骤,然后返回到 PowerShell 窗口。 脚本步骤包括主 VM 的故障转移、副本 VM 的故障转移和可选的反向复制。

运行以下脚本:

 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
}