ASM to ARM. Notes from the field.
Microsoft have some documentation on how to do this. Lots of documentation. Enough documentation to get lost in for days!
Take the flow diagram in this article for example:
I've completed several migrations from ASM to ARM with no side effects, but looking at this flow diagram even now I still feel my heart rate start to pick up as the anxiety kicks in and I worry that I've not followed the flow properly, and maybe I've done something wrong.
Then I remind myself that the subscriptions are running properly under the resource manager management plane, and there's no need to panic.
There are four main Microsoft articles that link to each other in a very circular fashion.
Let's call them:
1) Overview
2) Planning
3) Technical deep dive
4) PowerShell
You can find all of them from the one I've linked above.
I found that over a strong cup of coffee, I went around and around and around and around, and I still had questions. What happens with my SQL servers or my SQL databases, what about Shared Access Keys and Storage Accounts or Service Bus Queues, what exactly will happen, what will break, what will work?
Fortunately I had a few UAT subscriptions to start with, and using these as a learning platform, we have now completed all UAT and all PROD subscriptions without any issues.
It's pretty straight forward in the end.
Let me preface this by saying, I'm sure it isn't always straight forward. I have no doubt that this can and does go horribly wrong, but in my real world experience of multi region Cloud Services, Web Apps and App services, API's, VM's, web roles and worker roles, Storage Accounts, Recovery Services Vaults, SQL Servers and SQL Databases, Service Bus Queues and Log Analytics, it was completely painless.
I've taken the following PowerShell from the MS articles and polished it a bit in places, but not too much.
I like to see the workings. I find too often that an 800 line script I can download to tackle a problem tries to encompass every eventuality, but for me it just means unpicking 700 lines that don't apply to me, so I appreciate you could script everything start to finish, but I haven't.
OK, assuming we're on PowerShell 5.1, which you can verify with:
(get-host).version
and assuming we have the modules for ASM and ARM installed, or in case you haven't:
Find-Module -Name Azure, AzureRM | Install-Module -Force -AllowClobber
We then log in to both ARM and ASM as follows:
Connect-AzureRmAccount Add-AzureAccount
PowerShell in Azure is focused at the subscription level, so we need to pick a subscription if there are more than one. Let's say I'm working with a subscription called Training:
Select-AzureSubscription –SubscriptionName “Training”
Select-AzureRmSubscription –SubscriptionName “Training”
It's probably obvious, but for clarity pretty much anything with "AzureRm" is Resource manager PowerShell, anything without is Services Manager PowerShell.
I'm now in my Training Subscription, and I can look for Vnets that I want to migrate.
Register the Migration Resource Provider
This has to be done per subscription.
Register-AzureRmResourceProvider -ProviderNamespace Microsoft.ClassicInfrastructureMigrate
You can check if the provider is already registered:
$state = (get-AzureRmResourceProvider -ProviderNamespace Microsoft.ClassicInfrastructureMigrate).RegistrationState
$state
And you could script something that checks and then installs :
if (!($state)) {Register-AzureRmResourceProvider -ProviderNamespace Microsoft.ClassicInfrastructureMigrate}
but it's as quick once off to just re-register.
Virtual Networks
The first issue I came across is that the virtual network name can often be different from that shown in the Portal, so selecting a Vnet from the portal can cause issues in PowerShell.
Assume I have a Vnet called Training in the portal.
So I run the following PowerShell to validate the migration for this Vnet:
$vnetName = “Training”
(Move-AzureVirtualNetwork -Validate -VirtualNetworkName $vnetName).ValidationMessages
I got the following Error.
It turns out my Vnet isn't called "Training", it's called something else, which I can get at with:
$vnetName = (Get-AzureVnetSite).Name
so let's bundle that into the following PowerShell to validate the Vnet for migration:
(Move-AzureVirtualNetwork -Validate -VirtualNetworkName $((Get-AzureVnetSite).Name )).ValidationMessages
This will give me back every reason why my Vnet isn't ready to migrate. In my case, it was predominantly always one of two issues. VM extensions, and Endpoint ACL's
BGInfo and any other XML extensions will just get removed, so we can safely ignore those.
I played with scripted removal of endpoint ACL's using the following PowerShell:
Get-AzureVM -ServiceName "Training" -Name "VM01" | Remove-AzureAclConfig -EndpointName "RDP" | Update-AzureVM
but my experience was that it just didn't work!
The PowerShell completed but the ACL just stayed in place.
Maybe I was being impatient.
I suspect removing ACL's in PowerShell is like removing or adding them on VM's in an availability set via the Portal. When you are updating one VM, you can't make further changes to other VM's in the set until the current update completes.
This means the script completes to quickly to actually do anything past the first change, so in the end I removed them manually from the Portal.
When all the issues are taken care of, I get a clean validation for Vnet migration.
When the following PowerShell returns no output, we're good to go.
(Move-AzureVirtualNetwork -Validate -VirtualNetworkName $((Get-AzureVnetSite).Name )).ValidationMessages | ? {!($_.category -like "Information")}
Now we're on the home straight as far as the Vnet is concerned.
Let's check how many ARM resources we have before we start.
(Get-AzureRmResource | Measure-Object ).count
In my case, it's 58. All my resources are in a single Vnet in this subscription.
We prepare the Vnet with the following:
Move-AzureVirtualNetwork -Prepare –VirtualNetworkName $((Get-AzureVnetSite).Name)
At this point, I now have 81 resources, because both management planes are available. I can see both the classic resources and the modern resources in the Portal.
Let's have a closer look at one of the resource groups with a single VM in a Cloud Service called Training.
Before I start, I have two resources.
When I finish I have 6.
I have both a Classic and Modern resource for the VM, as well as a NIC, a Public IP address and a Public Load Balancer to replace the previous VM Endpoint.
Note the "-Migrated" suffix that get's appended to the migrated resource groups.
At this stage, I can abort, or commit, depending on the outcome of any testing that needs to be done.
Move-AzureVirtualNetwork -Abort -VirtualNetworkName $((Get-AzureVnetSite).Name ) Or Move-AzureVirtualNetwork -Commit -VirtualNetworkName $((Get-AzureVnetSite).Name )
Abort takes a few minutes to roll back, commit about the same.
I can prepare and abort as often as I want at this stage, nothing happens until I commit.
But once I commit, there is no way back!
Storage
Storage has to be migrated separately, and after the Vnet and VM migration.
Any VM's using the storage for disks need to be ARM resources before you can migrate the Storage Account. Storage Accounts used by Classic VM's can't be migrated
I can get all the Storage Accounts in a subscription with the following:
$storageAccounts = (Get-AzureStorageAccount).storageaccountname
And I can check to see if they are still being used for legacy VM's with the following:
Foreach ($storageAccount in $storageAccounts)
{
Get-AzureDisk | where-Object {$_.MediaLink.Host.Contains($storageAccount)} | Select-Object -ExpandProperty AttachedTo -Property DiskName | Format-List -Property RoleName, DiskName
}
I can see if there are any unattached classic disks with:
Foreach ($storageAccount in $storageAccounts)
{
Get-AzureDisk | where-Object {$_.MediaLink.Host.Contains($storageAccount)} | Where-Object -Property AttachedTo -EQ $null | Format-List -Property DiskName
}
No news is good news here, we don't want any output from either of those commands, if we get a list of RoleNames and DiskNames, we can't migrate these storage accounts yet.
If the command above gives the all clear, this next one will validate each storage account for migration:
Foreach ($storageAccount in $storageAccounts)
{
(Move-AzureStorageAccount -Validate -StorageAccountName $storageAccount).ValidationMessages
}
Once we are eligible for migration:
Foreach ($storageAccount in $storageAccounts)
{
Move-AzureStorageAccount -Prepare -StorageAccountName $storageAccount
}
You can easily see where this could go:
Foreach ($storageAccount in $((Get-AzureStorageAccount).storageaccountname))
{
$numDisksInUse = (Get-AzureDisk | where-Object {$_.MediaLink.Host.Contains($storageAccount)} | Select-Object -ExpandProperty AttachedTo -Property DiskName | measure-object).count
$numDetachedDisks = (Get-AzureDisk | where-Object {$_.MediaLink.Host.Contains($storageAccount)} | ? -Property AttachedTo -EQ $null | measure-object).count
If (($numDisksInUse -eq “0”) –AND ($numDetachedDisks –eq “0”))
{
"$storageAccount ready to validate"
$numErrors = ((Move-AzureVirtualNetwork -Validate -VirtualNetworkName $vnetName ).ValidationMessages | ? {!($_.category -like "Information")} | measure-object).count
if ($numErrors -eq "0")
{
"Preparing $storageAccount for migration"
Move-AzureStorageAccount -prepare -StorageAccountName $storageAccount
""
}
}
else
{
"$storageAccount not ready to validate"
}
}
When I want to commit or abort the storage account migration, either scripted or otherwise:
Move-AzureStorageAccount -commit -StorageAccountName $storageAccount
or
Move-AzureStorageAccount -abort -StorageAccountName $storageAccount
Believe it or not this actually took longer to write up that it took to accomplish on most of our subscriptions, although this doesn't take into account the testing we carried out before we committed any changes.
Don't let me imply we didn't take the process seriously, we just didn't have any issues with it.
You have been most industrious. Seamless conversions! Well done!