Come costruire un razzo per gli script PowerCLI 

Prima o poi, qualsiasi amministratore di sistema VMware arriva ad automatizzare le attività di routine. Tutto inizia con la riga di comando, poi arriva PowerShell o VMware PowerCLI.

Supponiamo che tu abbia padroneggiato PowerShell un po' oltre il lancio di ISE e l'utilizzo di cmdlet standard da moduli che funzionano grazie a "una sorta di magia". Quando inizi a contare centinaia di macchine virtuali, scoprirai che gli script che aiutano su piccola scala funzionano notevolmente più lentamente su larga scala. 

In questa situazione, 2 strumenti ti aiuteranno:

  • Spazi di esecuzione di PowerShell – un approccio che permette di parallelizzare l’esecuzione dei processi in thread separati; 
  • Ottieni-Visualizza – una funzione PowerCLI di base, un analogo di Get-WMIObject in Windows. Questo cmdlet non estrae oggetti che accompagnano le entità, ma riceve informazioni sotto forma di un oggetto semplice con tipi di dati semplici. In molti casi esce più velocemente.

Successivamente, parlerò brevemente di ciascuno strumento e mostrerò esempi di utilizzo. Analizziamo script specifici e vediamo quando uno funziona meglio dell'altro. Andare!

Come costruire un razzo per gli script PowerCLI

Prima fase: Runspace

Pertanto, Runspace è progettato per l'elaborazione parallela di attività esterne al modulo principale. Naturalmente, puoi avviare un altro processo che consumerà memoria, processore, ecc. Se il tuo script viene eseguito in un paio di minuti e consuma un gigabyte di memoria, molto probabilmente non avrai bisogno di Runspace. Ma per gli script per decine di migliaia di oggetti è necessario.

Puoi iniziare a imparare qui: 
Inizio dell'uso dei runspace di PowerShell: parte 1

Cosa offre l'utilizzo di Runspace:

  • velocità limitando l'elenco dei comandi eseguiti,
  • esecuzione parallela di compiti,
  • sicurezza.

Ecco un esempio da Internet in cui Runspace aiuta:

“Il conflitto di storage è uno dei parametri più difficili da monitorare in vSphere. All'interno di vCenter, non puoi semplicemente andare a vedere quale VM consuma più risorse di storage. Fortunatamente, puoi raccogliere questi dati in pochi minuti grazie a PowerShell.
Condividerò uno script che consentirà agli amministratori di sistema VMware di effettuare rapidamente ricerche in vCenter e ricevere un elenco di VM con i dati sul loro consumo medio.  
Lo script utilizza gli spazi di esecuzione di PowerShell per consentire a ciascun host ESXi di raccogliere informazioni sul consumo dalle proprie VM in uno spazio di esecuzione separato e segnalare immediatamente il completamento. Ciò consente a PowerShell di chiudere immediatamente i lavori, anziché scorrere gli host e attendere che ciascuno completi la propria richiesta.

Fonte: Come mostrare l'I/O della macchina virtuale su una dashboard ESXi

Nel caso seguente, Runspace non è più utile:

“Sto cercando di scrivere uno script che raccolga molti dati da una VM e scriva nuovi dati quando necessario. Il problema è che ci sono molte VM e su una macchina vengono spesi 5-8 secondi”. 

Fonte: PowerCLI multithread con RunspacePool

Qui avrai bisogno di Get-View, passiamo ad esso. 

Seconda fase: Get-View

Per capire perché Get-View è utile, vale la pena ricordare come funzionano i cmdlet in generale. 

I cmdlet sono necessari per ottenere comodamente informazioni senza la necessità di studiare libri di riferimento sulle API e reinventare la ruota successiva. Ciò che ai vecchi tempi richiedeva cento o due righe di codice, PowerShell ti consente di farlo con un solo comando. Paghiamo questa comodità con velocità. Non c'è magia all'interno dei cmdlet stessi: la stessa sceneggiatura, ma a un livello inferiore, scritta dalle abili mani di un maestro della soleggiata India.

Ora, per fare un confronto con Get-View, prendiamo il cmdlet Get-VM: accede alla macchina virtuale e restituisce un oggetto composito, cioè associa ad essa altri oggetti correlati: VMHost, Datastore, ecc.  

Get-View al suo posto non aggiunge nulla di non necessario all'oggetto restituito. Inoltre, ci consente di specificare rigorosamente quali informazioni abbiamo bisogno, il che renderà più semplice l'oggetto di output. In Windows Server in generale e in Hyper-V in particolare, il cmdlet Get-WMIObject è un analogo diretto: l'idea è esattamente la stessa.

Get-View è scomodo per le operazioni di routine su oggetti punto. Ma quando si tratta di migliaia e decine di migliaia di oggetti, non ha prezzo.

Puoi leggere di più sul blog VMware: Introduzione a Get-View

Ora ti mostrerò tutto utilizzando un caso reale. 

Scrivere uno script per scaricare una VM

Un giorno il mio collega mi ha chiesto di ottimizzare la sua sceneggiatura. L'attività è una routine comune: trovare tutte le VM con un parametro cloud.uuid duplicato (sì, questo è possibile quando si clona una VM in vCloud Director). 

