Azure – Azure VM Festplatte verkleinern mit PowerShell

In diesem Beitrag beschäftigen wir uns mit der Verkleinerung von Datenträgern bei Azure VMs.

Azure VMs werden standardmäßig (meistens) mit einem mindestens 128GB Datenträger ausgeliefert. Für Tests oder andere Anwendungsfälle kann dies Speicherplatz sein, der zwar nicht benötigt wird, aber trotzdem bezahlt werden muss. Wird bei den VMs auch noch SSD-Speicher eingesetzt, kann dies mit erheblichen Kosten verbunden sein.

Da Azure die Verkleinerung innerhalb des Azure Portals nicht unterstützt, muss auf eine andere Methode zurückgegriffen werden.

Mit den hier gezeigten Schritten ist es möglich den Betriebssystemdatenträger zu verkleinern und so bis zu 25% an Kosten einzusparen.

Voraussetzungen

Azure: VM Contributor und Storage Account Contributor, sowie Zugriffsrechte auf Managed Disks

Hinweis: Alles auf eigene Gefahr!

Lösung

Im ersten Schritt auf die Virtuelle Azure VM aufschalten.

Über die Systemsteuerung oder Rechtsklick auf den Start-Button die Datenträgerverwaltung öffnen. In der Datenträgerverwaltung die Windows-Partition mit Rechtsklick anwählen und auf Volumen verkleinern klicken.

Im nächsten Fenster muss der zu verkleinernde Speicherplatz in MB angegeben werden.

Hier kommt es darauf an, welche Festplattengröße nach der Verkleinerung bestehen soll. In diesem Beispiel möchte ich aus einem 128GB Volumen ein maximal 32GB Volumen machen. Dazu muss der zu verkleinernde Speicherplatz mit 100000MB ( ca. 100GB) angegeben werden. Als Gesamtspeicherplatz nach der Verkleinerung werden nun 29547 MB (ca. 29GB) angegeben, was in das angestrebte 32GB Budget passt. Mit verkleinern bestätigen.

Hinweis: Für die Speicherplatzkalkulation sind auch andere Partitionen des Datenträgers wie „System reserviert“ mit anzurechnen. Der Datenträger des temporären Speichers ist nicht relevant.

Nach der Verkleinerung sollte die Windows-Partition auf die angestrebte Speichergröße verkleinert worden und ein Block mit nicht zugewiesenem Speicherplatz entstanden sein.

Hinweis: Das war es leider noch nicht. Microsoft wird immer noch die kompletten 128GB in Rechnung stellen, da uns der Speicherplatz in der Theorie bereitstehen würde. Es muss also nicht nur der Speicherplatz auf Windows-Ebene angepasst werden, sondern auch auf Azure Infrastruktur-Ebene. In diesem Fall soll aus einer physikalischen 128GB Festplatte eine 32GB Festplatte werden.

Die VM herunterfahren.

In das Azure Portal gehen und die Übersichtsseite der virtuellen Maschine öffnen.

Die VM dort beenden.

Warten bis die Zuordnung aufgehoben wurde.

Anschließend von der VM-Übersichtsseite in den Reiter Datenträger wechseln und dort auf den Betriebssystemdatenträger klicken.

In diesem Fenster in den Punkt Eigenschaften wechseln und den Link der Ressourcen-ID zwischenspeichern.

Als letzte Information wird der Abo-Name oder die Abo-ID benötigt. Beide Informationen können über Abonnements herausgefunden werden.

Nachdem alle Vorbereitungen getroffen und alle nötige Informationen zusammengetragen wurden, geht es nun an die Umsetzung. Als Hilfe dient uns hierbei ein vollautomatisches PowerShell-Script

Was macht dieses Script? :

  • Erstellung eines temporären Speicherkontos
  • Erstellung eines temporären Datenträgers in diesem Speicherkonto
  • Kopieren des Bestandsdatenträgers in das temporäre Speicherkonto
  • Speicherplatzverkleinerung des kopierten Bestandsdatenträgers
  • Konvertierung zu einer Managed Disk
  • Austausch des alten Bestandsdatenträgers mit dem neuen Datenträger
  • Löschen des temporären Speicherkontos, sowie der darin befindlichen, temporären Datenträgern
