We are often asked to review a customer's Azure environment. Part of that includes the review of specific applications for stability, availability, scalability and overall design. As customers move to Azure, they have to forget some of the best practices they've implemented in their on-premise environments. The cloud is different. Many customer's first experience with Azure is an IaaS experience. Even there, things are different. Features like Availability Sets, Storage Accounts, Upgrade and Fault Domains come into play. Decisions with these features can have long lasting effects on their applications and environments. One relatively common scenario we see is that while deploying systems for an application, the Storage Account is normally ignored and all VMs of a specific role (or even all instances of an application) are placed in a single Storage Account. In fact, we see Availability Sets and Storage Accounts map 1 to 1 fairly regularly. We wont go into some of the issues that you may run into with a single Storage Account (that's a whole other blog post), but will focus on the fact that it can become a single point of failure. If all the VMs of a specific role reside in a single Storage Account and something happens to that Storage Account, no more VMs. Availability Set be damned. Your VMs (and application) are offline.
Lets see how you can use PowerShell to move VHDs (OS and/or Data) from one Storage Account to another. Let's get started...
Once you have your PowerShell console up, you will have to log into Azure:
Note that this may fail if you have never used your computer to connect to Azure using PowerShell. If that is the case, you will need to download and import the PublishSettings file form Windows Azure and import it into your computer. Instructions on how to do this are found here.
At this point, you will be presented with an Azure login dialog, enter in your credentials and your PowerShell session will be connected to and authenticated against Azure.
Next, you'll need the Resource Group that the VM (and therefore VHD) resides in. You can find that in the Azure Portal by simply navigating to the Virtual Machines Blade:
Let's store that in a variable:
[powershell]$RGname = "Default-Web-WestUS"[/powershell]
We'll also need the name of the VM:
[powershell]$vmname = "VM1"[/powershell]
Before we can move the VHD to the second Storage Account, the VM has to be turned off (can't move the disk if its being accessed). As you can see in the screenshot above, my VM is already stopped. The following command will stop the VM if it happens to be running:
[powershell]get-azurermvm -name $vmname -ResourceGroupName $RGname | stop-azurermvm[/powershell]
The last VM specific property we need is the name of the VHD. This can be found in the Storage Account Blade (Storage Accounts -> sourceStorageAccount -> Blobs -> ContainerName):
Notice that the Container name is vhds, which is the default name when creating your first VM in the Storage Account. Yours might be different, so make a note of it.
Let's store the name of the VHD in a variable:
[powershell]$vhdName = "VM12016621122342.vhd"[/powershell]
Next, We'll need some information about the source and destination Storage Accounts:
- Names of the Storage Accounts
- Access Keys for the Storage Accounts
- Names of the Containers storing the VHDs
The names of the Storage Accounts and the names of the Containers were shown in the previous screenshots. The Access Keys for the Storage Accounts are stored in the Settings blade for the Storage Account:
Note: Keep these keys secret as they can grant anyone access to your Storage Account. Microsoft recommends that you keep one key for connection, and regenerate the other. Be aware that if you regenerate the keys, any application that is connecting to this Storage Account will need to be updated with the newly regenerated key.
Now that we have this information, we can set up the next group of variables. For the source Storage Account:
[powershell]$sourceSAName = "storageaccountmigration1"
$sourceSAKey = "InsertKeyHere"
$sourceSAContainerName = "vhds" [/powershell]
And for the destination Storage Account:
[powershell]$destinationSAName = "storageaccountmigration2"
$destinationSAKey = "InsertKeyHere"
$destinationContainerName = "vhds" [/powershell]
A Storage context for each of the Storage Accounts is needed. This Storage Context will be used when copying the VHDs between the 2 Blob Storage locations (more information about the Storage Context commands can be found here):
[powershell]$sourceContext = New-AzureStorageContext -StorageAccountName $sourceSAName -StorageAccountKey $sourceSAKey
$destinationContext = New-AzureStorageContext –StorageAccountName $destinationSAName -StorageAccountKey $destinationSAKey [/powershell]
Note: This script assumes that the destination container has already been created. If you need to create this container, you can either use the Portal, or the following PowerShell command:
[powershell]$destinationContainerName = "destinationvhds"
New-AzureStorageContainer -Name $destinationContainerName -Context $destinationContext[/powershell]
The final step is to start the blob copy. For this, we use the Start-AzureStorageBlobCopy command.
[powershell]$blobCopy = Start-AzureStorageBlobCopy -DestContainer $destinationContainerName -DestContext $destinationContext -SrcBlob $vhdName -Context $sourceContext -SrcContainer $sourceSAContainerName [/powershell]
You will notice that executing this cmdlet will return the prompt after a few seconds. The blob copy is not actually done, it's just running in the background. To following command will show you its current status:
($blobCopy | Get-AzureStorageBlobCopyState).Status [/powershell]
The status will be Pending until the copy is completed. Alternately, running the following will run in a loop and show you the actual progress of the copy (refreshed every 10 seconds):
[powershell]$TotalBytes = ($blobCopy | Get-AzureStorageBlobCopyState).TotalBytes
while(($blobCopy | Get-AzureStorageBlobCopyState).Status -eq "Pending")
$BytesCopied = ($blobCopy | Get-AzureStorageBlobCopyState).BytesCopied
$PercentCopied = [math]::Round($BytesCopied/$TotalBytes * 100,2)
Write-Progress -Activity "Blob Copy in Progress" -Status "$PercentCopied% Complete:" -PercentComplete $PercentCopied
Once the copy is completed, the dialog box will disappear and you will be returned to the PowerShell prompt.
And the full script:
[powershell]# Login to Azure</pre>
# Set Resource Group Name
# Set VM Name
$vmname = "VM1"
# Stop VM
get-azurermvm -name $vmname -ResourceGroupName $RGname | stop-azurermvm
# Set name of VHD blob to copy
$vhdName = "VM12016621122342.vhd"
# Source Storage Account Information
$sourceSAName = "storageaccountmigration1"
$sourceSAKey = "1UzJEeop8MW/jOE5eX9ejilO1x6gwxxcMGIVdO36uchtwL128h3LzGQAt1CpFxs03E5FlGveCNkwhpvxQTCTTA=="
$sourceSAContainerName = "vhds"
# Destination Storage Account Information
$destinationSAName = "storageaccountmigration2"
$destinationSAKey = "dN6rMnqeUxkBkzpeOLS5wns6UJcL2zjGIj7cTGZ8if0ZNumyvrdDytW9LuiW6Qc/knkeoeTg+ejrFrHsmqzb4w=="
$destinationContainerName = "vhds"
# Source Storage Account Context
$sourceContext = New-AzureStorageContext -StorageAccountName $sourceSAName -StorageAccountKey $sourceSAKey
# Destination Storage Account Context
$destinationContext = New-AzureStorageContext –StorageAccountName $destinationSAName -StorageAccountKey $destinationSAKey
# Copy the blob
$blobCopy = Start-AzureStorageBlobCopy -DestContainer $destinationContainerName -DestContext $destinationContext -SrcBlob $vhdName -Context $sourceContext -SrcContainer $sourceSAContainerName [/powershell]
Note that you can also use the AzCopy command to perform some of these actions.
We've shown you how straightforward it is to move your VHDs from one Storage Account to another. In part 2 (coming soon) we will look at two things; first, we'll automate the script to remove the hardcoded variables and make it simpler to select each of the properties needed for the VHD move. Second we'll look at actually creating the new VM with the disks in the new Storage Account.