Jak vytvořit raketový zesilovač pro skripty PowerCLI 

Dříve nebo později každý správce systému VMware přijde k automatizaci rutinních úloh. Vše začíná příkazovým řádkem, pak přichází PowerShell nebo VMware PowerCLI.

Řekněme, že jste zvládli PowerShell o něco dále než spouštění ISE a používání standardních cmdletů z modulů, které fungují díky „nějakému kouzlu“. Když začnete počítat virtuální stroje na stovky, zjistíte, že skripty, které pomáhají v malém měřítku, běží ve velkém znatelně pomaleji. 

V této situaci vám pomohou 2 nástroje:

  • PowerShell Runspaces – přístup, který umožňuje paralelizovat provádění procesů v samostatných vláknech; 
  • Get-View – základní funkce PowerCLI, obdoba Get-WMIObject ve Windows. Tato rutina nevytahuje objekty doprovázející entity, ale přijímá informace ve formě jednoduchého objektu s jednoduchými datovými typy. V mnoha případech to vyjde rychleji.

Dále stručně pohovořím o každém nástroji a ukážu příklady použití. Pojďme analyzovat konkrétní skripty a uvidíme, kdy jeden funguje lépe než druhý. Jít!

Jak vytvořit raketový zesilovač pro skripty PowerCLI

První fáze: Runspace

Runspace je tedy navržen pro paralelní zpracování úloh mimo hlavní modul. Samozřejmě můžete spustit další proces, který zabere paměť, procesor atd. Pokud se váš skript spustí za pár minut a zabere gigabajt paměti, s největší pravděpodobností nebudete Runspace potřebovat. Ale pro skripty pro desítky tisíc objektů je to potřeba.

Můžete se začít učit zde: 
Zahájení používání běhových prostorů PowerShellu: Část 1

Co použití Runspace dává:

  • rychlost omezením seznamu provedených příkazů,
  • paralelní provádění úkolů,
  • bezpečnostní.

Zde je příklad z internetu, kdy Runspace pomáhá:

„Spor o úložiště je jednou z nejobtížněji sledovatelných metrik ve vSphere. Uvnitř vCenter se nemůžete jen tak podívat, který virtuální počítač spotřebovává více prostředků úložiště. Naštěstí můžete tato data sbírat během několika minut díky PowerShellu.
Podělím se o skript, který umožní správcům systému VMware rychle vyhledávat v celém vCenter a získat seznam VM s údaji o jejich průměrné spotřebě.  
Skript používá běhové prostory PowerShellu, aby umožnil každému hostiteli ESXi shromažďovat informace o spotřebě z vlastních virtuálních počítačů v samostatném běhovém prostoru a okamžitě hlásit dokončení. To PowerShellu umožňuje okamžitě zavřít úlohy, místo aby procházelo hostiteli a čekalo, až každý dokončí svůj požadavek.

Zdroj: Jak zobrazit I/O virtuálního stroje na ESXi Dashboard

V níže uvedeném případě již Runspace není užitečný:

„Snažím se napsat skript, který shromažďuje spoustu dat z virtuálního počítače a v případě potřeby zapisuje nová data. Problém je v tom, že existuje poměrně hodně virtuálních počítačů a na jednom počítači se stráví 5–8 sekund.“ 

Zdroj: Multithreading PowerCLI s RunspacePool

Zde budete potřebovat Get-View, pojďme na to. 

Druhá fáze: Get-View

Abyste pochopili, proč je Get-View užitečný, stojí za to si připomenout, jak rutiny obecně fungují. 

Rutiny jsou potřebné k pohodlnému získávání informací bez nutnosti studovat referenční knihy API a znovu vymýšlet další kolo. To, co za starých časů vyžadovalo sto nebo dva řádky kódu, vám PowerShell umožňuje udělat jedním příkazem. Za tento komfort platíme rychlostí. V samotných cmdletech není žádné kouzlo: stejný skript, ale na nižší úrovni, napsaný šikovnýma rukama mistra ze slunné Indie.

Nyní, pro srovnání s Get-View, vezmeme rutinu Get-VM: přistupuje k virtuálnímu počítači a vrací složený objekt, to znamená, že k němu připojuje další související objekty: VMHost, Datastore atd.  

Get-View na jeho místě nepřidává k vrácenému objektu nic zbytečného. Navíc nám umožňuje přesně specifikovat, jaké informace potřebujeme, což usnadní výstupní objekt. V systému Windows Server obecně a konkrétně v Hyper-V je rutina Get-WMIObject přímým analogem – myšlenka je úplně stejná.

Get-View je nepohodlné pro rutinní operace s bodovými objekty. Ale když jde o tisíce a desetitisíce předmětů, nemá to cenu.

Více si můžete přečíst na blogu VMware: Úvod do Get-View

Nyní vám vše ukážu na skutečném případu. 

Psaní skriptu pro uvolnění virtuálního počítače

Jednoho dne mě můj kolega požádal, abych optimalizoval jeho skript. Úkol je běžná rutina: najít všechny virtuální počítače s duplicitním parametrem cloud.uuid (ano, to je možné při klonování virtuálního počítače ve vCloud Director). 

