Hoe u een raketbooster voor PowerCLI-scripts bouwt 

Vroeg of laat komt elke VMware-systeembeheerder routinetaken automatiseren. Het begint allemaal met de opdrachtregel en dan komt PowerShell of VMware PowerCLI.

Stel dat u PowerShell iets verder onder de knie heeft dan het starten van ISE en het gebruiken van standaard-cmdlets van modules die werken vanwege “een soort magie”. Wanneer je de honderden virtuele machines gaat tellen, zul je merken dat scripts die op kleine schaal helpen, op grote schaal merkbaar langzamer werken. 

In deze situatie zullen 2 hulpmiddelen helpen:

  • PowerShell-runspaces – een aanpak waarmee u de uitvoering van processen in afzonderlijke threads kunt parallelliseren; 
  • Bekijk bekijken – een basis PowerCLI-functie, een analoog van Get-WMIObject in Windows. Deze cmdlet haalt geen objecten op die entiteiten begeleiden, maar ontvangt informatie in de vorm van een eenvoudig object met eenvoudige gegevenstypen. In veel gevallen komt het er sneller uit.

Vervolgens zal ik kort over elke tool praten en voorbeelden van gebruik laten zien. Laten we specifieke scripts analyseren en kijken wanneer het ene beter werkt dan het andere. Gaan!

Hoe u een raketbooster voor PowerCLI-scripts bouwt

Eerste fase: Runspace

Runspace is dus ontworpen voor parallelle verwerking van taken buiten de hoofdmodule. Natuurlijk kunt u een ander proces starten dat wat geheugen, processor, enz. Opslokt. Als uw script binnen een paar minuten wordt uitgevoerd en een gigabyte aan geheugen in beslag neemt, heeft u hoogstwaarschijnlijk geen Runspace nodig. Maar voor scripts voor tienduizenden objecten is het wel nodig.

Je kunt hier beginnen met leren: 
Begingebruik van PowerShell Runspaces: deel 1

Wat levert het gebruik van Runspace op:

  • snelheid door de lijst met uitgevoerde opdrachten te beperken,
  • parallelle uitvoering van taken,
  • veiligheid.

Hier is een voorbeeld van internet waarbij Runspace helpt:

“Opslagconflicten zijn een van de statistieken die moeilijk te volgen zijn in vSphere. Binnen vCenter kun je niet zomaar gaan kijken welke VM meer opslagbronnen verbruikt. Gelukkig kun je deze gegevens binnen enkele minuten verzamelen dankzij PowerShell.
Ik zal een script delen waarmee VMware-systeembeheerders snel door vCenter kunnen zoeken en een lijst met VM's kunnen ontvangen met gegevens over hun gemiddelde verbruik.  
Het script maakt gebruik van PowerShell-runspaces zodat elke ESXi-host verbruiksinformatie van zijn eigen VM's in een afzonderlijke Runspace kan verzamelen en onmiddellijk de voltooiing kan rapporteren. Hierdoor kan PowerShell taken onmiddellijk sluiten, in plaats van door hosts heen te lopen en te wachten tot iedereen zijn verzoek heeft voltooid.”

Bron: I/O van virtuele machines weergeven op een ESXi-dashboard

In het onderstaande geval is Runspace niet langer nuttig:

“Ik probeer een script te schrijven dat veel gegevens van een VM verzamelt en indien nodig nieuwe gegevens schrijft. Het probleem is dat er behoorlijk veel VM’s zijn en dat er 5 tot 8 seconden aan één machine worden besteed.” 

Bron: Multithreading PowerCLI met RunspacePool

Hier heb je Get-View nodig, laten we verder gaan. 

Tweede fase: Get-View

Om te begrijpen waarom Get-View nuttig is, is het de moeite waard om te onthouden hoe cmdlets in het algemeen werken. 

Er zijn cmdlets nodig om gemakkelijk informatie te verkrijgen zonder dat u API-referentieboeken hoeft te bestuderen en het volgende wiel opnieuw hoeft uit te vinden. Wat vroeger honderd of twee regels code kostte, kunt u met PowerShell met één commando doen. Wij betalen voor dit gemak met snelheid. Er zit geen magie in de cmdlets zelf: hetzelfde script, maar op een lager niveau, geschreven door de bekwame handen van een meester uit het zonnige India.

Laten we nu ter vergelijking met Get-View de cmdlet Get-VM nemen: deze heeft toegang tot de virtuele machine en retourneert een samengesteld object, dat wil zeggen dat er andere gerelateerde objecten aan worden gekoppeld: VMHost, Datastore, enz.  

Get-View voegt in plaats daarvan niets onnodigs toe aan het geretourneerde object. Bovendien stelt het ons in staat om strikt te specificeren welke informatie we nodig hebben, wat het uitvoerobject eenvoudiger zal maken. In Windows Server in het algemeen en in Hyper-V in het bijzonder is de Get-WMIObject-cmdlet een directe analoog - het idee is precies hetzelfde.

Get-View is lastig voor routinematige bewerkingen op puntobjecten. Maar als het om duizenden en tienduizenden objecten gaat, heeft het geen prijs.

U kunt meer lezen op de VMware-blog: Inleiding tot Get-View

Nu zal ik je alles laten zien aan de hand van een echte case. 

Een script schrijven om een ​​VM te ontladen

Op een dag vroeg mijn collega mij om zijn script te optimaliseren. De taak is een veel voorkomende routine: zoek alle VM's met een dubbele cloud.uuid-parameter (ja, dit is mogelijk bij het klonen van een VM in vCloud Director). 

