Como construír un foguete booster para scripts PowerCLI 

Tarde ou cedo, calquera administrador do sistema VMware chega a automatizar as tarefas rutineiras. Todo comeza coa liña de comandos, despois vén PowerShell ou VMware PowerCLI.

Digamos que dominas PowerShell un pouco máis que lanzar ISE e empregar cmdlets estándar de módulos que funcionan por "algún tipo de maxia". Cando comeces a contar máquinas virtuais por centos, descubrirás que os scripts que axudan a pequena escala corren notablemente máis lentos a grande escala. 

Nesta situación, 2 ferramentas axudarán:

  • Espazos de execución de PowerShell – un enfoque que permite paralelizar a execución de procesos en fíos separados; 
  • Obter-Ver – unha función PowerCLI básica, un análogo de Get-WMIObject en Windows. Este cmdlet non extrae obxectos que acompañan a entidades, senón que recibe información en forma de obxecto sinxelo con tipos de datos sinxelos. En moitos casos sae máis rápido.

A continuación, falarei brevemente de cada ferramenta e mostrarei exemplos de uso. Analizemos guións específicos e vexamos cando un funciona mellor que o outro. Vaia!

Como construír un foguete booster para scripts PowerCLI

Primeira fase: espazo de execución

Así, Runspace está deseñado para o procesamento paralelo de tarefas fóra do módulo principal. Por suposto, pode iniciar outro proceso que consumirá memoria, procesador, etc. Se o seu script se executa nun par de minutos e consome un gigabyte de memoria, o máis probable é que non necesite Runspace. Pero para scripts para decenas de miles de obxectos é necesario.

Podes comezar a aprender aquí: 
Uso inicial dos espazos de execución de PowerShell: parte 1

Que dá usar Runspace:

  • velocidade limitando a lista de comandos executados,
  • execución paralela de tarefas,
  • seguridade.

Aquí tes un exemplo de Internet cando Runspace axuda:

"A contención de almacenamento é unha das métricas máis difíciles de rastrexar en vSphere. Dentro de vCenter, non podes ir a ver que máquina virtual está a consumir máis recursos de almacenamento. Afortunadamente, podes recoller estes datos en minutos grazas a PowerShell.
Compartirei un script que permitirá aos administradores do sistema VMware buscar rapidamente en vCenter e recibir unha lista de máquinas virtuales con datos sobre o seu consumo medio.  
O script usa espazos de execución de PowerShell para permitir que cada host ESXi recompile información de consumo das súas propias máquinas virtuales nun espazo de execución separado e informe inmediatamente da finalización. Isto permite que PowerShell peche traballos inmediatamente, en lugar de iterar a través dos hosts e esperar a que cada un complete a súa solicitude.

Fonte: Como mostrar E/S da máquina virtual nun panel ESXi

No seguinte caso, Runspace xa non é útil:

"Estou tentando escribir un script que recolla moitos datos dunha máquina virtual e escriba novos datos cando sexa necesario. O problema é que hai moitas máquinas virtuales e gástanse entre 5 e 8 segundos nunha mesma máquina. 

Fonte: Multithreading PowerCLI con RunspacePool

Aquí necesitarás Get-View, imos a el. 

Segunda etapa: Get-View

Para entender por que Get-View é útil, vale a pena lembrar como funcionan os cmdlets en xeral. 

Os cmdlets son necesarios para obter información convenientemente sen necesidade de estudar os libros de referencia da API e reinventar a seguinte roda. O que nos vellos tempos levaba cen ou dúas liñas de código, PowerShell permíteche facer cun só comando. Pagamos esta comodidade con rapidez. Non hai maxia dentro dos propios cmdlets: o mesmo guión, pero nun nivel inferior, escrito polas hábiles mans dun mestre da soleada India.

Agora, para comparar con Get-View, tomemos o cmdlet Get-VM: accede á máquina virtual e devolve un obxecto composto, é dicir, engádelle outros obxectos relacionados: VMHost, Datastore, etc.  

Get-View no seu lugar non engade nada innecesario ao obxecto devolto. Ademais, permítenos especificar con rigor que información necesitamos, o que facilitará o obxecto de saída. En Windows Server en xeral e en Hyper-V en particular, o cmdlet Get-WMIObject é un análogo directo: a idea é exactamente a mesma.

Get-View é inconveniente para operacións rutineiras en obxectos puntuales. Pero cando se trata de miles e decenas de miles de obxectos, non ten prezo.

Podes ler máis no blog de VMware: Introdución a Get-View

Agora mostrarei todo usando un caso real. 

Escribindo un script para descargar unha máquina virtual

Un día o meu compañeiro pediume que optimizase o seu guión. A tarefa é unha rutina común: atopar todas as máquinas virtuales cun parámetro cloud.uuid duplicado (si, isto é posible ao clonar unha máquina virtual en vCloud Director). 

