Technical Deep Dive: Backup und Snapshots mit Microsoft Azure


Robert Rasp Dieser Gastbeitrag wurde freundlicherweise von der sepago GmbH zur Verfügung gestellt. Autor ist Robert Rasp, IT-Consultant bei sepago und erfahrener Systemadministrator.


Microsoft bietet mit Azure eine flexible und umfangreiche Cloud-Lösung, deren Funktionsumfang stetig wächst. Neben Web-Diensten, Services für mobile Endgeräte, Datenbanken, Active Directory und vielen weiteren Angeboten stehen auch virtuelle Maschinen zur Verfügung. All dies kann man dank einer kostenlosen Trail-Version für einen begrenzten Zeitraum kostenfrei testen. Inhaber einer MSDN Subscription erhalten ebenfalls Zugriff auf die Microsoft Cloud und können sich daher z.B. eine Testumgebung einrichten.

Durch die Menge an zur Verfügung stehenden Images ist schnell eine stolze Zahl von Systemen ausgerollt. In die Konfiguration und Erstellung der Daten investiert man, wie bei normalen Systemen, einige Stunden, Tage oder mehr.

Dieser Artikel soll eine Möglichkeit aufzeigen, wie man seine Systeme unter Microsoft Azure mit Bordmitteln schützen kann. Nebenbei lernt man einige Features der Cloud-Plattform kennen, wie z.B. die Automatisierungserweiterung oder Azure PowerShell. Eine Verwendung findet auf eigene Gefahr statt. Ausgiebiges Testen vor der Verwendung mit wichtigen Systemen versteht sich von selbst.

Voraussetzungen:

  • Ein Zugang zu Microsoft Azure
  • Azure PowerShell muss auf dem Arbeits-PC installiert sein
  • In diesem Artikel wird nur mit virtuellen Maschinen (VM) gearbeitet, die mit Windows 2012R2 laufen
  • Die virtuellen Festplatten (VHD) einer VM befinden sich alle im gleichen Container
  • Innerhalb der VM befinden sich die Daten nicht auf Systemlaufwerken und auch nicht auf den temporären VHDs

Zusätzlich zu diesen Voraussetzungen ist es hilfreich, die von Azure verwendete Speicher-Technologie zu kennen. Kenntnis über Automatisierung, Runbooks und Assets in Azure ist ebenfalls hilfreich.

Zielsetzung:

  • Sichern von laufenden VMs
    • Automatisches Sichern auswählbarer VMs per Scheduled Task
    • Sichern der ausgewählten VMs auf Knopfdruck
    • Datendienste, die VSS unterstützen, werden per VSS-Writer in einen konsistenten Zustand versetzt

  • Automatisiertes Installieren der Plug-ins auf den VMs
  • Wiederherstellung durch Skripte weitgehend automatisiert
  • Klonen von VMs

Bevor es so richtig losgeht: Ich arbeite aktuell im Azure Portal und nicht in der Preview zum neuen Portal – die Screenshots stammen also alle von dort.

Über Runbooks und Assets

Als eine der Zielsetzung gilt „Die Sicherung soll automatisch erfolgen“. Um dies zu erfüllen, müssen zwei Dinge vorbereitet werden:

  1. Anmeldeinformationen
  2. Zeitpläne

Zurerst die Anmeldeinformationen:

Anmeldeinformationen, die durch Runbooks verwendet werden, sollten im Azure-AD Standardverzeichnis liegen:

Wählen Sie „Azure Active Directory“ und dann „Benutzer“ in Ihrem Standardverzeichnis. Dort erstellen Sie einen Benutzer mit ausreichenden Rechten. Meinen Benutzer habe ich „auto“ genannt mit „automation“ als Anzeigename. Er ist „Globaler Administrator“, somit darf er alles, was ich auch darf. 🙂

Tipp: Das Passwort muss bei der ersten Anmeldung geändert werden. Sie müssen sich also mit diesem Konto bei Microsoft Azure am Webportal anmelden und ein neues Passwort vergeben. Verwenden Sie hierzu die Funktion „InPrivate Browsen“ des Internet Explorer oder einer vergleichbare Funktion Ihres Lieblingsbrowsers. Dann müssen Sie sich mit Ihrem normalen Benutzer nicht abmelden.

Und nun die Zeitpläne:

Es werden nicht nur Zeitpläne im Automatisierungs-Konto hinzugefügt. Daher empfehle ich, sich für das Backup ein neues Automatisierungs-Konto mit den Namen „Backup“ zu erstellen. Dies hat den einfachen Vorteil, dass sich die Ressourcen und Skripte nicht mit anderen Automatisierungs-Aufgaben vermischen und so gegenseitig beeinflussen. Als geografischer Standort empfiehlt es sich, möglichst nah an den zu verwaltenden Objekten, den VMs, zu bleiben.

In dem Bild sehen Sie die erstellten Ressourcen, oft auch Assets genannt, in meinem Automatisierungs-Konto „Backup“. Dorthin gelangen Sie durch einen Klick auf das Automatisierungs-Icon (roter Rahmen links außen).

Übrigens: Die Bezeichnung „Automatisierungs-Konto“ empfinde ich ein wenig verwirrend. Es handelt sich hierbei weder um ein Konto, an dem man sich anmelden kann, noch hat es mit Geld zu tun. Es ist viel eher eine Art Container für alles, was zur Automatisierung innerhalb von Azure nötig ist.

Das Modul mit dem Namen „Azure“ wird automatisch erstellt. Die anderen Assets sind Handarbeit. Die Zeitpläne sind schnell und einfach angelegt, die Namensgebung ist Ihnen überlassen. Es besteht die Möglichkeit, einen Zeitplan der täglich ausgeführt wird, zu erstellen. Wenn an bestimmten Tagen keine Sicherung erfolgen soll, erstellt man z.B. für Montag, ausgewählt durch „Beginnt um“, mit einer Wiederholung von 7 einen wöchentlichen Plan:

Anmeldedaten für PowerShell:

Die automatisch gestarteten Skripte, Runbooks genannt, müssen sich gegen Azure authentifizieren, um arbeiten zu können. Hierfür gibt es mehrere Möglichkeiten: Die einfachste ist per Benutzername und Passwort. Klartext im Skript würde zwar funktionieren, ist aber keine empfehlenswerte Vorgehensweise. Besser ist es die Accountdaten als Asset zu hinterlegen.

Beim Erstellen der Zeitpläne haben Sie sicher schon bemerkt, dass es noch andere Arten von Assets gibt. Legen Sie einfach ein Asset vom Typ „Anmeldeinformationen“ an und wählen Sie „Windows PowerShell Anmeldeinformationen“ aus. Geben Sie dem Asset einen Namen (muss nicht mit dem Anmeldenamen übereinstimmen). Danach die Anmeldedaten eintragen, der Benutzername muss dabei als User Principal Name angegeben werden (z.B. auto@bliblablu.net).

