Hur man bygger en raketbooster för PowerCLI-skript 

Förr eller senare kommer alla VMware-systemadministratörer att automatisera rutinuppgifter. Allt börjar med kommandoraden, sedan kommer PowerShell eller VMware PowerCLI.

Låt oss säga att du har bemästrat PowerShell lite längre än att lansera ISE och använda vanliga cmdlets från moduler som fungerar på grund av "någon sorts magi". När du börjar räkna virtuella maskiner i hundratals kommer du att upptäcka att skript som hjälper till i liten skala körs märkbart långsammare i stor skala. 

I den här situationen kommer två verktyg att hjälpa till:

  • PowerShell Runspaces – ett tillvägagångssätt som låter dig parallellisera exekveringen av processer i separata trådar; 
  • Get-View – en grundläggande PowerCLI-funktion, en analog till Get-WMIObject i Windows. Denna cmdlet drar inte objekt som åtföljer entiteter, utan tar emot information i form av ett enkelt objekt med enkla datatyper. I många fall kommer det ut snabbare.

Därefter ska jag kort prata om varje verktyg och visa exempel på användning. Låt oss analysera specifika skript och se när det ena fungerar bättre än det andra. Gå!

Hur man bygger en raketbooster för PowerCLI-skript

Första etappen: Runspace

Så, Runspace är designat för parallell bearbetning av uppgifter utanför huvudmodulen. Naturligtvis kan du starta en annan process som kommer att äta upp lite minne, processor etc. Om ditt skript körs på ett par minuter och förbrukar en gigabyte minne behöver du troligen inte Runspace. Men för skript för tiotusentals objekt behövs det.

Du kan börja lära dig här: 
Att börja använda PowerShell Runspaces: Del 1

Vad ger användning av Runspace:

  • hastighet genom att begränsa listan över utförda kommandon,
  • parallellt utförande av uppgifter,
  • säkerhet.

Här är ett exempel från Internet när Runspace hjälper:

"Lagringsstrid är en av de svåraste mätvärdena att spåra i vSphere. Inuti vCenter kan du inte bara gå och se vilken virtuell dator som förbrukar mer lagringsresurser. Lyckligtvis kan du samla in denna data på några minuter tack vare PowerShell.
Jag kommer att dela ett skript som gör det möjligt för VMware-systemadministratörer att snabbt söka i vCenter och få en lista över virtuella datorer med data om deras genomsnittliga förbrukning.  
Skriptet använder PowerShell runspaces för att tillåta varje ESXi-värd att samla in förbrukningsinformation från sina egna virtuella datorer i ett separat Runspace och omedelbart rapportera slutförande. Detta gör att PowerShell kan stänga jobb omedelbart, snarare än att iterera genom värdar och vänta på att var och en ska slutföra sin begäran."

Källa: Hur man visar virtuell maskin I/O på en ESXi Dashboard

I fallet nedan är Runspace inte längre användbart:

”Jag försöker skriva ett skript som samlar in mycket data från en virtuell dator och skriver ny data när det behövs. Problemet är att det finns ganska många virtuella datorer, och 5-8 sekunder spenderas på en maskin." 

Källa: Multithreading PowerCLI med RunspacePool

Här behöver du Get-View, låt oss gå vidare till det. 

Andra etappen: Get-View

För att förstå varför Get-View är användbart är det värt att komma ihåg hur cmdlets fungerar i allmänhet. 

Cmdlets behövs för att enkelt skaffa information utan att behöva studera API-referensböcker och återuppfinna nästa hjul. Det som i gamla dagar tog hundra eller två rader kod låter PowerShell dig göra med ett kommando. Vi betalar för denna bekvämlighet med snabbhet. Det finns ingen magi inuti själva cmdletarna: samma manus, men på en lägre nivå, skrivet av de skickliga händerna på en mästare från soliga Indien.

Nu, för jämförelse med Get-View, låt oss ta Get-VM cmdlet: den kommer åt den virtuella maskinen och returnerar ett sammansatt objekt, det vill säga den bifogar andra relaterade objekt till den: VMHost, Datastore, etc.  

Get-View i dess ställe lägger inte till något onödigt till det returnerade objektet. Dessutom tillåter det oss att strikt specificera vilken information vi behöver, vilket kommer att göra utdataobjektet enklare. I Windows Server i allmänhet och i Hyper-V i synnerhet är Get-WMIObject cmdlet en direkt analog - idén är exakt densamma.

Get-View är obekvämt för rutinoperationer på punktobjekt. Men när det handlar om tusentals och tiotusentals föremål har det inget pris.

Du kan läsa mer på VMware-bloggen: Introduktion till Get-View

Nu ska jag visa dig allt med ett riktigt fodral. 

Att skriva ett skript för att ladda ner en virtuell dator

En dag bad min kollega mig att optimera hans manus. Uppgiften är en vanlig rutin: hitta alla virtuella datorer med en dubblett cloud.uuid-parameter (ja, detta är möjligt när du klona en virtuell dator i vCloud Director). 