A solución obvia que se me ocorre é:

  1. Obter unha lista de todas as máquinas virtuales.
  2. Analiza a lista dalgún xeito.

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

Todo é moi sinxelo e claro. Pódese escribir nun par de minutos cunha pausa para o café. Atornille a filtración e xa está.

Pero imos medir o tempo:

Como construír un foguete booster para scripts PowerCLI

Como construír un foguete booster para scripts PowerCLI

2 minutos 47 segundos ao procesar case 10k máquinas virtuales. Unha vantaxe é a ausencia de filtros e a necesidade de ordenar manualmente os resultados. Obviamente, o script require optimización.

Os espazos de execución son os primeiros en acudir ao rescate cando necesitas obter simultaneamente métricas de host de vCenter ou necesitas procesar decenas de miles de obxectos. Vexamos que trae este enfoque.

Activa a primeira velocidade: PowerShell Runspaces

O primeiro que se me ocorre para este script é executar o bucle non de forma secuencial, senón en fíos paralelos, recoller todos os datos nun só obxecto e filtralos. 

Pero hai un problema: PowerCLI non nos permitirá abrir moitas sesións independentes a vCenter e arroxará un erro divertido:

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.

Para solucionar isto, primeiro debes pasar a información da sesión dentro do fluxo. Lembremos que PowerShell funciona con obxectos que se poden pasar como parámetros a unha función ou a un ScriptBlock. Imos pasar a sesión en forma de tal obxecto, evitando $global:DefaultVIServers (Connect-VIServer coa chave -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 $_
           }
       }
   }

Agora imos implementar o multithreading a través de Runspace Pools.  

O algoritmo é o seguinte:

  1. Temos unha lista de todas as máquinas virtuales.
  2. En fluxos paralelos obtemos cloud.uuid.
  3. Recollemos datos de fluxos nun só obxecto.
  4. Filtramos o obxecto agrupándoo polo valor do campo CloudUUID: aquelas onde o número de valores únicos é maior que 1 son as máquinas virtuales que buscamos.

Como resultado, obtemos o 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
}

A beleza deste script é que se pode usar noutros casos similares simplemente substituíndo o ScriptBlock e os parámetros que se pasarán ao fluxo. ¡Explotalo!

Medimos o tempo:

Como construír un foguete booster para scripts PowerCLI

55 segundos. É mellor, pero aínda pode ser máis rápido. 

Pasemos á segunda velocidade: GetView

Imos descubrir o que está mal.
En primeiro lugar, o cmdlet Get-VM leva moito tempo en executarse.
En segundo lugar, o cmdlet Get-AdvancedOptions tarda aínda máis en completarse.
Imos tratar primeiro co segundo. 

Get-AdvancedOptions é conveniente para obxectos VM individuais, pero moi torpe cando se traballa con moitos obxectos. Podemos obter a mesma información do propio obxecto da máquina virtual (Get-VM). Está ben enterrado no obxecto ExtensionData. Armados con filtrado, aceleramos o proceso de obtención dos datos necesarios.

Cun lixeiro movemento da man isto é:


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

Transfórmase neste:


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

A saída é a mesma que Get-AdvancedOptions, pero funciona moitas veces máis rápido. 

Agora a Get-VM. Non é rápido porque trata con obxectos complexos. Xorde unha pregunta lóxica: por que necesitamos información adicional e un PSObject monstruoso neste caso, cando só necesitamos o nome da máquina virtual, o seu estado e o valor dun atributo complicado?  

Ademais, o obstáculo en forma de Get-AdvancedOptions foi eliminado do script. Usar Runspace Pools agora parece exagerado, xa que xa non hai necesidade de paralelizar unha tarefa lenta entre fíos agachados ao entregar unha sesión. A ferramenta é boa, pero non para este caso. 

Vexamos a saída de ExtensionData: non é máis que un obxecto Get-View. 

Imos chamar á antiga técnica dos mestres de PowerShell: unha liña usando filtros, clasificación e agrupación. Todo o horror anterior colapsase elegantemente nunha soa liña e execútase nunha sesión:


$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

Medimos o tempo:

Como construír un foguete booster para scripts PowerCLI

9 segundos para case 10k obxectos con filtrado pola condición desexada. Genial!

En vez de unha conclusión

Un resultado aceptable depende directamente da elección da ferramenta. Moitas veces é difícil dicir con certeza o que se debe escoller exactamente para logralo. Cada un dos métodos enumerados para acelerar os scripts é bo dentro dos límites da súa aplicabilidade. Espero que este artigo che axude na difícil tarefa de comprender os conceptos básicos da automatización e optimización de procesos na túa infraestrutura.

PS: O autor agradece a todos os membros da comunidade a súa axuda e apoio na preparación do artigo. Mesmo os que teñen patas. E ata os que non teñen pernas, coma unha boa constrictora.

Fonte: www.habr.com

Engadir un comentario