Jasné řešení, které mě napadá, je:

  1. Získejte seznam všech virtuálních počítačů.
  2. Seznam nějak rozebrat.

Původní verze byl 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še je extrémně jednoduché a přehledné. Dá se to napsat za pár minut s přestávkou na kávu. Našroubujte filtraci a je hotovo.

Ale pojďme měřit čas:

Jak vytvořit raketový zesilovač pro skripty PowerCLI

Jak vytvořit raketový zesilovač pro skripty PowerCLI

2 minuty 47 sekund při zpracování téměř 10k VM. Bonusem je absence filtrů a nutnost ručního řazení výsledků. Je zřejmé, že skript vyžaduje optimalizaci.

Runspace jsou první, kdo přijdou na pomoc, když potřebujete současně získat metriky hostitele z vCenter nebo potřebujete zpracovat desítky tisíc objektů. Pojďme se podívat, co tento přístup přináší.

Zapněte první rychlost: PowerShell Runspaces

První věc, která vás u tohoto skriptu napadne, je provést smyčku ne sekvenčně, ale v paralelních vláknech, shromáždit všechna data do jednoho objektu a filtrovat je. 

Ale je tu problém: PowerCLI nám nedovolí otevřít mnoho nezávislých relací ve vCenter a vyvolá vtipnou 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.

Chcete-li to vyřešit, musíte nejprve předat informace o relaci uvnitř proudu. Připomeňme, že PowerShell pracuje s objekty, které lze předat jako parametr buď funkci, nebo ScriptBlocku. Předejme relaci ve formě takového objektu, vynecháme $global:DefaultVIServers (Connect-VIServer s klíč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 $_
           }
       }
   }

Nyní implementujme multithreading prostřednictvím Runspace Pools.  

Algoritmus je následující:

  1. Získáme seznam všech virtuálních počítačů.
  2. V paralelních tocích dostáváme cloud.uuid.
  3. Shromažďujeme data ze streamů do jednoho objektu.
  4. Objekt filtrujeme jeho seskupením podle hodnoty pole CloudUUID: ty, kde je počet jedinečných hodnot větší než 1, jsou virtuální počítače, které hledá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 tohoto skriptu spočívá v tom, že jej lze použít v jiných podobných případech jednoduchým nahrazením ScriptBlock a parametrů, které budou předány streamu. Využijte toho!

Měříme čas:

Jak vytvořit raketový zesilovač pro skripty PowerCLI

55 sekund. Je to lepší, ale pořád to může být rychlejší. 

Přejdeme k druhé rychlosti: GetView

Pojďme zjistit, co je špatně.
Za prvé a především, rutina Get-VM trvá dlouho, než se spustí.
Za druhé, dokončení rutiny Get-AdvancedOptions trvá ještě déle.
Pojďme se nejprve zabývat tím druhým. 

Get-AdvancedOptions je vhodný pro jednotlivé objekty virtuálních počítačů, ale při práci s mnoha objekty je velmi neohrabaný. Stejné informace můžeme získat ze samotného objektu virtuálního stroje (Get-VM). Je jen dobře pohřben v objektu ExtensionData. Vyzbrojeni filtrováním urychlujeme proces získávání potřebných dat.

Lehkým pohybem 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}}

Promění se v 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 stejný jako u Get-AdvancedOptions, ale funguje mnohonásobně rychleji. 

Nyní k Get-VM. Není rychlý, protože se zabývá složitými objekty. Nabízí se logická otázka: proč v tomto případě potřebujeme další informace a monstrózní PSObject, když nám stačí jméno VM, jeho stav a hodnota ošidného atributu?  

Navíc byla ze skriptu odstraněna překážka v podobě Get-AdvancedOptions. Používání Runspace Pools se nyní zdá být přehnané, protože již není potřeba paralelizovat pomalou úlohu napříč squatovými vlákny při předávání relace. Nástroj je dobrý, ale ne pro tento případ. 

Podívejme se na výstup ExtensionData: není to nic jiného než objekt Get-View. 

Zavolejme na starodávnou techniku ​​mistrů PowerShellu: jeden řádek pomocí filtrů, řazení a seskupování. Všechny předchozí horory jsou elegantně sbaleny do jedné linie a provedeny v jedné relaci:


$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

Měříme čas:

Jak vytvořit raketový zesilovač pro skripty PowerCLI

9 sekund pro téměř 10k objektů s filtrováním podle požadované podmínky. Skvělý!

Místo závěru

Přijatelný výsledek přímo závisí na výběru nástroje. Často je těžké s jistotou říci, co přesně je třeba zvolit, abychom toho dosáhli. Každá z uvedených metod pro zrychlení skriptů je dobrá v mezích své použitelnosti. Doufám, že vám tento článek pomůže v nelehkém úkolu pochopit základy automatizace a optimalizace procesů ve vaší infrastruktuře.

PS: Autor děkuje všem členům komunity za pomoc a podporu při přípravě článku. I ti s tlapkami. A dokonce i ti, kteří nemají nohy, jako hroznýš.

Zdroj: www.habr.com

Přidat komentář