Hvordan bygge en rakettforsterker for PowerCLI-skript 

Før eller siden kommer enhver VMware-systemadministrator for å automatisere rutineoppgaver. Det hele starter med kommandolinjen, deretter kommer PowerShell eller VMware PowerCLI.

La oss si at du har mestret PowerShell litt lenger enn å lansere ISE og bruke standard cmdlets fra moduler som fungerer på grunn av "en slags magi". Når du begynner å telle virtuelle maskiner i hundrevis, vil du oppdage at skript som hjelper i liten skala kjører merkbart langsommere i stor skala. 

I denne situasjonen vil 2 verktøy hjelpe:

  • PowerShell Runspaces – en tilnærming som lar deg parallellisere utførelsen av prosesser i separate tråder; 
  • Get-View – en grunnleggende PowerCLI-funksjon, en analog av Get-WMIObject i Windows. Denne cmdleten trekker ikke objekter som følger med enheter, men mottar informasjon i form av et enkelt objekt med enkle datatyper. I mange tilfeller kommer det ut raskere.

Deretter vil jeg kort snakke om hvert verktøy og vise eksempler på bruk. La oss analysere spesifikke skript og se når det ene fungerer bedre enn det andre. Gå!

Hvordan bygge en rakettforsterker for PowerCLI-skript

Første trinn: Runspace

Så, Runspace er designet for parallell behandling av oppgaver utenfor hovedmodulen. Selvfølgelig kan du starte en annen prosess som vil spise opp litt minne, prosessor osv. Hvis skriptet kjører på et par minutter og bruker en gigabyte minne, trenger du mest sannsynlig ikke Runspace. Men for skript for titusenvis av objekter er det nødvendig.

Du kan begynne å lære her: 
Begynnende bruk av PowerShell Runspaces: Del 1

Hva gir bruk av Runspace:

  • hastighet ved å begrense listen over utførte kommandoer,
  • parallell utførelse av oppgaver,
  • sikkerhet.

Her er et eksempel fra Internett når Runspace hjelper:

"Lagringsstrid er en av de vanskeligste beregningene å spore i vSphere. Inne i vCenter kan du ikke bare gå og se hvilken VM som bruker mer lagringsressurser. Heldigvis kan du samle inn disse dataene på få minutter takket være PowerShell.
Jeg vil dele et skript som lar VMware-systemadministratorer raskt søke gjennom vCenter og motta en liste over VM-er med data om gjennomsnittlig forbruk.  
Skriptet bruker PowerShell runspaces for å tillate hver ESXi-vert å samle inn forbruksinformasjon fra sine egne VM-er i et separat Runspace og umiddelbart rapportere fullføring. Dette gjør at PowerShell kan stenge jobber umiddelbart, i stedet for å iterere gjennom verter og vente på at hver enkelt skal fullføre forespørselen."

Kilde: Hvordan vise Virtual Machine I/O på et ESXi Dashboard

I tilfellet nedenfor er Runspace ikke lenger nyttig:

«Jeg prøver å skrive et skript som samler inn mye data fra en VM og skriver nye data når det er nødvendig. Problemet er at det er ganske mange VM-er, og 5-8 sekunder brukes på én maskin." 

Kilde: Multithreading PowerCLI med RunspacePool

Her trenger du Get-View, la oss gå videre til det. 

Andre trinn: Get-View

For å forstå hvorfor Get-View er nyttig, er det verdt å huske hvordan cmdlets fungerer generelt. 

Cmdlets er nødvendig for å enkelt skaffe informasjon uten å måtte studere API-referansebøker og finne opp det neste hjulet på nytt. Det som i gamle dager tok hundre eller to linjer med kode, lar PowerShell deg gjøre med én kommando. Vi betaler for denne bekvemmeligheten med hurtighet. Det er ingen magi inne i cmdletene selv: det samme manuset, men på et lavere nivå, skrevet av de dyktige hendene til en mester fra solfylte India.

Nå, for sammenligning med Get-View, la oss ta Get-VM cmdleten: den får tilgang til den virtuelle maskinen og returnerer et sammensatt objekt, det vil si at den knytter andre relaterte objekter til den: VMHost, Datastore, etc.  

Get-View i stedet legger ikke til noe unødvendig til det returnerte objektet. Dessuten lar det oss strengt spesifisere hvilken informasjon vi trenger, noe som vil gjøre utdataobjektet enklere. I Windows Server generelt og i Hyper-V spesielt, er Get-WMIObject cmdlet en direkte analog - ideen er nøyaktig den samme.

Get-View er upraktisk for rutineoperasjoner på punktobjekter. Men når det kommer til tusenvis og titusenvis av gjenstander, har det ingen pris.

Du kan lese mer på VMware-bloggen: Introduksjon til Get-View

Nå skal jeg vise deg alt ved hjelp av en ekte sak. 

Skrive et skript for å laste ut en VM

En dag ba kollegaen min meg om å optimalisere manuset hans. Oppgaven er en vanlig rutine: finn alle VM-er med en duplikat cloud.uuid-parameter (ja, dette er mulig når en VM klones i vCloud Director). 