Es wird noch ein weiterer Benutzer benötigt. Hierbei handelt es sich um einen Account mit Berechtigung auf den VMs, die gesichert werden sollen. Der Account muss genügend Rechte haben, um eine PS-Authentifizierung durchzuführen und Schattenkopien der Laufwerke zu erstellen. Wenn Ihre VMs einer Domäne angehören, bietet sich ein Domänen-Account mit entsprechenden Rechten an. In einer Workgroup muss der Benutzer auf jeder VM angelegt werden. Die Vorgehensweise ist ansonsten wie beim „auto“-Konto.

Nun fehlt nur noch eine Variable, in der die Informationen der VMs abgelegt werden. Der Name einer VM ist nicht ausreichend, um die VM eindeutig zu identifizieren, daher muss auch der ServiceName der VM gespeichert werden. Unter PowerShell ist dies am einfachsten per Hash-Tabelle möglich. Aus der Sicht von Azure handelt es sich dabei um eine sogenannte komplexe Variable, diese kann man im Variablentyp „Nicht definiert“ ablegen. Legen Sie eine Variable an, wie im Bild beispielhaft dargestellt:

Der nächste logische Schritt ist das Auswählen der VMs: Dies geschieht am besten auf dem eigenen PC, da man dann mit der GUI arbeiten kann. Da ein direkter Zugriff auf Assets nicht möglich ist, lösen wir dies mit einem einfach Runbook, welches als Schnittstelle dient:

1 workflow AutomationAsset

2  {

3   param

4     (

5      [Parameter(Mandatory=$True)]

6     [ValidateSet('GetVariable','SetVariable')]

7      [string] $Type,

8      [Parameter(Mandatory=$True)]

9      [string]$Name,

10      [string]$Value

11      )

12   $Val = $null

13    if($Type -eq "SetVariable")

14     {

15      $DeSerializedIntput = [System.Management.Automation.PSSerializer]::DeSerialize($Value)

16       Set-AutomationVariable -Name $Name -Value $DeSerializedIntput

17       }

18    if($Type -eq "GetVariable" -or $Type -eq "SetVariable")

19       {

20       $Val = Get-AutomationVariable -Name $Name

21       }

22    if(!$Val)

23       {

24       throw "Automation asset '$Name' of type $Type does not exist"

25      }

26    else

27       {

28       $SerializedOutput = [System.Management.Automation.PSSerializer]::Serialize($Val, 32)

29       $SerializedOutput

30       }

31    }

Dieses Runbook hat drei Parameter:

[string] Type Die möglichen Werte sind „GetVariable“ und „SetVariable“. Hiermit wird festgelegt, ob man eine Variable lesen oder schreiben will.
[string] Name Hiermit wird der Name der Variable übergeben.
[string] Value Bei „SetVariable“ wird die Variable mit diesem Wert gefüllt.

Der Transport der Werte wird per XML durchgeführt. In den Zeilen 18 und 32 werden die Werte entsprechend konvertiert. Beim Setzen des Wertes wird die Variable hinterher nochmals ausgelesen und zurückgegeben.

Importieren Sie dieses Runbook einfach und veröffentlichten Sie es, damit es vom folgenden Skript verwendet werden kann:

Das Skript zum Auswählen der VMs fällt ebenfalls sehr kurz und einfach aus. Es kann direkt per Maus gestartet werden und fragt, bei fehlender Anmeldung, nach den Zugangsdaten zu Azure.

1 $error.Clear()

2 if ( -not (Get-Module -Name Azure) )

3    {

4    Import-Module azure

5    }

7 $VMs = Get-AzureVM  -ErrorAction SilentlyContinue | select Name, ServiceName, Status

8 if ( $Error.Count -gt 0 )

9    {

10    if ( $Error.Exception -like "*please run Add-AzureAccount*" )

11       {

12       Add-AzureAccount

13       }

14    else

15       {

16       $error

17       exit

18       }

19    $VMs = Get-AzureVM | select Name, ServiceName, Status

20    }

21 

22 $Var = "Backup"

23 $AutoAccount = "Backup"

24 $VM = $VMs | Out-GridView -Title "Select your VMs to Backup" -PassThru

25 $VM = $VM | select Name, ServiceName

26 $VMxml = [System.Management.Automation.PSSerializer]::Serialize($VM)

27 

28 $Job = Start-AzureAutomationRunbook -AutomationAccountName $AutoAccount -Name AutomationAsset `

29    -Parameters @{"Type" = "SetVariable"; "Name" = "$Var"; "Value" = "$VMxml"}

Dieses Skript benötigt keine Parameter.

Zeile Kommentar
2 – 5 Azure-Modul bei Bedarf nachladen
8, 14-20 Einfache Fehlerbehandlung, um fehlende Anmeldung zu erkennen.
10 – 13 Anmeldung an Azure durchführen, falls nötig.
22 – 23 Variablennamen festlegen. Dies müssen Sie Ihrer Umgebung anpassen.
24 Darstellen aller VMs und auf Auswahl warten
26 Umwandeln der Hash-Table in XML
28 – 29 Aufrufen des RunBooks „AutomationAsset“ mit Parameter

In Zeile 22 wird der Name der Variable angegeben, die zum Speicher der zu sichernden VMs verwendet wird. In Zeile 23 wird der Name des Automatisierungs-Kontos hinterlegt. Die Ausgabe sollte etwa so aussehen:

Ohne Agent geht es nicht

So sieht der Geplante Ablauf aus:

Backup                                                                                         Restore (Fall 2)

Der grüne, gestrichelte Pfeil soll den Zustand des Blobs beim Restore besser sichtbar machen. Wie man in diesem Diagramm sieht, wird mit der VM per PS-Remote kommuniziert. Somit können Daten und Applikationen in einen Backup-Modus versetzt werden. In diesem Artikel wollen wir uns damit begnügen, dass ein VSS erstellt wird. Applikationen, die einen VSS-Writer mitbringen, kann man, für den Augenblick des Erstellens, in einen konsistenten Zustand bringen.

Durch das Diagramm ist erkennbar, welche Tasks durch die VM selbst erledigt werden müssen:

  • Backup: Erstellen eines VSS und eines Scheduled Task mit einem Trigger auf Systemstart.
  • Backup: Löschen des VSS und des Tasks nach Sicherung des Blobs.
  • Restore: Wiederherstellen des VSS und löschen des Tasks.
  • Restore: (Optional) Ein Neustart, damit die Dienste mit den korrekten Daten starten.

Die Agent-Skripte wurden von mir unter den Namen „create-vss.ps1“, „del-vss.ps1“ und „revert-vss.ps1“ gespeichert:

Create-VSS

1 $Path = split-path -parent $MyInvocation.MyCommand.Definition

2 $Log ="VSS.log"

4 $LogFile = join-path -path $Path -childpath $Log

6 function LogWrite {

7     Param

8       (

9       [string]$logString

10       )

11     $nowDate = Get-Date -format dd.MM.yyyy

12     $nowTime = Get-Date -format HH:mm:ss

13    Add-content $LogFile -value "[$nowDate][$nowTime] - $logString"

14    Write-Output "[$nowDate][$nowTime] - $logString"

15 }

16 LOGWrite "--- Create VSS ---"

17 LogWrite "Path: $Path"

18 

19 $Vols = vssadmin list volumes

20 $Disks = (($Vols | where { $_ -like "*path*" -or $_ -like "Volumepfad*" }).split(' ') | where {

21    $_ -like "*:\" }) -replace('\\','')

22 

23 $File = join-path -Path $Path -ChildPath revert-vss.ps1

24 $Prog = "%systemroot%\system32\WindowsPowerShell\v1.0\powershell.exe"

25 $Opt = "-ExecutionPolicy RemoteSigned -File " + $File

26 LogWrite "Create Scheduled Task VSS-Revert: $Prog $Opt"

27 $Action = New-ScheduledTaskAction -Execute $Prog -Argument $Opt

28 $Trigger = New-ScheduledTaskTrigger -AtStartup

29 $Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

30 $Task = Register-ScheduledTask -Action $Action -Trigger $Trigger -Principal $Principal `

