So erstellen Sie einen Raketenbooster für PowerCLI-Skripte 

Früher oder später kommt jeder VMware-Systemadministrator dazu, Routineaufgaben zu automatisieren. Alles beginnt mit der Befehlszeile, dann kommt PowerShell oder VMware PowerCLI.

Nehmen wir an, Sie beherrschen PowerShell etwas weiter als das Starten von ISE und die Verwendung von Standard-Cmdlets von Modulen, die aufgrund „irgendeiner Art von Magie“ funktionieren. Wenn Sie anfangen, Hunderte virtuelle Maschinen zu zählen, werden Sie feststellen, dass Skripte, die im kleinen Maßstab helfen, im großen Maßstab merklich langsamer laufen. 

In dieser Situation helfen 2 Tools:

  • PowerShell-Runspaces – ein Ansatz, der es Ihnen ermöglicht, die Ausführung von Prozessen in separaten Threads zu parallelisieren; 
  • Get-View – eine grundlegende PowerCLI-Funktion, ein Analogon von Get-WMIObject in Windows. Dieses Cmdlet ruft keine Objekte ab, die Entitäten begleiten, sondern empfängt Informationen in Form eines einfachen Objekts mit einfachen Datentypen. In vielen Fällen kommt es schneller heraus.

Als nächstes werde ich kurz auf jedes Tool eingehen und Anwendungsbeispiele zeigen. Lassen Sie uns bestimmte Skripte analysieren und sehen, wann eines besser funktioniert als das andere. Gehen!

So erstellen Sie einen Raketenbooster für PowerCLI-Skripte

Erste Stufe: Runspace

Runspace ist also für die parallele Bearbeitung von Aufgaben außerhalb des Hauptmoduls konzipiert. Natürlich können Sie einen anderen Prozess starten, der etwas Speicher, Prozessor usw. verbraucht. Wenn Ihr Skript in ein paar Minuten ausgeführt wird und ein Gigabyte Speicher verbraucht, benötigen Sie Runspace höchstwahrscheinlich nicht. Aber für Skripte für Zehntausende von Objekten wird es benötigt.

Hier können Sie mit dem Lernen beginnen: 
Beginn der Verwendung von PowerShell Runspaces: Teil 1

Was bringt die Verwendung von Runspace:

  • Geschwindigkeit durch Begrenzung der Liste der ausgeführten Befehle,
  • parallele Ausführung von Aufgaben,
  • Sicherheit.

Hier ist ein Beispiel aus dem Internet, wo Runspace hilft:

„Speicherkonflikte sind eine der am schwierigsten zu verfolgenden Kennzahlen in vSphere. Innerhalb von vCenter können Sie nicht einfach nachsehen, welche VM mehr Speicherressourcen verbraucht. Glücklicherweise können Sie diese Daten dank PowerShell in wenigen Minuten erfassen.
Ich werde ein Skript teilen, das es VMware-Systemadministratoren ermöglicht, vCenter schnell zu durchsuchen und eine Liste der VMs mit Daten zu ihrem durchschnittlichen Verbrauch zu erhalten.  
Das Skript verwendet PowerShell-Runspaces, um es jedem ESXi-Host zu ermöglichen, Verbrauchsinformationen von seinen eigenen VMs in einem separaten Runspace zu sammeln und den Abschluss sofort zu melden. Dies ermöglicht es PowerShell, Jobs sofort zu schließen, anstatt durch Hosts zu iterieren und darauf zu warten, dass jeder einzelne seine Anfrage abschließt.“

Source: So zeigen Sie die E/A einer virtuellen Maschine auf einem ESXi-Dashboard an

Im folgenden Fall ist Runspace nicht mehr nützlich:

„Ich versuche, ein Skript zu schreiben, das viele Daten von einer VM sammelt und bei Bedarf neue Daten schreibt. Das Problem ist, dass es ziemlich viele VMs gibt und 5-8 Sekunden auf einer Maschine verbracht werden.“ 

Source: Multithreading PowerCLI mit RunspacePool

Hier benötigen Sie Get-View, machen wir weiter. 

Zweite Stufe: Get-View

Um zu verstehen, warum Get-View nützlich ist, lohnt es sich, sich daran zu erinnern, wie Cmdlets im Allgemeinen funktionieren. 

Cmdlets werden benötigt, um Informationen bequem abzurufen, ohne API-Nachschlagewerke studieren und das nächste Rad neu erfinden zu müssen. Was früher hundert oder zwei Codezeilen erforderte, können Sie mit PowerShell mit einem Befehl erledigen. Diesen Komfort bezahlen wir mit Schnelligkeit. In den Cmdlets selbst steckt keine Magie: das gleiche Skript, aber auf einer niedrigeren Ebene, geschrieben von den geschickten Händen eines Meisters aus dem sonnigen Indien.

