Com crear un reforç de coets per a scripts de PowerCLI 

Tard o d'hora, qualsevol administrador del sistema VMware arriba a automatitzar les tasques rutinàries. Tot comença amb la línia d'ordres, després ve PowerShell o VMware PowerCLI.

Suposem que has dominat PowerShell una mica més enllà de llançar ISE i utilitzar cmdlets estàndard de mòduls que funcionen per "algun tipus de màgia". Quan comenceu a comptar les màquines virtuals per centenars, trobareu que els scripts que ajuden a petita escala funcionen notablement més lents a gran escala. 

En aquesta situació, 2 eines ajudaran:

  • Espais d'execució de PowerShell – un enfocament que permet paral·lelitzar l'execució de processos en fils separats; 
  • Get-View – una funció bàsica de PowerCLI, un anàleg de Get-WMIObject a Windows. Aquest cmdlet no extreu objectes que acompanyen entitats, sinó que rep informació en forma d'objecte simple amb tipus de dades simples. En molts casos surt més ràpid.

A continuació, parlaré breument de cada eina i mostraré exemples d'ús. Analitzem scripts específics i veiem quan un funciona millor que l'altre. Va!

Com crear un reforç de coets per a scripts de PowerCLI

Primera etapa: Runspace

Per tant, Runspace està dissenyat per al processament paral·lel de tasques fora del mòdul principal. Per descomptat, podeu iniciar un altre procés que consumirà una mica de memòria, processador, etc. Si el vostre script s'executa en un parell de minuts i consumeix un gigabyte de memòria, el més probable és que no necessiteu Runspace. Però per a scripts per a desenes de milers d'objectes és necessari.

Pots començar a aprendre aquí: 
Inici de l'ús dels espais d'execució de PowerShell: part 1

Què dóna l'ús de Runspace:

  • velocitat limitant la llista d'ordres executades,
  • execució paral·lela de tasques,
  • seguretat.

Aquí teniu un exemple d'Internet quan Runspace ajuda:

"La disputa d'emmagatzematge és una de les mètriques més difícils de fer un seguiment a vSphere. Dins de vCenter, no podeu anar a veure quina màquina virtual consumeix més recursos d'emmagatzematge. Afortunadament, podeu recollir aquestes dades en qüestió de minuts gràcies a PowerShell.
Compartiré un script que permetrà als administradors del sistema VMware cercar ràpidament a vCenter i rebre una llista de màquines virtuals amb dades sobre el seu consum mitjà.  
L'script utilitza espais d'execució de PowerShell per permetre que cada amfitrió ESXi reculli informació de consum de les seves pròpies màquines virtuals en un espai d'execució independent i informi immediatament de la finalització. Això permet que PowerShell tanqui els treballs immediatament, en lloc d'iterar a través dels amfitrions i esperar que cadascun completi la seva sol·licitud.

Font: Com mostrar l'E/S de la màquina virtual en un tauler ESXi

En el cas següent, Runspace ja no és útil:

"Estic intentant escriure un script que reculli moltes dades d'una màquina virtual i escrigui dades noves quan sigui necessari. El problema és que hi ha moltes màquines virtuals i es dediquen entre 5 i 8 segons en una màquina". 

Font: Multithreading PowerCLI amb RunspacePool

Aquí necessitareu Get-View, passem-hi. 

Segona etapa: Get-View

Per entendre per què és útil Get-View, val la pena recordar com funcionen els cmdlets en general. 

Els cmdlets són necessaris per obtenir informació còmodament sense necessitat d'estudiar els llibres de referència de l'API i reinventar la següent roda. El que en els vells temps necessitava cent o dues línies de codi, PowerShell us permet fer-ho amb una ordre. Paguem aquesta comoditat amb rapidesa. No hi ha màgia dins dels mateixos cmdlets: el mateix guió, però a un nivell inferior, escrit per les mans hàbils d'un mestre de la assolellada Índia.

Ara, per comparar-lo amb Get-View, prenem el cmdlet Get-VM: accedeix a la màquina virtual i retorna un objecte compost, és a dir, li adjunta altres objectes relacionats: VMHost, Datastore, etc.  

Get-View al seu lloc no afegeix res innecessari a l'objecte retornat. A més, ens permet especificar estrictament quina informació necessitem, cosa que facilitarà l'objecte de sortida. A Windows Server en general i a Hyper-V en particular, el cmdlet Get-WMIObject és un anàleg directe: la idea és exactament la mateixa.

Get-View és inconvenient per a operacions rutinàries en objectes puntuals. Però quan es tracta de milers i desenes de milers d'objectes, no té preu.

Podeu llegir més informació al bloc de VMware: Introducció a Get-View

Ara us mostraré tot utilitzant un cas real. 

Escriptura d'un script per descarregar una VM

Un dia el meu company em va demanar que optimitzés el seu guió. La tasca és una rutina habitual: trobar totes les VM amb un paràmetre cloud.uuid duplicat (sí, això és possible quan es clonen una VM a vCloud Director). 

