Verschieben von VMs in Azure mit PowerShell

Gerade mit der demnächst verfügbaren Microsoft Cloud in Deutschland stellt sich die Frage, wie man Storage oder virtuelle Server innerhalb Azure verschiebt. Das wollen wir uns etwas genauer ansehen, ist auch nicht ganz so einfach. Streng genommen gibt es auch kein Verschieben, sondern wir können nur Kopieren und anschließend Löschen. Ganz ehrlich ist mir das auch lieber, immer noch ein Fallback zu haben…

Das wichtigste vorweg: Letztendlich ist das Verschieben einer VM nicht wesentlich anders als das Verschieben von Storage, es sind nur ein paar Extrapunkte zu beachten. Und bevor alle am Ende enttäuscht sind: Wir konzentrieren uns hier auf das Verschieben der VM, nicht auf die Übernahme aller Details der Konfiguration. So ein paar Hinweise gebe ich zwar, aber etwas Nacharbeit kann schon nötig sein, insbesondere bei komplexen Netzwerk-Strukturen.

Hatte ich eigentlich schon erwähnt, dass wir das ganz in PowerShell machen werden? Nein? Na dann jetzt halt Smiley. Damit wir nicht immer mit Platzhaltern in den Befehlen arbeiten müssen, einigen wir uns mal auf das folgende Szenario (Variablen beginnen immer mit $source für Quellangaben und mit $dest für Zielangaben)

Quelle: $sourceName (Name der alten VM), $sourceService (Name des bisherigen Service)

Ziel: $destName (Name der Ziel-VM), $destService (Name des neuen Service)

Den gesamten Script kann man sich auch kopieren von GitHub.

Die Quelle

Wir fangen an, in dem wir erst mal alle Informationen über unsere VM sammeln:

$sourceVM = Get-AzureVM –Name $sourceName –ServiceName $sourceService

Wir speichern sicherheitshalber mal die komplette Konfiguration lokal in eine XML-Datei:

$sourceVM | Export-AzureVM –Path “c:\temp\export.xml”

Zeit, sich Informationen über die Disks zu besorgen. Azure unterscheidet zwischen OS- und Datendisks, wir holen uns natürlich beides:

$sourceOSDisk = $sourceVm.VM.OSVirtualHardDisk

$sourceDataDisks = $sourceVm.VM.DataVirtualHardDisks

…und setzen auch gleich den Storage Context. Wer jetzt grad nicht weiß, was ein Storage Context ist: Das ist ein Objekt, dass quasi die Storage-Informationen über den Namen, das Account und den Zugriffsschlüssel zusammenfasst:

$sourceStoragename = ($sourceOSDisk.MediaLink.Host -split "\.")[0]

$sourceStorageAccount = Get-AzureStorageAccount –StorageAccountName
           $sourceStorageName

$sourceStorageKey = (Get-AzureStorageKey -StorageAccountName
$sourceStorageName).Primary

$sourceContext = New-AzureStorageContext –StorageAccountName
$sourceStorageName -StorageAccountKey $sourceStorageKey

Sieht etwas komisch aus, wie wir uns den Namen des Storages besorgt haben (1. Zeile), aber der ist versteckt im Medialink, und dort der erste Teil des Hostnamens. Wir trennen also quasi den Hostnamen an den Punkten und nehmen den ersten Teil (Array-Nummerierung startet bei Null!). Wenn jemand eine bessere Art weiß, bitte melden, ich finde, das zeigt super die Mächtigkeit von PowerShell…

Auf der Quellseite sind wir fertig. Wer die abgespeicherte Konfigurationsdatei sich anschaut, findet dort alle wichtigen Angaben für unsere neue Umgebung, also insbesondere die Größe und eventuelle Endpunkte. Wer nicht in die Datei schauen möchte, der kann das auch so erfahren:

$sourceVM.VM.RoleSize

$sourceVM | Get-AzureEndpoint