La soluzione ovvia che mi viene in mente è:

  1. Ottieni un elenco di tutte le VM.
  2. Analizza l'elenco in qualche modo.

La versione originale era questo semplice 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
}
# Далее РУКАМИ парсим полученный результат

Tutto è estremamente semplice e chiaro. Può essere scritto in un paio di minuti con una pausa caffè. Avvitare il filtraggio e il gioco è fatto.

Ma misuriamo il tempo:

Come costruire un razzo per gli script PowerCLI

Come costruire un razzo per gli script PowerCLI

2 minuti 47 secondi durante l'elaborazione di quasi 10 VM. Un vantaggio è l'assenza di filtri e la necessità di ordinare manualmente i risultati. Ovviamente, lo script richiede ottimizzazione.

I runspace sono i primi a venire in soccorso quando è necessario ottenere contemporaneamente le metriche dell'host da vCenter o elaborare decine di migliaia di oggetti. Vediamo cosa comporta questo approccio.

Attiva la prima velocità: PowerShell Runspaces

La prima cosa che mi viene in mente per questo script è eseguire il ciclo non in sequenza, ma in thread paralleli, raccogliere tutti i dati in un oggetto e filtrarli. 

Ma c'è un problema: PowerCLI non ci permetterà di aprire molte sessioni indipendenti su vCenter e genererà un errore divertente:

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.

Per risolvere questo problema, devi prima passare le informazioni sulla sessione all'interno dello stream. Ricordiamo che PowerShell funziona con oggetti che possono essere passati come parametro sia ad una funzione che ad uno ScriptBlock. Passiamo la sessione sotto forma di tale oggetto, bypassando $global:DefaultVIServers (Connect-VIServer con la chiave -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 $_
           }
       }
   }

Ora implementiamo il multithreading tramite Runspace Pools.  

L'algoritmo è il seguente:

  1. Otteniamo un elenco di tutte le VM.
  2. Nei flussi paralleli otteniamo cloud.uuid.
  3. Raccogliamo dati dai flussi in un unico oggetto.
  4. Filtriamo l'oggetto raggruppandolo per il valore del campo CloudUUID: quelle dove il numero di valori univoci è maggiore di 1 sono le VM che stiamo cercando.

Di conseguenza, otteniamo lo 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
}

Il bello di questo script è che può essere utilizzato in altri casi simili semplicemente sostituendo lo ScriptBlock ed i parametri che verranno passati allo stream. Sfruttalo!

Misuriamo il tempo:

Come costruire un razzo per gli script PowerCLI

55 secondo. È meglio, ma può comunque essere più veloce. 

Passiamo alla seconda velocità: GetView

Scopriamo cosa c'è che non va.
Innanzitutto, l'esecuzione del cmdlet Get-VM richiede molto tempo.
In secondo luogo, il completamento del cmdlet Get-AdvancedOptions richiede ancora più tempo.
Affrontiamo prima il secondo. 

Get-AdvancedOptions è utile per singoli oggetti VM, ma molto goffo quando si lavora con molti oggetti. Possiamo ottenere le stesse informazioni dall'oggetto macchina virtuale stesso (Get-VM). È semplicemente sepolto bene nell'oggetto ExtensionData. Armati di filtraggio, acceleriamo il processo di ottenimento dei dati necessari.

Con un leggero movimento della mano questo è:


VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}

Si trasforma in questo:


$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}}

L'output è lo stesso di Get-AdvancedOptions, ma funziona molto più velocemente. 

Ora passiamo a Get-VM. Non è veloce perché tratta oggetti complessi. Sorge una domanda logica: perché in questo caso abbiamo bisogno di informazioni extra e di un mostruoso PSObject, quando abbiamo solo bisogno del nome della VM, del suo stato e del valore di un attributo complicato?  

Inoltre, l'ostacolo sotto forma di Get-AdvancedOptions è stato rimosso dallo script. L'utilizzo dei Runspace Pools ora sembra eccessivo poiché non è più necessario parallelizzare un'attività lenta tra thread tozzi quando si passa una sessione. Lo strumento è buono, ma non per questo caso. 

Diamo un'occhiata all'output di ExtensionData: non è altro che un oggetto Get-View. 

Facciamo appello all'antica tecnica dei maestri di PowerShell: una riga utilizzando filtri, ordinamento e raggruppamento. Tutto l'orrore precedente è elegantemente compresso in una riga ed eseguito in una sessione:


$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

Misuriamo il tempo:

Come costruire un razzo per gli script PowerCLI

secondi 9 per quasi 10 oggetti con filtraggio in base alla condizione desiderata. Grande!

Invece di una conclusione

Un risultato accettabile dipende direttamente dalla scelta dello strumento. Spesso è difficile dire con certezza cosa si dovrebbe scegliere esattamente per raggiungerlo. Ciascuno dei metodi elencati per velocizzare gli script è valido entro i limiti della sua applicabilità. Spero che questo articolo ti aiuti nel difficile compito di comprendere le basi dell'automazione e dell'ottimizzazione dei processi nella tua infrastruttura.

PS: L'autore ringrazia tutti i membri della comunità per il loro aiuto e supporto nella preparazione dell'articolo. Anche quelli con le zampe. E anche quelli che non hanno le gambe, come un boa constrictor.

Fonte: habr.com

Aggiungi un commento