Ako vytvoriť raketový zosilňovač pre skripty PowerCLI 

Skôr či neskôr príde na automatizáciu rutinných úloh ktorýkoľvek správca systému VMware. Všetko to začína príkazovým riadkom, potom prichádza PowerShell alebo VMware PowerCLI.

Povedzme, že ste si osvojili PowerShell o niečo ďalej ako spustenie ISE a používanie štandardných cmdletov z modulov, ktoré fungujú vďaka „nejakej mágii“. Keď začnete počítať virtuálne stroje v stovkách, zistíte, že skripty, ktoré pomáhajú v malom meradle, bežia vo veľkom meradle výrazne pomalšie. 

V tejto situácii vám pomôžu 2 nástroje:

  • Priestory PowerShell – prístup, ktorý vám umožňuje paralelizovať vykonávanie procesov v samostatných vláknach; 
  • Get-View – základná funkcia PowerCLI, analóg Get-WMIObject v systéme Windows. Tento cmdlet neťahá objekty sprevádzajúce entity, ale prijíma informácie vo forme jednoduchého objektu s jednoduchými dátovými typmi. V mnohých prípadoch to vyjde rýchlejšie.

Ďalej stručne poviem o každom nástroji a ukážem príklady použitia. Poďme analyzovať konkrétne skripty a uvidíme, kedy jeden funguje lepšie ako druhý. Choď!

Ako vytvoriť raketový zosilňovač pre skripty PowerCLI

Prvá fáza: Runspace

Runspace je teda určený na paralelné spracovanie úloh mimo hlavného modulu. Samozrejme, môžete spustiť ďalší proces, ktorý zaberie časť pamäte, procesora atď. Ak sa váš skript spustí za pár minút a zaberie gigabajt pamäte, s najväčšou pravdepodobnosťou nebudete Runspace potrebovať. Ale pre skripty pre desiatky tisíc objektov je to potrebné.

Učiť sa môžeš začať tu: 
Začiatok používania prevádzkových priestorov PowerShell: 1. časť

Čo prináša používanie Runspace:

  • rýchlosť obmedzením zoznamu vykonaných príkazov,
  • paralelné vykonávanie úloh,
  • bezpečnosť.

Tu je príklad z internetu, keď Runspace pomáha:

„Spor o úložisko je jednou z najťažších metrík na sledovanie vo vSphere. Vo vCenter sa nemôžete len tak pozrieť, ktorý virtuálny počítač spotrebúva viac úložných prostriedkov. Našťastie môžete tieto údaje zbierať v priebehu niekoľkých minút vďaka PowerShell.
Podelím sa o skript, ktorý umožní správcom systému VMware rýchlo vyhľadávať v celom vCenter a získať zoznam VM s údajmi o ich priemernej spotrebe.  
Skript používa prevádzkové priestory PowerShell, aby umožnil každému hostiteľovi ESXi zhromažďovať informácie o spotrebe z vlastných virtuálnych počítačov v samostatnom prevádzkovom priestore a okamžite hlásiť dokončenie. To umožňuje PowerShellu okamžite zatvárať úlohy, namiesto toho, aby iteroval cez hostiteľov a čakal, kým každý dokončí svoju požiadavku.

Zdroj: Ako zobraziť I/O virtuálneho stroja na ovládacom paneli ESXi

V nižšie uvedenom prípade už Runspace nie je užitočný:

„Snažím sa napísať skript, ktorý zhromažďuje veľa údajov z virtuálneho počítača a v prípade potreby zapisuje nové údaje. Problém je v tom, že existuje pomerne veľa VM a na jednom počítači sa strávi 5-8 sekúnd. 

Zdroj: Multithreading PowerCLI s RunspacePool

Tu budete potrebovať Get-View, prejdime na to. 

Druhá fáza: Get-View

Aby ste pochopili, prečo je funkcia Get-View užitočná, oplatí sa pripomenúť, ako vo všeobecnosti fungujú cmdlety. 

Cmdlets sú potrebné na pohodlné získavanie informácií bez toho, aby ste museli študovať referenčné knihy API a objavovať ďalšie koleso. To, čo za starých čias vyžadovalo sto alebo dva riadky kódu, vám PowerShell umožňuje urobiť jedným príkazom. Za tento komfort platíme rýchlosťou. Vo vnútri samotných cmdletov nie je žiadna mágia: rovnaký skript, ale na nižšej úrovni, napísaný šikovnými rukami majstra zo slnečnej Indie.

Teraz, na porovnanie s Get-View, zoberme cmdlet Get-VM: pristupuje k virtuálnemu stroju a vracia zložený objekt, to znamená, že k nemu pripája ďalšie súvisiace objekty: VMHost, Datastore atď.  

Get-View na svojom mieste nepridáva do vráteného objektu nič zbytočné. Navyše nám umožňuje presne špecifikovať, aké informácie potrebujeme, čo uľahčí výstupný objekt. V systéme Windows Server vo všeobecnosti a konkrétne v Hyper-V je cmdlet Get-WMIObject priamym analógom - myšlienka je úplne rovnaká.

Get-View je nepohodlné pre rutinné operácie s bodovými objektmi. Ale keď ide o tisíce a desaťtisíce predmetov, nemá to cenu.

Viac si môžete prečítať na blogu VMware: Úvod do Get-View

Teraz vám všetko ukážem na skutočnom prípade. 

Písanie skriptu na uvoľnenie virtuálneho počítača

Jedného dňa ma môj kolega požiadal, aby som mu optimalizoval scenár. Úloha je bežná rutina: nájsť všetky VM s duplicitným parametrom cloud.uuid (áno, je to možné pri klonovaní VM v vCloud Director). 

