《Windows Azure Platform 系列文章目录》
为什么要写这篇Blog?
之前遇到过很多客户提问:
(1)我之前创建的虚拟机,没有加入虚拟网络。现在需要重新加入虚拟机网络,应该如何操作?
(2)之前创建的虚拟机,想重新设置DNS。但是又想保留虚拟机里面的内容,应该如何操作?
(3)我之前部署在订阅A下面的一些虚拟机,现在想迁移到订阅B下面,应该如何操作?
在回答这些问题之前,我们先回顾一下Azure虚拟机可能需要的资源:
(1)虚拟网络(Azure Virtual Network)
定义Subnet和IP Rang,Azure虚拟机的内网IP地址是由Azure Virtual Network
(2)云服务(Cloud Service)
定义了Azure虚拟机的DNS地址
(3)VHD文件(Virtual Hard Disk)
我们在虚拟机安装的任何软件和配置,都是持久化保存在Azure VHD文件里的。
所以只要能保留VHD,就可以迁移Azure虚拟机,同时保留虚拟机里面的内容
好了,看到这里,各位看官明白了,其实我们只要迁移Azure虚拟机所在的VHD文件即可。
然后还有一个问题请读者注意:
如果你的Azure VM所在的DNS Name已经固定的公网IP地址(Reserved IP)
(Azure China (8) 使用Azure PowerShell创建虚拟机,并设置固定Virtual IP Address和Private IP)
我们在迁移Azure虚拟机的时候需要注意,固定公网IP地址只能属于某一个订阅,无法跨订阅,或者是多个订阅共享同一个公网IP地址。
所以当我们将某一台虚拟机,从订阅A迁移到订阅B的时候,订阅A下的该虚拟机的公网IP地址,是无法转换到订阅B下的。
当我们跨订阅迁移Azure虚拟机的时候,笔者建议的做法是:先将订阅A下的公网IP地址进行释放。然后再订阅B下申请新的公网IP地址。
如果客户已经设置了自定义域名的A记录,需要重新设置,指向到订阅B下的公网IP地址。
好了,描述了那么多的内容。我们开始今天的文章吧。
首先笔者先模拟一个场景,假设我们有两个订阅,Subscription_A和Subscription_B
在订阅Subscription_A下有以下部署:
- 虚拟网络名称VNetA,有一个子网Subnet-1
- 有1台虚拟机,虚拟机DNS Name为LeiCloudServiceA
- 该虚拟机的机器名为LeiVMA,操作系统为Windows Server 2012 中文版
- 这台虚拟机有2块磁盘
我们需要把上述虚拟机,迁移到订阅Subscription_B下
- 手动创建虚拟网络VNetB
- 新的DNS Name为LeiCloudServiceB,新的虚拟机的机器名为LeiVMB
- 需要保留原虚拟机里的磁盘内容
关键步骤有以下几点:
(1)请在Azure PowerShell下运行
(2)输入源订阅,和目标订阅的Azure用户名和密码
(3)手动创建目标订阅下的虚拟机网络和子网
(4)更新PowerShell下的相关参数
(5)PowerShell会提示:首先把需要迁移的Azure虚拟机关机
(6)PowerShell会在目标订阅下,创建新的存储账户
(7)PowerShell会将源订阅的下的,需要迁移的虚拟机的所有VHD文件,拷贝到目标订阅下的,新的存储账号里
(8)在目标订阅下,将VHD系统文件创建为虚拟机镜像,将VHD数据文件创建为虚拟机磁盘镜像
(9)如果源订阅和目标订阅是同一个订阅的话,会有提示:Please update the Diskname in the configuration file
客户手动修改PowerShell同一目录下的,ExportedVMConfig文件,修改以下XML节点:
<DataVirtualHardDisks>和<OSVirtualHardDisk>
在DiskName里,增加相应的后缀。
比如$DiskNameSuffix变量,我们赋值为-prem
则修改上面的红色部分的DiskName,参数增加后缀-prem。如下图:
修改完毕后,直接按Enter 继续执行PowerShell
PowerShell源代码如下:
<#
Modified by Lei Zhang on 2016-04-29
#> Param
(
#源订阅ID
[string] $SourceSubscriptionId="e2eaa986-29d9-48c9-8302-1e2900a4504b", #源云服务名称
[string] $SourceCloudServiceName="lei2012chnvm", #源虚拟机名称
[string] $SourceVMName="lei2012chnvm01", #源Azure Storage Container Name
[string] $SourceStorageContainerName="vhds", #目标订阅ID
[string] $DestSubscritpionId="919ae904-1260-4f4b-85a6-8ac9d44d3106", #目标云服务名称
[string] $DestCloudServiceName="LeiNewVM", #目标虚拟机名称
[string] $DestVMName="LeiNewVM01", #目标存储账户名称
[string] $DestStorageAccountName="leinewvmstorage1", #目标Azure Storage Container Name
[string] $DestStorageContainerName="vhds", #目标虚拟机所在数据中心,分别为China North和China East
[string] $DestLocationName, #目标虚拟机所在虚拟网络名称
[string] $DestVNetName="LeiDesVNet", #目标虚拟机所在子网名称
[string] $DestSubNet="Subnet-1", #目标虚拟机磁盘文件后缀
[string] $DiskNameSuffix="-prem"
) $IsSameSub = $false if (($SourceSubscriptionId -eq $DestSubscritpionId) -or ($DestSubscritpionId -eq ""))
{
Write-Host "VM is copied at the same subscription!" -ForegroundColor Green
$IsSameSub = $true
$DestSubscritpionId = $SourceSubscriptionId
} if ($SourceStorageContainerName -eq "")
{
Write-Host "Using the default source storage container vhds!" -ForegroundColor Green
$SourceStorageContainerName = "vhds"
} if ($DestStorageContainerName -eq "")
{
Write-Host "Using the default destination storage container vhds!" -ForegroundColor Green
$DestStorageContainerName = "vhds"
} if ($DestLocationName -eq "")
{
$DestLocationName = "China East"
} if ($DestSubNet -eq "")
{
$DestSubNet = "Subnet-1"
} if (($DiskNameSuffix -eq $null) -or ($DiskNameSuffix -eq ""))
{
$DiskNameSuffix = "-prem"
Write-Host "Set the copyed Disk Name Suffix as:"+ $DiskNameSuffix -ForegroundColor Green
} Write-Host "`t================= Migration Setting =======================" -ForegroundColor Green
Write-Host "`t Source Subscription ID = $SourceSubscriptionId " -ForegroundColor Green
Write-Host "`t Source Cloud Service Name = $SourceCloudServiceName " -ForegroundColor Green
Write-Host "`t Source VM Name = $SourceVMName " -ForegroundColor Green
Write-Host "`t Dest Subscription ID = $DestSubscritpionId " -ForegroundColor Green
Write-Host "`t Dest Cloud Service Name = $DestCloudServiceName " -ForegroundColor Green
Write-Host "`t Dest Storage Account Name = $DestStorageAccountName " -ForegroundColor Green
Write-Host "`t Source Storage Container Name = $SourceStorageContainerName " -ForegroundColor Green
Write-Host "`t Dest Storage Container Name = $DestStorageContainerName " -ForegroundColor Green
Write-Host "`t Dest Location = $DestLocationName " -ForegroundColor Green
Write-Host "`t Dest VNET = $DestVNetName " -ForegroundColor Green
Write-Host "`t Dest Subnet = $DestSubNet " -ForegroundColor Green
Write-Host "`t Disk Name Prefix = $DiskNameSuffix " -ForegroundColor Green
Write-Host "`t===============================================================" -ForegroundColor Green #######################################################################
# Verify Azure Source Subscription and Azure Desination Subscription
#######################################################################
Write-Host "Please verify the Source Azure Subscription" -ForegroundColor Green
Add-AzureAccount -Environment AzureChinaCloud Write-Host "Please verify the Destination Azure Subscription" -ForegroundColor Green
Add-AzureAccount -Environment AzureChinaCloud $ErrorActionPreference = "Stop" try{ stop-transcript|out-null }
catch [System.InvalidOperationException] { } $workingDir = (Get-Location).Path
$log = $workingDir + "\VM-" + $SourceCloudServiceName + "-" + $SourceVMName + ".log"
Start-Transcript -Path $log -Append -Force Select-AzureSubscription -SubscriptionId $SourceSubscriptionId #######################################################################
# Check if the VM is shut down
# Stopping the VM is a required step so that the file system is consistent when you do the copy operation.
# Azure does not support live migration at this time..
#######################################################################
$sourceVM = Get-AzureVM –ServiceName $SourceCloudServiceName –Name $SourceVMName
if ( $sourceVM -eq $null )
{
Write-Host "[ERROR] - The source VM doesn't exist. Exiting." -ForegroundColor Red
Exit
} # check if VM is shut down
if ( $sourceVM.Status -notmatch "Stopped" )
{
Write-Host "[Warning] - Stopping the VM is a required step so that the file system is consistent when you do the copy operation. Azure does not support live migration at this time. If you’d like to create a VM from a generalized image, sys-prep the Virtual Machine before stopping it." -ForegroundColor Yellow
$ContinueAnswer = Read-Host "`n`tDo you wish to stop $SourceVMName now? (Y/N)"
If ($ContinueAnswer -ne "Y") { Write-Host "`n Exiting." -ForegroundColor Red; Exit }
$sourceVM | Stop-AzureVM -StayProvisioned # wait until the VM is shut down
$sourceVMStatus = (Get-AzureVM –ServiceName $SourceCloudServiceName –Name $SourceVMName).Status
while ($sourceVMStatus -notmatch "Stopped")
{
Write-Host "Waiting VM $vmName to shut down, current status is $sourceVMStatus" -ForegroundColor Green
Sleep -Seconds 5
$sourceVMStatus = (Get-AzureVM –ServiceName $SourceCloudServiceName –Name $SourceVMName).Status
}
} # exporting the source vm to a configuration file, you can restore the original VM by importing this config file
# see more information for Import-AzureVM
$vmConfigurationPath = $workingDir + "\ExportedVMConfig-" + $SourceCloudServiceName + "-" + $SourceVMName +".xml"
Write-Host "Exporting VM configuration to $vmConfigurationPath" -ForegroundColor Green
$sourceVM | Export-AzureVM -Path $vmConfigurationPath #######################################################################
# Copy the vhds of the source vm
# You can choose to copy all disks including os and data disks by specifying the
# parameter -DataDiskOnly to be $false. The default is to copy only data disk vhds
# and the new VM will boot from the original os disk.
####################################################################### $sourceOSDisk = $sourceVM.VM.OSVirtualHardDisk
$sourceDataDisks = $sourceVM.VM.DataVirtualHardDisks # Get source storage account information, not considering the data disks and os disks are in different accounts
$sourceStorageAccountName = $sourceOSDisk.MediaLink.Host -split "\." | select -First 1
$sourceStorageAccount = Get-AzureStorageAccount –StorageAccountName $sourceStorageAccountName
$sourceStorageKey = (Get-AzureStorageKey -StorageAccountName $sourceStorageAccountName).Primary Select-AzureSubscription -SubscriptionId $DestSubscritpionId
# Create destination context
$destStorageAccount = Get-AzureStorageAccount | ? {$_.StorageAccountName -eq $DestStorageAccountName} | select -first 1
if ($destStorageAccount -eq $null)
{
New-AzureStorageAccount -StorageAccountName $DestStorageAccountName -Location $DestLocationName
$destStorageAccount = Get-AzureStorageAccount -StorageAccountName $DestStorageAccountName
}
$DestStorageAccountName = $destStorageAccount.StorageAccountName
$destStorageKey = (Get-AzureStorageKey -StorageAccountName $DestStorageAccountName).Primary $sourceContext = New-AzureStorageContext –StorageAccountName $sourceStorageAccountName -StorageAccountKey $sourceStorageKey -Environment AzureChinaCloud
$destContext = New-AzureStorageContext –StorageAccountName $DestStorageAccountName -StorageAccountKey $destStorageKey # Create a container of vhds if it doesn't exist
Set-AzureSubscription -CurrentStorageAccountName $DestStorageAccountName -SubscriptionId $DestSubscritpionId
#if ((Get-AzureStorageContainer -Context $destContext -Name vhds -ErrorAction SilentlyContinue) -eq $null)
if ((Get-AzureStorageContainer -Name $DestStorageContainerName -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host "Creating a container vhds in the destination storage account." -ForegroundColor Green
# New-AzureStorageContainer -Context $destContext -Name vhds
New-AzureStorageContainer -Name $DestStorageContainerName
} $allDisks = @($sourceOSDisk) + $sourceDataDisks
$destDataDisks = @()
# Copy all data disk vhds
# Start all async copy requests in parallel.
foreach($disk in $allDisks)
{
$blobName = $disk.MediaLink.Segments[2]
# copy all data disks
Write-Host "Starting copying data disk $($disk.DiskName) at $(get-date)." -ForegroundColor Green
$sourceBlob = "https://" + $disk.MediaLink.Host + "/" + $SourceStorageContainerName + "/"
$targetBlob = $destStorageAccount.Endpoints[0] + $DestStorageContainerName + "/"
$azcopylog = "azcopy-" + $SourceCloudServiceName + "-" + $SourceVMName +".log" Write-Host "Start copy vhd to destination storage account" -ForegroundColor Green
#Write-Host .\azcopy\AzCopy\AzCopy.exe /Source:$sourceBlob /Dest:$targetBlob /SourceKey:$sourceStorageKey /DestKey:$destStorageKey /Pattern:$blobName /SyncCopy /v:$azcopylog -ForegroundColor Green #cd 'C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy'
#AzCopy.exe /Source:$sourceBlob /Dest:$targetBlob /SourceKey:$sourceStorageKey /DestKey:$destStorageKey /Pattern:$blobName /SyncCopy /v:$azcopylog #cd D:\AzCopy
#.\AzCopy.exe /Source:$sourceBlob /Dest:$targetBlob /SourceKey:$sourceStorageKey /DestKey:$destStorageKey /Pattern:$blobName /SyncCopy /v:$azcopylog #Start-AzureStorageBlobCopy is too slow
Start-AzureStorageBlobCopy -SrcContainer $SourceStorageContainerName -SrcBlob $blobName -DestContainer $DestStorageContainerName -DestBlob $blobName -Context $sourceContext -DestContext $destContext -Force if ($disk –eq $sourceOSDisk)
{
$destOSDisk = $targetBlob + $blobName
}
else
{
$destDataDisks += $targetBlob + $blobName
}
} # Wait until all vhd files are copied.
$CopyStatusReportInterval = 15
$diskComplete = @()
do
{
Write-Host "`n[WORKITEM] - Waiting for all disk copy to complete. Checking status every $CopyStatusReportInterval seconds." -ForegroundColor Yellow
# check status every 30 seconds
Sleep -Seconds $CopyStatusReportInterval
foreach ( $disk in $allDisks)
{
if ($diskComplete -contains $disk)
{
Continue
}
$blobName = $disk.MediaLink.Segments[2]
$copyState = Get-AzureStorageBlobCopyState -Blob $blobName -Container vhds -Context $destContext
if ($copyState.Status -eq "Success")
{
Write-Host "`n[Status] - Success for disk copy $($disk.DiskName) at $($copyState.CompletionTime)" -ForegroundColor Green
$diskComplete += $disk
}
else
{
if ($copyState.TotalBytes -gt 0)
{
$percent = ($copyState.BytesCopied / $copyState.TotalBytes) * 100
Write-Host "`n[Status] - $('{0:N2}' -f $percent)% Complete for disk copy $($disk.DiskName)" -ForegroundColor Green
}
}
}
}
while($diskComplete.Count -lt $allDisks.Count) # Create OS and data disks
Write-Host "Add VM OS Disk. OS "+ $sourceOSDisk.OS +"diskName:" + $sourceOSDisk.DiskName + "Medialink:"+ $destOSDisk -ForegroundColor Green # 设置源VM的Disk Name和目标VM的Disk Name
$disknameOS = $sourceOSDisk.DiskName
if($IsSameSub)
{
#OSDisk, 如果在同一个订阅下,则增加后缀以区分VHD文件名
$disknameOS = $sourceOSDisk.DiskName + $DiskNameSuffix
} Add-AzureDisk -OS $sourceOSDisk.OS -DiskName $disknameOS -MediaLocation $destOSDisk
# Attached the copied data disks to the new VM
foreach($currenDataDisk in $destDataDisks)
{
$diskName = ($sourceDataDisks | ? {$currenDataDisk.EndsWith($_.MediaLink.Segments[2])}).DiskName
if($IsSameSub)
{
#DataDisk, 如果在同一个订阅下,则增加后缀以区分VHD文件名
$diskName = ($sourceDataDisks | ? {$currenDataDisk.EndsWith($_.MediaLink.Segments[2])}).DiskName + $DiskNameSuffix
}
Write-Host "Add VM Data Disk $diskName" -ForegroundColor Green
Add-AzureDisk -DiskName $diskName -MediaLocation $currenDataDisk
} Write-Host "Import VM from " $vmConfigurationPath -ForegroundColor Green
Set-AzureSubscription -SubscriptionId $DestSubscritpionId -CurrentStorageAccountName $DestStorageAccountName # Manually change the data diskname in the same subscription coz it can't be same
if($IsSameSub)
{
$ContinueAnswer = Read-Host "`n`tPlease update the Diskname in the configuration file "+ $vmConfigurationPath +", just add your suffix $DiskNameSuffix to the filename! Then press ENTER to continue.."
}
# Import VM from previous exported configuration plus vnet info
if (( Get-AzureService | Where { $_.ServiceName -eq $DestCloudServiceName } ).Count -eq 0 )
{
New-AzureService -ServiceName $DestCloudServiceName -Location $DestLocationName
} Write-Host "`n import-AzureVM -Path $vmConfigurationPath | Set-AzureSubnet -SubnetNames $DestSubNet | New-AzureVM -ServiceName $DestCloudServiceName -VNetName $DestVNetName -WaitForBoot" -ForegroundColor Green Import-AzureVM -Path $vmConfigurationPath | Set-AzureSubnet -SubnetNames $DestSubNet | New-AzureVM -ServiceName $DestCloudServiceName -VNetName $DestVNetName -WaitForBoot