So. Dann beenden wir die VM mal:

Stop-AzureVM –Name $sourceName –ServiceName $sourceService

Wir erinnern uns, dass uns – falls das die letzte VM in diesem Service ist – die IP-Adresse abhanden kommt. Das können wir verhindern, indem wir “-StayProvisioned” anfügen, dann kostet die VM aber weiterhin…

Das Ziel

Wechseln wir mal zur anderen Seite, in unsere zweite Subscription

Select-AzureSubscription –SubscriptionName “Zielsubscription”

Set-AzureSubscription –SubscriptionName “Zielsubscription”
–CurrentStorageAccountName “zielstorage”

Hinweis: Die Azure-Umgebung ist mit der Subscription verbandelt, daher ist es wichtig, Dinge wie zum Beispiel den StorageContext zu definieren, bevor man die Subscription wechselt, da eine andere Subscription eventuell eine andere Azure-Umgebung hat und damit andere Endpunkte. Wichtig wird das für die Microsoft Cloud in Deutschland, da ist das nämlich so…

Wir haben gewechselt, also alles bereit, und los geht’s wieder mit dem StorageContext, diesmal für’s Ziel:

$destStorageName=”zielstorage”

$destStorageAccount = Get-AzureStorageAccount -StorageAccountName
$destStorageName

$deststoragekey= (Get-AzureStorageKey -StorageAccountName
$destStorageName).Primary

$destContext = New-AzureStorageContext –StorageAccountName
$destStorageName -StorageAccountKey $destStorageKey

Für die Folge gehen wir davon aus, dass wir in den Storagecontainer “vhds” speichern und dass dieser bereits existiert. Falls nicht, dann bitte anlegen mit

New-AzureStorageContainer –Context $destContext –Name vhds

Das Kopieren

Jetzt gehen wir einfach alle Disks durch, die wir haben, und kopieren sie von Alt nach Neu. Das Kommando dazu heißt Start-CopyAzureStorageBlob und braucht

  • den alten und den neuen Container,
  • den alten und den neuen Blobnamen und
  • den alten und den neuen StorageContext.

Das Kommando startet (wie der Name schon vermuten lässt) nur den Kopiervorgang, wenn wir das zurückgelieferte Objekt speichern, dann können wir über Get-AzureStorageBlobCopyState den aktuellen Status des Jobs anschauen. Übrigens: Nein, ich weiß nicht, wieso hier die Reihenfolge der Copy-Azure-Storage-Blob-Teile so unterschiedlich ist.

Der folgende Codeblock geht also durch alle Disks, startet das Kopieren, und frägt alle 10 Sekunden den Status ab:

$allDisks = @($sourceOSDisk) + $sourceDataDisks

$destDataDisks = @()

foreach($disk in $allDisks)

{

    $sourceContName = ($disk.MediaLink.Segments[1] -split "\/")[0]

    $sourceBlobName = $disk.MediaLink.Segments[2]

    $destBlobName = $sourceBlobName

    $destBlob = Start-CopyAzureStorageBlob
–SrcContainer $sourceContName
-SrcBlob $sourceBlobName
-DestContainer vhds
-DestBlob $destBlobName
-Context $sourceContext -DestContext $destContext
-Force

    Write-Host "Copying blob $sourceBlobName"

    $copyState = $destBlob | Get-AzureStorageBlobCopyState

    while ($copyState.Status -ne "Success")

    {

        Write-Host "$copyState.BytesCopied von $copyState.TotalBytes"

        sleep -Seconds 10

        $copyState = $destBlob | Get-AzureStorageBlobCopyState

    }

    If ($disk -eq $sourceOSDisk)

    {

        $destOSDisk = $destBlob

    }

    Else

    {

        $destDataDisks += $destBlob

    }

}

 

Die Nacharbeit

