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 videre end blot at starte ISE og bruge standard cmdlets fra moduler, der fungerer gennem "noget magi". Når du begynder at tælle virtuelle maskiner i hundredvis, vil du opdage, at de scripts, der fungerede godt i små skalaer, fungerer mærkbart langsommere på store maskiner.
I denne situation vil 2 værktøjer hjælpe:
- PowerShell-runspaces – en tilgang, der tillader parallelisering af udførelsen af processer i separate tråde;
- Hent visning – en grundlæggende PowerCLI-funktion, analog med Get-WMIObject i WindowsDenne cmdlet henter ikke relaterede enheder, men henter information som et simpelt objekt med simple datatyper. Dette er hurtigere i mange tilfælde.
Dernæst vil jeg kort gennemgå hvert værktøj og vise eksempler på brug. Lad os se på specifikke scripts og se, hvornår det ene fungerer bedre end det andet. Lad os gå!

Første fase: Runspace
Så Runspace er designet til parallel behandling af opgaver uden for hovedmodulet. Du kan selvfølgelig starte en anden proces, der vil bruge noget hukommelse, processor osv. Hvis dit script kører på et par minutter og bruger en gigabyte hukommelse, behøver du højst sandsynligt ikke Runspace. Men for scripts til titusindvis af objekter er det nødvendigt.
Du kan begynde at lære her:
Hvad giver brugen af Runspace dig:
- hastighed ved at begrænse listen over udførte kommandoer,
- parallel udførelse af opgaver,
- sikkerhed.
Her er et eksempel fra internettet, hvor Runspace hjælper:
"Lagerkonflikt er en af de målinger, der er svære at spore i vSphere. I vCenter kan man ikke bare se på, hvilken VM der bruger flest lagerressourcer. Heldigvis kan man indsamle disse data på få minutter ved hjælp af PowerShell."
Jeg vil dele et script, der giver VMware-systemadministratorer mulighed for hurtigt at søge på tværs af hele vCenter og få en liste over VM'er med data om deres gennemsnitlige forbrug.
Scriptet bruger PowerShell-runspaces til at få hver ESXi-vært til 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 job med det samme, i stedet for at skulle gennemgå værter én efter én og vente på, at hver enkelt fuldfører sin anmodning.Kilde:
I nedenstående tilfælde er Runspace ikke længere relevant:
"Jeg prøver at skrive et script, der indsamler en masse data fra en VM og skriver nye data, når det er nødvendigt. Problemet er, at der er en hel del VM'er, og det tager 5-8 sekunder for hver maskine."
Kilde:
Her skal vi bruge Get-View, lad os gå videre til det.
Andet trin: Get-View
For at forstå hvorfor Get-View er nyttigt, er det værd at huske på, hvordan cmdlets fungerer generelt.
Cmdlets er nødvendige for nemt at indhente information uden at skulle studere API-referencer og genopfinde hjulet. Hvad der i gamle dage ville have krævet hundrede eller to linjer kode, giver PowerShell dig mulighed for at gøre med én kommando. Vi betaler for denne bekvemmelighed med hastighed. Der er ingen magi inde i selve cmdlet'erne: det samme script, men på et lavere niveau, skrevet af de dygtige hænder af en mester fra det solrige Indien.
For at sammenligne med Get-View, lad os nu tage Get-VM cmdlet'en: den tilgår en virtuel maskine og returnerer et sammensat objekt, det vil sige, den vedhæfter andre relaterede objekter til den: VMHost, Datastore osv.
Get-View tilføjer ikke noget ekstra til det returnerede objekt. Desuden giver det os mulighed for præcist at specificere, hvilke oplysninger vi har brug for, hvilket vil forenkle outputobjektet. Windows Server Generelt, og især i Hyper-V, er Get-WMIObject-cmdlet'en en direkte analog – ideen er fuldstændig den samme.
Get-View er upraktisk til rutinemæssige operationer på punktobjekter. Men når det kommer til tusinder og titusindvis af genstande, er det uvurderligt.
Du kan læse mere på VMware-bloggen:
Nu vil jeg vise alt ved hjælp af en rigtig case.
Vi skriver et script til at aflæse en VM
En dag bad min kollega mig om at optimere hans manuskript. Opgaven er rutinemæssig: find alle VM'er med en duplikat af cloud.uuid-parameteren (ja, dette er muligt, når man kloner VM'er i vCloud Director).
Den oplagte løsning, der falder mig ind, er:
- Hent en liste over alle VM'er.
- Få listen analyseret på en eller anden måde.
Den originale version var et simpelt script som dette:
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 klart. Den kan skrives på et par minutter med en kaffepause. Skru filteret på, og så er du færdig.
Men lad os måle tiden:

![]()
2 minutter 47 sekunder når man behandler næsten 10 VM'er. En bonus er fraværet af filtre og behovet for manuelt at sortere resultaterne. Det er tydeligt, at scriptet trænger til optimering.
Runspaces er de første, der kommer til undsætning, når du har brug for at hente værtsmålinger fra vCenter samtidigt eller har brug for at behandle titusindvis af objekter. Lad os se, hvad denne tilgang vil give.
Tænder den første hastighed: PowerShell Runspaces
Det første, der falder én ind i forbindelse med dette script, er at udføre løkken ikke sekventielt, men i parallelle strømme, samle alle data i ét objekt og filtrere det.
Men der er et problem: PowerCLI tillader os ikke at åbne flere uafhængige sessioner til vCenter og giver 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 oplysninger om sessionen til tråden. Husk at PowerShell fungerer med objekter, der kan sendes som en parameter til enten en funktion eller en ScriptBlock. Lad os udføre sessionen i form af et sådant objekt, der omgår $global:DefaultVIServers (Connect-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 via 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 indsamler data fra strømme i ét objekt.
- Vi filtrerer objektet ved at gruppere efter værdien af CloudUUID-feltet: dem, hvor antallet af unikke værdier er større end 1, er de ønskede VM'er.
Som følge heraf 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
}
Det smukke ved dette script er, at det kan bruges i andre lignende tilfælde ved blot at erstatte ScriptBlock og de parametre, der vil blive sendt til strømmen. Udnyt det!
Lad os måle tiden:

55 sekunder. Det er allerede bedre, men det kunne stadig være hurtigere.
Går videre til 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 køre.
Lad os behandle den anden først.
Get-AdvancedOptions er praktisk på individuelle VM-objekter, men er meget besværligt, når man arbejder med mange objekter. Vi kan få de samme oplysninger fra selve det virtuelle maskinobjekt (Get-VM). Den er bare godt begravet i ExtensionData-objektet. Bevæbnet med filtrering fremskynder vi processen med at indhente de nødvendige data.
Med et håndledsvip 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}}
Bliver til dette:
$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}}
Outputtet er det samme som Get-AdvancedOptions, men det fungerer meget hurtigere.
Nu til Get-VM. Den udføres ikke hurtigt, da den omhandler komplekse objekter. Et logisk spørgsmål opstår: hvorfor har vi brug for ekstra information og et uhyrligt PSObject i dette tilfælde, når alt, hvad vi behøver, er navnet på den virtuelle maskine, dens tilstand og værdien af den vanskelige attribut?
Derudover har scriptet mistet afmatningen i form af Get-AdvancedOptions. Brug af Runspace Pools virker nu overflødig, da der ikke længere er behov for at parallelisere en langsom opgave på tværs af tråde med squat, når en session gennemføres. Værktøjet er godt, men ikke til dette tilfælde.
Lad os se på ExtensionData-outputtet: det er ikke andet end et Get-View-objekt.
Lad os benytte os af en gammel teknik fra PowerShell-mestrene: én linje med filtre, sortering og gruppering. Al den foregående rædsel er elegant samlet i én linje og udført i én session:
$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
Lad os måle tiden:

9 sekunder for næsten 10 objekter med filtrering efter den krævede betingelse. Stor!
I stedet for en konklusion
Det acceptable 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 rammerne af dens anvendelighed. Jeg håber, at denne artikel vil hjælpe dig med den vanskelige opgave at forstå det grundlæggende i procesautomatisering og -optimering i din infrastruktur.
PS: Forfatteren takker alle deltagere i kommunen for deres hjælp og støtte i forbindelse med udarbejdelsen af artiklen. Selv dem med poter. Og selv dem der ikke har poter, som en boa constrictor.
Kilde: www.habr.com
