Vroeg of laat begint elke VMware-systeembeheerder met het automatiseren van routinetaken. Het begint allemaal met de opdrachtregel, daarna komt PowerShell of VMware PowerCLI.
Stel dat je PowerShell wat beter beheerst dan het opstarten van de ISE en dat je standaard-cmdlets van modules gebruikt die via "een beetje magie" werken. Wanneer u honderden virtuele machines gaat tellen, zult u merken dat de scripts die op kleine schaal goed werkten, op grote schaal merkbaar langzamer werken.
In deze situatie zijn er twee hulpmiddelen die kunnen helpen:
- PowerShell-runspaces – een aanpak die het mogelijk maakt om de uitvoering van processen in afzonderlijke threads te paralleliseren;
- Get-View – een basisfunctie van PowerCLI, analoog aan Get-WMIObject in WindowsDeze cmdlet haalt geen gerelateerde entiteiten op, maar haalt informatie op als een eenvoudig object met eenvoudige gegevenstypen. Dit is in veel gevallen sneller.
Hieronder zal ik kort iets over elk gereedschap vertellen en voorbeelden van het gebruik ervan laten zien. Laten we eens naar specifieke scripts kijken en bepalen wanneer de ene beter werkt dan de andere. Laten we gaan!

Eerste fase: Runspace
Runspace is daarom ontworpen voor parallelle verwerking van taken buiten de hoofdmodule. U kunt natuurlijk ook een ander proces starten dat geheugen, processor, etc. gebruikt. Als uw script in een paar minuten wordt uitgevoerd en een gigabyte aan geheugen gebruikt, hebt u waarschijnlijk geen Runspace nodig. Maar voor scripts voor tienduizenden objecten is het nodig.
Hier kunt u beginnen met leren:
Wat levert Runspace u op:
- snelheid door de lijst met uitgevoerde opdrachten te beperken,
- parallelle uitvoering van taken,
- veiligheid.
Hier is een voorbeeld van internet waar Runspace helpt:
Opslagconflicten zijn een van de statistieken die moeilijk te volgen zijn in vSphere. Binnen vCenter kun je niet alleen kijken welke VM de meeste opslagresources verbruikt. Gelukkig kun je deze gegevens binnen enkele minuten verzamelen met PowerShell.
Ik ga een script delen waarmee VMware-systeembeheerders snel in het hele vCenter kunnen zoeken en een lijst met virtuele machines kunnen opvragen, met gegevens over hun gemiddelde verbruik.
Het script maakt gebruik van PowerShell-runspaces om ervoor te zorgen dat elke ESXi-host verbruiksinformatie van zijn eigen VM's in een aparte Runspace verzamelt en direct de voltooiing meldt. Hierdoor kan PowerShell taken onmiddellijk sluiten, in plaats van dat de hosts één voor één worden doorlopen en moet worden gewacht tot elke host zijn verzoek heeft voltooid.Bron:
In het onderstaande geval is Runspace niet langer relevant:
Ik probeer een script te schrijven dat veel data van een virtuele machine verzamelt en indien nodig nieuwe data schrijft. Het probleem is dat er nogal wat virtuele machines zijn en dat elke machine 5-8 seconden nodig heeft.
Bron:
Hier hebben we Get-View nodig, laten we daarmee verdergaan.
Tweede stap: Get-View
Om te begrijpen waarom Get-View nuttig is, is het goed om te weten hoe cmdlets in het algemeen werken.
Cmdlets zijn nodig om op een eenvoudige manier informatie te verkrijgen zonder dat u API-referenties hoeft te bestuderen en het wiel opnieuw hoeft uit te vinden. Wat vroeger honderd of twee regels aan code kostte, kunt u met PowerShell met één opdracht doen. Wij betalen voor dit gemak met snelheid. Er zit geen magie in de cmdlets zelf: het is hetzelfde script, maar dan op een lager niveau, geschreven door de bekwame handen van een meester uit het zonnige India.
Ter vergelijking met Get-View gebruiken we de cmdlet Get-VM. Deze cmdlet benadert een virtuele machine en retourneert een samengesteld object, dat wil zeggen dat het andere, gerelateerde objecten eraan koppelt: VMHost, Datastore, enzovoort.
Get-View voegt niets extra's toe aan het geretourneerde object. Bovendien stelt het ons in staat om precies aan te geven welke informatie we nodig hebben, wat het uitvoerobject zal vereenvoudigen. Windows Server In het algemeen, en met name in Hyper-V, is de cmdlet Get-WMIObject een directe analogie – het idee is absoluut hetzelfde.
Get-View is onhandig voor routinematige bewerkingen op puntobjecten. Maar als het om duizenden en tienduizenden objecten gaat, is het onbetaalbaar.
Meer informatie vindt u in de VMware-blog:
Ik ga nu alles aan de hand van een echte case laten zien.
We schrijven een script voor het ontladen van een virtuele machine
Op een dag vroeg mijn collega mij om zijn script te optimaliseren. Het is een routinetaak: alle virtuele machines met een dubbele cloud.uuid-parameter vinden (ja, dit is mogelijk bij het klonen van virtuele machines in vCloud Director).
De voor de hand liggende oplossing die in me opkomt is:
- Ontvang een lijst met alle VM's.
- Probeer de lijst op de een of andere manier te ontleden.
De originele versie was een eenvoudig script, zoals dit:
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
}
# Далее РУКАМИ парсим полученный результатAlles is uiterst eenvoudig en duidelijk. Je kunt het in een paar minuten schrijven, eventueel met een koffiepauze erbij. Filter erop draaien en klaar.
Maar laten we de tijd eens meten:

