Sådan bygger du en raketbooster til PowerCLI-scripts 

Før eller siden kommer enhver VMware-systemadministrator til at automatisere rutineopgaver. Det hele starter med kommandolinjen, derefter kommer PowerShell eller VMware PowerCLI.

Lad os sige, at du har mestret PowerShell lidt længere end at lancere ISE og bruge standard cmdlets fra moduler, der fungerer på grund af "en slags magi". Når du begynder at tælle virtuelle maskiner i hundredvis, vil du opdage, at scripts, der hjælper i lille skala, kører mærkbart langsommere i stor skala. 

I denne situation vil 2 værktøjer hjælpe:

  • PowerShell Runspaces – en tilgang, der giver dig mulighed for at parallelisere udførelsen af ​​processer i separate tråde; 
  • Get-View – en grundlæggende PowerCLI-funktion, en analog af Get-WMIObject i Windows. Denne cmdlet trækker ikke objekter, der ledsager enheder, men modtager information i form af et simpelt objekt med simple datatyper. I mange tilfælde kommer det hurtigere ud.

Dernæst vil jeg kort fortælle om hvert værktøj og vise eksempler på brug. Lad os analysere specifikke scripts og se, hvornår det ene fungerer bedre end det andet. Gå!

Sådan bygger du en raketbooster til PowerCLI-scripts

Første etape: Runspace

Så Runspace er designet til parallel behandling af opgaver uden for hovedmodulet. Selvfølgelig kan du starte en anden proces, der vil æde noget hukommelse, processor osv. Hvis dit script kører på et par minutter og bruger en gigabyte hukommelse, har du højst sandsynligt ikke brug for Runspace. Men for scripts til titusindvis af objekter er det nødvendigt.

Du kan begynde at lære her: 
Begyndende brug af PowerShell Runspaces: Del 1

Hvad giver det at bruge Runspace:

  • hastighed ved at begrænse listen over udførte kommandoer,
  • parallel udførelse af opgaver,
  • sikkerhed.

Her er et eksempel fra internettet, når Runspace hjælper:

"Storagekonflikter er en af ​​de sværeste metrics at spore i vSphere. Inde i vCenter kan du ikke bare gå og se, hvilken VM der bruger flere lagerressourcer. Heldigvis kan du indsamle disse data på få minutter takket være PowerShell.
Jeg vil dele et script, der giver VMware-systemadministratorer mulighed for hurtigt at søge i vCenter og modtage en liste over VM'er med data om deres gennemsnitlige forbrug.  
Scriptet bruger PowerShell-runspaces til at give hver ESXi-vært mulighed for at indsamle forbrugsoplysninger fra sine egne VM'er i et separat Runspace og straks rapportere færdiggørelse. Dette gør det muligt for PowerShell at lukke jobs med det samme, i stedet for at iterere gennem værter og vente på, at hver enkelt fuldfører sin anmodning."

Kilde: Sådan viser du Virtual Machine I/O på et ESXi Dashboard

I nedenstående tilfælde er Runspace ikke længere nyttig:

“Jeg forsøger at skrive et script, der samler en masse data fra en VM og skriver nye data, når det er nødvendigt. Problemet er, at der er ret mange VM'er, og der bruges 5-8 sekunder på én maskine." 

Kilde: Multithreading PowerCLI med RunspacePool

Her skal du bruge Get-View, lad os gå videre til det. 

Anden fase: Get-View

For at forstå, hvorfor Get-View er nyttigt, er det værd at huske, hvordan cmdlets fungerer generelt. 

Cmdlets er nødvendige for bekvemt at få information uden at skulle studere API-opslagsbøger og genopfinde det næste hjul. Hvad der i gamle dage tog hundrede eller to linjer kode, giver PowerShell dig mulighed for at gøre med én kommando. Vi betaler for denne bekvemmelighed med hurtighed. Der er ingen magi inde i selve cmdlet'erne: det samme script, men på et lavere niveau, skrevet af de dygtige hænder fra en mester fra det solrige Indien.

Nu, til sammenligning med Get-View, lad os tage Get-VM cmdlet'en: den får adgang til den virtuelle maskine og returnerer et sammensat objekt, det vil sige, den vedhæfter andre relaterede objekter til det: VMHost, Datastore osv.  

Get-View i stedet tilføjer ikke noget unødvendigt til det returnerede objekt. Desuden giver det os mulighed for nøje at specificere, hvilke oplysninger vi har brug for, hvilket vil gøre outputobjektet lettere. I Windows Server generelt og i Hyper-V i særdeleshed er Get-WMIObject cmdlet'en en direkte analog - ideen er nøjagtig den samme.

Get-View er ubelejligt til rutineoperationer på punktobjekter. Men når det kommer til tusinder og titusinder af genstande, har det ingen pris.

Du kan læse mere på VMware-bloggen: Introduktion til Get-View

Nu vil jeg vise dig alt ved hjælp af en ægte sag. 

At skrive et script for at fjerne en VM

En dag bad min kollega mig om at optimere hans manuskript. Opgaven er en fælles rutine: find alle VM'er med en dublet cloud.uuid-parameter (ja, dette er muligt, når du kloner en VM i vCloud Director). 