31    -TaskName "VSS-Revert" -Description "Reverts VSS created by Backup"

32 

33 $Snaps = @()

34 foreach ( $Disk in $Disks )

35    {

36    LogWrite "Create VSS for $Disk"

37    $Snap = ((vssadmin create shadow /for=$Disk | where { $_ -like "*ID:*" }).split(' '))[-1]

38    LogWrite "Created VSS $Snap"

39    $Snaps += $Snap

40    }

41 

42 $File = join-path -Path $Path -ChildPath snaps.txt

43 $Snaps | out-file -Encoding utf8 -filepath $File

Zeile Kommentar
1 – 15 Definition der LOG-Funktion.
19 – 21 VSS-fähige Volumes ermitteln.
23 – 31 Scheduled Task erstellen.
33 – 40 VSS auf ermittelten Volumes erstellen.
43 IDs der VSS in Datei ablegen.

Del-VSS

1 $Path = split-path -parent $MyInvocation.MyCommand.Definition

2 $Log ="VSS.log"

4 $LogFile = join-path -path $Path -childpath $Log

5 $File = join-path -path $Path -childpath snaps.txt

7 function LogWrite {

8     Param

9       (

10       [string]$logString

11       )

12     $nowDate = Get-Date -format dd.MM.yyyy

13     $nowTime = Get-Date -format HH:mm:ss

14    Add-content $LogFile -value "[$nowDate][$nowTime] - $logString"

15    Write-Output "[$nowDate][$nowTime] - $logString"

16 }

17 LOGWrite "--- Delete VSS ---"

18 LogWrite "Delete Scheduled Task VSS-Revert"

19 Get-ScheduledTask | where { $_.TaskName -eq "VSS-Revert" } | Unregister-ScheduledTask -Confirm:$false

20 

21 if ( -not (test-path $File) )

22    {

23    LogWrite "$File not found! Exit."

24    exit

25    }

26 

27 $Snaps = get-content $File

28 

29 foreach ( $Snap in $Snaps )

30    {

31    LogWrite "Remove VSS $Snap"

32    $tmp = vssadmin Delete Shadows /Shadow=$Snap /quiet

33    }

34 

35 remove-item $File -force

Zeile Kommentar
1 – 16 Definition der LOG-Funktion
19 Scheduled Task löschen
27 Datei mit den IDs der erstellten VSS lesen
29 – 33 VSS löschen
35 Datei mit IDs löschen

Revert-VSS

1 $Path = split-path -parent $MyInvocation.MyCommand.Definition

2 $Log ="VSS.log"

4 $LogFile = join-path -path $Path -childpath $Log

5 $File = join-path -path $Path -childpath snaps.txt

7 function LogWrite {

8    Param

9       (

10       [string]$logString

11       )

12    $nowDate = Get-Date -format dd.MM.yyyy

13    $nowTime = Get-Date -format HH:mm:ss

14    Add-content $LogFile -value "[$nowDate][$nowTime] - $logString"

15    Write-Output "[$nowDate][$nowTime] - $logString"

16 }

17 LOGWrite "--- Revert VSS ---"

18 

19 if ( -not (test-path $File) )

20    {

21    LogWrite "$File not found! Exit."

22    exit

23    }

24 

25 LogWrite "Read File $File"

26 $Snaps = get-content $File

27 

28 foreach ( $Snap in $Snaps )

29    {

30    LogWrite "Revert Snapshot $Snap"

31    vssadmin Revert Shadow /Shadow=$Snap /ForceDismount /quiet | out-null

32    if ( $LASTEXITCODE -ne 0 )

33       {

34       LogWrite "Revert was not possible. Delete Snapshot $Snap"

35       vssadmin delete Shadows /Shadow=$Snap /quiet | out-null

36       }

37    else

38       {

39       LogWrite "Revert done."

40       }

41    }

42 

43 LogWrite "Delete Scheduled Task VSS-Revert"

44 Get-ScheduledTask | where { $_.TaskName -eq "VSS-Revert" } | Unregister-ScheduledTask -Confirm:$false

45 remove-item $File -force

Zeile Kommentar
1 – 16 Definition der LOG-Funktion
26 Datei mit IDs der VSS löschen
28 – 42 VSS, wenn möglich, wiederherstellen
44 – 45 Scheduled Task und Datei mit IDs löschen

Der Upload und die Installation der drei Skripte erfolgt durch die folgenden beiden Installer-Skripte:

upload-snap.ps1

1 param

2    (

3    [Parameter(Mandatory=$true)][string]$VMName,

4    [Parameter(Mandatory=$true)][string]$ServiceName,

5    )

7 $VM = Get-AzureVM -Name $VMName -ServiceName $ServiceName

9 if ( $VM.count -ne 1 )

10    {

11    write-host -ForegroundColor red "Keine passende VM gefunden"

12    exit

13    }

<P<14 

15 $Source = split-path -parent $MyInvocation.MyCommand.Definition

16 

17 $Fnames = @()

18 $Fnames += "install-snap.ps1"

19 $Fnames += "Snap.zip"

20 

21 $Files = @()

22 foreach ( $Fname in $Fnames )

23    {

24    $Files += Join-path -path $Source -childpath $Fname

25    }

26 

27 $StorKey = Get-AzureStorageKey -StorageAccountName $VM.VM.OSVirtualHardDisk.MediaLink.Host.Split('.')[0]