Fast fertig. Wir haben jetzt alle Disks kopiert in unseren neuen Storage. Bisher sind das aber im Ziel nur Dateien, noch keine Disks. Das machen wir mit Add-AzureDisk, und zwar einmal für die OS-Disk und dann noch für jede Datendisk. Sieht erneut etwas wild aus, wie wir auf die Namen kommen, an dieser Stelle ein herzlicher Dank an Devon Musgrave, von dem diese Beispiele stammen!

Add-AzureDisk -OS $sourceOSDisk.OS -DiskName $sourceOSDisk.DiskName
-MediaLocation $destOSDisk.ICloudBlob.Uri

foreach($currentDataDisk in $destDataDisks)

{

    $diskName = ($sourceDataDisks | ? {$_.MediaLink.Segments[2] -eq
$currentDataDisk.Name}).DiskName

    Add-AzureDisk -DiskName $diskName
-MediaLocation $currenDataDisk.ICloudBlob.Uri

}

Und schon haben wir lauter Disks anstatt nur Blobs. Der Rest ist einfach, erfordert aber wie oben angedroht eventuell Nacharbeit. Soll aber hier jetzt nicht mein Problem sein, ich lege einfach eine neue VM mit irgendeiner InstanceSize an, nur gebe ich keinen ImageName an, sondern einen DiskName, nämlich unsere oben kopierte OS-Disk (nicht wundern, wir haben die Namen der Disk ja einfach übernommen):

$destVM=New-AzureVMConfig -name $destName -InstanceSize Small
-DiskName $sourceOSDisk.DiskName

New-AzureVM –ServiceName $destService –VMs $destVM

Jetzt wäre der Moment, um nachzuarbeiten, also Endpunkte, Netze etc., und dann muss die VM nur noch gestartet werden. Admin-User und Passwort sind übrigens ja Teil der VM und nicht der Azure-Konfiguration, das wird also alles direkt übernommen.

Die Zusammenfassung

Der Hauptteil der Arbeit liegt offensichtlich darin, alle Informationen zusammen zu bekommen, also Storagename, Diskname etc, aber wenn wir alles zusammen haben, dann geht das Kopieren mit einem einzigen Befehl. Nicht vergessen sollten wir, die alten Disks ggf. zu löschen, braucht ja keiner mehr. Es sollte jetzt eine leichte Übung sein, das Beispiel so abzuändern, dass man damit ganz einfach auch andere StorageBlobs verschieben kann: Einfach die Wandlung in Disks weglassen und das Start/Stop der VMs logischerweise.

Noch ein Wort zur Microsoft Cloud in Deutschland: Für den ein oder anderen verblüffend, aber der Mechanismus funktioniert ohne jegliche Änderung auch für das Kopieren zwischen der Public Azure Cloud und der Microsoft Cloud in Deutschland. Es dauert evtl. etwas länger, aber das macht man ja auch nicht jeden Tag. Wichtig ist nur wie oben schon erwähnt, dass man den StorageContext etc. in der jeweiligen Subscription erzeugt. Macht man das nicht, dann fügt Azure unter Umständen beispielsweise beim Erstellen einer URL (MediaLink) die falschen Suffixe hinzu. Die Unterschiede werden klarer, wenn man mal ein Get-AzureEnvironment macht…

Viel Spaß beim Kopieren!

Comments

  • Anonymous
    February 04, 2016
    Download-Link korrigiert...
  • Anonymous
    February 22, 2016
    Hallo Ralf,
    über das neue Portal gibt es die Möglichkeit gewisse Ressourcen in eine andere RG zu verschieben. Man kann sogar eine andere Subscription angeben. Wenn man jetzt VM/CloudService/StorageAccount verschieben kann, ließe sich das sogar über das Portal mit wenigen Klicks realisieren. Ich habe bis jetzt nur StorageAccounts in andere RGs verschoben. Das hat funktioniert.
    Danke für den Code hier und Gruß Christian
  • Anonymous
    March 01, 2016
    Wie macht man aus einer VM ein Image?