又来分享一篇Azure相关的blog,同样是关于创建虚拟机的,这算是在Azure中最常见的任务了。

    

    很多时候我们在从本地迁移到Azure或者是从一个Azure租户迁移到另外一个时,都会遇到如何迁移虚拟机或者物理机的问题,通常意义来说,迁移VHD是最简单的做法,Azure本身就是构建在Hyper-V之上,如果之前使用的是Azure或者是一代Hyper-V虚拟机,那迁移如果不考虑业务是否允许中断的情况,单纯从技术上其实并不是件太复杂的事


    首先如果是本地的虚拟机或者物理机,经过转换得到VHD文件之后需要上传到Azure Storage Account中,以便用来创建虚拟机,上传的方法其实很多,一般比较推荐的是使用AZcopy,这个是微软自己的工具,可以支持很多文件拷贝的场景,包括从不同的storage account迁移数据,Azure File迁移数据等等,关于AZcopy更详细的介绍可以参考微软官方的文档https://docs.microsoft.com/zh-cn/azure/storage/common/storage-use-azcopy


    文档是Azure global的, 但是这个工具本身也是支持Mooncake的,如果要下载AZcopy,可以直接通过这个短地址下载http://aka.ms/downloadazcopy 


    AZcopy的使用方法就不介绍了,并不复杂,微软的文档也写的很详细。下边来说下将VHD上传到Azure Storage Account之后,如何创建虚拟机,众所周知的是,ARM不像ASM一样可以直接从portal使用VHD创建虚拟机,这一操作在ARM下需要用PowerShell来完成,类似的脚本其实不少,但是一般来说做成比较灵活的参数化的比较少,都是需要自己修改很多变量的那种,今天来分享一个我自己用的脚本


    先上代码,这个脚本其实和我之前分享的那些一样,可以支持MoonCake和Azure Global,使用时如果是MoonCake需要用switch AzureMoonCake控制,脚本本身需要指定的参数有很多,因为创建虚拟机的时候其实要用到很多参数,比如VNET,SUBNET,ResourceGroup等等,有一些参数是必须要指定的,比如VHD的URI,虚拟机的名字,subnet和Vnet名字等,要注意的是,这个脚本里如果ResourceGroup,Subnet或者Vnet不存在的话会自动进行创建,地址空间则是用VNetPrefix等来控制,这些是有默认值的,如果想用自己的,请在参数里指定出来,脚本里自动生成网卡这些function其实是参考的script center里的一个脚本,再加上自己根据需要进行了一定的修改,这里要特别感谢下

param
(
	[parameter(Mandatory = $false)]	[switch]$AzureMoonCake,
	[parameter(Mandatory = $false)]	[switch]$DoNotLogin,
	[Parameter(Mandatory = $true)]	[string]$LocationName,
	[Parameter(Mandatory = $true)]	[string]$ResourceGroupName,
	[parameter(Mandatory = $true)]	[string]$VMName,
	[parameter(Mandatory = $true)]	[string]$VhdUri,
	[parameter(Mandatory = $true)]	[string]$VnetName,
	[parameter(Mandatory = $true)]	[string]$SubnetName,
	[parameter(Mandatory = $false)]	[switch]$HybridBenefit,
	[parameter(Mandatory = $false)]	[string]$VMSizeName = "Standard_D2",
	[parameter(Mandatory = $false)]	[string]$AvailabilitySetName,
	[parameter(Mandatory = $false)]	[ValidateSet("Windows", "Linux")][string]$OS = 'Windows',
	[parameter(Mandatory = $false)]	[string]$VnetPrefix = "10.140.0.0/16",
	[parameter(Mandatory = $false)]	[string]$SubnetPrefix = "10.140.0.0/24",
	[parameter(Mandatory = $false)]	[ValidateSet("Static", "Dynamic")][string]$PublicIPAllocationMethod = "Dynamic"
	
)