# Variables
$DiskID = "DISKID" #Hier die Ressourcen-ID eintragen
$VMName = "VMNAME" #Hier den VM-Namen eintragen
$DiskSizeGB = 32 #Hier die gewünschte Speichergröße eintragen
$AzSubscription = "ABONAME" #Hier den Abo-Namen oder die Abo-ID eintragen
$Sku = "Standard_LRS" #Zur Auswahl: Standard_LRS (HDD), StandardSSD_LRS (SSD) Premium_LRS (Premium SSD)

# Script
# Provide your Azure admin credentials
Connect-AzAccount

#Provide the subscription Id of the subscription where snapshot is created
Select-AzSubscription -Subscription $AzSubscription

# VM to resize disk of
$VM = Get-AzVm | ? Name -eq $VMName

#Provide the name of your resource group where snapshot is created
$resourceGroupName = $VM.ResourceGroupName

# Get Disk from ID
$Disk = Get-AzDisk | ? Id -eq $DiskID

# Get VM/Disk generation from Disk
$HyperVGen = $Disk.HyperVGeneration

# Get Disk Name from Disk
$DiskName = $Disk.Name

# Get SAS URI for the Managed disk
$SAS = Grant-AzDiskAccess -ResourceGroupName $resourceGroupName -DiskName $DiskName -Access 'Read' -DurationInSecond 600000;

#Provide the managed disk name
#$managedDiskName = "yourManagedDiskName" 

#Provide Shared Access Signature (SAS) expiry duration in seconds e.g. 3600.
#$sasExpiryDuration = "3600"

#Provide storage account name where you want to copy the snapshot - the script will create a new one temporarily
$storageAccountName = "shrink" + [system.guid]::NewGuid().tostring().replace('-','').substring(1,18)

#Name of the storage container where the downloaded snapshot will be stored
$storageContainerName = $storageAccountName

#Provide the key of the storage account where you want to copy snapshot. 
#$storageAccountKey = "yourStorageAccountKey"

#Provide the name of the VHD file to which snapshot will be copied.
$destinationVHDFileName = "$($VM.StorageProfile.OsDisk.Name).vhd"

#Generate the SAS for the managed disk
#$sas = Grant-AzureRmDiskAccess -ResourceGroupName $resourceGroupName -DiskName $managedDiskName -Access Read -DurationInSecond $sasExpiryDuration

#Create the context for the storage account which will be used to copy snapshot to the storage account 
$StorageAccount = New-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName -SkuName $Sku -Location $VM.Location
$destinationContext = $StorageAccount.Context
$container = New-AzStorageContainer -Name $storageContainerName -Permission Off -Context $destinationContext

#Copy the snapshot to the storage account and wait for it to complete
Start-AzStorageBlobCopy -AbsoluteUri $SAS.AccessSAS -DestContainer $storageContainerName -DestBlob $destinationVHDFileName -DestContext $destinationContext
while(($state = Get-AzStorageBlobCopyState -Context $destinationContext -Blob $destinationVHDFileName -Container $storageContainerName).Status -ne "Success") { $state; Start-Sleep -Seconds 20 }
$state

# Revoke SAS token
Revoke-AzDiskAccess -ResourceGroupName $resourceGroupName -DiskName $DiskName

# Emtpy disk to get footer from
$emptydiskforfootername = "$($VM.StorageProfile.OsDisk.Name)-empty.vhd"

# Empty disk URI
#$EmptyDiskURI = $container.CloudBlobContainer.Uri.AbsoluteUri + "/" + $emptydiskforfooter

$diskConfig = New-AzDiskConfig `
    -Location $VM.Location `
    -CreateOption Empty `
    -DiskSizeGB $DiskSizeGB `
    -HyperVGeneration $HyperVGen

$dataDisk = New-AzDisk `
    -ResourceGroupName $resourceGroupName `
    -DiskName $emptydiskforfootername `
    -Disk $diskConfig
    

$VM = Add-AzVMDataDisk `
    -VM $VM `
    -Name $emptydiskforfootername `
    -CreateOption Attach `
    -ManagedDiskId $dataDisk.Id `
    -Lun 63

Update-AzVM -ResourceGroupName $resourceGroupName -VM $VM

$VM | Stop-AzVM -Force


# Get SAS token for the empty disk
$SAS = Grant-AzDiskAccess -ResourceGroupName $resourceGroupName -DiskName $emptydiskforfootername -Access 'Read' -DurationInSecond 600000;