![]()
2 minuten 47 seconden bij het verwerken van bijna 10 VM's. Een voordeel is dat er geen filters zijn en dat u de resultaten niet handmatig hoeft te sorteren. Het is duidelijk dat het script geoptimaliseerd moet worden.
Runspaces zijn de eerste hulpmiddelen als u gelijktijdig hoststatistieken van vCenter moet ophalen of wanneer u tienduizenden objecten moet verwerken. Laten we eens kijken wat deze aanpak oplevert.
De eerste snelheid inschakelen: PowerShell Runspaces
Het eerste waar ik aan denk bij dit script is om de lus niet sequentieel, maar in parallelle stromen uit te voeren, alle data in één object te verzamelen en deze te filteren.
Maar er is een probleem: PowerCLI staat niet toe dat we meerdere onafhankelijke sessies naar vCenter openen en genereert een vreemde foutmelding:
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.Om dit op te lossen, moet u eerst informatie over de sessie aan de thread doorgeven. Houd er rekening mee dat PowerShell werkt met objecten die als parameter aan een functie of ScriptBlock kunnen worden doorgegeven. Laten we de sessie in de vorm van zo'n object doorgeven, waarbij we $global:DefaultVIServers omzeilen (Connect-VIServer met de sleutel -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 $_
}
}
}Laten we nu multithreading implementeren via Runspace Pools.
Het algoritme is als volgt:
- We krijgen een lijst met alle VM's.
- In parallelle stromen krijgen we cloud.uuid.
- Wij verzamelen gegevens uit stromen in één object.
- We filteren het object door te groeperen op de waarde van het veld CloudUUID: de objecten waarbij het aantal unieke waarden groter is dan 1, zijn de gewenste VM's.
Als resultaat krijgen we het script:
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
}Het mooie van dit script is dat het in andere, vergelijkbare gevallen gebruikt kan worden door simpelweg het ScriptBlock en de parameters die aan de stream worden doorgegeven, te vervangen. Maak er gebruik van!
Laten we de tijd meten:

55 seconden. Het is al beter, maar het kan nog sneller.
Overstappen naar de tweede snelheid: GetView
Laten we eens kijken wat er mis is.
Allereerst duurt het uitvoeren van de cmdlet Get-VM erg lang.
Ten tweede duurt het nog langer om de cmdlet Get-AdvancedOptions uit te voeren.
Laten we eerst de tweede behandelen.
Get-AdvancedOptions is handig voor individuele VM-objecten, maar is erg omslachtig bij het werken met veel objecten. We kunnen dezelfde informatie verkrijgen van het virtuele machineobject zelf (Get-VM). Het zit gewoon goed verstopt in het ExtensionData-object. Met behulp van filtering versnellen we het proces om de benodigde gegevens te verkrijgen.
Met een handomdraai is dit:
VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}Verandert in dit:
$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}}De uitvoer is hetzelfde als Get-AdvancedOptions, maar het werkt veel sneller.
Nu naar Get-VM. Het wordt niet snel uitgevoerd, omdat het om complexe objecten gaat. Dan rijst de logische vraag: waarom hebben we in dit geval extra informatie en een monsterlijk PSObject nodig, terwijl we alleen de naam van de virtuele machine, de status ervan en de waarde van het tricky kenmerk nodig hebben?
Bovendien is de vertraging in de vorm van Get-AdvancedOptions verdwenen uit het script. Het gebruik van Runspace Pools lijkt nu overbodig, aangezien het niet langer nodig is om een langzame taak over meerdere threads te paralleliseren met squats bij het doorgeven van een sessie. Het hulpmiddel is goed, maar niet geschikt voor dit geval.
Laten we eens kijken naar de ExtensionData-uitvoer: het is niets meer dan een Get-View-object.
Laten we een oude techniek van de PowerShell-meesters aanwenden: één regel die gebruikmaakt van filters, sorteren en groeperen. Alle voorgaande horror is op elegante wijze samengevat in één regel en uitgevoerd in één sessie:
$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 GroupLaten we de tijd meten:

9 seconden voor bijna 10k objecten met filtering op de gewenste voorwaarde. Geweldig!
In plaats Output
Het acceptabele resultaat hangt direct af van de keuze van het gereedschap. Vaak is het moeilijk om met zekerheid te zeggen wat je precies moet kiezen om dat doel te bereiken. Elk van de genoemde methoden voor het versnellen van scripts is goed binnen de grenzen van zijn toepasbaarheid. Ik hoop dat dit artikel u helpt bij de moeilijke taak om de basisbeginselen van procesautomatisering en -optimalisatie in uw infrastructuur te begrijpen.
PS: De auteur dankt alle deelnemers aan de commune voor hun hulp en steun bij het voorbereiden van het artikel. Zelfs die met poten. En zelfs degenen die geen poten hebben, zoals een boa constrictor.
Bron: www.habr.com
