Kako napraviti raketni pojačivač za PowerCLI skripte 

Prije ili kasnije, svaki VMware sistemski administrator dođe da automatizira rutinske zadatke. Sve počinje sa komandnom linijom, zatim dolazi PowerShell ili VMware PowerCLI.

Recimo da ste savladali PowerShell malo dalje od pokretanja ISE-a i korištenja standardnih cmdleta iz modula koji rade zahvaljujući „nekoj vrsti magije“. Kada počnete da brojite virtuelne mašine u stotinama, otkrićete da skripte koje pomažu u malim razmerama rade primetno sporije u velikim razmerama. 

U ovoj situaciji će vam pomoći 2 alata:

  • PowerShell Runspaces – pristup koji vam omogućava da paralelizirate izvršavanje procesa u odvojenim nitima; 
  • Get-View – osnovna funkcija PowerCLI, analogna Get-WMIObject u Windows-u. Ovaj cmdlet ne povlači objekte koji prate entitete, već prima informacije u obliku jednostavnog objekta s jednostavnim tipovima podataka. U mnogim slučajevima izlazi brže.

Zatim ću ukratko govoriti o svakom alatu i pokazati primjere korištenja. Hajde da analiziramo određene skripte i vidimo kada jedna radi bolje od druge. Idi!

Kako napraviti raketni pojačivač za PowerCLI skripte

Prva faza: Runspace

Dakle, Runspace je dizajniran za paralelnu obradu zadataka izvan glavnog modula. Naravno, možete pokrenuti neki drugi proces koji će pojesti nešto memorije, procesora itd. Ako se vaša skripta pokrene za nekoliko minuta i potroši gigabajt memorije, najvjerovatnije vam neće trebati Runspace. Ali za skripte za desetine hiljada objekata to je potrebno.

Možete početi učiti ovdje: 
Početak korištenja PowerShell runspacea: 1. dio

Šta daje korištenje Runspace-a:

  • brzina ograničavanjem liste izvršenih komandi,
  • paralelno izvršavanje zadataka,
  • sigurnost.

Evo primjera sa interneta kada Runspace pomaže:

„Kontrola za skladištenje je jedna od najtežih metrika za praćenje u vSphere. Unutar vCenter-a, ne možete samo otići i vidjeti koji VM troši više resursa za pohranu. Srećom, ove podatke možete prikupiti za nekoliko minuta zahvaljujući PowerShell-u.
Podijeliću skriptu koja će omogućiti VMware sistem administratorima da brzo pretražuju po vCenter-u i dobiju listu VM-ova sa podacima o njihovoj prosječnoj potrošnji.  
Skripta koristi PowerShell runspaces kako bi omogućila svakom ESXi hostu da prikupi informacije o potrošnji sa svojih VM-ova u zasebnom Runspace-u i odmah prijavi završetak. Ovo omogućava PowerShell-u da odmah zatvori poslove, umjesto da se ponavlja kroz hostove i čeka da svaki od njih završi svoj zahtjev.”

izvor: Kako prikazati I/O virtuelne mašine na ESXi kontrolnoj tabli

U slučaju ispod, Runspace više nije koristan:

“Pokušavam da napišem skriptu koja prikuplja mnogo podataka sa VM-a i piše nove podatke kada je to potrebno. Problem je što ima dosta VM-ova, a na jednoj mašini se troši 5-8 sekundi.” 

izvor: Multithreading PowerCLI sa RunspacePool

Ovdje će vam trebati Get-View, prijeđimo na njega. 

Druga faza: Get-View

Da biste razumjeli zašto je Get-View koristan, vrijedi se prisjetiti kako cmdleti funkcioniraju općenito. 

Komande su potrebne za praktično dobijanje informacija bez potrebe za proučavanjem API referentnih knjiga i izmišljanjem novog točka. Ono što je u starim danima trebalo sto ili dve linije koda, PowerShell vam omogućava da uradite sa jednom komandom. Ovu pogodnost plaćamo brzinom. U samim cmdletima nema magije: ista skripta, ali na nižem nivou, napisana vještim rukama majstora iz sunčane Indije.

Sada, za poređenje sa Get-View, uzmimo Get-VM cmdlet: on pristupa virtuelnoj mašini i vraća kompozitni objekat, to jest, pridružuje mu druge povezane objekte: VMHost, Datastore, itd.  

Get-View umjesto njega ne dodaje ništa nepotrebno vraćenom objektu. Štaviše, omogućava nam da striktno specificiramo koje informacije su nam potrebne, što će olakšati izlazni objekat. U Windows Server-u općenito, a posebno u Hyper-V-u, Get-WMIObject cmdlet je direktan analog - ideja je potpuno ista.

Get-View je nezgodan za rutinske operacije na tačkastim objektima. Ali kada su u pitanju hiljade i desetine hiljada objekata, to nema cenu.

Više možete pročitati na VMware blogu: Uvod u Get-View

Sada ću vam sve pokazati koristeći pravi slučaj. 

Pisanje skripte za istovar VM

Jednog dana me je kolega zamolio da optimizujem njegov scenario. Zadatak je uobičajena rutina: pronaći sve VM-ove s duplikatom cloud.uuid parametrom (da, to je moguće kada klonirate VM u vCloud Director). 