La solució òbvia que em ve al cap és:

  1. Obteniu una llista de totes les màquines virtuals.
  2. Analitzeu la llista d'alguna manera.

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

Tot és extremadament senzill i clar. Es pot escriure en un parell de minuts amb una pausa per al cafè. Enrosqueu la filtració i ja està.

Però mesurem el temps:

Com crear un reforç de coets per a scripts de PowerCLI

Com crear un reforç de coets per a scripts de PowerCLI

2 minuts 47 segons quan es processen gairebé 10 màquines virtuals. Un avantatge és l'absència de filtres i la necessitat d'ordenar manualment els resultats. Òbviament, l'script requereix optimització.

Els espais d'execució són els primers que acudeixen al rescat quan necessiteu obtenir simultàniament mètriques d'amfitrió de vCenter o necessiteu processar desenes de milers d'objectes. Vegem què aporta aquest enfocament.

Activa la primera velocitat: PowerShell Runspaces

El primer que em ve al cap per a aquest script és executar el bucle no de manera seqüencial, sinó en fils paral·lels, recopilar totes les dades en un objecte i filtrar-les. 

Però hi ha un problema: PowerCLI no ens permetrà obrir moltes sessions independents a vCenter i llançarà un error divertit:

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 solucionar-ho, primer heu de passar la informació de la sessió dins del flux. Recordem que PowerShell funciona amb objectes que es poden passar com a paràmetres a una funció o a un ScriptBlock. Passem la sessió en forma d'aquest objecte, sense passar per $global:DefaultVIServers (Connecte-VIServer amb la clau -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 $_
           }
       }
   }

Ara implementem el multithreading mitjançant Runspace Pools.  

L'algorisme és el següent:

  1. Obtenim una llista de totes les màquines virtuals.
  2. En corrents paral·lels obtenim cloud.uuid.
  3. Recopilem dades dels fluxos en un objecte.
  4. Filtrem l'objecte agrupant-lo pel valor del camp CloudUUID: aquells on el nombre de valors únics és superior a 1 són les VM que busquem.

Com a resultat, obtenim el guió:


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
}

La bellesa d'aquest script és que es pot utilitzar en altres casos similars simplement substituint l'ScriptBlock i els paràmetres que es passaran al flux. Aprofiteu-lo!

Mesurem el temps:

Com crear un reforç de coets per a scripts de PowerCLI

55 segons. És millor, però encara pot ser més ràpid. 

Passem a la segona velocitat: GetView

Anem a esbrinar què passa.
En primer lloc, el cmdlet Get-VM triga molt a executar-se.
En segon lloc, el cmdlet Get-AdvancedOptions triga encara més a completar-se.
Tractem primer amb el segon. 

Get-AdvancedOptions és convenient per a objectes VM individuals, però molt maldestre quan es treballa amb molts objectes. Podem obtenir la mateixa informació del mateix objecte màquina virtual (Get-VM). Només està ben enterrat a l'objecte ExtensionData. Armats amb filtratge, agilitzem el procés d'obtenció de les dades necessàries.

Amb un lleuger moviment de la mà això és:


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

Es converteix en això:


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

La sortida és la mateixa que Get-AdvancedOptions, però funciona moltes vegades més ràpid. 

Ara a Get-VM. No és ràpid perquè tracta objectes complexos. Sorgeix una pregunta lògica: per què necessitem informació addicional i un PSObject monstruós en aquest cas, quan només necessitem el nom de la VM, el seu estat i el valor d'un atribut complicat?  

A més, l'obstacle en forma de Get-AdvancedOptions s'ha eliminat de l'script. L'ús de Runspace Pools ara sembla exagerat, ja que ja no cal paral·lelitzar una tasca lenta a través dels fils a la gatzoneta quan es lliura una sessió. L'eina és bona, però no per a aquest cas. 

Vegem la sortida d'ExtensionData: no és més que un objecte Get-View. 

Fem una crida a l'antiga tècnica dels mestres de PowerShell: una línia utilitzant filtres, ordenació i agrupació. Tot l'horror anterior es col·lapsa elegantment en una línia i s'executa en una sessió:


$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

Mesurem el temps:

Com crear un reforç de coets per a scripts de PowerCLI

9 segons per a gairebé 10 objectes amb filtratge per la condició desitjada. Genial!

En lloc d'una conclusió

Un resultat acceptable depèn directament de l'elecció de l'eina. Sovint és difícil dir amb certesa què s'ha de triar exactament per aconseguir-ho. Cadascun dels mètodes enumerats per accelerar els scripts és bo dins dels límits de la seva aplicabilitat. Espero que aquest article us ajudi en la difícil tasca d'entendre els conceptes bàsics de l'automatització i optimització de processos a la vostra infraestructura.

PS: L'autor agraeix a tots els membres de la comunitat la seva ajuda i suport en la preparació de l'article. Fins i tot els que tenen potes. I fins i tot els que no tenen cames, com una boa constrictor.

Font: www.habr.com

Afegeix comentari