Kako izgraditi raketni pojačivač za PowerCLI skripte 

Prije ili kasnije, svaki administrator VMware sustava dolazi do automatizacije rutinskih zadataka. Sve počinje s naredbenim redom, zatim dolazi PowerShell ili VMware PowerCLI.

Recimo da ste svladali PowerShell malo dalje od pokretanja ISE-a i korištenja standardnih cmdleta iz modula koji rade zahvaljujući “nekoj vrsti magije”. Kada počnete brojati virtualne strojeve u stotinama, otkrit ćete da skripte koje pomažu u maloj mjeri rade znatno sporije u velikoj mjeri. 

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

  • PowerShell Runspaces – pristup koji vam omogućuje paraleliziranje izvođenja procesa u zasebnim nitima; 
  • Get-View – osnovna PowerCLI funkcija, analog Get-WMIObject u sustavu Windows. 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. Analizirajmo određene skripte i vidimo kada jedna radi bolje od druge. Ići!

Kako izgraditi 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 zauzme gigabajt memorije, najvjerojatnije vam neće trebati Runspace. Ali za skripte za desetke tisuća objekata potrebno je.

Ovdje možete početi učiti: 
Početak korištenja PowerShell Runspaces: 1. dio

Što korištenje Runspacea daje:

  • brzina ograničavanjem popisa izvršenih naredbi,
  • paralelno izvršavanje zadataka,
  • sigurnost.

Evo primjera s Interneta kada Runspace pomaže:

“Spor za pohranu jedan je od najtežih pokazatelja za praćenje u vSphereu. Unutar vCenter-a ne možete jednostavno 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 PowerShellu.
Podijelit ću skriptu koja će administratorima sustava VMware omogućiti brzo pretraživanje kroz vCenter i primanje popisa VM-ova s ​​podacima o njihovoj prosječnoj potrošnji.  
Skripta koristi PowerShell runspaces kako bi svakom ESXi hostu omogućila prikupljanje podataka o potrošnji sa svojih VM-ova u zasebnom Runspaceu i odmah prijavilo završetak. To omogućuje PowerShell-u da odmah zatvori poslove, umjesto ponavljanja kroz hostove i čekanja da svaki ispuni svoj zahtjev.”

Izvor: Kako prikazati I/O virtualnog stroja na nadzornoj ploči ESXi

U donjem slučaju Runspace više nije koristan:

“Pokušavam napisati skriptu koja prikuplja puno podataka iz VM-a i piše nove podatke kada je to potrebno. Problem je što ima dosta VM-ova, a na jednom stroju se potroši 5-8 sekundi.” 

Izvor: Višenitni PowerCLI s 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 općenito rade. 

Cmdleti su potrebni za prikladno dobivanje informacija bez potrebe za proučavanjem referentnih knjiga API-ja i ponovnog izuma sljedećeg kotača. Ono što je u stara vremena zahtijevalo sto ili dvije linije koda, PowerShell vam omogućuje da učinite jednom naredbom. Ovu pogodnost plaćamo brzinom. Unutar samih cmdleta nema nikakve magije: ista skripta, ali na nižoj razini, napisana vještim rukama majstora iz sunčane Indije.

Sada, za usporedbu s Get-Viewom, uzmimo Get-VM cmdlet: on pristupa virtualnom stroju i vraća kompozitni objekt, odnosno prilaže mu druge povezane objekte: VMHost, Datastore itd.  

Get-View na svom mjestu ne dodaje ništa nepotrebno vraćenom objektu. Štoviše, omogućuje nam da strogo odredimo koje informacije trebamo, što će olakšati izlazni objekt. Općenito u sustavu Windows Server, a posebno u Hyper-V, cmdlet Get-WMIObject izravni je analog - ideja je potpuno ista.

Get-View je nezgodan za rutinske operacije na točkastim objektima. Ali kada je riječ o tisućama i desecima tisuća predmeta, to nema cijenu.

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-a