Zum Vergleich mit Get-View nehmen wir nun das Cmdlet Get-VM: Es greift auf die virtuelle Maschine zu und gibt ein zusammengesetztes Objekt zurück, das heißt, es hängt andere verwandte Objekte daran an: VMHost, Datastore usw.  

Get-View fügt stattdessen dem zurückgegebenen Objekt nichts Unnötiges hinzu. Darüber hinaus können wir genau festlegen, welche Informationen wir benötigen, was die Ausgabe des Objekts vereinfacht. In Windows Server im Allgemeinen und in Hyper-V im Besonderen ist das Cmdlet Get-WMIObject ein direktes Analogon – die Idee ist genau die gleiche.

Get-View ist für Routineoperationen an Punktobjekten unpraktisch. Aber wenn es um Tausende und Abertausende von Objekten geht, hat es keinen Preis.

Weitere Informationen finden Sie im VMware-Blog: Einführung in Get-View

Jetzt zeige ich Ihnen alles anhand eines echten Falles. 

Schreiben eines Skripts zum Entladen einer VM

Eines Tages bat mich mein Kollege, sein Skript zu optimieren. Die Aufgabe ist eine gängige Routine: alle VMs mit einem doppelten cloud.uuid-Parameter finden (ja, dies ist beim Klonen einer VM in vCloud Director möglich). 

Die offensichtliche Lösung, die mir in den Sinn kommt, ist:

  1. Rufen Sie eine Liste aller VMs ab.
  2. Analysieren Sie die Liste irgendwie.

Die Originalversion war dieses einfache Skript:

function Get-CloudUUID1 {
   # Получаем список всех ВМ
   $vms = Get-VM
   $report = @()

   # Обрабатываем каждый объект, получая из него только 2 свойства: Имя ВМ и Cloud UUID.
   # Заносим данные в новый PS-объект с полями VM и UUID
   foreach ($vm in $vms)
   {
       $table = "" | select VM,UUID

       $table.VM = $vm.name
       $table.UUID = ($vm | Get-AdvancedSetting -Name cloud.uuid).Value
          
       $report += $table
   }
# Возвращаем все объекты
   $report
}
# Далее РУКАМИ парсим полученный результат

Alles ist äußerst einfach und klar. Es kann in ein paar Minuten mit einer Kaffeepause geschrieben werden. Filter anschrauben und fertig.

Aber messen wir die Zeit:

So erstellen Sie einen Raketenbooster für PowerCLI-Skripte

So erstellen Sie einen Raketenbooster für PowerCLI-Skripte

2 Minuten 47 Sekunden bei der Verarbeitung von fast 10 VMs. Ein Bonus ist das Fehlen von Filtern und die Notwendigkeit, die Ergebnisse manuell zu sortieren. Offensichtlich muss das Skript optimiert werden.

Runspaces sind die ersten, die zur Rettung kommen, wenn Sie gleichzeitig Hostmetriken von vCenter abrufen oder Zehntausende von Objekten verarbeiten müssen. Mal sehen, was dieser Ansatz bringt.

Schalten Sie die erste Geschwindigkeit ein: PowerShell Runspaces

Das erste, was mir bei diesem Skript in den Sinn kommt, ist, die Schleife nicht sequentiell, sondern in parallelen Threads auszuführen, alle Daten in einem Objekt zu sammeln und zu filtern. 

Es gibt jedoch ein Problem: PowerCLI erlaubt uns nicht, viele unabhängige Sitzungen mit vCenter zu öffnen und wirft einen lustigen Fehler aus:

You have modified the global:DefaultVIServer and global:DefaultVIServers system variables. This is not allowed. Please reset them to $null and reconnect to the vSphere server.

Um dieses Problem zu lösen, müssen Sie zunächst Sitzungsinformationen innerhalb des Streams übergeben. Denken Sie daran, dass PowerShell mit Objekten funktioniert, die als Parameter entweder an eine Funktion oder an einen ScriptBlock übergeben werden können. Übergeben wir die Sitzung in Form eines solchen Objekts und umgehen dabei $global:DefaultVIServers (Connect-VIServer mit dem Schlüssel -NotDefault):

