Sådan bygger du en raketbooster til PowerCLI-scripts
Før eller siden kommer enhver VMware-systemadministrator til at automatisere rutineopgaver. Det hele starter med kommandolinjen, derefter kommer PowerShell eller VMware PowerCLI.
Lad os sige, at du har mestret PowerShell lidt længere end at lancere ISE og bruge standard cmdlets fra moduler, der fungerer på grund af "en slags magi". Når du begynder at tælle virtuelle maskiner i hundredvis, vil du opdage, at scripts, der hjælper i lille skala, kører mærkbart langsommere i stor skala.
I denne situation vil 2 værktøjer hjælpe:
PowerShell Runspaces – en tilgang, der giver dig mulighed for at parallelisere udførelsen af processer i separate tråde;
Get-View – en grundlæggende PowerCLI-funktion, en analog af Get-WMIObject i Windows. Denne cmdlet trækker ikke objekter, der ledsager enheder, men modtager information i form af et simpelt objekt med simple datatyper. I mange tilfælde kommer det hurtigere ud.
Dernæst vil jeg kort fortælle om hvert værktøj og vise eksempler på brug. Lad os analysere specifikke scripts og se, hvornår det ene fungerer bedre end det andet. Gå!
Første etape: Runspace
Så Runspace er designet til parallel behandling af opgaver uden for hovedmodulet. Selvfølgelig kan du starte en anden proces, der vil æde noget hukommelse, processor osv. Hvis dit script kører på et par minutter og bruger en gigabyte hukommelse, har du højst sandsynligt ikke brug for Runspace. Men for scripts til titusindvis af objekter er det nødvendigt.
hastighed ved at begrænse listen over udførte kommandoer,
parallel udførelse af opgaver,
sikkerhed.
Her er et eksempel fra internettet, når Runspace hjælper:
"Storagekonflikter er en af de sværeste metrics at spore i vSphere. Inde i vCenter kan du ikke bare gå og se, hvilken VM der bruger flere lagerressourcer. Heldigvis kan du indsamle disse data på få minutter takket være PowerShell.
Jeg vil dele et script, der giver VMware-systemadministratorer mulighed for hurtigt at søge i vCenter og modtage en liste over VM'er med data om deres gennemsnitlige forbrug.
Scriptet bruger PowerShell-runspaces til at give hver ESXi-vært mulighed for at indsamle forbrugsoplysninger fra sine egne VM'er i et separat Runspace og straks rapportere færdiggørelse. Dette gør det muligt for PowerShell at lukke jobs med det samme, i stedet for at iterere gennem værter og vente på, at hver enkelt fuldfører sin anmodning."
I nedenstående tilfælde er Runspace ikke længere nyttig:
“Jeg forsøger at skrive et script, der samler en masse data fra en VM og skriver nye data, når det er nødvendigt. Problemet er, at der er ret mange VM'er, og der bruges 5-8 sekunder på én maskine."
Her skal du bruge Get-View, lad os gå videre til det.
Anden fase: Get-View
For at forstå, hvorfor Get-View er nyttigt, er det værd at huske, hvordan cmdlets fungerer generelt.
Cmdlets er nødvendige for bekvemt at få information uden at skulle studere API-opslagsbøger og genopfinde det næste hjul. Hvad der i gamle dage tog hundrede eller to linjer kode, giver PowerShell dig mulighed for at gøre med én kommando. Vi betaler for denne bekvemmelighed med hurtighed. Der er ingen magi inde i selve cmdlet'erne: det samme script, men på et lavere niveau, skrevet af de dygtige hænder fra en mester fra det solrige Indien.
Nu, til sammenligning med Get-View, lad os tage Get-VM cmdlet'en: den får adgang til den virtuelle maskine og returnerer et sammensat objekt, det vil sige, den vedhæfter andre relaterede objekter til det: VMHost, Datastore osv.
Get-View i stedet tilføjer ikke noget unødvendigt til det returnerede objekt. Desuden giver det os mulighed for nøje at specificere, hvilke oplysninger vi har brug for, hvilket vil gøre outputobjektet lettere. I Windows Server generelt og i Hyper-V i særdeleshed er Get-WMIObject cmdlet'en en direkte analog - ideen er nøjagtig den samme.
Get-View er ubelejligt til rutineoperationer på punktobjekter. Men når det kommer til tusinder og titusinder af genstande, har det ingen pris.
En dag bad min kollega mig om at optimere hans manuskript. Opgaven er en fælles rutine: find alle VM'er med en dublet cloud.uuid-parameter (ja, dette er muligt, når du kloner en VM i vCloud Director).
Den åbenlyse løsning, der kommer til at tænke på, er:
Få en liste over alle VM'er.
Parse listen på en eller anden måde.
Den originale version var dette simple script:
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
}
# Далее РУКАМИ парсим полученный результат
Alt er ekstremt enkelt og overskueligt. Det kan skrives på et par minutter med en kaffepause. Skru filtreringen på og det er færdigt.
Men lad os måle tiden:
2 minutter 47 sekunder ved behandling af næsten 10 VM'er. En bonus er fraværet af filtre og behovet for manuelt at sortere resultaterne. Det er klart, at scriptet kræver optimering.
Runspaces er de første, der kommer til undsætning, når du samtidig skal hente værtsmålinger fra vCenter eller skal behandle titusindvis af objekter. Lad os se, hvad denne tilgang bringer.
Slå den første hastighed til: PowerShell Runspaces
Den første ting, der kommer til at tænke på for dette script, er at udføre løkken ikke sekventielt, men i parallelle tråde, samle alle data i et objekt og filtrere det.
Men der er et problem: PowerCLI vil ikke tillade os at åbne mange uafhængige sessioner til vCenter og vil kaste en sjov fejl:
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.
For at løse dette skal du først sende sessionsoplysninger inde i streamen. Lad os huske, at PowerShell arbejder med objekter, der kan overføres som en parameter enten til en funktion eller til en ScriptBlock. Lad os videregive sessionen i form af et sådant objekt og omgå $global:DefaultVIServers (Forbind-VIServer med -NotDefault-nøglen):
$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 $_
}
}
}
Lad os nu implementere multithreading gennem Runspace Pools.
Algoritmen er følgende:
Vi får en liste over alle VM'er.
I parallelle strømme får vi cloud.uuid.
Vi samler data fra strømme til ét objekt.
Vi filtrerer objektet ved at gruppere det efter værdien af CloudUUID-feltet: dem, hvor antallet af unikke værdier er større end 1, er de VM'er, vi leder efter.
Som et resultat får vi scriptet:
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
}
Skønheden ved dette script er, at det kan bruges i andre lignende tilfælde ved blot at erstatte ScriptBlock og de parametre, der sendes til streamen. Udnyt det!
Vi måler tid:
55 sekunder. Det er bedre, men det kan stadig være hurtigere.
Lad os gå til den anden hastighed: GetView
Lad os finde ud af, hvad der er galt.
Først og fremmest tager Get-VM cmdlet'en lang tid at udføre.
For det andet tager Get-AdvancedOptions cmdlet'en endnu længere tid at fuldføre.
Lad os først behandle den anden.
Get-AdvancedOptions er praktisk til individuelle VM-objekter, men meget klodset, når du arbejder med mange objekter. Vi kan få den samme information fra selve det virtuelle maskinobjekt (Get-VM). Det er bare begravet godt i ExtensionData-objektet. Bevæbnet med filtrering fremskynder vi processen med at skaffe de nødvendige data.
Med en let bevægelse af hånden er dette:
VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}
Outputtet er det samme som Get-AdvancedOptions, men det virker mange gange hurtigere.
Nu til Get-VM. Det er ikke hurtigt, fordi det omhandler komplekse genstande. Et logisk spørgsmål opstår: hvorfor har vi brug for ekstra information og et monstrøst PSO-objekt i dette tilfælde, når vi bare har brug for navnet på VM'en, dens tilstand og værdien af en vanskelig attribut?
Derudover er forhindringen i form af Get-AdvancedOptions blevet fjernet fra scriptet. At bruge Runspace Pools virker nu som overkill, da der ikke længere er behov for at parallelisere en langsom opgave på tværs af squat-tråde, når du afleverer en session. Værktøjet er godt, men ikke til dette tilfælde.
Lad os se på outputtet af ExtensionData: det er intet andet end et Get-View-objekt.
Lad os kalde på PowerShell-mestrenes ældgamle teknik: én linje ved hjælp af filtre, sortering og gruppering. Al den tidligere rædsel er elegant kollapset i én linje og udført i én session:
9 sekunder for næsten 10k objekter med filtrering efter den ønskede tilstand. Store!
I stedet for en konklusion
Et acceptabelt resultat afhænger direkte af valget af værktøj. Det er ofte svært at sige med sikkerhed, hvad der præcist skal vælges for at opnå det. Hver af de anførte metoder til at fremskynde scripts er god inden for grænserne af dens anvendelighed. Jeg håber, at denne artikel vil hjælpe dig i den vanskelige opgave at forstå det grundlæggende i procesautomatisering og optimering i din infrastruktur.
PS: Forfatteren takker alle fællesskabsmedlemmer for deres hjælp og støtte til at forberede artiklen. Selv dem med poter. Og selv dem, der ikke har ben, som en boa constrictor.