Jasné riešenie, ktoré prichádza na myseľ, je:

  1. Získajte zoznam všetkých virtuálnych počítačov.
  2. Zoznam nejako analyzujte.

Pôvodná verzia bola tento jednoduchý 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
}
# Далее РУКАМИ парсим полученный результат

Všetko je mimoriadne jednoduché a prehľadné. Dá sa napísať za pár minút s prestávkou na kávu. Naskrutkujte filtráciu a je hotovo.

Ale poďme merať čas:

Ako vytvoriť raketový zosilňovač pre skripty PowerCLI

Ako vytvoriť raketový zosilňovač pre skripty PowerCLI

2 minút 47 sekúnd pri spracovaní takmer 10 XNUMX VM. Bonusom je absencia filtrov a nutnosť manuálneho triedenia výsledkov. Je zrejmé, že skript vyžaduje optimalizáciu.

Runspaces sú prvé, ktoré prídu na pomoc, keď potrebujete súčasne získať metriky hostiteľa z vCenter alebo potrebujete spracovať desiatky tisíc objektov. Pozrime sa, čo tento prístup prináša.

Zapnite prvú rýchlosť: PowerShell Runspaces

Prvá vec, ktorá príde na myseľ pri tomto skripte, je spustenie slučky nie sekvenčne, ale v paralelných vláknach, zhromaždenie všetkých údajov do jedného objektu a ich filtrovanie. 

Je tu však problém: PowerCLI nám nedovolí otvoriť veľa nezávislých relácií vo vCenter a vyvolá zábavnú chybu:

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.

Aby ste to vyriešili, musíte najprv v streame odovzdať informácie o relácii. Pripomeňme si, že PowerShell pracuje s objektmi, ktoré možno odovzdať ako parameter funkcii alebo bloku ScriptBlock. Prenesme reláciu vo forme takéhoto objektu, pričom obídeme $global:DefaultVIServers (Connect-VIServer s kľúčom -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 $_
           }
       }
   }

Teraz poďme implementovať multithreading cez Runspace Pools.  

Algoritmus je nasledujúci:

  1. Získame zoznam všetkých VM.
  2. V paralelných prúdoch dostaneme cloud.uuid.
  3. Zbierame dáta zo streamov do jedného objektu.
  4. Objekt filtrujeme tak, že ho zoskupíme podľa hodnoty poľa CloudUUID: tie, kde je počet jedinečných hodnôt väčší ako 1, sú VM, ktoré hľadáme.

V dôsledku toho dostaneme 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
}

Krása tohto skriptu je v tom, že ho možno použiť v iných podobných prípadoch jednoduchým nahradením ScriptBlock a parametrov, ktoré budú odovzdané streamu. Využite to!

Meriame čas:

Ako vytvoriť raketový zosilňovač pre skripty PowerCLI

55 sekúnd. Je to lepšie, ale stále to môže byť rýchlejšie. 

Prejdime k druhej rýchlosti: GetView

Poďme zistiť, čo je zlé.
Po prvé a predovšetkým, spustenie cmdlet Get-VM trvá dlho.
Po druhé, dokončenie cmdlet Get-AdvancedOptions trvá ešte dlhšie.
Poďme sa najprv zaoberať tým druhým. 

Get-AdvancedOptions je vhodný pre jednotlivé objekty VM, ale pri práci s mnohými objektmi je veľmi nemotorný. Rovnaké informácie môžeme získať zo samotného objektu virtuálneho stroja (Get-VM). Je to len dobre pochované v objekte ExtensionData. Vyzbrojení filtrovaním zrýchlime proces získavania potrebných údajov.

Pri miernom pohybe ruky 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}}

Zmení sa na toto:


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

Výstup je rovnaký ako pri Get-AdvancedOptions, ale funguje mnohonásobne rýchlejšie. 

Teraz k Get-VM. Nie je rýchly, pretože sa zaoberá zložitými objektmi. Vynára sa logická otázka: prečo v tomto prípade potrebujeme ďalšie informácie a obludný PSObject, keď nám stačí názov VM, jeho stav a hodnota zložitého atribútu?  

Okrem toho bola zo skriptu odstránená prekážka v podobe Get-AdvancedOptions. Používanie Runspace Pools sa teraz javí ako prehnané, pretože už nie je potrebné paralelizovať pomalú úlohu cez squatové vlákna pri odovzdávaní relácie. Nástroj je dobrý, ale nie pre tento prípad. 

Pozrime sa na výstup ExtensionData: nie je to nič iné ako objekt Get-View. 

Využime starodávnu techniku ​​majstrov PowerShellu: jeden riadok pomocou filtrov, triedenie a zoskupovanie. Všetok predchádzajúci horor je elegantne zbalený do jednej línie a vykonaný v jednej relácii:


$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

Meriame čas:

Ako vytvoriť raketový zosilňovač pre skripty PowerCLI

9 sekúnd pre takmer 10k objektov s filtrovaním podľa požadovaného stavu. Skvelé!

namiesto záveru

Prijateľný výsledok priamo závisí od výberu nástroja. Často je ťažké s istotou povedať, čo presne by sa malo zvoliť, aby sa to dosiahlo. Každá z uvedených metód na zrýchlenie skriptov je dobrá v medziach svojej použiteľnosti. Dúfam, že vám tento článok pomôže v neľahkej úlohe pochopiť základy automatizácie a optimalizácie procesov vo vašej infraštruktúre.

PS: Autor ďakuje všetkým členom komunity za pomoc a podporu pri príprave článku. Aj tie s labkami. A dokonca aj tí, ktorí nemajú nohy, ako boa constrictor.

Zdroj: hab.com

Pridať komentár