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:
- Anmeldeinformationen
- 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 } 6 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" 3 4 $LogFile = join-path -path $Path -childpath $Log 5 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" 3 4 $LogFile = join-path -path $Path -childpath $Log 5 $File = join-path -path $Path -childpath snaps.txt 6 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" 3 4 $LogFile = join-path -path $Path -childpath $Log 5 $File = join-path -path $Path -childpath snaps.txt 6 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 ) 6 7 $VM = Get-AzureVM -Name $VMName -ServiceName $ServiceName 8 9 if ( $VM.count -ne 1 ) 10 { 11 write-host -ForegroundColor red "Keine passende VM gefunden" 12 exit 13 } <P<1415 $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] 4 5 $Dest = "c:\Backup" 6 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" 6 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:
- Wiederherstellung einer VM, die komplett gelöscht wurde.
- Wiederherstellung einer VM, während die VM noch verfügbar ist.
- 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.