Očigledno rješenje koje vam pada na pamet je:

  1. Dobijte listu svih VM-ova.
  2. Nekako analizirajte listu.

Originalna verzija je bila ova jednostavna skripta:

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

Sve je krajnje jednostavno i jasno. Može se napisati za par minuta uz pauzu za kafu. Zašrafite filtraciju i gotovo.

Ali hajde da izmerimo vreme:

Kako napraviti raketni pojačivač za PowerCLI skripte

Kako napraviti raketni pojačivač za PowerCLI skripte

2 minuta 47 sekundi prilikom obrade skoro 10k VM-a. Bonus je odsustvo filtera i potreba za ručnim sortiranjem rezultata. Očigledno, skripta zahtijeva optimizaciju.

Runspaces su prvi koji priskaču u pomoć kada morate istovremeno da dobijete metriku hosta od vCenter-a ili morate da obradite desetine hiljada objekata. Hajde da vidimo šta ovaj pristup donosi.

Uključite prvu brzinu: PowerShell Runspaces

Prvo što vam padne na pamet za ovu skriptu je da petlju izvrši ne sekvencijalno, već u paralelnim nitima, prikupi sve podatke u jedan objekat i filtrira ih. 

Ali postoji problem: PowerCLI nam neće dozvoliti da otvorimo mnoge nezavisne sesije za vCenter i ispostavit će smiješnu grešku:

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.

Da biste to riješili, prvo morate proslijediti informacije o sesiji unutar toka. Podsjetimo da PowerShell radi s objektima koji se mogu proslijediti kao parametar bilo funkciji ili ScriptBlocku. Propustimo sesiju u obliku takvog objekta, zaobilazeći $global:DefaultVIServers (Connect-VIServer sa -NotDefault ključem):

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

Sada implementirajmo višenitnost kroz Runspace Pools.  

Algoritam je sljedeći:

  1. Dobijamo listu svih VM-ova.
  2. U paralelnim tokovima dobijamo cloud.uuid.
  3. Prikupljamo podatke iz tokova u jedan objekt.
  4. Objekt filtriramo tako što ga grupišemo prema vrijednosti polja CloudUUID: oni kod kojih je broj jedinstvenih vrijednosti veći od 1 su VM-ovi koje tražimo.

Kao rezultat, dobijamo skriptu:


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
}

Ljepota ove skripte je u tome što se može koristiti u drugim sličnim slučajevima jednostavnom zamjenom ScriptBlocka i parametara koji će biti proslijeđeni u stream. Iskoristite to!

Vrijeme mjerimo:

Kako napraviti raketni pojačivač za PowerCLI skripte

55 sekundi. Bolje je, ali ipak može biti brže. 

Pređimo na drugu brzinu: GetView

Hajde da saznamo šta nije u redu.
Prvo i najvažnije, Get-VM cmdlet-u je potrebno mnogo vremena da se izvrši.
Drugo, Get-AdvancedOptions cmdlet treba još duže da se završi.
Hajde da se prvo pozabavimo drugom. 

Get-AdvancedOptions je zgodan za pojedinačne VM objekte, ali vrlo nespretan kada se radi sa mnogo objekata. Iste informacije možemo dobiti od samog objekta virtuelne mašine (Get-VM). Jednostavno je dobro zakopan u objektu ExtensionData. Naoružani filtriranjem, ubrzavamo proces dobijanja potrebnih podataka.

Laganim pokretom ruke ovo je:


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

Pretvara se u ovo:


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

Izlaz je isti kao Get-AdvancedOptions, ali radi mnogo puta brže. 

Sada na Get-VM. Nije brz jer se bavi složenim objektima. Postavlja se logično pitanje: zašto su nam u ovom slučaju potrebne dodatne informacije i monstruozni PSObject, kada nam je potrebno samo ime VM-a, njegovo stanje i vrijednost škakljivog atributa?  

Osim toga, prepreka u obliku Get-AdvancedOptions je uklonjena iz skripte. Korištenje Runspace Pulova sada izgleda kao pretjerano jer više nema potrebe za paralelizacijom sporog zadatka preko squat niti prilikom predaje sesije. Alat je dobar, ali nije za ovaj slučaj. 

Pogledajmo izlaz ExtensionData: to nije ništa više od Get-View objekta. 

Pozovimo se na drevnu tehniku ​​PowerShell majstora: jedan red koji koristi filtere, sortiranje i grupiranje. Sav prethodni horor je elegantno skupljen u jedan red i izveden u jednoj sesiji:


$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

Vrijeme mjerimo:

Kako napraviti raketni pojačivač za PowerCLI skripte

9 sekundi za skoro 10k objekata sa filtriranjem po željenom stanju. Odlično!

Umjesto zaključka

Prihvatljivi rezultat direktno ovisi o izboru alata. Često je teško sa sigurnošću reći šta tačno treba izabrati da se to postigne. Svaka od navedenih metoda za ubrzavanje skripti je dobra u granicama svoje primjenjivosti. Nadam se da će vam ovaj članak pomoći u teškom zadatku razumijevanja osnova automatizacije procesa i optimizacije vaše infrastrukture.

PS: Autor zahvaljuje svim članovima zajednice na pomoći i podršci u pripremi članka. Čak i one sa šapama. Pa čak i oni koji nemaju noge, poput boa constrictor.

izvor: www.habr.com

Dodajte komentar