Den uppenbara lösningen som kommer att tänka på är:

  1. Få en lista över alla virtuella datorer.
  2. Analysera listan på något sätt.

Den ursprungliga versionen var detta enkla 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
}
# Далее РУКАМИ парсим полученный результат

Allt är extremt enkelt och tydligt. Den kan skrivas på ett par minuter med en fika. Skruva på filtreringen och det är klart.

Men låt oss mäta tiden:

Hur man bygger en raketbooster för PowerCLI-skript

Hur man bygger en raketbooster för PowerCLI-skript

2 minuter 47 sekunder vid bearbetning av nästan 10 XNUMX virtuella datorer. En bonus är frånvaron av filter och behovet av att manuellt sortera resultaten. Uppenbarligen kräver skriptet optimering.

Runspaces är de första som kommer till undsättning när du samtidigt behöver hämta värddata från vCenter eller behöver bearbeta tiotusentals objekt. Låt oss se vad detta tillvägagångssätt ger.

Slå på den första hastigheten: PowerShell Runspaces

Det första som kommer att tänka på för det här skriptet är att köra slingan inte sekventiellt, utan i parallella trådar, samla all data till ett objekt och filtrera den. 

Men det finns ett problem: PowerCLI kommer inte att tillåta oss att öppna många oberoende sessioner till vCenter och kommer att ge ett roligt fel:

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.

För att lösa detta måste du först skicka sessionsinformation inuti strömmen. Låt oss komma ihåg att PowerShell fungerar med objekt som kan skickas som en parameter antingen till en funktion eller till ett ScriptBlock. Låt oss skicka sessionen i form av ett sådant objekt, förbi $global:DefaultVIServers (Anslut-VIServer med -NotDefault-nyckeln):

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

Låt oss nu implementera multithreading genom Runspace Pools.  

Algoritmen är följande:

  1. Vi får en lista över alla virtuella datorer.
  2. I parallella strömmar får vi cloud.uuid.
  3. Vi samlar in data från strömmar till ett objekt.
  4. Vi filtrerar objektet genom att gruppera det efter värdet i CloudUUID-fältet: de där antalet unika värden är större än 1 är de virtuella datorerna vi letar efter.

Som ett resultat får vi skriptet:


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
}

Det fina med detta skript är att det kan användas i andra liknande fall genom att helt enkelt ersätta ScriptBlock och parametrarna som kommer att skickas till strömmen. Utnyttja det!

Vi mäter tid:

Hur man bygger en raketbooster för PowerCLI-skript

55 sekunder. Det är bättre, men det kan fortfarande gå snabbare. 

Låt oss gå till den andra hastigheten: GetView

Låt oss ta reda på vad som är fel.
Först och främst tar Get-VM cmdlet lång tid att köra.
För det andra tar cmdleten Get-AdvancedOptions ännu längre tid att slutföra.
Låt oss ta itu med den andra först. 

Get-AdvancedOptions är bekvämt för enskilda VM-objekt, men väldigt klumpigt när man arbetar med många objekt. Vi kan få samma information från själva virtuella maskinobjektet (Get-VM). Det är bara begravt i ExtensionData-objektet. Beväpnade med filtrering påskyndar vi processen för att erhålla nödvändig data.

Med en lätt rörelse av handen är detta:


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

Blir till detta:


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

Utdata är samma som Get-AdvancedOptions, men det fungerar många gånger snabbare. 

Nu till Get-VM. Den är inte snabb eftersom den hanterar komplexa föremål. En logisk fråga uppstår: varför behöver vi extra information och ett monstruöst PSO-objekt i det här fallet, när vi bara behöver namnet på den virtuella datorn, dess tillstånd och värdet av ett knepigt attribut?  

Dessutom har hindret i form av Get-AdvancedOptions tagits bort från skriptet. Att använda Runspace Pools verkar nu vara överdrivet eftersom det inte längre finns ett behov av att parallellisera en långsam uppgift över squat-trådar när man överlämnar en session. Verktyget är bra, men inte för det här fallet. 

Låt oss titta på utdata från ExtensionData: det är inget annat än ett Get-View-objekt. 

Låt oss använda PowerShell-mästarnas uråldriga teknik: en rad med filter, sortering och gruppering. All den tidigare skräcken är elegant kollapsad till en rad och exekveras i en session:


$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

Vi mäter tid:

Hur man bygger en raketbooster för PowerCLI-skript

9 sekunder för nästan 10 XNUMX objekt med filtrering efter önskat tillstånd. Bra!

I stället för en slutsats

Ett acceptabelt resultat beror direkt på valet av verktyg. Det är ofta svårt att säga säkert vad som ska väljas för att uppnå det. Var och en av de listade metoderna för att påskynda skript är bra inom gränserna för dess tillämplighet. Jag hoppas att den här artikeln kommer att hjälpa dig i den svåra uppgiften att förstå grunderna för processautomation och optimering i din infrastruktur.

PS: Författaren tackar alla gemenskapsmedlemmar för deras hjälp och stöd vid utarbetandet av artikeln. Även de med tassar. Och även de som inte har ben, som en boa constrictor.

Källa: will.com

Lägg en kommentar