$ConnectionString = @()
foreach ($vCenter in $vCenters)
   {
       try {
           $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -AllLinked -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
       }
       catch {
           if ($er.Message -like "*not part of a linked mode*")
           {
               try {
                   $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
               }
               catch {
                   throw $_
               }
              
           }
           else {
               throw $_
           }
       }
   }

Lassen Sie uns nun Multithreading über Runspace-Pools implementieren.  

Der Algorithmus ist der folgende:

  1. Wir erhalten eine Liste aller VMs.
  2. In parallelen Streams erhalten wir cloud.uuid.
  3. Wir sammeln Daten aus Streams in einem Objekt.
  4. Wir filtern das Objekt, indem wir es nach dem Wert des CloudUUID-Felds gruppieren: Diejenigen, bei denen die Anzahl der eindeutigen Werte größer als 1 ist, sind die VMs, nach denen wir suchen.

Als Ergebnis erhalten wir das Skript:


function Get-VMCloudUUID {
   param (
       [string[]]
       [ValidateNotNullOrEmpty()]
       $vCenters = @(),
       [int]$MaxThreads,
       [System.Management.Automation.PSCredential]
       [System.Management.Automation.Credential()]
       $Credential
   )

   $ConnectionString = @()

   # Создаем объект с сессионным ключом
   foreach ($vCenter in $vCenters)
   {
       try {
           $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -AllLinked -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
       }
       catch {
           if ($er.Message -like "*not part of a linked mode*")
           {
               try {
                   $ConnectionString += Connect-VIServer -Server $vCenter -Credential $Credential -NotDefault -Force -ErrorAction stop -WarningAction SilentlyContinue -ErrorVariable er
               }
               catch {
                   throw $_
               }
              
           }
           else {
               throw $_
           }
       }
   }

   # Получаем список всех ВМ
   $Global:AllVMs = Get-VM -Server $ConnectionString

   # Поехали!
   $ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
   $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
   $RunspacePool.ApartmentState = "MTA"
   $RunspacePool.Open()
   $Jobs = @()

# ScriptBlock с магией!)))
# Именно он будет выполняться в потоке
   $scriptblock = {
       Param (
       $ConnectionString,
       $VM
       )

       $Data = $VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}

       return $Data
   }
# Генерируем потоки

   foreach($VM in $AllVMs)
   {
       $PowershellThread = [PowerShell]::Create()
# Добавляем скрипт
       $null = $PowershellThread.AddScript($scriptblock)
# И объекты, которые передадим в качестве параметров скрипту
       $null = $PowershellThread.AddArgument($ConnectionString)
       $null = $PowershellThread.AddArgument($VM)
       $PowershellThread.RunspacePool = $RunspacePool
       $Handle = $PowershellThread.BeginInvoke()
       $Job = "" | Select-Object Handle, Thread, object
       $Job.Handle = $Handle
       $Job.Thread = $PowershellThread
       $Job.Object = $VM.ToString()
       $Jobs += $Job
   }

# Ставим градусник, чтобы наглядно отслеживать выполнение заданий
# И здесь же прибиваем отработавшие задания
   While (@($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0)
   {
       $Remaining = "$($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).object)"

       If ($Remaining.Length -gt 60) {
           $Remaining = $Remaining.Substring(0,60) + "..."
       }

       Write-Progress -Activity "Waiting for Jobs - $($MaxThreads - $($RunspacePool.GetAvailableRunspaces())) of $MaxThreads threads running" -PercentComplete (($Jobs.count - $($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).count)) / $Jobs.Count * 100) -Status "$(@($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False})).count) remaining - $remaining"

       ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
           $Job.Thread.EndInvoke($Job.Handle)     
           $Job.Thread.Dispose()
           $Job.Thread = $Null
           $Job.Handle = $Null
       }
   }

   $RunspacePool.Close() | Out-Null
   $RunspacePool.Dispose() | Out-Null
}


function Get-CloudUUID2
{
   [CmdletBinding()]
   param(
   [string[]]
   [ValidateNotNullOrEmpty()]
   $vCenters = @(),
   [int]$MaxThreads = 50,
   [System.Management.Automation.PSCredential]
   [System.Management.Automation.Credential()]
   $Credential)

   if(!$Credential)
   {
       $Credential = Get-Credential -Message "Please enter vCenter credentials."
   }

   # Вызов функции Get-VMCloudUUID, где мы распараллеливаем операцию
   $AllCloudVMs = Get-VMCloudUUID -vCenters $vCenters -MaxThreads $MaxThreads -Credential $Credential
   $Result = $AllCloudVMs | Sort-Object Value | Group-Object -Property CloudUUID | Where-Object -FilterScript {$_.Count -gt 1} | Select-Object -ExpandProperty Group
   $Result
}