28 $StorContext = New-AzureStorageContext -StorageAccountName $StorKey.StorageAccountName `

29    -StorageAccountKey $StorKey.Primary

30 $Container = $VM.VM.OSVirtualHardDisk.MediaLink.AbsolutePath.Split('/')[1]

31 

32 foreach( $File in $Files )

33    {

34    Set-AzureStorageBlobContent -Container $Container -Context $StorContext -File $File -Force

35    }

36 

37Set-AzureVMCustomScriptExtension -VM $VM -ContainerName $Container -FileName $Fnames | Update-AzureVM

38   -Verbose

Zeile Kommentar
3 Parameter für den Namen der VM
4 Parameter für den ServiceNamen der VM
17 – 25 Erfassen der Dateien
27 – 30 Ermitteln des Ziel-Container in Azure
32 – 35 Upload der Dateien
37 – 38 Installation per CustomScriptExtension

„install-snap.ps1“

1 Add-Type -A System.IO.Compression.FileSystem

2 $Source = split-path -parent $MyInvocation.MyCommand.Definition

3 $Script = $MyInvocation.MyCommand.Definition.split('\')[-1]

5 $Dest = "c:\Backup"

7 if ( -not ( test-path $Dest ) )

8    {

9    New-Item -path $Dest -type directory

10    }

11 

12 $Files = Get-ChildItem -Filter *.zip -Path $Source

13 

14 foreach ( $File in $Files )

15    {

16    [IO.Compression.ZipFile]::ExtractToDirectory( $File.FullName, $Source )

17    }

18 $Source = Join-Path -Path $Source -childpath "*.ps1"

19 $Script = Join-Path -Path $Dest -childpath $Script

20 Copy-Item -path $Source -destination $Dest -force

21 Remove-Item -path $script -force

Zeile Kommentar
1 Namespace für ExtractToDirectory hinzufügen
5 Ziel-Verzeichnis festlegen
12 – 17 Zip-Files entpacken
18 – 20 PS1-Files in Zielverzeichnis kopieren
21 Installer löschen

Um die Installation der drei Agent-Skripte durch die Installer-Skripte zu ermöglichen, müssen die Agent-Skripte in ein ZIP-File mit dem Namen „snap.zip“ gepackt werden. „upload-snap.ps1“ und „install-snap.ps1“ müssen im gleichen Verzeichnis liegen:

 

Aus der Azure PowerShell-Konsole ruft man das Skript „upload-snap-ps1“ auf und gibt dabei den Namen und ServiceNamen der VM an. Die Installation kann etwa ein bis zwei Minuten dauern.

Backup

Das Backup-Skript wird als Runbook angelegt. Dort läuft es, getriggert von einem Zeitplan, oder von Hand gestartet. Vergessen Sie das Verknüpfen mit den anfangs angelegten Zeitplänen nicht. Parameter sind in Form von Konstanten innerhalb des Workbooks hinterlegt. Die restlichen Informationen stehen innerhalb der Assets zur Verfügung.

1 workflow SnapShot

2 {

3 $Var = "Backup"

4 $AutomationAccount = "auto"

5 $BCKuser = "BCKuser"

7 $Cred = Get-AutomationPSCredential -Name $AutomationAccount

8 Add-AzureAccount -Credential $Cred

9 $VMs = Get-AutomationVariable -Name $Var

10 $Cred = Get-AutomationPSCredential -Name $BCKuser

11 

12 InlineScript

13    {

14    $Subscr = "MSDN-Plattformen"

15    $MaxSnaps = 3        

16 

17    Select-AzureSubscription -SubscriptionName $Subscr

18    $BCKuser = $Using:Cred

19    $VMs = $Using:VMs | Get-AzureVM

20 

21    Function Get-LineNumber

22       {

23       $MyInvocation.ScriptLineNumber

24       }

25    New-Alias -Name __LINE__ -Value Get-LineNumber

26 

27    Function Write-Dbg ([String]$Message)

28       {

29       If ($Dbg -and $Message -ne "")

30           {

31           $Date = ( Get-Date -format T ).ToString()

32 

33           Write-Output "DEBUG $Date : $Message"

34           }

35       }

36 

37    $Dbg = $true

38 

39    if ( $VMs.count -eq 0 )

40           {

41           write-dbg "$(__LINE__): Keine passenden VMs gefunden"

42           exit

43           }

44    $PSSO = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck

45    write-dbg "$(__LINE__): VMs: $($VMs.count)"

46    foreach ( $VM in $VMs )

47       {

48       write-dbg "$(__LINE__): Backup $($VM.Name)"

49       $Disks = @()

50       $Disks += Get-AzureOSDisk -VM $VM.VM |   Select DiskName, MediaLink, OS

51       $Disks += Get-AzureDataDisk -VM $VM.VM | Select DiskName, MediaLink, OS

52 

53       $StorKey = Get-AzureStorageKey -StorageAccountName $Disks[0].MediaLink.Host.Split('.')[0]

54       $StorContext = New-AzureStorageContext -StorageAccountName $StorKey.StorageAccountName `

55           -StorageAccountKey $StorKey.Primary

56       $AllCont = Get-AzureStorageContainer -Context $StorContext | where { `

57           $_.Name -match ("snap-" + $VM.Name.ToLower() + "-(\d+)$") }

58       $AllCont = $AllCont | select Name, @{Name="Count";Expression={[int]($_.Name.split('-')[-1] `

59           -creplace '[^\d]','')}}, CloudBlobContainer | Sort-Object Count -Descending

60 

61       if ( $AllCont )

