Paano bumuo ng rocket booster para sa mga script ng PowerCLI 

Maaga o huli, ang sinumang VMware system administrator ay darating upang i-automate ang mga nakagawiang gawain. Nagsisimula ang lahat sa command line, pagkatapos ay ang PowerShell o VMware PowerCLI.

Sabihin nating na-master mo na ang PowerShell nang kaunti pa kaysa sa paglulunsad ng ISE at paggamit ng mga karaniwang cmdlet mula sa mga module na gumagana dahil sa “ilang uri ng mahika”. Kapag nagsimula kang magbilang ng mga virtual machine sa daan-daan, makikita mo na ang mga script na tumutulong sa maliit na sukat ay tumatakbo nang kapansin-pansing mas mabagal sa malaking sukat. 

Sa sitwasyong ito, makakatulong ang 2 tool:

  • PowerShell Runspaces – isang diskarte na nagbibigay-daan sa iyo upang iparallelize ang pagpapatupad ng mga proseso sa magkahiwalay na mga thread; 
  • Get-View – isang pangunahing function ng PowerCLI, isang analogue ng Get-WMIObject sa Windows. Ang cmdlet na ito ay hindi humihila ng mga bagay na kasama ng mga entity, ngunit tumatanggap ng impormasyon sa anyo ng isang simpleng bagay na may mga simpleng uri ng data. Sa maraming kaso, mas mabilis itong lumalabas.

Susunod, maikling pag-uusapan ko ang tungkol sa bawat tool at magpapakita ng mga halimbawa ng paggamit. Suriin natin ang mga partikular na script at tingnan kung ang isa ay gumagana nang mas mahusay kaysa sa isa. Go!

Paano bumuo ng rocket booster para sa mga script ng PowerCLI

Unang yugto: Runspace

Kaya, ang Runspace ay idinisenyo para sa parallel na pagproseso ng mga gawain sa labas ng pangunahing module. Siyempre, maaari kang maglunsad ng isa pang proseso na kakain ng ilang memorya, processor, atbp. Kung ang iyong script ay tumakbo sa loob ng ilang minuto at kumonsumo ng isang gigabyte ng memorya, malamang na hindi mo kakailanganin ang Runspace. Ngunit para sa mga script para sa sampu-sampung libong mga bagay ito ay kinakailangan.

Maaari kang magsimulang mag-aral dito: 
Panimulang Paggamit ng PowerShell Runspaces: Part 1

Ano ang ibinibigay ng paggamit ng Runspace:

  • bilis sa pamamagitan ng paglilimita sa listahan ng mga naisagawang utos,
  • parallel na pagpapatupad ng mga gawain,
  • kaligtasan.

Narito ang isang halimbawa mula sa Internet nang tumulong ang Runspace:

“Ang pagtatalo sa storage ay isa sa pinakamahirap na sukatan na subaybayan sa vSphere. Sa loob ng vCenter, hindi ka maaaring pumunta lang at tingnan kung aling VM ang kumonsumo ng mas maraming mapagkukunan ng storage. Sa kabutihang palad, maaari mong kolektahin ang data na ito sa ilang minuto salamat sa PowerShell.
Magbabahagi ako ng script na magbibigay-daan sa mga administrator ng VMware system na mabilis na maghanap sa buong vCenter at makatanggap ng listahan ng mga VM na may data sa kanilang average na pagkonsumo.  
Gumagamit ang script ng mga PowerShell runspace upang payagan ang bawat ESXi host na mangolekta ng impormasyon sa pagkonsumo mula sa sarili nitong mga VM sa isang hiwalay na Runspace at agad na iulat ang pagkumpleto. Nagbibigay-daan ito sa PowerShell na isara kaagad ang mga trabaho, sa halip na mag-ulit sa mga host at maghintay para sa bawat isa na makumpleto ang kahilingan nito.

Pinagmulan: Paano Ipakita ang Virtual Machine I/O sa isang ESXi Dashboard

Sa kaso sa ibaba, hindi na kapaki-pakinabang ang Runspace:

“Sinusubukan kong magsulat ng script na nangongolekta ng maraming data mula sa isang VM at nagsusulat ng bagong data kapag kinakailangan. Ang problema ay napakaraming VM, at 5-8 segundo ang ginugugol sa isang makina.” 

Pinagmulan: Multithreading PowerCLI na may RunspacePool

Dito kakailanganin mo ang Get-View, magpatuloy tayo dito. 