De voor de hand liggende oplossing die in je opkomt is:

  1. Krijg een lijst met alle VM's.
  2. Parseer de lijst op de een of andere manier.

De originele versie was dit eenvoudige script:

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 is uiterst eenvoudig en duidelijk. Het kan in een paar minuten met een koffiepauze worden geschreven. Filter erop schroeven en klaar.

Maar laten we de tijd meten:

Hoe u een raketbooster voor PowerCLI-scripts bouwt

Hoe u een raketbooster voor PowerCLI-scripts bouwt

2 minuten 47 seconden bij het verwerken van bijna 10 VM's. Een bonus is de afwezigheid van filters en de noodzaak om de resultaten handmatig te sorteren. Het is duidelijk dat het script optimalisatie vereist.

Runspaces zijn de eerste die u te hulp schieten wanneer u tegelijkertijd hoststatistieken van vCenter moet verkrijgen of tienduizenden objecten moet verwerken. Laten we eens kijken wat deze aanpak brengt.

Schakel de eerste snelheid in: PowerShell Runspaces

Het eerste dat in je opkomt bij dit script is om de lus niet opeenvolgend, maar in parallelle threads uit te voeren, alle gegevens in één object te verzamelen en deze te filteren. 

Maar er is een probleem: PowerCLI staat ons niet toe om veel onafhankelijke sessies naar vCenter te openen en zal een grappige fout genereren:

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.

Om dit op te lossen, moet u eerst sessie-informatie in de stream doorgeven. Laten we niet vergeten dat PowerShell werkt met objecten die als parameter aan een functie of aan een ScriptBlock kunnen worden doorgegeven. Laten we de sessie doorgeven in de vorm van een dergelijk object, waarbij we $global:DefaultVIServers (Connect-VIServer met de sleutel -NotDefault) omzeilen:

$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 $_
           }
       }
   }

Laten we nu multithreading implementeren via Runspace Pools.  

Het algoritme is als volgt:

  1. We krijgen een lijst met alle VM's.
  2. In parallelle streams krijgen we cloud.uuid.
  3. We verzamelen gegevens uit stromen in één object.
  4. We filteren het object door het te groeperen op de waarde van het CloudUUID-veld: degenen waarbij het aantal unieke waarden groter is dan 1 zijn de VM’s die we zoeken.

Als resultaat krijgen we het script:


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
}

Het mooie van dit script is dat het in andere vergelijkbare gevallen kan worden gebruikt door simpelweg het ScriptBlock en de parameters die aan de stream worden doorgegeven te vervangen. Maak er misbruik van!

Wij meten tijd:

Hoe u een raketbooster voor PowerCLI-scripts bouwt

55 seconden. Het is beter, maar het kan nog steeds sneller. 

Laten we naar de tweede snelheid gaan: GetView

Laten we uitzoeken wat er mis is.
Eerst en vooral duurt het lang voordat de Get-VM-cmdlet wordt uitgevoerd.
Ten tweede duurt het nog langer voordat de cmdlet Get-AdvancedOptions is voltooid.
Laten we eerst de tweede behandelen. 

Get-AdvancedOptions is handig voor individuele VM-objecten, maar erg onhandig bij het werken met veel objecten. We kunnen dezelfde informatie verkrijgen van het virtuele machine-object zelf (Get-VM). Het zit gewoon goed verborgen in het ExtensionData-object. Gewapend met filtering versnellen we het proces van het verkrijgen van de benodigde gegevens.

Met een lichte beweging van de hand is dit:


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

Wordt dit:


$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}}

De uitvoer is hetzelfde als Get-AdvancedOptions, maar werkt vele malen sneller. 

Nu naar Get-VM. Het is niet snel omdat het complexe objecten behandelt. Er rijst een logische vraag: waarom hebben we in dit geval extra informatie en een monsterlijk PSObject nodig, terwijl we alleen de naam van de VM, de staat ervan en de waarde van een lastig attribuut nodig hebben?  

Bovendien is het obstakel in de vorm van Get-AdvancedOptions uit het script verwijderd. Het gebruik van Runspace Pools lijkt nu overdreven, omdat het niet langer nodig is om een ​​langzame taak over squat-threads te parallelliseren bij het overdragen van een sessie. De tool is goed, maar niet voor dit geval. 

Laten we eens kijken naar de uitvoer van ExtensionData: het is niets meer dan een Get-View-object. 

Laten we een beroep doen op de eeuwenoude techniek van de PowerShell-meesters: één regel met behulp van filters, sorteren en groeperen. Alle voorgaande horror is elegant in één regel samengevat en in één sessie uitgevoerd:


$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

Wij meten tijd:

Hoe u een raketbooster voor PowerCLI-scripts bouwt

9 seconden voor bijna 10 objecten met filtering op de gewenste voorwaarde. Geweldig!

In plaats Output

Een acceptabel resultaat hangt rechtstreeks af van de keuze van het hulpmiddel. Het is vaak moeilijk om met zekerheid te zeggen wat er precies moet worden gekozen om dit te bereiken. Elk van de genoemde methoden voor het versnellen van scripts is goed binnen de grenzen van de toepasbaarheid ervan. Ik hoop dat dit artikel je zal helpen bij de moeilijke taak om de basisprincipes van procesautomatisering en -optimalisatie in je infrastructuur te begrijpen.

PS: De auteur bedankt alle leden van de gemeenschap voor hun hulp en steun bij het voorbereiden van het artikel. Zelfs degenen met pootjes. En zelfs degenen die geen benen hebben, zoals een boa constrictor.

Bron: www.habr.com

Voeg een reactie