# Copy the empty disk to blob storage
Start-AzStorageBlobCopy -AbsoluteUri $SAS.AccessSAS -DestContainer $storageContainerName -DestBlob $emptydiskforfootername -DestContext $destinationContext
while(($state = Get-AzStorageBlobCopyState -Context $destinationContext -Blob $emptydiskforfootername -Container $storageContainerName).Status -ne "Success") { $state; Start-Sleep -Seconds 20 }
$state

# Revoke SAS token
Revoke-AzDiskAccess -ResourceGroupName $resourceGroupName -DiskName $emptydiskforfootername

# Remove temp empty disk
Remove-AzVMDataDisk -VM $VM -DataDiskNames $emptydiskforfootername
Update-AzVM -ResourceGroupName $resourceGroupName -VM $VM

# Delete temp disk
Remove-AzDisk -ResourceGroupName $resourceGroupName -DiskName $emptydiskforfootername -Force;

# Get the blobs
$emptyDiskblob = Get-AzStorageBlob -Context $destinationContext -Container $storageContainerName -Blob $emptydiskforfootername
$osdisk = Get-AzStorageBlob -Context $destinationContext -Container $storageContainerName -Blob $destinationVHDFileName

$footer = New-Object -TypeName byte[] -ArgumentList 512
write-output "Get footer of empty disk"

$downloaded = $emptyDiskblob.ICloudBlob.DownloadRangeToByteArray($footer, 0, $emptyDiskblob.Length - 512, 512)

$osDisk.ICloudBlob.Resize($emptyDiskblob.Length)
$footerStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList (,$footer)
write-output "Write footer of empty disk to OSDisk"
$osDisk.ICloudBlob.WritePages($footerStream, $emptyDiskblob.Length - 512)

Write-Output -InputObject "Removing empty disk blobs"
$emptyDiskblob | Remove-AzStorageBlob -Force


#Provide the name of the Managed Disk
$NewDiskName = "$DiskName" + "-new"

#Create the new disk with the same SKU as the current one
$accountType = $Disk.Sku.Name

# Get the new disk URI
$vhdUri = $osdisk.ICloudBlob.Uri.AbsoluteUri

# Specify the disk options
$diskConfig = New-AzDiskConfig -AccountType $accountType -Location $VM.location -DiskSizeGB $DiskSizeGB -SourceUri $vhdUri -CreateOption Import -StorageAccountId $StorageAccount.Id -HyperVGeneration $HyperVGen

#Create Managed disk
$NewManagedDisk = New-AzDisk -DiskName $NewDiskName -Disk $diskConfig -ResourceGroupName $resourceGroupName

$VM | Stop-AzVM -Force

# Set the VM configuration to point to the new disk
Set-AzVMOSDisk -VM $VM -ManagedDiskId $NewManagedDisk.Id -Name $NewManagedDisk.Name

# Update the VM with the new OS disk
Update-AzVM -ResourceGroupName $resourceGroupName -VM $VM

$VM | Start-AzVM

start-sleep 180
# Please check the VM is running before proceeding with the below tidy-up steps

# Delete old Managed Disk
Remove-AzDisk -ResourceGroupName $resourceGroupName -DiskName $DiskName -Force;

# Delete old blob storage
$osdisk | Remove-AzStorageBlob -Force

# Delete temp storage account
$StorageAccount | Remove-AzStorageAccount -Force

Das obere Script kann in das PowerShell-ISE kopiert, die entsprechenden Werte angepasst und dann ausgeführt werden. Für die Ausführung des Scripts werden spezielle PS-Module benötigt. Die Installation dieser Module mit Ja bestätigen.

Darauf achten, dass die Anmeldung bei Azure mit dem korrekten Konto erfolgt!

Je nach Umfang kann es eine Weile dauern, bis das Script fertig ist. Im besten Fall wurde es ohne Fehler durchlaufen.

Hinweis: Bei Fehlern empfiehlt es sich das Script Schritt-für-Schritt auszuführen.

Den Erfolg können wir über das Azure Portal verifizieren.

Im letzten Schritt wieder auf die VM aufschalten und in die Datenträgerverwaltung wechseln.

Dort finden wir u.u. einen kleinen Block mit nicht zugewiesenem Speicherplatz.

Dieser kann durch einen Rechtsklick auf die Windows-Partition mit Volumen erweitern dieser Partition hinzugefügt werden.

Nachdem der Assistent durchlaufen wurde, lässt ich auch in Windows verifizieren, dass aus einer 128GB Festplatte eine 32GB Festplatte gemacht wurde.

Als Basis der Microsoft Abrechnung wird nun eine 32 GB Festplatte zugrunde gelegt.