Aus der Praxis unserer IT Camps: Virtuelle Maschinen per Azure Automation

Das Problem

In unseren IT Camps zum Thema Hybrid IT können unsere Teilnehmer immer aktiv mitarbeiten. Dazu geben wir ihnen Azure Passes jeweils mit einem Guthaben von €75 zur freien Verwendung. Voraussetzung ist allerdings: man braucht ein neues* Microsoft-Konto (ehemals Live ID).  Dabei gibt es allerdings einen kleinen Haken: von einer IP-Adresse kann man aus Sicherheitsgründen nur 5 Microsoft-Konten pro Tag anlegen. Bei meist mehr als 20 Teilnehmern pro Workshop ist dies bei weitem nicht ausreichend, wenn alle Teilnehmer über den gleichen ISP rausgehen.

(* Wir empfehlen immer ein neues Microsoft-Konto zu verwenden, weil nur ein Testkonto pro Microsoft-Konto verwendet werden kann. So haben die Teilnehmer später die Möglichkeit selbst noch einmal ein Test-Konto auf ihr eigenes Microsoft-Konto anzulegen.)

Die Lösung

Die Lösung besteht darin, dass wir jedem Teilnehmer für die Registrierung eines Microsoft-Kontos eine eigene VM geben, zu dieser sich die Teilnehmer per RDP verbinden. Auf diese Weise hat jeder Teilnehmer bei der Registrierung eine eigene IP-Adresse und kein Sicherheitsmechanismus schlägt zu.

Daher lasse ich morgens vor dem IT Camp immer ca. 30 VMs hochfahren. Anfangs habe ich das noch mit einem PowerShell-Skript gemacht, aber dann musste ich morgens daran auch denken, das zu erledigen und der Client, auf dem das PowerShell-Skript läuft, muss auch aktiv bleiben. Aus dem Grund habe ich das PowerShell-Skript in ein PowerShell Workflow-Skript überführt und als Runbook in Azure Automation erstellt.

Noch einmal anders formuliert: PowerShell Workflow-Skripte werden in Azure Automation Runbooks genannt und man kann ein fertiges Workflow-Skript direkt hochladen und importieren.

Das Charmante an Runbooks in Azure Automation ist, dass ich sie per Zeitplan starten lassen kann und dass ich jede Ausführung parametrisieren kann. Im Vergleich zu einem normalen PowerShell-Skript: dort muss ich die Skripte auf meinem Client starten (lassen) und der Client muss während der Ausführung eingeschaltet sein. Außerdem speichert man die Einstellungen (Parameter) für eine Ausführung meist in Variablen im PowerShell-Skript direkt.

Azure Automation

Im aktuellen Verwaltungsportal findet man Azure Automation in der Navigation unter dem Punkt Automatisierung (engl. Automation), der durch ein kleines und ein großes Zahnrad dargestellt wird. Hier habe ich ein Automation-Konto namens “itcamp-stage” angelegt.

SNAGHTML4962a2e

Mit einem Klick auf das Automation-Konto komme ich zum Dashboard, wo ich etliche Informationen zu meinem Automation-Konto sehen kann. Darunter:

  • Eine Zeitskala, wann und wie viele Aufträge abgearbeitet wurden oder evtl. fehlgeschlagen sind.
  • Eine Nutzungsübersicht in Minuten, in denen meine Aufträge ausgeführt wurden. Pro Azure-Abonnement hat man 500 Freiminuten pro Monat. Für die IT Camps, die wir einmal pro Woche durchführen, sind diese Freiminuten für mich ausreichend.
    • Braucht man mehr, muss man über den Punkt Skalierung (engl. Scale) den Kostenplan von “Kostenlos” auf “Basic” umschalten. Wie man auf der Preisübersicht für Azure Automation sehen kann, sind die Preise sehr moderat. Stand 14.04.2015 liegt der Minutenpreis bei €0,0015/Minute. Das bedeutet, das Freikontingent von 500 Minuten hat einen Wahnsinnsgegenwert von €0,75! ;-)
  • Eine Übersicht der abgeschlossenen oder laufenden Aufträge der letzten 30 Tage.

SNAGHTML4982fc2

Im Bereich “Runbooks” werden die PowerShell Workflow-Skripte aufgelistet, die man dem Automation-Konto bereits hinzugefügt hat. Hier sieht man zum Beispiel, dass ich meine beiden Skripte für das IT Camp im Zeitraum von 4 Monaten insgesamt 27 mal ausgeführt habe.