Den åbenlyse løsning, der kommer til at tænke på, er:

  1. Få en liste over alle VM'er.
  2. Parse listen på en eller anden måde.

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

Alt er ekstremt enkelt og overskueligt. Det kan skrives på et par minutter med en kaffepause. Skru filtreringen på og det er færdigt.

Men lad os måle tiden:

Sådan bygger du en raketbooster til PowerCLI-scripts

Sådan bygger du en raketbooster til PowerCLI-scripts

2 minutter 47 sekunder ved behandling af næsten 10 VM'er. En bonus er fraværet af filtre og behovet for manuelt at sortere resultaterne. Det er klart, at scriptet kræver optimering.

Runspaces er de første, der kommer til undsætning, når du samtidig skal hente værtsmålinger fra vCenter eller skal behandle titusindvis af objekter. Lad os se, hvad denne tilgang bringer.

Slå den første hastighed til: PowerShell Runspaces

Den første ting, der kommer til at tænke på for dette script, er at udføre løkken ikke sekventielt, men i parallelle tråde, samle alle data i et objekt og filtrere det. 

Men der er et problem: PowerCLI vil ikke tillade os at åbne mange uafhængige sessioner til vCenter og vil kaste en sjov fejl:

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 at løse dette skal du først sende sessionsoplysninger inde i streamen. Lad os huske, at PowerShell arbejder med objekter, der kan overføres som en parameter enten til en funktion eller til en ScriptBlock. Lad os videregive sessionen i form af et sådant objekt og omgå $global:DefaultVIServers (Forbind-VIServer med -NotDefault-nøglen):

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

Lad os nu implementere multithreading gennem Runspace Pools.  

Algoritmen er følgende:

  1. Vi får en liste over alle VM'er.
  2. I parallelle strømme får vi cloud.uuid.
  3. Vi samler data fra strømme til ét objekt.
  4. Vi filtrerer objektet ved at gruppere det efter værdien af ​​CloudUUID-feltet: dem, hvor antallet af unikke værdier er større end 1, er de VM'er, vi leder efter.

Som et resultat får vi scriptet:


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
}

Skønheden ved dette script er, at det kan bruges i andre lignende tilfælde ved blot at erstatte ScriptBlock og de parametre, der sendes til streamen. Udnyt det!

Vi måler tid:

Sådan bygger du en raketbooster til PowerCLI-scripts

55 sekunder. Det er bedre, men det kan stadig være hurtigere. 

Lad os gå til den anden hastighed: GetView

Lad os finde ud af, hvad der er galt.
Først og fremmest tager Get-VM cmdlet'en lang tid at udføre.
For det andet tager Get-AdvancedOptions cmdlet'en endnu længere tid at fuldføre.
Lad os først behandle den anden. 

Get-AdvancedOptions er praktisk til individuelle VM-objekter, men meget klodset, når du arbejder med mange objekter. Vi kan få den samme information fra selve det virtuelle maskinobjekt (Get-VM). Det er bare begravet godt i ExtensionData-objektet. Bevæbnet med filtrering fremskynder vi processen med at skaffe de nødvendige data.

Med en let bevægelse af 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}}

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

Outputtet er det samme som Get-AdvancedOptions, men det virker mange gange hurtigere. 

Nu til Get-VM. Det er ikke hurtigt, fordi det omhandler komplekse genstande. Et logisk spørgsmål opstår: hvorfor har vi brug for ekstra information og et monstrøst PSO-objekt i dette tilfælde, når vi bare har brug for navnet på VM'en, dens tilstand og værdien af ​​en vanskelig attribut?  

Derudover er forhindringen i form af Get-AdvancedOptions blevet fjernet fra scriptet. At bruge Runspace Pools virker nu som overkill, da der ikke længere er behov for at parallelisere en langsom opgave på tværs af squat-tråde, når du afleverer en session. Værktøjet er godt, men ikke til dette tilfælde. 

Lad os se på outputtet af ExtensionData: det er intet andet end et Get-View-objekt. 

Lad os kalde på PowerShell-mestrenes ældgamle teknik: én linje ved hjælp af filtre, sortering og gruppering. Al den tidligere rædsel er elegant kollapset i én linje og udført i én 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åler tid:

Sådan bygger du en raketbooster til PowerCLI-scripts

9 sekunder for næsten 10k objekter med filtrering efter den ønskede tilstand. Store!

I stedet for en konklusion

Et acceptabelt resultat afhænger direkte af valget af værktøj. Det er ofte svært at sige med sikkerhed, hvad der præcist skal vælges for at opnå det. Hver af de anførte metoder til at fremskynde scripts er god inden for grænserne af dens anvendelighed. Jeg håber, at denne artikel vil hjælpe dig i den vanskelige opgave at forstå det grundlæggende i procesautomatisering og optimering i din infrastruktur.

PS: Forfatteren takker alle fællesskabsmedlemmer for deres hjælp og støtte til at forberede artiklen. Selv dem med poter. Og selv dem, der ikke har ben, som en boa constrictor.

Kilde: www.habr.com

Tilføj en kommentar