Den åpenbare løsningen du tenker på er:

  1. Få en liste over alle VM-er.
  2. Analyser listen på en eller annen måte.

Den originale versjonen var dette enkle skriptet:

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

Alt er ekstremt enkelt og oversiktlig. Den kan skrives på et par minutter med en kaffepause. Skru på filtreringen og den er ferdig.

Men la oss måle tiden:

Hvordan bygge en rakettforsterker for PowerCLI-skript

Hvordan bygge en rakettforsterker for PowerCLI-skript

2 minutter og 47 sekunder ved behandling av nesten 10 XNUMX VM-er. En bonus er fraværet av filtre og behovet for å sortere resultatene manuelt. Det er klart at skriptet krever optimalisering.

Runspaces er de første som kommer til unnsetning når du samtidig trenger å skaffe vertsmålinger fra vCenter eller trenger å behandle titusenvis av objekter. La oss se hva denne tilnærmingen bringer.

Slå på den første hastigheten: PowerShell Runspaces

Det første du tenker på for dette skriptet er å kjøre løkken ikke sekvensielt, men i parallelle tråder, samle alle dataene til ett objekt og filtrere det. 

Men det er et problem: PowerCLI vil ikke tillate oss å åpne mange uavhengige økter til vCenter og vil gi en morsom feil:

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.

For å løse dette må du først sende øktinformasjon inne i strømmen. La oss huske at PowerShell fungerer med objekter som kan sendes som en parameter enten til en funksjon eller til en ScriptBlock. La oss sende økten i form av et slikt objekt, og omgå $global:DefaultVIServers (Koble til-VIServer med -NotDefault-nøkkelen):

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

La oss nå implementere multithreading gjennom Runspace Pools.  

Algoritmen er som følger:

  1. Vi får en liste over alle VM-er.
  2. I parallelle strømmer får vi cloud.uuid.
  3. Vi samler data fra strømmer til ett objekt.
  4. Vi filtrerer objektet ved å gruppere det etter verdien til CloudUUID-feltet: de hvor antallet unike verdier er større enn 1 er VM-ene vi ser etter.

Som et resultat får vi manuset:


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 fine med dette skriptet er at det kan brukes i andre lignende tilfeller ved ganske enkelt å erstatte ScriptBlock og parametrene som vil bli sendt til strømmen. Utnytt det!

Vi måler tid:

Hvordan bygge en rakettforsterker for PowerCLI-skript

55 sekunder. Det er bedre, men det kan fortsatt være raskere. 

La oss gå til den andre hastigheten: GetView

La oss finne ut hva som er galt.
Først og fremst tar Get-VM cmdlet lang tid å utføre.
For det andre tar Get-AdvancedOptions cmdlet enda lengre tid å fullføre.
La oss ta for oss den andre først. 

Get-AdvancedOptions er praktisk for individuelle VM-objekter, men veldig klønete når du arbeider med mange objekter. Vi kan få den samme informasjonen fra selve det virtuelle maskinobjektet (Get-VM). Det er bare begravet godt i ExtensionData-objektet. Bevæpnet med filtrering fremskynder vi prosessen med å skaffe de nødvendige dataene.

Med en liten bevegelse av hånden er dette:


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 til dette:


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

Utgangen er den samme som Get-AdvancedOptions, men den fungerer mange ganger raskere. 

Nå til Get-VM. Det er ikke raskt fordi det omhandler komplekse objekter. Et logisk spørsmål dukker opp: hvorfor trenger vi ekstra informasjon og et monstrøst PSO-objekt i dette tilfellet, når vi bare trenger navnet på VM, dens tilstand og verdien av et vanskelig attributt?  

I tillegg er hindringen i form av Get-AdvancedOptions fjernet fra scriptet. Å bruke Runspace Pools virker nå som overkill siden det ikke lenger er behov for å parallellisere en langsom oppgave på tvers av knebøy-tråder når du overleverer en økt. Verktøyet er bra, men ikke for dette tilfellet. 

La oss se på utdataene til ExtensionData: det er ikke noe mer enn et Get-View-objekt. 

La oss bruke den eldgamle teknikken til PowerShell-mesterne: én linje ved hjelp av filtre, sortering og gruppering. All den forrige horroren er elegant kollapset i én linje og utført i én økt:


$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åler tid:

Hvordan bygge en rakettforsterker for PowerCLI-skript

9 sekunder for nesten 10k objekter med filtrering etter ønsket tilstand. Flott!

I stedet for en konklusjon

Et akseptabelt resultat avhenger direkte av valget av verktøy. Det er ofte vanskelig å si sikkert hva som skal velges for å oppnå det. Hver av de oppførte metodene for å øke hastigheten på skript er gode innenfor grensene for dens anvendelighet. Jeg håper denne artikkelen vil hjelpe deg i den vanskelige oppgaven med å forstå det grunnleggende om prosessautomatisering og optimalisering i infrastrukturen din.

PS: Forfatteren takker alle fellesskapsmedlemmer for deres hjelp og støtte til å utarbeide artikkelen. Selv de med poter. Og selv de som ikke har bein, som en boa-konstriktor.

Kilde: www.habr.com

Legg til en kommentar