SNAGHTML49a180a

Im folgenden Bild als Beispiel das Runbook Start-ITCampManagementVMs. Da das Runbook bereits veröffentlicht wurde (also nicht mehr im Bearbeitungsmodus ist), wird es grau dargestellt. Mit einem Klick auf “Bearbeiten” kann ich das Skript wieder verändern. Mein aktuell veröffentlichtes Skript bleibt allerdings so lange erhalten, bis ich die Änderungen erneut veröffentliche. Zwischendurch kann man die Veränderungen beliebig oft speichern, ohne dass dies Auswirkungen auf das veröffentlichte Runbook hat. Auf diese Weise kann ich weiterhin regelmäßig ein Runbook (auch zeitgesteuert) ausführen lassen und parallel an einer neuer Version arbeiten.

SNAGHTML49e439b

Runbooks

Schauen wir uns die beiden Runbooks, die ich für die IT Camps nutze mal genauer an. Ich habe zwei Runbooks erstellt: einmal zum Erstellen von VMs und einmal zum Löschen.

Start-ITCampManagementVMs.ps1

Das Runbook Start-ITCampManagementVMs.ps1 zum Erstellen der VMs kann man in fünf Abschnitte unterteilen.

  1. Parameter des Runbooks
  2. Anmeldung an Microsoft Azure
  3. Auswählen einer VM-Vorlage (Image)
  4. Erstellen der Namen für die VMs und Cloud-Dienste
  5. Erstellen der Speicherkonten und VMs
Parameter des Runbooks

Das Runbook hat sechs Parameter.

  1. Angabe des Azure-Abonnements, in dem die VMs erstellt werden sollen.
  2. Benutzername des lokalen Administrators.
  3. Passwort des lokalen Administrators.
  4. Region, in der die VMs erstellt werden sollen.
  5. Anzahl der VMs, die erstellt werden sollen.
  6. Präfix, der allen VM- und Cloud-Namen hinzugefügt wird.
Anmeldung an Microsoft Azure

Ich verwende für die Anmeldung an Azure ein Verwaltungszertifikat. Man kann für die Anmeldung generell auf zwei Methoden zurückgreifen. Einmal über die Verwendung von Verwaltungszertifikaten und einmal über die Anmeldung mittels eines Azure Active Directory-Kontos. Für meinen Zweck erscheint mir die Verwendung eines Verwaltungszertifikats einfacher als die Anmeldung über ein Dienst-Konto, für das ich noch ein Azure AD hätte erstellen müssen. (Sollte ich später einmal ein Azure AD pflegen, dann kann sich das natürlich ändern.)

Hier zwei gute Blog-Artikel, die sowohl die Anmeldung mittels Verwaltungszertifikaten als auch Azure AD erklären.

Verwaltungszertifikate: Managing Azure Services with the Microsoft Azure Automation Preview Service

Dienst-Konto in Azure AD: Azure Automation: Authenticating to Azure using Azure Active Directory

Auswählen einer VM-Vorlage (Image)

Für die VMs verwende ich die neuste Vorlage, die für “Windows Server 2012 R2 Datacenter” vorliegt.

Wie das funktioniert, habe ich schon einmal in diesem Blog beschrieben. Siehe den Artikel “Tipp: VM-Vorlagen in Azure per PowerShell auflisten und filtern”.

Erstellen der Namen für die VMs und Cloud-Dienste

Im nächsten Abschnitt (create account names) sorge ich dafür, dass die erstellen Namen für die Cloud-Dienste und die VMs akzeptiert werden. Bei Cloud-Diensten besteht die Herausforderung darin, Namen zu finden, die global eindeutig sind, da sie Sub Domains für cloudapp.net darstellen. Meine Lösung hier ist trivial, da ich mir eine Pseudo-Zufallszahl erstelle, indem ich einfach den Wert der Millisekunden der aktuellen Uhrzeit verwende. Die Chance ist gering, dass mit dem gewählten Präfix aus den Parametern und dieser vierstellen Zahl die Domäne schon vergeben ist.

Als zweites erstelle ich Hostnamen für die VMs, die maximal 15 Zeichen lang sind. Mir sind die Hintergründe nicht im Detail klar, aber ich vermute, dass dies noch auf die NetBIOS-Zeiten zurückgeht.