62           {

63           write-dbg "$(__LINE__): Container: $($AllCont.count)"

64           $Next = [int]($AllCont[0].Count) +1

65           $ContName = "snap-" + $VM.Name.ToLower() + "-" + $Next.ToString()

66 

67           if ( $AllCont.count -ge $MaxSnaps )

68              {

69              for ($i=$MaxSnaps; $i -le ($AllCont.count -1); $i++)

70                 {

71                 write-dbg "$(__LINE__): Remove Container: $($AllCont[$i].Name)"

72                 Remove-AzureStorageContainer -Context $StorContext -Name $AllCont[$i].Name `

73                    -Confirm:$false -force

74                 }

75              }

76           }

77       else

78           {

79           $ContName = "snap-" + $VM.Name.ToLower() + "-1"

80           }

81      write-dbg "$(__LINE__): New-AzureStorageContainer Context $StorContext `t Name $ContName"

82       $NewCont = New-AzureStorageContainer -Context $StorContext -Name $ContName -Permission off

83 

84       $DNR = 0

85       $Date = Get-Date -uformat "%Y%m%d-%H%M%S"

86       $DSTName = "$($VM.ServiceName)-$($VM.Name)-$Date"

87       $BLOBs = @()

88       foreach ( $Disk in $Disks )

89           {

90           $tmp = $Disk.MediaLink.AbsolutePath.Split('/')

91           $BLOBs += Get-AzureStorageBlob -Context $StorContext -Container $tmp[1] -Blob $tmp[-1]

92          }

93 

94       $VMNET = ($VM.VM.ConfigurationSets | where { $_.ConfigurationSetType -eq "NetworkConfiguration"

95   }).InputEndpoints | where { $_.LocalPort -eq "5986" }

96 

97       if ( $VMNET -and $VM.Status -eq "ReadyRole" )

98           {

99           $ConUri = "https://" + $VMNET.VIP + ":" + $VMNET.Port

100           Invoke-Command -ConnectionUri $ConUri -Credential $BCKuser { C:\backup\create-vss.ps1 }

101   -SessionOption $PSSO

102          }

103 

104       $SNAPs = @()

105       foreach ( $BLOB in $BLOBs )

106           {

107           write-dbg "$(__LINE__): Create Snapshot for $($BLOB.Name)"

108           $SNAPs += $BLOB.ICloudBlob.CreateSnapshot()

109           }

110 

111       if ( $VMNET -and $VM.Status -eq "ReadyRole" )

112           {

113           Invoke-Command -ConnectionUri $ConUri -Credential $BCKuser { C:\backup\del-vss.ps1 }

114    -SessionOption $PSSO

115           }

116 

117       foreach ( $SNAP in $SNAPs )

118           {

119           if ( $Disks[$DNR].OS )

120              {

121              $Blob = $DSTName + "-OS.vhd"

122              }

123           else

124              {

125              $Blob = $DSTName + "-$DNR.vhd"

126              }

127           $URI = $SNAP.Uri.AbsoluteUri

128           $Count = 5

129           do

130              {

131              $Error.clear()

132             write-dbg "$(__LINE__): Start BlobCopy $Uri --> $($NewCont.Name)"

133              $BlobCopy = Start-AzureStorageBlobCopy -Context $StorContext -SrcUri $Uri `

134                 -DestContainer $NewCont.Name -DestBlob $Blob -Force

135              if ( $Error.count -eq 0 )

136                 {

137                 break

138                 }

139              $Count --

140              write-dbg "$(__LINE__): Error at BlobCopy. Trying an other $Count times!"

141              sleep 60

142              } until ( $Count -eq 0 )

143           if ( $Error.count -eq 0 )

144              {

145              $BlobCopyState = Get-AzureStorageBlobCopyState -Context $StorContext -Container `

146                 $NewCont.Name -Blob $Blob -WaitForComplete

147              }

148           else

149              {

150              write-dbg "$(__LINE__): Error at BlobCopy. Cant Backup $($SNAP.Name)"

151              }

152           write-dbg "$(__LINE__): Delete Snapshot $($SNAP.Name)"

153          $SNAP.Delete()

154           $DNR ++

155           }

156      }

157    }

158 }

Zeile Kommentar
3 – 5 Namen der Assets definieren
7, 9 – 10 Assets auslesen
8 Authentifizierung durchführen
14 Subscription-Typ hinterlegen
15 Maximale Anzahl alter Backups festlegen
21 – 35 Funktionen zur Debug-Ausgabe definieren
37 Debug auf TRUE setzen
44 Zertifikats-Checks für PS-Sessions abschalten
46 Start Schleife für Backup
49 – 51 VHDs der VM ermittlen und in Array packen
53 – 59 StorrageKey und Container mit alten Backups ermitteln
64 – 65 Name des Ziel-Containers festlegen
67 – 75 Alte Backups löschen
79 Name des Ziel-Containers festlegen, wenn es das erste Backup ist
82 Ziel-Container erstellen
85 – 86 Basisname für Blobs festlegen
88 – 92 Ermitteln aller VHDs der VM. Diese sind jeweils als Blob abgelegt
94 – 102 Wenn Remote-PS erlaubt ist, wird „create-vss.ps1“ in der VM gestartet (synchroner Aufruf)
104 – 109 Blob-Snapshot erstellen
111 – 115 Wenn Remote-PS erlaubt ist, wird „del-vss.ps1“ in der VM gestartet (synchroner Aufruf)
119 – 126 Name des Ziel-Blobs festlegen
128 – 151 Blob-Snapshot nach Ziel-Blob kopieren (maximal 5 Versuche)
153 Blob-Snapshot löschen

Funktionserklärung des Backup-Skripts

Wie man sehen kann, wird im Skript an fast allen Stellen auf eine Fehlerbehandlung verzichtet. Dadurch wird das Skript deutlich übersichtlicher und leichter zu verstehen. Da die meisten VMs mehrere VHDs haben, müssen pro VM mehrere Blobs kopiert werden. Die Standardgröße einer System-VHD und somit die Größe des entsprechenden Blobs beträgt 128 GB. Die Größe der Daten-VHDs variiert.

Würde man mit einem einfachen StorageBlobCopy die Daten kopieren, würde der Zeitpunkt, an dem der erste Blob kopiert wurde, und der Zeitpunkt, wenn der letzte Kopiervorgang abgeschlossen ist, u.U. weit auseinander liegen. Die Konsistenz der Daten wäre dann gefährdet. Mit Snapshots kann diesem begegnet werden. Vorher muss jedoch die VM in einen Backup-Status gebracht werden. Dabei sollen z.B. Datenbanken in einen konsistenten Zustand versetzt werden. Dafür kommt „create-vss.ps1“ zum Zug. Es wird per „Invoke-Command“ aufgerufen. Zum Erstellen des VSS wird VSSADMIN verwendet. Damit werden auf allen Laufwerken Schattenkopien erstellt. Leider hat VSSADMIN die Einschränkung, dass zwar auf jedem Laufwerk ein VSS erstellt werden kann, aber auf Systemlaufwerken diese nicht wieder hergestellt werden können. Datenbanken, Fileshares u.ä. sollten also unbedingt auf zusätzliche Laufwerke gelegt werden.

Nun werden die vorher erwähnten Blob-Snapshots gemacht. Der Status der VHDs ist dadurch in den Snapshots „eingefroren“. Die Schattenkopien innerhalb der VM sind damit überflüssig geworden und werden durch einen Aufruf von „del-vss.ps1“ von der VM gelöscht. Für die VM ist das Backup abgeschlossen und sie läuft ungestört weiter.

Auf dem Storage wird mit „Start-AzureStorageBlobCopy“ ein Hintergrund-Prozess zum Kopieren des Blob-Snapshots erstellt. Der Blob-Snapshot ist, wie andere Snapshots auch, Read-Only. Durch den Kopiervorgang wird ein beschreibbarer Blob daraus. Die dafür nötige Zeit spielt nun keine Rolle mehr. Sobald alle Blobs kopiert sind, werden die Blob-Snapshots ebenfalls gelöscht. Das Backup ist nun auch auf der Storage-Ebene fertig.

Aber etwas fehlt. Leider, denn folgendes kann nicht gesichert werden:

  • Der Arbeitsspeicher der laufenden VM
  • Das Azure-VM-Objekt in dem u.a. die Azure-Netzwerkeinstellungen hinterlegt sind

Hierfür arbeite ich an einer Lösung.

Restore