#检查Location是否存在,并返回结果
function Check-AzureRmLocation()
{
	param
	(
		[string]$LocationName = $(throw "Parameter missing: -LocationName LocationName")
	)
	Write-Host "$(Get-Date) * Checking location $LocationName" -ForegroundColor Green
	$Location = Get-AzureRmLocation | Where-Object { $_.Location -eq $LocationName }
	If (-not ($Location))
	{
		Write-Host "$(Get-Date) * The location" $LocationName "does not exist." -ForegroundColor Red
		return $false
	}
	Else
	{
		Write-Host "$(Get-Date) * Location $LocationName exists" -ForegroundColor Green
		return $true
	}
}

#检查RG是否存在,不存在则创建新的RG
function Check-AzureRmResourceGroup()
{
	param
	(
		[string]$ResourceGroupName = $(throw "Parameter missing: -ResourceGroupName ResourceGroupName"),
		[string]$LocationName = $(throw "Parameter missing: -LocationName LocationName")
	)
	Write-Host "$(Get-Date) * Checking resource group $ResourceGroupName, if not, created it." -ForegroundColor Green
	Try
	{
		$ResourceGroup = Get-AzureRmResourceGroup -Name $ResourceGroupName -Location $LocationName -ErrorAction SilentlyContinue
		If (-not ($ResourceGroup))
		{
			Write-Host "$(Get-Date) * Creating resource group" $ResourceGroupName "..." -ForegroundColor Green
			New-AzureRmResourceGroup -Name $ResourceGroupName -Location $LocationName -ErrorAction Stop
			return $true
		}
		Else
		{
			Write-Host "$(Get-Date) * Resource group $ResourceGroupName exists" -ForegroundColor Green
			return $true
		}
	}
	Catch
	{
		Write-Host -ForegroundColor Red "$(Get-Date) * Create resource group" $LocationName "failed." $_.Exception.Message
		return $false
	}
}


#随机生成新的NIC
function AutoGenerate-AzureRmNetworkInterface()
{
	param
	(
		[string]$ResourceGroupName = $(throw "Parameter missing: -ResourceGroupName ResourceGroupName"),
		[string]$LocationName = $(throw "Parameter missing: -LocationName LocationName"),
		[string]$VMName = $(throw "Parameter missing: -VMName VMName"),
		[string]$SubnetName,
		[string]$VnetName,
		[string]$SubnetPrefix = "10.140.0.0/24",
		[string]$VnetPrefix = "10.140.0.0/16",
		[switch]$Create,
		[string]$PublicIPAllocationMethod = "Dynamic"
	)
	
	Try
	{
		$RandomNum = Get-Random -minimum 100 -maximum 9999
		$IpName = $VMName + "-ip" + $RandomNum
		$NicName = $VMName + "-ni" + $RandomNum
		
		Write-Host "$(Get-Date) * Auto generate network interface $NicName" -ForegroundColor Green
		$Pip = New-AzureRmPublicIpAddress -Name $IpName -ResourceGroupName $ResourceGroupName -Location $LocationName -AllocationMethod $PublicIPAllocationMethod -ErrorAction Stop
		
		if ($Create)
		{
			#Vnet does not exist
			$Subnet = New-AzureRmVirtualNetworkSubnetConfig -Name $SubnetName -AddressPrefix $SubnetPrefix -ErrorAction Stop
			$Vnet = New-AzureRmVirtualNetwork -Name $VnetName -ResourceGroupName $ResourceGroupName -Location $LocationName -AddressPrefix $VnetPrefix -Subnet $Subnet -ErrorAction Stop
			$Nic = New-AzureRmNetworkInterface -Name $NicName -ResourceGroupName $ResourceGroupName -Location $LocationName -SubnetId $Vnet.Subnets[0].Id -PublicIpAddressId $Pip.Id -ErrorAction Stop
		}
		else
		{
			#Vnet exist
			$Vnet = Get-AzureRmVirtualNetwork -ResourceGroupName $ResourceGroupName -Name $VnetName -ErrorAction stop
			$subnet = Get-AzureRmVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $SubnetName -ErrorAction SilentlyContinue
			if ($subnet -eq $null)
			{
				#subnet does not exist
				Write-Host "$(Get-Date) * Subnet $SubnetName does not exist,create it,subnet prefix $SubnetPrefix" -ForegroundColor Green
				$Subnet = New-AzureRmVirtualNetworkSubnetConfig -Name $SubnetName -AddressPrefix $SubnetPrefix -ErrorAction Stop
				$vnet.subnets += $Subnet
				#update Vnet
				Set-AzureRmVirtualNetwork -VirtualNetwork $Vnet -ErrorAction Stop
				#get vnet again
				$Vnet = Get-AzureRmVirtualNetwork -ResourceGroupName $ResourceGroupName -Name $VnetName -ErrorAction stop
				$Subnet = Get-AzureRmVirtualNetworkSubnetConfig -Name $SubnetName -VirtualNetwork $Vnet -ErrorAction stop
				$Nic = New-AzureRmNetworkInterface -Name $NicName -ResourceGroupName $ResourceGroupName -Location $LocationName -SubnetId $Subnet.Id -PublicIpAddressId $Pip.Id -ErrorAction Stop
				
				
			}
			else
			{
				#subnet exist
				Write-Host "$(Get-Date) * Subnet $SubnetName exist" -ForegroundColor Green
				$Nic = New-AzureRmNetworkInterface -Name $NicName -ResourceGroupName $ResourceGroupName -Location $LocationName -SubnetId $Subnet.Id -PublicIpAddressId $Pip.Id -ErrorAction Stop
			}
			
		}
		
		
		
		return $Nic.Id
	}
	Catch
	{
		Write-Host -ForegroundColor Red "$(Get-Date) * Auto generate network interface" $_.Exception.Message
		return $false
	}
}