Das Schöne an diesem Skript ist, dass es in anderen ähnlichen Fällen verwendet werden kann, indem einfach der ScriptBlock und die Parameter ersetzt werden, die an den Stream übergeben werden. Nutze es aus!

Wir messen die Zeit:

So erstellen Sie einen Raketenbooster für PowerCLI-Skripte

55 zweite. Es ist besser, aber es kann immer noch schneller sein. 

Kommen wir zur zweiten Geschwindigkeit: GetView

Finden wir heraus, was los ist.
Erstens dauert die Ausführung des Get-VM-Cmdlets sehr lange.
Zweitens dauert die Ausführung des Cmdlets „Get-AdvancedOptions“ sogar noch länger.
Befassen wir uns zunächst mit dem zweiten. 

Get-AdvancedOptions ist für einzelne VM-Objekte praktisch, bei der Arbeit mit vielen Objekten jedoch sehr umständlich. Wir können die gleichen Informationen vom Objekt der virtuellen Maschine selbst erhalten (Get-VM). Es ist einfach gut im ExtensionData-Objekt vergraben. Mit der Filterung können wir den Prozess der Beschaffung der erforderlichen Daten beschleunigen.

Mit einer leichten Handbewegung ist das:


VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}

Daraus ergibt sich:


$VM | Where-Object {($_.ExtensionData.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value -ne $null} | Select-Object @{N="VMName";E={$_.Name}},@{N="CloudUUID";E={($_.ExtensionData.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value}},@{N="PowerState";E={$_.summary.runtime.powerstate}}

Die Ausgabe ist die gleiche wie bei Get-AdvancedOptions, funktioniert aber um ein Vielfaches schneller. 

Nun zu Get-VM. Es ist nicht schnell, da es sich um komplexe Objekte handelt. Es stellt sich eine logische Frage: Warum brauchen wir in diesem Fall zusätzliche Informationen und ein monströses PSObject, wenn wir nur den Namen der VM, ihren Status und den Wert eines kniffligen Attributs benötigen?  

Darüber hinaus wurde das Hindernis in Form von Get-AdvancedOptions aus dem Skript entfernt. Die Verwendung von Runspace-Pools scheint jetzt übertrieben zu sein, da bei der Übergabe einer Sitzung keine langsame Aufgabe mehr über Squat-Threads hinweg parallelisiert werden muss. Das Tool ist gut, aber nicht für diesen Fall. 

Schauen wir uns die Ausgabe von ExtensionData an: Es ist nichts weiter als ein Get-View-Objekt. 

Nutzen wir die alte Technik der PowerShell-Meister: eine Zeile mit Filtern, Sortieren und Gruppieren. Der gesamte bisherige Horror wird elegant in einer Zeile zusammengefasst und in einer Sitzung ausgeführt:


$AllVMs = Get-View -viewtype VirtualMachine -Property Name,Config.ExtraConfig,summary.runtime.powerstate | Where-Object {($_.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value -ne $null} | Select-Object @{N="VMName";E={$_.Name}},@{N="CloudUUID";E={($_.Config.ExtraConfig | Where-Object {$_.key -eq "cloud.uuid"}).Value}},@{N="PowerState";E={$_.summary.runtime.powerstate}} | Sort-Object CloudUUID | Group-Object -Property CloudUUID | Where-Object -FilterScript {$_.Count -gt 1} | Select-Object -ExpandProperty Group

Wir messen die Zeit:

So erstellen Sie einen Raketenbooster für PowerCLI-Skripte

9 Sekunden für fast 10 Objekte mit Filterung nach der gewünschten Bedingung. Großartig!

Statt einer Schlussfolgerung

Ein akzeptables Ergebnis hängt direkt von der Wahl des Werkzeugs ab. Es ist oft schwierig, mit Sicherheit zu sagen, was genau gewählt werden sollte, um dieses Ziel zu erreichen. Jede der aufgeführten Methoden zur Beschleunigung von Skripten ist im Rahmen ihrer Anwendbarkeit gut. Ich hoffe, dieser Artikel wird Ihnen bei der schwierigen Aufgabe helfen, die Grundlagen der Prozessautomatisierung und -optimierung in Ihrer Infrastruktur zu verstehen.

PS: Der Autor dankt allen Community-Mitgliedern für ihre Hilfe und Unterstützung bei der Erstellung des Artikels. Sogar diejenigen mit Pfoten. Und sogar diejenigen, die keine Beine haben, wie eine Boa constrictor.

Source: habr.com

Kommentar hinzufügen