Das Skript für den Restore bietet drei unterschiedliche Funktionen:

  1. Wiederherstellung einer VM, die komplett gelöscht wurde.
  2. Wiederherstellung einer VM, während die VM noch verfügbar ist.
  3. Klonen einer VM. Dafür muss die VM ebenfalls noch verfügbar sein.

Neun Parameter steuern die Funktion des Skripts. Eine Überprüfung der Eingaben wird nur begrenzt durchgeführt!

  • [string] VMName - Der Parameter ist Pflicht.
  • [string] ServiceName - Dieser Parameter ist ebenfalls Pflicht und wird benötigt wegen Eindeutigkeit der VM.
  • [string] StorageAccount - Dieser Parameter wird nur benötigt, wenn die VM komplett gelöscht wurde. Ansonsten wir der StorageAccount automatisch ermittelt.
  • [int] RestoreID - Die gespeicherten Backups werden nummeriert. Anhand dieser ID kann man das zu verwendende Backup auswählen. Gibt man diesen Parameter nicht an, werden alle verfügbaren Backups aufgelistet.
  • [string] DstCont - Auch dieser Parameter ist Pflicht. Mit ihm wird der Ziel-Container ausgewählt, in dem die VM wiederhergestellt wird. Der Container muss vorhanden sein.
  • [switch] Deleted - Mit diesem Switch wird der Fall 1 aktiviert: VM ist komplett gelöscht.
  • [switch]Keep - Mit diesem Switch wird das Klonen aktiviert.
  • [string] NewServiceName - Wenn „keep“ verwendet wird, kann man hiermit den ServiceName des Clones bestimmen. Der Service muss existieren.
  • [switch] Dbg - Mit diesem Switch werden die Debug-Ausgaben eingeschaltet.

Wenn man die RestoreID nicht kennt, lässt man den Parameter einfach weg und erhält eine Ausgabe wie diese:

In der Spalte „Backup“ werden die RestoreIDs angezeigt.

Restore einer gelöschten VM (Fall 1)

Die Wiederherstellung für diesen Fall ist für das Skript relativ einfach. Es werden lediglich die Blobs aus dem gewählten Backup-Container in den Ziel-Container kopiert. Die Namen der Blobs setzen sich wie folgt zusammen:

System-VHDs:            [ServiceName]-[VMName]-[Timestamp]-OS

Daten-VHDs:               [ServiceName]-[VMName]-[Timestamp]-[Laufende Nummer]

Sobald das erledigt ist, werden die Blobs als Azure-Disks, mit der gleichen Namenskonvention, registriert. Der Timestamp wird im Script einmalig berechnet. Somit haben alle Blobs von einem Restore den gleichen Timestamp.

Dieses Bild zeigt die Wiederherstellung einer VM mit einer VHD (128GB).

Sobald das Script fertig ist, muss die VM manuell registriert werden. Dazu meldet man sich am Portal an und fügt eine VM hinzu. Bei der Frage nach dem zu verwendeten Image, wählt man die eigene VHD aus:

Die Namen in dem Bild sind nur beispielhaft. Die weiteren Daten-VHDs kann man leider nicht sofort einbinden, sondern erst im Portal bei den VM-Eigenschaften:

Sobald das erledigt ist, läuft die VM wieder wie zuvor. Der erste Neustart nach einer Wiederherstellung kann manchmal ungewöhnlich lange dauern.

Restore und Klonen einer vorhandenen VM (Fall 2 und 3)

Bei der Wiederherstellung einer vorhandenen VM hat man den Vorteil, dass das AzureVM-Objekt einfach ausgelesen werden kann. Darin sind die Konfigurationen der VM abgelegt. Ein Nachteil ist, dass die VHDs zugewiesen sind. Bei den Daten-VHDs ist das kein Problem. Diese könnten einfach von der VM gelöst, gelöscht und aus dem Backup wiederhergestellt werden. Die System-VHD kann nicht von der VM gelöst und somit nicht gelöscht werden. Daher ist man gezwungen, die Konfiguration aus dem AzureVM-Objekt zu speichern und die VM zusammen mit den alten VHDs zu löschen. Danach kann die VM mit dem AzureVM-Objekt neu erstellt und die neuen VHDs dabei eingebunden werden.

Die alten VHDs haben hier schon so manches Problem bereitet, denn das Löschen geschieht nicht synchron. Die Blobs, in denen die VHDs liegt, werden scheinbar nur zum Löschen markiert. Das Löschen geschieht dann verzögert. Es gab Fälle, da war der Blob nach 30 Minuten immer noch vorhanden. Hier kommt wieder die Namenskonvention der neuen Blobs ins Spiel. Genau wie im vorhergehenden Kapitel beschrieben, bekommen die Blobs bei der Wiederherstellung einen neuen Namen, bestehend aus [ServiceName]-[VMName]-[Timestamp]-[OS oder LfNr].

Durch den neuen Namen der VHDs kann das Löschen der alten VM bis zuletzt hinausgezögert werden. Wenn das Skript vorher auf einen Fehler läuft, kann es durch eine nachgerüstete Fehlerbehandlung abgebrochen werden. Die alte VM wäre immer noch im gleichen Zustand!

Es werden alle Daten-VHDs eingebunden und die Netzwerkkonfiguration wird aus dem AzureVM-Objekt übernommen.

Klonen: Wird der Switch „keep“ angegeben, erhält die neue VM auch einen neuen Namen. Der Name wird hierbei einfach um die RestoreID erweitert. Öffentliche Ports werden, bei der Aktivierung von „keep“, auf einen neuen Port per Zufallsgenerator zugewiesen. Eine Abfrage, ob dieser Port schon zugewiesen ist, wurde noch nicht implementiert.

Die Wiederherstellung einer VM mit einer Daten-VHD stellt sich wie folgt dar:

Die System-VHD hat per Default 128 GB, die Daten-VHD hat in diesem Fall 50GB.

1  param

2     (

3     [Parameter(Mandatory=$true)][string]$VMName,

4     [Parameter(Mandatory=$true)][string]$ServiceName,

5     [string]$StorageAccount,

6     [int]$RestoreID,

7     [Parameter(Mandatory=$true)][string]$DstCont,

8     [switch]$Deleted,

9     [switch]$keep,

10     [string]$NewServiceName,

11     [switch]$Dbg

12     )

13  

14  Function Get-LineNumber

15     {

16     $MyInvocation.ScriptLineNumber

17     }

18  New-Alias -Name __LINE__ -Value Get-LineNumber

19  

20  Function Write-Dbg ([String]$Message)

21     {

22     If ($Dbg -and $Message -ne "")

23        {

24        $Date = ( Get-Date -format G ).ToString()

25  

26        Write-host -ForegroundColor green "DEBUG Start - $Date"("-" * (66 - $Date.Length))

27        Write-host -ForegroundColor green "$Message"

28        Write-host -ForegroundColor green "DEBUG End"("-" * 71)

29        }

30     }

31  #-------------------------------------------------------------------------------------