Erstellen der Speicherkonten und VMs

Zum Schluss gehe ich in einer Schleife alle Instanznamen durch, die ich eben erstellt habe, und lasse für jede VM eine separates Speicherkonto erstellen. Von der Performance her könnte ich locker bis zu 66 VMs mit einem Speicherkonto bedienen, habe aber darauf verzichtet, alle 66 VMs ein neues Speicherkonto zu erstellen, um das Skript simpel zu halten. Außerdem sind Speicherkonten schnell erstellt und von den Kosten macht es keinen Unterschied, ob ich ein einzelnes oder mehrere Speicherkonto erstelle. (Wer sich fragt, wie man auf die 66 VMs kommt, sollte sich die folgende Seite mit den Limits zu den Azure-Diensten ansehen: Azure Subscription and Service Limits, Quotas, and Constraints.)

Für VHDs, die in Betrieb sind, reichen Speicherkonten aus, die nur eine lokale Redundanz aufweisen. Weitere Informationen zu den Redundanzoptionen sind in meinem Blog-Artikel “Grundlagen: Speicherkonten in Azure per PowerShell erstellen” nachzulesen.

Für die VMs verwende ich jeweils eine Basic A2, da die VMs in den IT Camps als Verwaltungs-VMs verwendet werden sollen. Das heißt, sie sind nur für die interaktive Nutzung gedacht und dabei braucht man keine Load Balancer oder Auto-Skalierung, wie sie die Standard-VMs bieten (im Gegensatz zu Basic-VMs).

Das ganze Runbook sieht wie folgt aus:

Stop-ITCampManagementVMs.ps1

Das Runbook Stop-ITCampManagementVMs.ps1 ist gegenüber dem Start-Skript wesentlich kürzer. Auch hier gibt es anfangs einen Bereich für die Parameter, nur dass wir hier nur den Namen des Abonnements und den Namenspräfix benötigen. Alle Cloud-Dienste und Speicherkonten mit dem angegebenen Präfix werden gelöscht.

Komfortabel ist es hier für uns, dass der PowerShell-Befehl Remove-AzureService einen Parameter DeleteAll aufweist, der alle VMs entfernt, die Registrierungen für die VHDs aufhebt und schließlich auch die VHDs selbst löscht.

Schlussendlich werden auch noch die Speicherkonten entfernt. Hier ist das Runbook allerdings noch etwas verbesserungsfähig, da die Aufhebung der Registrierungen der VHDs meist etwas länger dauert und die Speicherkonten an dieser Stelle noch nicht gelöscht werden können. Hier lasse ich die Speicherkonten später löschen, indem ich das Runbook einfach noch einmal mit denselben Parametern aufrufe. Das hat allerdings meist auch Zeit, da die Speicherkonten selbst nichts kosten und die VHDs durch den Befehl Remove-AzureService schon entfernt wurden.

Weiterführende Informationen

PowerShell: Azure-Automatisierung für Einsteiger (Microsoft Virtual Academy)

Grundlagen: Azure & PowerShell: VMs erstellen (MSDN Blogs)

Tipp: VM-Vorlagen in Azure per PowerShell auflisten und filtern (MSDN Blogs)

Grundlagen: VMs in Azure per PowerShell provisionieren (MSDN Blogs)

Grundlagen: Speicherkonten in Azure per PowerShell erstellen (MSDN Blogs)

Azure Subscription and Service Limits, Quotas, and Constraints

Skripte

Für den Fall, dass die eingebetteten Skripte auf GitHub Gist nicht aufrufbar sind, sind diese hier noch einmal unformatiert aufgeführt. (Es kann durch das Layout der Blog-Webseite sein, dass die Code-Zeilen abgeschnitten sind. Dann bitte einfach im Quelltext der Seite nachschauen.)