#Login Azure with ARM mode
Import-Module AzureRM.Profile
$Error.Clear()

if (!$DoNotLogin)
{
	if ($AzureMoonCake)
	{
		Write-Warning "$(Get-Date) * Current environment is Azure China(Mooncake)"
		Login-AzureRmAccount -EnvironmentName AzureChinaCloud
	}
	else
	{
		Write-Warning "$(Get-Date) * Current environment is Azure Global"
		Login-AzureRmAccount
	}
	
	if ($? -eq $true)
	{
		Write-Host "$(Get-Date) * Login succeeded!" -ForegroundColor Green
	}
	else
	{
		Write-Host $Error[0].Exception.Message -ForegroundColor Red
		break
	}
	
	
}
else
{
	$CurrentSubscription = Get-AzureRmSubscription
	if ($CurrentSubscription -eq $null)
	{
		Write-Warning "$(Get-Date) * Didn't find any subscription for now! Please login"
		break
		
	}
}


try
{
	#check location
	$Error.clear()
	if (Check-AzureRmLocation -LocationName $LocationName)
	{
		#check RM resource group, if not exist, create one
		if (Check-AzureRmResourceGroup -ResourceGroupName $ResourceGroupName -LocationName $LocationName)
		{
			
			#Check VM Name
			If (Get-AzureRmVM -Name $VMName -ResourceGroupName $ResourceGroupName -ErrorAction Ignore)
			{
				Write-Host -ForegroundColor Red "$(Get-Date) * VM $VMName has already exist."
			}
			else
			{
				#Check VM Size
				Write-Host "$(Get-Date) * Checking VM Size $VMSizeName" -ForegroundColor Green
				If (Get-AzureRmVMSize -Location $LocationName | Where-Object { $_.Name -eq $VMSizeName })
				{
					Write-Host "$(Get-Date) * VM Size $VMSizeName exist" -ForegroundColor Green
					
					If ($VhdUri)
					{
						#Create a network interface
						$Vnet = Get-AzureRmVirtualNetwork -ResourceGroupName $ResourceGroupName -Name $VnetName -ErrorAction SilentlyContinue
						if ($Vnet -eq $null)
						{
							Write-Host "$(Get-Date) * Virtual network $VnetName does not exist,create it,vnet prefix $VnetPrefix" -ForegroundColor Green
							$Nid = AutoGenerate-AzureRmNetworkInterface -Location $LocationName -ResourceGroupName $ResourceGroupName -VMName $VMName -VnetName $VnetName -VnetPrefix $VnetPrefix -SubnetName $SubnetName -SubnetPrefix $SubnetPrefix -PublicIPAllocationMethod $PublicIPAllocationMethod -Create
						}
						else
						{
							Write-Host "$(Get-Date) * Virtual network $VnetName exist" -ForegroundColor Green
							$Nid = AutoGenerate-AzureRmNetworkInterface -Location $LocationName -ResourceGroupName $ResourceGroupName -VMName $VMName -VnetName $VnetName -VnetPrefix $VnetPrefix -SubnetName $SubnetName -SubnetPrefix $SubnetPrefix -PublicIPAllocationMethod $PublicIPAllocationMethod
							
						}
						
						
						If ($Nid)
						{
							Write-Host "$(Get-Date) * Creating VM $VMName ..." -ForegroundColor Green
							
							if ($AvailabilitySetName)
							{
								Write-Host "$(Get-Date) * Verify availability set" -ForegroundColor Green
								$AvailabilitySet = Get-AzureRmAvailabilitySet -ResourceGroupName $ResourceGroupName -Name $AvailabilitySetName -ErrorAction SilentlyContinue
								if (!$AvailabilitySet)
								{
									write-host "$(Get-Date) * AvailabilitySet $AvailabilitySetName does not exist, create a new one" -ForegroundColor Green
									$AvailabilitySet = New-AzureRmAvailabilitySet -ResourceGroupName $ResourceGroupName -Name $AvailabilitySetName -Location $LocationName -ErrorAction Stop
									$VM = New-AzureRmVMConfig -VMName $VMName -VMSize $VMSizeName -AvailabilitySetId $AvailabilitySet.Id -ErrorAction Stop
									
								}
								else
								{
									$VM = New-AzureRmVMConfig -VMName $VMName -VMSize $VMSizeName -AvailabilitySetId $AvailabilitySet.Id -ErrorAction Stop
									
								}
								
							}
							else
							{
								
								
								$VM = New-AzureRmVMConfig -VMName $VMName -VMSize $VMSizeName -ErrorAction Stop
								
							}
							
							
							
						}
						
						
						#Choose source image
						#$VM = Set-AzureRmVMSourceImage -VM $VM -PublisherName $PublisherName -Offer $OfferName -Skus $SkusName -Version "latest" -ErrorAction Stop
						
						#Add the network interface to the configuration.
						$VM = Add-AzureRmVMNetworkInterface -VM $VM -Id $Nid -ErrorAction Stop
						$DiskName = "vmosdisk"
						#$vmConfig = Set-AzureRmVMOSDisk -VM $vmConfig -Name $osDiskName -VhdUri $destinationVhd -CreateOption Attach -Linux
						#$VM = Set-AzureRmVMOSDisk -VM $VM -Name $DiskName -VhdUri $OSDiskUri -CreateOption fromImage -Caching $OSDiskCaching -ErrorAction Stop
						
						if ($OS -eq "Windows")
						{
							
							$VM = Set-AzureRmVMOSDisk -VM $VM -Name $DiskName -VhdUri $VhdUri -CreateOption attach -Caching None -Windows -ErrorAction Stop
							
						}
						else
						{
							$VM = Set-AzureRmVMOSDisk -VM $VM -Name $DiskName -VhdUri $VhdUri -CreateOption attach -Caching None -Linux -ErrorAction Stop
							
						}
						
						
						#Create a virtual machine
						
						if ($HybridBenefit)
						{
							Write-Host "$(Get-Date) * Hybrid Benefit enabled for VM $VMName !" -ForegroundColor Green
							New-AzureRmVM -ResourceGroupName $ResourceGroupName -Location $LocationName -VM $VM -ErrorAction Stop -LicenseType "Windows_Server" | Out-Null
						}
						else
						{	
							New-AzureRmVM -ResourceGroupName $ResourceGroupName -Location $LocationName -VM $VM -ErrorAction Stop | Out-Null
						}
						
						
						Write-Host "$(Get-Date) * Create virtual machine $VMName successfully!" -ForegroundColor Green
						
						#Set private nic to static
						<#
						start-sleep 5
						$NicName = $NID.split("/")[-1]
						$Nic = Get-AzureRmNetworkInterface -Name $NicName -ResourceGroupName $ResourceGroupName
						$Nic.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
						Set-AzureRmNetworkInterface -NetworkInterface $Nic | out-null
						#>
										
					}
				}
				else
				{
					Write-Host -ForegroundColor Red "$(Get-Date) * VM Size $VMSizeName does not exist."
				}
				
				
				
				
			}
			
		}
		
	}
}
catch
{
	Write-Host -ForegroundColor Red "$(Get-Date) * Create a virtual machine $VMName failed" $_.Exception.Message
}



    这个脚本本身偷懒没写太复杂的帮助信息,所以拿到后可能不太好入手到底如何运行,下边举个最基础的例子,在这之前先把脚本的限制说下

    

    本身这个脚本可以支持创建ARM的虚拟机,包括高级存储或者是普通HDD的,但是在创建高级存储的虚拟机之前,请确保你的VHD文件也是放在Preimum Storage Account里,否则创建的时候肯定会报错的。另外这个脚本只支持非托管磁盘,托管磁盘暂时还没搞,准备以后搞出来后再把脚本更新下


    VNETNAME这些参数基本就不再介绍了,都很好理解,HybridBenefit要特别说下,因为现在是可以直接使用本地的Windows Server 的license给Azure的虚拟机的(当然,这个是有前提条件的,不多谈),这种情况下创建虚拟机时就需要指定HybridBenefit了,脚本里加了一个HybridBenefit的switch来让用户指定是否创建混合权益的虚拟机


    下边举个最简单的创建windows 虚拟机的例子,运行脚本时需要指定下边的参数,因为是中国版所以要加AzureMoonCake,因为之前已经登录过了所以要加-DoNotLogin,否则还会要你再登陆一次,后边的基本就不需要多说了,意思很清楚了

    

PS C:\Users\mxy> D:\Create-AzureRMVMFromImageV4.ps1 -AzureMoonCake -DoNotLogin -LocationName chinanorth -ResourceGroupName PS_ResourceMG -VMName ttt -VhdUri https://mxymigration.blob.core.chi

nacloudapi.cn/vhds/azuretest.vhd -VnetName mxyansible -SubnetName subnet1-HybridBenefit -VMSizeName Standard_D2 -OS Windows -PublicIPAllocationMethod Static


    运行时截图是这样的

    如何使用PowerShell从VHD创建虚拟机_第1张图片



    要注意的是,使用本地上传的VHD创建时,很有可能脚本是没办法执行完成的,会卡在最后Create VM那里,最后可能会得到这样一个报错

    如何使用PowerShell从VHD创建虚拟机_第2张图片


    这个其实不用紧张,实际上虚拟机已经创建完成了,但是因为本地的VM默认情况下是不会安装VM Agent的,系统检测不到时就会出现这种问题,虚拟机其实是没问题的,如果想避免这种问题,可以提前将VM Agent在本地安装好, 后边的下载地址   VM Agent


    基本上就是这样了,在不同的场景下,可以通过不同的参数来控制虚拟机的属性。