32  

33  WRITE-DBG "$(__LINE__): Get-AzureVM"  

34  $VM = Get-AzureVM -Name $VMName -ServiceName $ServiceName

35  

36  if ( $VM.count -ne 1 )

37     {

38     if ( -not $Deleted )

39        {

40        write-host -ForegroundColor red "$(__LINE__): Keine passende VM gefunden"

41        exit

42        }

43     }

44  elseif ( $Deleted -eq $true -and $VM.count -ne 0 )

45     {

46     write-host -ForegroundColor red "$(__LINE__): Wenn Deleted angegeben ist, darf keine"`

47        "VM gefunden werden"

48     exit

49     }

50  

51  write-dbg "$(__LINE__): VMs: $($VM.count)"

52  

53  if ( $Deleted )

54     {

55     write-dbg "$(__LINE__): Deleted ist aktiv. StorageKey wird vom Account '$StorageAccount' geladen"

56     if ( -not $StorageAccount )

57        {

58        write-host -ForegroundColor red "$(__LINE__): Bei aktivem Deleted muss der "`

59            "StorageAccount angegeben werden"

60        exit

61        }

62     $StorKey = Get-AzureStorageKey -StorageAccountName $StorageAccount

63     if ( -not $StorKey )

64        {

65        write-host -ForegroundColor red "$(__LINE__): SorageAccount '$StorageAccount' "`

66            "nicht gefunden."

67        exit

68        }

69     }

70  else

71     {

72     if ( $StorageAccount )

73        {

74        write-host -ForegroundColor red "$(__LINE__): Der StorrageAccount darf nur bei "`

75            "aktive Deleted angegeben werden"

76        exit

77        }

78     $Disks = @()

79     $Disks += Get-AzureOSDisk -VM $VM.VM |   Select DiskName, MediaLink, OS

80     $Disks += Get-AzureDataDisk -VM $VM.VM | Select DiskName, MediaLink, OS

81     $StorKey = Get-AzureStorageKey -StorageAccountName `

82        $Disks[0].MediaLink.Host.Split('.')[0] -Verbose

83     }

84  

85  $StorContext = New-AzureStorageContext -StorageAccountName $StorKey.StorageAccountName `

86     -StorageAccountKey $StorKey.Primary

87  $AllCont = Get-AzureStorageContainer -Context $StorContext

88  $Dest = $AllCont | where { $_.Name -eq $DstCont }

89  $BckCont = $AllCont | where { $_.Name -like ("snap-" + $VMName.ToLower() + "-*") }