Start-ITCampManagementVMs.ps1

 workflow Start-ITCampManagementVMs
{
    Param (
        # Name of the target Azure subscription.
        [Parameter(Mandatory=$True)]
        [String]$SubscriptionName = "MSFT IT Camp Stage",
        [Parameter(Mandatory=$False)]
        [String]$AdminUsername = "adm_camp",
        [Parameter(Mandatory=$False)]
        [String]$AdminPassword = "Azureisttoll!",
        [Parameter(Mandatory=$False)]
        [String]$Location = "West Europe",  # Get-AzureLocation
        [Parameter(Mandatory=$True)]
        [Int]$NumberOfVMs = 30,
        [Parameter(Mandatory=$False)]
        [String]$NamePrefix = "itc0202"
    )

    # Get the Azure connection asset that is stored in the Automation service based on the name that was passed into the runbook 
    $AzureConn = Get-AutomationConnection -Name $SubscriptionName
    # Get the Azure management certificate that is used to connect to this subscription
    $Certificate = Get-AutomationCertificate -Name $AzureConn.AutomationCertificateName
    # Set the Azure subscription configuration
    Set-AzureSubscription -SubscriptionName $SubscriptionName -SubscriptionId $AzureConn.SubscriptionID -Certificate $Certificate
    # Select the Azure subscription.
    Select-AzureSubscription -SubscriptionName $SubscriptionName
    
    # Pre-configured images
    # Get latest image for Windows Server 2012 R2.
    $imageName = Get-AzureVMImage | 
        Where-Object -Property ImageFamily -eq "Windows Server 2012 R2 Datacenter" | 
        Sort-Object -Property PublishedDate -Descending | 
        Select-Object -ExpandProperty ImageName -First 1

    $randomPart = "{0:ffff}" -f $(Get-Date)
        
    # Create account names.
    $instanceNames = InlineScript {       
        $instanceNames = @()    
        $prefix = $Using:NamePrefix
        
        # Create a short prefix, because the host name cannot longer than 15 characters.
        $shortPrefix = $prefix.Substring(0, ([System.Math]::Min($prefix.Length,12)))     
        for ($i = 0; $i -lt $Using:NumberOfVMs; $i++) {            
            $counter = "{0:00}" -f $i
            
            # Create a host name and a DNS safe name for the lcoud service and storage account.
            $instanceNames += @{HostName = "$shortPrefix-$counter"; 
                                DnsSafe = "$prefix$Using:randomPart$counter"}
        }
        $instanceNames
    }
        
    Checkpoint-Workflow

    foreach ($instance in $instanceNames) {
        Write-Warning "Instance.DnsSafe: $($instance.DnsSafe)"
        Write-Warning "Instance.HostName: $($instance.HostName)"

        New-AzureStorageAccount -Location $Location -StorageAccountName $instance.DnsSafe -Type Standard_LRS
        
        Checkpoint-Workflow
        
        Set-AzureSubscription -SubscriptionName $SubscriptionName -CurrentStorageAccountName $instance.DnsSafe
            
        New-AzureQuickVM -ImageName $imageName -ServiceName $instance.DnsSafe -Windows -AdminUsername $AdminUsername -InstanceSize Basic_A2 -Location $Location -Name $instance.HostName -Password $AdminPassword        
    
        Checkpoint-Workflow
    }
}

Stop-ITCampManagementVMs.ps1

 workflow Stop-ITCampManagementVMs
{
    Param (
        # Name of the target Azure subscription.
        [Parameter(Mandatory=$True)]
        [String]$SubscriptionName = "MSFT IT Camp Stage",
        [Parameter(Mandatory=$False)]
        [String]$NamePrefix = "itc0203"
    )

    # Get the Azure connection asset that is stored in the Automation service based on the name that was passed into the runbook 
    $AzureConn = Get-AutomationConnection -Name $SubscriptionName
    # Get the Azure management certificate that is used to connect to this subscription
    $Certificate = Get-AutomationCertificate -Name $AzureConn.AutomationCertificateName
    # Set the Azure subscription configuration
    Set-AzureSubscription -SubscriptionName $SubscriptionName -SubscriptionId $AzureConn.SubscriptionID -Certificate $Certificate
    # Select the Azure subscription.
    Select-AzureSubscription -SubscriptionName $SubscriptionName
   
    $matchingCloudServices = Get-AzureService | Where-Object -Property ServiceName -ilike "$NamePrefix*"
   
    foreach -Parallel ($service in $matchingCloudServices) {
        Write-Warning "Removing $($service.ServiceName)"
        Remove-AzureService -ServiceName $service.ServiceName -DeleteAll -Force
    }
    
    $matchingStorageAccounts = Get-AzureStorageAccount | Where-Object -Property StorageAccountName -ilike "$NamePrefix*"
    
    foreach -Parallel ($storage in $matchingStorageAccounts) {
        Write-Warning "Removing $($storage.StorageAccountName)"
        Remove-AzureStorageAccount -StorageAccountName $storage.StorageAccountName
    }
}