Ikalawang yugto: Get-View

Upang maunawaan kung bakit kapaki-pakinabang ang Get-View, sulit na alalahanin kung paano gumagana ang mga cmdlet sa pangkalahatan. 

Ang mga Cmdlet ay kinakailangan upang maginhawang makakuha ng impormasyon nang hindi kinakailangang pag-aralan ang mga aklat ng sangguniang API at muling likhain ang susunod na gulong. Kung ano ang kinuha noong unang panahon ng isang daan o dalawang linya ng code, pinapayagan ka ng PowerShell na gawin sa isang utos. Binabayaran namin ang kaginhawaan na ito nang mabilis. Walang magic sa loob ng mga cmdlet mismo: ang parehong script, ngunit sa isang mas mababang antas, na isinulat ng mga dalubhasang kamay ng isang master mula sa maaraw na India.

Ngayon, para sa paghahambing sa Get-View, kunin natin ang Get-VM cmdlet: ina-access nito ang virtual machine at ibinabalik ang isang pinagsama-samang bagay, iyon ay, ikinakabit nito ang iba pang nauugnay na mga bagay dito: VMHost, Datastore, atbp.  

Ang Get-View sa lugar nito ay hindi nagdaragdag ng anumang hindi kailangan sa ibinalik na bagay. Bukod dito, pinapayagan kaming mahigpit na tukuyin kung anong impormasyon ang kailangan namin, na gagawing mas madali ang output object. Sa Windows Server sa pangkalahatan at sa Hyper-V sa partikular, ang Get-WMIObject cmdlet ay isang direktang analogue - ang ideya ay eksaktong pareho.

Ang Get-View ay hindi maginhawa para sa mga nakagawiang operasyon sa mga point object. Ngunit pagdating sa libu-libo at sampu-sampung libong mga bagay, wala itong presyo.

Maaari kang magbasa nang higit pa sa VMware blog: Panimula sa Get-View

Ngayon ay ipapakita ko sa iyo ang lahat gamit ang isang tunay na kaso. 

Pagsusulat ng script para mag-unload ng VM

Isang araw hiniling sa akin ng aking kasamahan na i-optimize ang kanyang script. Ang gawain ay isang karaniwang gawain: hanapin ang lahat ng VM na may duplicate na cloud.uuid parameter (oo, posible ito kapag nag-clone ng VM sa vCloud Director). 

Ang malinaw na solusyon na nasa isip ay:

  1. Kumuha ng listahan ng lahat ng VM.
  2. I-parse ang listahan kahit papaano.

Ang orihinal na bersyon ay ang simpleng script na ito:

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

Ang lahat ay sobrang simple at malinaw. Maaari itong isulat sa loob ng ilang minuto na may coffee break. I-screw sa pagsasala at ito ay tapos na.

Ngunit sukatin natin ang oras:

Paano bumuo ng rocket booster para sa mga script ng PowerCLI

Paano bumuo ng rocket booster para sa mga script ng PowerCLI

2 minuto 47 segundo kapag nagproseso ng halos 10k VMs. Ang isang bonus ay ang kawalan ng mga filter at ang pangangailangang manu-manong ayusin ang mga resulta. Malinaw, ang script ay nangangailangan ng pag-optimize.

Ang mga runspace ang unang sumagip kapag kailangan mong sabay na kumuha ng mga sukatan ng host mula sa vCenter o kailangan mong iproseso ang libu-libong mga bagay. Tingnan natin kung ano ang dulot ng diskarteng ito.

I-on ang unang bilis: PowerShell Runspaces

Ang unang bagay na naiisip para sa script na ito ay ang pag-execute ng loop nang hindi sunud-sunod, ngunit sa parallel na mga thread, kolektahin ang lahat ng data sa isang bagay at i-filter ito. 

Ngunit may problema: Hindi kami papayagan ng PowerCLI na magbukas ng maraming independiyenteng session sa vCenter at magtapon ng isang nakakatawang error:

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.

Upang malutas ito, kailangan mo munang ipasa ang impormasyon ng session sa loob ng stream. Tandaan natin na gumagana ang PowerShell sa mga bagay na maaaring ipasa bilang parameter sa isang function o sa isang ScriptBlock. Ipasa natin ang session sa anyo ng isang bagay, na lampasan ang $global:DefaultVIServers (Connect-VIServer gamit ang -NotDefault key):

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

Ngayon, ipatupad natin ang multithreading sa pamamagitan ng Runspace Pools.  