90  $BckCont = $BckCont | select Name, @{Name="Backup";Expression={[int]($_.Name.split('-')[-1] `

91     -creplace '[^\d]','')}}, CloudBlobContainer, LastModified | Sort-Object Backup

92  

93  if ( ($Dest | measure).count -ne 1 )

94     {

95     write-host -ForegroundColor red "$(__LINE__): Der angegebene Zielkontainer ist "`

96        "unzulässig!"

97     exit

98     }

99  

100  

101  if ( $BckCont.count -eq 0 )

102     {

103     write-host -ForegroundColor red "$(__LINE__): Es wurden keine Sicherungen gefunden"

104     exit

105     }

106  

107  write-dbg "$(__LINE__): $($BckCont.count) Backups gefunden"

108  

109  if ( -not $RestoreID )

110     {

111     write-host -ForegroundColor red "$(__LINE__): Es wurde keine Sicherung ausgewählt! "`

112        "Verwenden Sie RestoreID"

113     write-host -ForegroundColor yellow "Folgende Sicherungen wurden gefunden:"

114     $BckCont | ft Backup, Name, LastModified -AutoSize

115     exit

116     }

117  

118  $SrcCont = $BckCont | where { $_.Backup -eq $RestoreID }

119  

120  

121  if ( -not $SrcCont )

122     {

123     write-host -ForegroundColor red "$(__LINE__): Diese Sicherung existet leider nicht!"

124     exit

125     }

126  

127  if ( $Deleted )

128     {

129     $Blobs = Get-AzureStorageBlob -Context $StorContext -Container $SrcCont.Name | `

130        where { $_.BlobType -eq "PageBlob" }

131     $Date = Get-Date -uformat "%Y%m%d-%H%M%S"

132     $DSTName = "$($ServiceName)-$($VMName)-$Date"

133     $CNT = 0

134     foreach ( $Blob in $Blobs )

135        {

136        if ( $Blob.Name.split('-')[-1].split('.')[0].ToUpper() -eq "OS" )

137            {

138            $DstBlob = $DSTName + "-OS.vhd"

139            }

140        else

141            {

142            $DstBlob = $DSTName + "-" + $CNT.ToString() + ".vhd"

143            }

144        write-dbg "$(__LINE__): $($Blob.ICloudBlob.Uri.AbsoluteUri) --> $($Dest.Name) --> $DstBlob"

145        $BlobCpy = Start-AzureStorageBlobCopy -Context $StorContext -SrcUri `

146            $Blob.ICloudBlob.Uri.AbsoluteUri -DestContainer $Dest.Name -DestBlob `

147            $DstBlob -Force -Verbose

148        $BlobCopyState = Get-AzureStorageBlobCopyState -Context $StorContext -Container `

149            $Dest.Name -Blob $DstBlob -WaitForComplete

150        if ( $Blob.Name.split('-')[-1].split('.')[0].ToUpper() -eq "OS" )

151            {

152            $NewDisk = Add-AzureDisk -DiskName ($DSTName + "-OS") -MediaLocation `

153            $BlobCpy.ICloudBlob.Uri.AbsoluteUri -OS "Windows" -Verbose

154            }

155        else

156            {

157            $NewDisk = Add-AzureDisk -DiskName ($DSTName + "-" + $CNT.ToString()) -MediaLocation `

158            $BlobCpy.ICloudBlob.Uri.AbsoluteUri -Verbose

159            $CNT ++

160            }

161        }

162     Write-Host "Die VHDs wurden wiederhergestellt und alle wurden in Azure registriert."

163     Write-Host "VM war gelöscht. Sie müssen diese im Portal selbst neu registrieren."

164     exit

165     }

166  

167  #----------------------------------------------------------------------------------------

168  # Zweiter Fall: VM existiert noch

169  #----------------------------------------------------------------------------------------

170  

171  write-dbg "$(__LINE__): Get-AzureSubscription"

172  $SubScr = Get-AzureSubscription

173  write-dbg "$(__LINE__): Get-AzureStorageAccount"

174  $StorAcc = Get-AzureStorageAccount -StorageAccountName $StorContext.StorageAccountName

175  write-dbg "$(__LINE__): Change CurrentStorageAccountName to $($StorAcc.Label)"

176  Set-AzureSubscription -SubscriptionName $SubScr.SubscriptionName `

177     -CurrentStorageAccountName $StorAcc.Label

178  

179  $Date = Get-Date -uformat "%Y%m%d-%H%M%S"

180  $DSTName = "$($VM.ServiceName)-$($VM.Name)-$Date"

181  

182  write-dbg "$(__LINE__): Prepare for AzureStorageBlobCopy"

183  $Blobs = Get-AzureStorageBlob -Context $StorContext -Container $SrcCont.Name | where `

184     { $_.BlobType -eq "PageBlob" }

185  $NewDisks = @()

186  $CNT = 0

187  foreach ( $Blob in $Blobs )

188     {

189     if ( $Blob.Name.split('-')[-1].split('.')[0].ToUpper() -eq "OS" )

190        {

191        $Dsk = $DSTName + "-OS.vhd"

192        write-dbg "$(__LINE__): $($Blob.MediaLink.AbsoluteUri) --> $($Dest.Name) --> $Dsk"

193        $BlobCpy = Start-AzureStorageBlobCopy -Context $StorContext -SrcUri `

194            $Blob.ICloudBlob.Uri.AbsoluteUri -DestContainer $Dest.Name -DestBlob `

195            $Dsk -Force -Verbose

196        $BlobCopyState = Get-AzureStorageBlobCopyState -Context $StorContext -Container `

197            $Dest.Name -Blob $Dsk -WaitForComplete

198        write-dbg "$(__LINE__): New OS Disk $($DSTName + "-OS")"

199        $NewOSDisk = Add-AzureDisk -DiskName $($DSTName + "-OS") -MediaLocation `

200            $BlobCpy.ICloudBlob.Uri.AbsoluteUri -OS "Windows" -Verbose

201        }

202     else

203        {

204        $Dsk = $DSTName + "-$($CNT.ToString()).vhd"

205        $BlobCpy = Start-AzureStorageBlobCopy -Context $StorContext -SrcUri `

206            $Blob.ICloudBlob.Uri.AbsoluteUri -DestContainer $Dest.Name -DestBlob `

207            $Dsk -Force -Verbose

208        $BlobCopyState = Get-AzureStorageBlobCopyState -Context $StorContext -Container `

209            $Dest.Name -Blob $Dsk -WaitForComplete

210        write-dbg "$(__LINE__): New Data Disk $($DSTName + "-" + $CNT.ToString())"

211        $NewDisks += Add-AzureDisk -DiskName $($DSTName + "-" + $CNT.ToString()) `

212            -MediaLocation $BlobCpy.ICloudBlob.Uri.AbsoluteUri -Verbose

213        $CNT ++

214        }

215     }

216  

217  write-dbg "$(__LINE__): Remove-VM $($VM.Name) `t $($VM.ServiceName)"

218  if ( -not $keep )

219     {

220     Remove-AzureVM -Name $VM.Name -ServiceName $VM.ServiceName -DeleteVHD -Verbose

221     $VMname = $VM.Name

222     }

223  else

224     {

225     $VMname = $VM.Name.ToLower() + "-" + $RestoreID

226     $VM.VM.RoleName = $VMname

227     $VM.InstanceName = $VMname

228     $VM.HostName = $VMname

229     $VM.Name = $VMname

230     }

231  

232  write-dbg "$(__LINE__): New-AzureVMConfig$($VM.Name)"

233  $NewConfig = New-AzureVMConfig -Name $VMname -InstanceSize $VM.InstanceSize -DiskName `

234     $NewOSDisk.DiskName | Add-AzureProvisioningConfig -Windows

235  

236  $NewConfig.DataVirtualHardDisks = $null

237  $CNT=0

238  Foreach ( $NewDisk in $NewDisks )

239     {

240     $NewConfig.DataVirtualHardDisks += $VM.VM.DataVirtualHardDisks[$CNT]

241     $NewConfig.DataVirtualHardDisks[$CNT].DiskLabel = $NewDisk.Label

242     $NewConfig.DataVirtualHardDisks[$CNT].DiskName = $NewDisk.DiskName

243     $NewConfig.DataVirtualHardDisks[$CNT].Lun = $CNT

244     $NewConfig.DataVirtualHardDisks[$CNT].LogicalDiskSizeInGB = $NewDisk.DiskSizeInGB

245   $NewConfig.DataVirtualHardDisks[$CNT].MediaLink = $NewDisk.MediaLink

246     $NewConfig.DataVirtualHardDisks[$CNT].IOType = $NewDisk.IOType

247     $CNT ++

248     }

249  

250  if ( $NewServiceName )

251     {

252     $VM.ServiceName = $NewServiceName

253     }

254  

255  $NewConfig.ConfigurationSets = $null

256  foreach ( $ConfigSet in $VM.VM.ConfigurationSets )

257     {

258     if ( $keep )

259        {

260        for ( $a=0; $a -lt $ConfigSet.InputEndpoints.count; $a++)

261            {

262            $RND = Get-Random -Maximum 64000 -Minimum 30000

263            $ConfigSet.InputEndpoints[$a].Port = $RND

264            }

265        }

266     $NewConfig.ConfigurationSets += $ConfigSet

267     }

268  

269  write-dbg "$(__LINE__): New-AzureVM`n Name: $VMName`n ServiceName : $($VM.ServiceName)"

270  $NewConfig | New-AzureVM -ServiceName $VM.ServiceName -Verbose

Zeile Kommentar
14 - 30 Definition einiger Funktionen
34 - 77 Überprüfung der Parameter und der Umgebung
78 - 82 VHDs auslesen
85 - 91 Ermitteln von StorageAccount, StorageKey und Container
93 - 105 Weitere Überprüfungen von Parameter und Umgebung
109 - 116 Wenn keine Backups angegeben wurden, werden die Verfügbaren ausgegeben
127 - 165 Behandlung von Fall eins
129 - 130 Gesicherte VHDs finden
136 - 143 Neue Namen für VHDs erstellen
145 - 149 Gesicherte VHDs wiederherstellen
150 - 160 VHDs in Azure registrieren
167 - Behandlung von Fall zwei
172 – 177 Setzen des Standard-StorageAccounts als Vorbereitung zum Erstellen der neuen VM
179 - 180 Neuen Namen für VHDs erstellen
183 - 184 Gesicherte VHDs finden
187 - 215 Gesicherte VHDs wiederherstellen und in Azure registrieren
220 Löschen der alten VM inklusive der VHDs
225 - 230 Parameter für Klonvorgang setzen
233 – 234 Vorbereiten der Konfiguration
238 – 253 VHD-Einstellungen übernehmen
255 - 267 Netzwerkeinstellungen übernehmen
270 Erstellen der neuen VM

 

Fazit

Dieser Artikel zeigt anschaulich, dass man mit Boardmitteln und ein paar PowerShell-Skripts ein schnelles Backup und ein genauso schnelles Restore erstellen kann, wodurch die eigene Testumgebung auch bei einem riskanten Manöver jederzeit wieder schnell bereitgestellt ist.

Comments (0)

Skip to main content