Jednog me dana kolega zamolio da optimiziram njegovu skriptu. Zadatak je uobičajena rutina: pronađite sve VM-ove s dupliciranim parametrom cloud.uuid (da, to je moguće pri kloniranju VM-a u vCloud Director). 

Očito rješenje koje pada na pamet je:

  1. Dobijte popis svih VM-ova.
  2. Nekako raščlanite popis.

Izvorna verzija bila je 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 u par minuta uz pauzu za kavu. Pričvrstite filtraciju i gotovo je.

Ali izmjerimo vrijeme:

Kako izgraditi raketni pojačivač za PowerCLI skripte

Kako izgraditi raketni pojačivač za PowerCLI skripte

2 minute 47 sekundi pri obradi gotovo 10k VM-ova. Bonus je nepostojanje filtera i potreba za ručnim sortiranjem rezultata. Očito, skripta zahtijeva optimizaciju.

Runspaces su prvi koji će priskočiti u pomoć kada trebate istovremeno dobiti metriku hosta od vCenter-a ili trebate obraditi desetke tisuća objekata. Pogledajmo što ovaj pristup donosi.

Uključite prvu brzinu: PowerShell Runspaces

Prva stvar koja pada na pamet za ovu skriptu je izvršiti petlju ne sekvencijalno, već u paralelnim nitima, prikupiti sve podatke u jedan objekt i filtrirati ih. 

Ali postoji problem: PowerCLI nam neće dopustiti da otvorimo mnoge neovisne sesije na vCenter i izbacit će smiješnu pogreš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 streama. Podsjetimo se da PowerShell radi s objektima koji se mogu proslijediti kao parametar ili funkciji ili ScriptBlocku. Propustimo sesiju u obliku takvog objekta, zaobilazeći $global:DefaultVIServers (Connect-VIServer s ključem -NotDefault):

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

Implementirajmo sada višenitnost kroz Runspace Pools.  

Algoritam je sljedeći:

  1. Dobivamo popis svih VM-ova.
  2. U paralelnim tokovima dobivamo cloud.uuid.
  3. Prikupljamo podatke iz tokova u jedan objekt.
  4. Filtriramo objekt tako da ga grupiramo prema vrijednosti polja CloudUUID: oni kod kojih je broj jedinstvenih vrijednosti veći od 1 su VM koje tražimo.

Kao rezultat, dobivamo 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 ScriptBlock-a i parametara koji će biti proslijeđeni streamu. Iskoristite to!

Vrijeme mjerimo:

Kako izgraditi raketni pojačivač za PowerCLI skripte

55 sekundi. Bolje je, ali još uvijek može biti brže. 

Prijeđimo na drugu brzinu: GetView

Idemo saznati što nije u redu.
Prije svega, cmdlet Get-VM treba dugo da se izvrši.
Drugo, cmdlet Get-AdvancedOptions traje čak i duže da se dovrši.
Pozabavimo se prvo ovim drugim. 

Get-AdvancedOptions je prikladan za pojedinačne VM objekte, ali vrlo nespretan kada se radi s mnogo objekata. Iste informacije možemo dobiti iz samog objekta virtualnog stroja (Get-VM). Samo je dobro zakopan u objektu ExtensionData. Naoružani filtriranjem, ubrzavamo proces dobivanja 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 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 trebamo samo ime VM-a, njegovo stanje i vrijednost lukavog atributa?  

Osim toga, iz skripte je uklonjena prepreka u obliku Get-AdvancedOptions. Korištenje Runspace Pools sada se čini kao pretjerano jer više nema potrebe za paraleliziranjem sporog zadatka kroz 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 redak pomoću filtara, sortiranje i grupiranje. Sav prethodni horor elegantno je sažet u jednu liniju 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 izgraditi raketni pojačivač za PowerCLI skripte

9 sekundi za gotovo 10k objekata uz filtriranje po željenom stanju. Sjajno!

Umjesto zaključka

Prihvatljiv rezultat izravno ovisi o izboru alata. Često je teško sa sigurnošću reći što točno treba odabrati 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 u vašoj infrastrukturi.

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

Izvor: www.habr.com

Dodajte komentar