Ang algorithm ay ang mga sumusunod:

  1. Kumuha kami ng listahan ng lahat ng VM.
  2. Sa parallel streams nakakakuha tayo ng cloud.uuid.
  3. Kinokolekta namin ang data mula sa mga stream sa isang bagay.
  4. Sinasala namin ang bagay sa pamamagitan ng pagpapangkat nito ayon sa halaga ng patlang ng CloudUUID: ang mga kung saan ang bilang ng mga natatanging halaga ay higit sa 1 ay ang mga VM na hinahanap namin.

Bilang resulta, nakukuha namin ang 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
}

Ang kagandahan ng script na ito ay maaari itong magamit sa iba pang katulad na mga kaso sa pamamagitan lamang ng pagpapalit ng ScriptBlock at ang mga parameter na ipapasa sa stream. pagsamantalahan ito!

Sinusukat namin ang oras:

Paano bumuo ng rocket booster para sa mga script ng PowerCLI

55 segundo. Ito ay mas mahusay, ngunit maaari pa rin itong maging mas mabilis. 

Lumipat tayo sa pangalawang bilis: GetView

Alamin natin kung ano ang mali.
Una at pangunahin, ang Get-VM cmdlet ay tumatagal ng mahabang panahon upang maisakatuparan.
Pangalawa, mas matagal bago makumpleto ang Get-AdvancedOptions cmdlet.
Harapin muna natin ang pangalawa. 

Ang Get-AdvancedOptions ay maginhawa para sa mga indibidwal na VM object, ngunit napaka-clumsy kapag nagtatrabaho sa maraming bagay. Makukuha natin ang parehong impormasyon mula sa mismong virtual machine object (Get-VM). Nakabaon lang ito ng maayos sa ExtensionData object. Gamit ang pag-filter, pinapabilis namin ang proseso ng pagkuha ng kinakailangang data.

Sa isang bahagyang paggalaw ng kamay ito ay:


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

Ito ay nagiging:


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

Ang output ay kapareho ng Get-AdvancedOptions, ngunit ito ay gumagana nang maraming beses nang mas mabilis. 

Ngayon sa Get-VM. Hindi ito mabilis dahil tumatalakay ito sa mga kumplikadong bagay. Isang lohikal na tanong ang lumitaw: bakit kailangan natin ng karagdagang impormasyon at isang napakalaking PSObject sa kasong ito, kung kailangan lang natin ang pangalan ng VM, ang estado nito at ang halaga ng isang nakakalito na katangian?  

Bilang karagdagan, ang balakid sa anyo ng Get-AdvancedOptions ay inalis mula sa script. Ang paggamit ng Runspace Pools ngayon ay parang overkill dahil hindi na kailangan na iparallelize ang isang mabagal na gawain sa mga squat thread kapag nag-aabot ng session. Ang tool ay mabuti, ngunit hindi para sa kasong ito. 

Tingnan natin ang output ng ExtensionData: ito ay walang iba kundi isang bagay na Get-View. 

Tawagan natin ang sinaunang pamamaraan ng PowerShell masters: isang linya gamit ang mga filter, pag-uuri at pagpapangkat. Ang lahat ng nakaraang katatakutan ay eleganteng na-collapse sa isang linya at naisakatuparan sa isang 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

Sinusukat namin ang oras:

Paano bumuo ng rocket booster para sa mga script ng PowerCLI

9 segundo para sa halos 10k na bagay na may pagsasala ayon sa nais na kundisyon. Malaki!

Sa halip ng isang konklusyon

Ang isang katanggap-tanggap na resulta ay direktang nakasalalay sa pagpili ng tool. Madalas mahirap sabihin ng tiyak kung ano ang eksaktong dapat piliin para makamit ito. Ang bawat isa sa mga nakalistang pamamaraan para sa pagpapabilis ng mga script ay mabuti sa loob ng mga limitasyon ng pagiging angkop nito. Umaasa ako na ang artikulong ito ay makakatulong sa iyo sa mahirap na gawain ng pag-unawa sa mga pangunahing kaalaman ng proseso ng automation at pag-optimize sa iyong imprastraktura.

PS: Nagpapasalamat ang may-akda sa lahat ng miyembro ng komunidad para sa kanilang tulong at suporta sa paghahanda ng artikulo. Kahit yung may paws. At kahit ang mga walang paa, parang boa constrictor.

Pinagmulan: www.habr.com

Magdagdag ng komento