Hogyan készítsünk rakétaerősítőt PowerCLI-szkriptekhez
Előbb-utóbb bármely VMware rendszergazda automatizálja a rutinfeladatokat. Minden a parancssorral kezdődik, majd jön a PowerShell vagy a VMware PowerCLI.
Tegyük fel, hogy egy kicsit jobban elsajátította a PowerShellt, mint az ISE elindítása és a szabványos parancsmagok használata olyan modulokból, amelyek „valamilyen varázslat” miatt működnek. Ha elkezdi több száz virtuális gépet számolni, azt fogja tapasztalni, hogy azok a szkriptek, amelyek kis léptékben segítenek, észrevehetően lassabban futnak nagy léptékben.
Ebben a helyzetben 2 eszköz segít:
PowerShell Runspaces – olyan megközelítés, amely lehetővé teszi a folyamatok külön szálon történő végrehajtásának párhuzamosítását;
Get-View – egy alap PowerCLI funkció, a Get-WMIObject analógja a Windows rendszerben. Ez a parancsmag nem húzza le az entitásokat kísérő objektumokat, hanem egyszerű objektumok formájában, egyszerű adattípusokkal fogadja az információkat. Sok esetben gyorsabban jön ki.
Ezután röviden beszélek az egyes eszközökről, és bemutatok használati példákat. Elemezzünk konkrét szkripteket, és nézzük meg, mikor működik jobban az egyik, mint a másik. Megy!
Első szakasz: Runspace
Tehát a Runspace-t a fő modulon kívüli feladatok párhuzamos feldolgozására tervezték. Természetesen elindíthat egy másik folyamatot, amely felemészti a memóriát, a processzort stb. Ha a szkript néhány percen belül lefut, és egy gigabájt memóriát fogyaszt, valószínűleg nem lesz szüksége Runspace-re. De több tízezer objektum szkriptjéhez szükség van rá.
sebesség a végrehajtott parancsok listájának korlátozásával,
párhuzamos feladatok végrehajtása,
biztonság.
Íme egy példa az internetről, amikor a Runspace segít:
„A tárolási versengés az egyik legnehezebben követhető mérőszám a vSphere-ben. A vCenteren belül nem nézheti meg, hogy melyik virtuális gép fogyaszt több tárhelyet. Szerencsére ezeket az adatokat percek alatt összegyűjtheti a PowerShellnek köszönhetően.
Megosztok egy szkriptet, amely lehetővé teszi a VMware rendszergazdák számára, hogy gyorsan kereshessenek a vCenterben, és megkapják a virtuális gépek listáját az átlagos fogyasztásuk adataival.
A szkript PowerShell-futási tereket használ annak érdekében, hogy minden ESXi-gazdagép fogyasztási információkat gyűjtsön a saját virtuális gépeiről egy külön futási területen, és azonnal jelentse a befejezést. Ez lehetővé teszi, hogy a PowerShell azonnal bezárja a feladatokat, ahelyett, hogy a gazdagépeken keresztül iterálna, és megvárná, amíg mindegyik teljesíti a kérését.
„Olyan szkriptet próbálok írni, amely sok adatot gyűjt egy virtuális gépről, és szükség esetén új adatokat ír. A probléma az, hogy elég sok virtuális gép van, és 5-8 másodpercet töltenek egy gépen.”
Ahhoz, hogy megértsük, miért hasznos a Get-View, érdemes megjegyezni, hogyan működnek általában a parancsmagok.
A parancsmagok szükségesek ahhoz, hogy kényelmesen szerezzenek információkat anélkül, hogy API referenciakönyveket kellene tanulmányozniuk és újra fel kellene találniuk a következő kereket. Ami a régi időkben száz-két sornyi kódot igényelt, a PowerShell egyetlen paranccsal megteheti. Gyorsasággal fizetünk ezért a kényelemért. Magukban a cmdletekben nincs varázslat: ugyanaz a szkript, de alacsonyabb szinten, egy napfényes Indiából származó mester ügyes kezei által.
Most a Get-View-val való összehasonlításhoz vegyük a Get-VM parancsmagot: hozzáfér a virtuális géphez, és egy összetett objektumot ad vissza, azaz más kapcsolódó objektumokat csatol hozzá: VMHost, Datastore stb.
A Get-View a helyén nem ad semmi feleslegeset a visszaadott objektumhoz. Ezenkívül lehetővé teszi számunkra, hogy szigorúan meghatározzuk, milyen információra van szükségünk, ami megkönnyíti a kimeneti objektumot. A Windows Server rendszerben általában, és különösen a Hyper-V-ben a Get-WMIObject parancsmag közvetlen analóg – az ötlet pontosan ugyanaz.
A Get-View kényelmetlen a pontobjektumokkal végzett rutinműveletek során. De ha több ezer és tízezer tárgyról van szó, annak nincs ára.
Egy nap a kollégám megkért, hogy optimalizáljam a forgatókönyvét. A feladat egy gyakori rutin: keresse meg az összes virtuális gépet, amelyek duplikált cloud.uuid paraméterrel rendelkeznek (igen, ez lehetséges, ha virtuális gépet klónoz a vCloud Directorban).
A kézenfekvő megoldás, ami eszembe jut:
Szerezze meg az összes virtuális gép listáját.
Elemezze valahogy a listát.
Az eredeti verzió a következő egyszerű szkript volt:
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
}
# Далее РУКАМИ парсим полученный результат
Minden rendkívül egyszerű és világos. Kávészünettel pár perc alatt megírható. Csavarja fel a szűrést és kész.
De mérjük az időt:
2 perc 47 másodperc közel 10 XNUMX virtuális gép feldolgozásakor. A bónusz a szűrők hiánya és az eredmények manuális rendezésének szükségessége. Nyilvánvaló, hogy a szkript optimalizálást igényel.
A Runspace-ek az elsők, amelyek segítenek, ha egyidejűleg gazdametrikákat kell beszereznie a vCentertől, vagy több tízezer objektumot kell feldolgoznia. Lássuk, mit hoz ez a megközelítés.
Kapcsolja be az első sebességet: PowerShell Runspaces
Az első dolog, ami eszünkbe jut ennél a szkriptnél, hogy a ciklust nem szekvenciálisan, hanem párhuzamos szálakon kell végrehajtani, az összes adatot egyetlen objektumba gyűjteni, és azt szűrni.
De van egy probléma: a PowerCLI nem engedi meg, hogy sok független munkamenetet nyissunk meg a vCenter számára, és vicces hibát dob:
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.
Ennek megoldásához először át kell adnia a munkamenet-információkat az adatfolyamon belül. Ne felejtsük el, hogy a PowerShell olyan objektumokkal dolgozik, amelyek paraméterként adhatók át akár egy függvénynek, akár egy ScriptBlocknak. Adjuk át a munkamenetet egy ilyen objektum formájában, a $global:DefaultVIServers megkerülésével (Connect-VIServer a -NotDefault kulccsal):
$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 $_
}
}
}
Most valósítsuk meg a többszálat a Runspace Pools-on keresztül.
Az algoritmus a következő:
Kapunk egy listát az összes virtuális gépről.
Párhuzamos folyamokban felhőt kapunk.uuid.
Az adatfolyamokból egyetlen objektumba gyűjtjük az adatokat.
Az objektumot a CloudUUID mező értéke alapján szűrjük: ahol az egyedi értékek száma nagyobb, mint 1, azok a keresett virtuális gépek.
Ennek eredményeként megkapjuk a szkriptet:
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
}
Ennek a szkriptnek az a szépsége, hogy más hasonló esetekben is használható, ha egyszerűen lecseréli a ScriptBlock-ot és a folyamnak átadandó paramétereket. Használd ki!
Időt mérünk:
55 másodperc. Ez jobb, de még mindig lehet gyorsabb.
Térjünk át a második sebességre: GetView
Találjuk ki, mi a baj.
Mindenekelőtt a Get-VM parancsmag végrehajtása hosszú ideig tart.
Másodszor, a Get-AdvancedOptions parancsmag elkészítése még tovább tart.
Először foglalkozzunk a másodikkal.
A Get-AdvancedOptions kényelmes az egyes virtuálisgép-objektumok számára, de nagyon ügyetlen, ha sok objektummal dolgozik. Ugyanezt az információt megkaphatjuk magából a virtuális gép objektumból (Get-VM). Csak jól el van temetve az ExtensionData objektumban. Szűréssel felvértezve felgyorsítjuk a szükséges adatok beszerzésének folyamatát.
Egy enyhe kézmozdulattal ez:
VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}
A kimenet ugyanaz, mint a Get-AdvancedOptions, de sokszor gyorsabban működik.
Most a Get-VM-hez. Nem gyors, mert összetett tárgyakkal foglalkozik. Felmerül egy logikus kérdés: miért van szükségünk ebben az esetben többletinformációra és egy szörnyű PSObjectre, amikor csak a virtuális gép nevére, állapotára és egy trükkös attribútum értékére van szükségünk?
Ezenkívül a Get-AdvancedOptions formájában lévő akadályt eltávolították a szkriptből. A Runspace Pools használata most túlzásnak tűnik, mivel a munkamenet átadásakor már nincs szükség a lassú feladatok párhuzamosítására a squat szálak között. Az eszköz jó, de nem erre az esetre.
Nézzük az ExtensionData kimenetét: ez nem más, mint egy Get-View objektum.
Hívjuk segítségül a PowerShell mesterek ősi technikáját: egy sor szűrőket, rendezést és csoportosítást. Az összes korábbi horror elegánsan egy sorba van összecsukva és egy munkamenetben végrehajtva:
9 másodperc közel 10 XNUMX objektumhoz a kívánt feltétel szerinti szűréssel. Nagy!
Ahelyett, hogy egy következtetés
Az elfogadható eredmény közvetlenül függ az eszköz megválasztásától. Gyakran nehéz biztosan megmondani, hogy pontosan mit kell választani ennek eléréséhez. A szkriptek felgyorsítására felsorolt módszerek mindegyike jó az alkalmazhatóság határain belül. Remélem, ez a cikk segít abban a nehéz feladatban, hogy megértse az infrastruktúra folyamatautomatizálásának és optimalizálásának alapjait.
PS: A szerző megköszöni a közösség minden tagjának segítségét és támogatását a cikk elkészítésében. Még azok is, akiknek mancsuk van. És még azok is, akiknek nincs lába, mint egy boa-szűkítő.