Kako zgraditi raketni pospeševalnik za skripte PowerCLI 

Prej ali slej vsak skrbnik sistema VMware avtomatizira rutinska opravila. Vse se začne z ukazno vrstico, nato pride PowerShell ali VMware PowerCLI.

Recimo, da ste obvladali PowerShell nekoliko dlje od zagona ISE in uporabe standardnih cmdletov iz modulov, ki delujejo zaradi "neke vrste čarovnije". Ko začnete šteti virtualne stroje v stotinah, boste ugotovili, da skripti, ki pomagajo v majhnem obsegu, v velikem obsegu delujejo opazno počasneje. 

V tej situaciji bosta pomagali 2 orodji:

  • PowerShell Runspaces – pristop, ki omogoča vzporedno izvajanje procesov v ločenih nitih; 
  • Get-View – osnovna funkcija PowerCLI, analog Get-WMIObject v sistemu Windows. Ta ukaz cmdlet ne vleče predmetov, ki spremljajo entitete, ampak prejema informacije v obliki preprostega predmeta s preprostimi tipi podatkov. V mnogih primerih pride hitreje.

Nato bom na kratko spregovoril o vsakem orodju in pokazal primere uporabe. Analizirajmo določene skripte in ugotovimo, kdaj eden deluje bolje kot drugi. Pojdi!

Kako zgraditi raketni pospeševalnik za skripte PowerCLI

Prva stopnja: Runspace

Torej je Runspace zasnovan za vzporedno obdelavo nalog zunaj glavnega modula. Seveda lahko zaženete drug proces, ki bo pojedel nekaj pomnilnika, procesorja itd. Če se vaš skript zažene v nekaj minutah in porabi gigabajt pomnilnika, najverjetneje ne boste potrebovali Runspace. Toda za skripte za več deset tisoč predmetov je to potrebno.

Učenje lahko začnete tukaj: 
Začetek uporabe PowerShell Runspaces: 1. del

Kaj daje uporaba Runspace:

  • hitrost z omejevanjem seznama izvedenih ukazov,
  • vzporedno izvajanje nalog,
  • varnost.

Tukaj je primer iz interneta, ko Runspace pomaga:

»Napori glede prostora za shranjevanje so ena najtežjih meritev, ki jim je v vSphere slediti. Znotraj vCenter ne morete kar pogledati, kateri VM porabi več virov za shranjevanje. Na srečo lahko te podatke zberete v nekaj minutah zahvaljujoč lupini PowerShell.
Delil bom skript, ki bo sistemskim skrbnikom VMware omogočil hitro iskanje po vCenterju in prejemanje seznama VM s podatki o njihovi povprečni porabi.  
Skript uporablja izvajalske prostore PowerShell, da vsakemu gostitelju ESXi omogoči zbiranje informacij o porabi iz lastnih navideznih računalnikov v ločenem izvajalnem prostoru in takojšnje poročanje o zaključku. To omogoča PowerShell, da takoj zapre opravila, namesto da ponavlja gostitelje in čaka, da vsak dokonča svojo zahtevo.«

Vir: Kako prikazati V/I navideznega stroja na nadzorni plošči ESXi

V spodnjem primeru Runspace ni več uporaben:

»Poskušam napisati skript, ki zbere veliko podatkov iz VM in zapiše nove podatke, ko je to potrebno. Težava je v tem, da je navideznih računalnikov kar veliko in en stroj porabi 5-8 sekund.« 

Vir: Večnitnost PowerCLI z RunspacePool

Tukaj boste potrebovali Get-View, pojdimo nanj. 

Druga stopnja: Get-View

Da bi razumeli, zakaj je Get-View uporaben, se je vredno spomniti, kako na splošno delujejo cmdleti. 

Cmdleti so potrebni za priročno pridobivanje informacij, ne da bi morali preučevati referenčne knjige API-jev in znova izumljati naslednje kolo. Kar je v starih časih zahtevalo sto ali dve vrstici kode, vam PowerShell omogoča, da naredite z enim ukazom. To udobje plačamo s hitrostjo. Znotraj samih cmdletov ni nobene čarovnije: isti scenarij, vendar na nižji ravni, ki so ga napisale spretne roke mojstra iz sončne Indije.

Zdaj, za primerjavo z Get-View, vzemimo cmdlet Get-VM: dostopa do navideznega stroja in vrne sestavljeni objekt, to pomeni, da mu pripne druge povezane objekte: VMHost, Datastore itd.  

Get-View namesto tega vrnjenemu predmetu ne doda ničesar nepotrebnega. Poleg tega nam omogoča, da natančno določimo, katere informacije potrebujemo, kar bo olajšalo izhodni objekt. V Windows Server na splošno in še posebej v Hyper-V je cmdlet Get-WMIObject neposreden analog - ideja je popolnoma enaka.

Get-View je nepriročen za rutinske operacije na točkovnih objektih. Ko pa gre za tisoče in desettisoče predmetov, to nima cene.

Več si lahko preberete na blogu VMware: Uvod v Get-View

Zdaj vam bom vse pokazal na resničnem primeru. 

Pisanje skripta za razkladanje VM

Nekega dne me je kolega prosil, naj optimiziram njegov scenarij. Naloga je običajna rutina: poiščite vse VM s podvojenim parametrom cloud.uuid (da, to je mogoče pri kloniranju VM v vCloud Director). 

