《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