Očitna rešitev, ki pride na misel, je:

  1. Pridobite seznam vseh VM-jev.
  2. Nekako razčlenite seznam.

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

Vse je izjemno preprosto in jasno. Lahko se napiše v nekaj minutah z odmorom za kavo. Privijte filtracijo in je končano.

Ampak izmerimo čas:

Kako zgraditi raketni pospeševalnik za skripte PowerCLI

Kako zgraditi raketni pospeševalnik za skripte PowerCLI

2 minute 47 sekund pri obdelavi skoraj 10k VM-jev. Bonus je odsotnost filtrov in potreba po ročnem razvrščanju rezultatov. Očitno skript zahteva optimizacijo.

Runspaces so prvi, ki priskočijo na pomoč, ko morate istočasno pridobiti metrike gostitelja iz vCenter ali morate obdelati več deset tisoč objektov. Poglejmo, kaj prinaša ta pristop.

Vklopite prvo hitrost: PowerShell Runspaces

Prva stvar, ki pride na misel pri tem skriptu, je izvajanje zanke ne zaporedno, ampak v vzporednih nitih, zbiranje vseh podatkov v en objekt in filtriranje. 

Vendar obstaja težava: PowerCLI nam ne bo dovolil odpreti veliko neodvisnih sej v vCenter in bo vrgel smešno napako:

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.

Če želite to rešiti, morate najprej posredovati informacije o seji znotraj toka. Spomnimo se, da PowerShell deluje s predmeti, ki jih je mogoče posredovati kot parameter v funkcijo ali v ScriptBlock. Prepustimo sejo v obliki takega objekta, mimo $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 $_
           }
       }
   }

Zdaj pa implementirajmo večnitnost prek bazenov Runspace.  

Algoritem je naslednji:

  1. Dobimo seznam vseh VM-jev.
  2. V vzporednih tokovih dobimo cloud.uuid.
  3. Podatke zbiramo iz tokov v en objekt.
  4. Predmet filtriramo tako, da ga združimo v skupine glede na vrednost polja CloudUUID: tisti, kjer je število edinstvenih vrednosti večje od 1, so VM-ji, ki jih iščemo.

Kot rezultat dobimo skript:


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
}

Lepota tega skripta je, da ga je mogoče uporabiti v drugih podobnih primerih s preprosto zamenjavo ScriptBlock in parametrov, ki bodo posredovani toku. Izkoristite ga!

Čas merimo:

Kako zgraditi raketni pospeševalnik za skripte PowerCLI

55 sekunde. Je bolje, a še vedno je lahko hitreje. 

Preidimo na drugo hitrost: GetView

Ugotovimo, kaj je narobe.
Najprej in najpomembneje, cmdlet Get-VM se izvaja dolgo.
Drugič, dokončanje cmdleta Get-AdvancedOptions traja še dlje.
Najprej se posvetimo drugemu. 

Get-AdvancedOptions je primeren za posamezne objekte VM, vendar je zelo neroden pri delu s številnimi objekti. Iste informacije lahko pridobimo iz samega objekta navideznega stroja (Get-VM). Samo dobro je zakopan v objektu ExtensionData. Oboroženi s filtriranjem pospešimo proces pridobivanja potrebnih podatkov.

Z rahlim premikom roke je to:


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

Spremeni se v tole:


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

Izhod je enak kot Get-AdvancedOptions, vendar deluje mnogokrat hitreje. 

Zdaj pa k Get-VM. Ni hiter, ker se ukvarja s kompleksnimi predmeti. Postavlja se logično vprašanje: zakaj v tem primeru potrebujemo dodatne informacije in pošastni PSObject, ko pa potrebujemo samo ime VM, njegovo stanje in vrednost zapletenega atributa?  

Poleg tega je bila iz skripta odstranjena ovira v obliki Get-AdvancedOptions. Uporaba Runspace Pools se zdaj zdi pretirana, saj pri predaji seje ni več potrebe po paraleliziranju počasne naloge v nitih squat. Orodje je dobro, vendar ne za ta primer. 

Poglejmo izhod ExtensionData: ni nič drugega kot objekt Get-View. 

Pokličimo starodavno tehniko mojstrov PowerShell: ena vrstica z uporabo filtrov, razvrščanje in združevanje. Vsa prejšnja groza je elegantno strnjena v eno vrstico in izvedena v eni seji:


$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

Čas merimo:

Kako zgraditi raketni pospeševalnik za skripte PowerCLI

9 sekund za skoraj 10k objektov s filtriranjem po želenem stanju. Super!

Namesto zaključka

Sprejemljiv rezultat je neposredno odvisen od izbire orodja. Pogosto je težko zagotovo reči, kaj točno je treba izbrati, da bi to dosegli. Vsak od naštetih načinov za pospešitev skriptov je dober v mejah svoje uporabnosti. Upam, da vam bo ta članek pomagal pri težki nalogi razumevanja osnov avtomatizacije in optimizacije procesov v vaši infrastrukturi.

PS: Avtor se zahvaljuje vsem članom skupnosti za pomoč in podporo pri pripravi članka. Tudi tiste s tacami. In tudi tisti, ki nimajo nog, kot udav.

Vir: www.habr.com

Dodaj komentar