Kiel konstrui raket-akcelilon por PowerCLI-skriptoj 

Pli aŭ malpli frue, iu ajn sistemadministranto de VMware venas por aŭtomatigi rutinajn taskojn. Ĉio komenciĝas per la komandlinio, poste venas PowerShell aŭ VMware PowerCLI.

Ni diru, ke vi regis PowerShell iom pli ol lanĉi ISE kaj uzi normajn cmdletojn de moduloj kiuj funkcias pro "ia magio". Kiam vi komencas kalkuli virtualajn maŝinojn en la centoj, vi trovos, ke skriptoj, kiuj helpas malgrandskale, funkcias videble pli malrapide grandskale. 

En ĉi tiu situacio, 2 iloj helpos:

  • PowerShell Runspaces - aliro, kiu ebligas al vi paraleligi la ekzekuton de procezoj en apartaj fadenoj; 
  • Akiri-Vidi - baza PowerCLI-funkcio, analogo de Get-WMIObject en Vindozo. Ĉi tiu cmdleto ne tiras objektojn akompanantajn entojn, sed ricevas informojn en la formo de simpla objekto kun simplaj datumtipoj. En multaj kazoj ĝi eliras pli rapide.

Poste, mi mallonge parolos pri ĉiu ilo kaj montros ekzemplojn de uzo. Ni analizu specifajn skriptojn kaj vidu kiam unu funkcias pli bone ol la alia. Iru!

Kiel konstrui raket-akcelilon por PowerCLI-skriptoj

Unua etapo: Runspace

Do, Runspace estas desegnita por paralela prilaborado de taskoj ekster la ĉefa modulo. Kompreneble, vi povas lanĉi alian procezon, kiu manĝos iom da memoro, procesoro, ktp. Se via skripto funkcias en kelkaj minutoj kaj konsumas gigabajton da memoro, plej verŝajne vi ne bezonos Runspace. Sed por skriptoj por dekoj da miloj da objektoj ĝi estas bezonata.

Vi povas komenci lerni ĉi tie: 
Komencante Uzon de PowerShell Runspaces: Parto 1

Kion donas uzado de Runspace:

  • rapido limigante la liston de ekzekutitaj komandoj,
  • paralela plenumo de taskoj,
  • sekureco.

Jen ekzemplo el la Interreto, kiam Runspace helpas:

"Stokada disputo estas unu el la plej malfacilaj mezuroj por spuri en vSphere. Ene de vCenter, vi ne povas simple iri kaj vidi, kiu VM konsumas pli da stokaj rimedoj. Feliĉe, vi povas kolekti ĉi tiujn datumojn en minutoj danke al PowerShell.
Mi dividos skripton, kiu permesos al administrantoj de VMware sistemaj rapide serĉi tra vCenter kaj ricevi liston de VM-oj kun datumoj pri ilia averaĝa konsumo.  
La skripto uzas PowerShell-runspacojn por permesi al ĉiu ESXi-gastiganto kolekti konsuminformojn de siaj propraj VM-oj en aparta Runspace kaj tuj raporti kompletigon. Ĉi tio permesas al PowerShell fermi laborpostenojn tuj, prefere ol ripetadi tra gastigantoj kaj atendi ke ĉiu kompletigos sian peton."

fonto: Kiel Montri Virtualan Maŝinon I/O sur ESXi Panelo

En la ĉi-suba kazo, Runspace ne plu utilas:

“Mi provas skribi skripton, kiu kolektas multajn datumojn de VM kaj skribas novajn datumojn kiam necese. La problemo estas, ke estas sufiĉe multaj VM-oj, kaj 5-8 sekundoj estas pasigitaj en unu maŝino." 

fonto: Multithreading PowerCLI kun RunspacePool

Ĉi tie vi bezonos Get-View, ni pluiru al ĝi. 

Dua etapo: Get-View

Por kompreni kial Get-View estas utila, indas memori kiel cmdletoj funkcias ĝenerale. 

Cmdlets estas necesaj por oportune akiri informojn sen la bezono studi API-referenclibrojn kaj reinventi la sekvan radon. Kio en la malnovaj tempoj prenis cent aŭ du liniojn de kodo, PowerShell permesas vin fari kun unu komando. Ni pagas ĉi tiun komforton rapide. Ne ekzistas magio ene de la cmdletoj mem: la sama skripto, sed je pli malalta nivelo, skribita de la lertaj manoj de majstro el suna Hindio.

Nun, por komparo kun Get-View, ni prenu la cmdlet Get-VM: ĝi aliras la virtualan maŝinon kaj resendas kunmetitan objekton, tio estas, ĝi aliĝas al ĝi aliajn rilatajn objektojn: VMHost, Datastore, ktp.  

Get-View en sia loko ne aldonas ion nenecesan al la redonita objekto. Krome, ĝi permesas al ni strikte specifi kiajn informojn ni bezonas, kio plifaciligos la eligan objekton. En Windows Server ĝenerale kaj en Hyper-V aparte, la cmdleto Get-WMIObject estas rekta analogo - la ideo estas ĝuste la sama.

Get-View estas maloportuna por rutinaj operacioj sur punktaj objektoj. Sed kiam temas pri miloj kaj dekoj da miloj da objektoj, ĝi ne havas prezon.

Vi povas legi pli en la blogo de VMware: Enkonduko al Get-View

Nun mi montros al vi ĉion uzante realan kazon. 

Skribante skripton por malŝarĝi VM

Iun tagon mia kolego petis min optimumigi sian skripton. La tasko estas ofta rutino: trovi ĉiujn VM-ojn kun duplikata parametro cloud.uuid (jes, tio eblas dum klonado de VM en vCloud Director). 

La evidenta solvo, kiu venas al la menso, estas:

  1. Akiru liston de ĉiuj VM-oj.
  2. Analizu la liston iel.

La originalversio estis ĉi tiu simpla manuskripto:

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

Ĉio estas ekstreme simpla kaj klara. Ĝi povas esti skribita en kelkaj minutoj kun kafpaŭzo. Alŝraŭbi la filtradon kaj ĝi estas farita.

Sed ni mezuru la tempon:

Kiel konstrui raket-akcelilon por PowerCLI-skriptoj

Kiel konstrui raket-akcelilon por PowerCLI-skriptoj

2 minutoj 47 sekundoj kiam oni prilaboras preskaŭ 10k VM-ojn. Gratifiko estas la foresto de filtriloj kaj la bezono mane ordigi la rezultojn. Evidente, la skripto postulas optimumigo.

Runspaces estas la unuaj, kiuj venas al la savo, kiam vi devas samtempe akiri gastigajn metrikojn de vCenter aŭ bezonas prilabori dekojn da miloj da objektoj. Ni vidu, kion ĉi tiu aliro alportas.

Ŝaltu la unuan rapidon: PowerShell Runspaces

La unua afero, kiu venas al la menso por ĉi tiu skripto, estas ekzekuti la buklon ne sinsekve, sed en paralelaj fadenoj, kolekti ĉiujn datumojn en unu objekton kaj filtri ĝin. 

Sed estas problemo: PowerCLI ne permesos al ni malfermi multajn sendependajn sesiojn al vCenter kaj ĵetos amuzan eraron:

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.

Por solvi ĉi tion, vi unue devas pasi sesiinformojn ene de la rivereto. Ni memoru, ke PowerShell funkcias kun objektoj, kiuj povas esti transdonitaj kiel parametro aŭ al funkcio aŭ al ScriptBlock. Ni pasigu la sesion en la formo de tia objekto, preterirante $global:DefaultVIServers (Konekti-VIServer per la klavo -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 $_
           }
       }
   }

Nun ni efektivigu multifadenadon per Runspace Pools.  

La algoritmo estas kiel sekvas:

  1. Ni ricevas liston de ĉiuj VM-oj.
  2. En paralelaj riveretoj ni ricevas cloud.uuid.
  3. Ni kolektas datumojn de fluoj en unu objekton.
  4. Ni filtras la objekton grupigante ĝin laŭ la valoro de la kampo CloudUUID: tiuj, kie la nombro da unikaj valoroj estas pli granda ol 1, estas la VM-oj, kiujn ni serĉas.

Kiel rezulto, ni ricevas la skripton:


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 beleco de ĉi tiu skripto estas, ke ĝi povas esti uzata en aliaj similaj kazoj simple anstataŭigante la ScriptBlock kaj la parametrojn, kiuj estos transdonitaj al la rivereto. Ekspluu ĝin!

Ni mezuras tempon:

Kiel konstrui raket-akcelilon por PowerCLI-skriptoj

55 sekundoj. Ĝi estas pli bona, sed ĝi ankoraŭ povas esti pli rapida. 

Ni movu al la dua rapido: GetView

Ni eksciu, kio estas malbona.
Unue kaj ĉefe, la cmdleto Get-VM bezonas longan tempon por ekzekuti.
Due, la cmdleto Get-AdvancedOptions daŭras eĉ pli longe por kompletigi.
Unue ni traktu la duan. 

Get-AdvancedOptions estas oportuna por individuaj VM-objektoj, sed tre mallerta kiam oni laboras kun multaj objektoj. Ni povas ricevi la samajn informojn de la virtuala maŝino objekto mem (Get-VM). Ĝi estas nur bone enterigita en la ExtensionData objekto. Armitaj per filtrado, ni akcelas la procezon akiri la necesajn datumojn.

Kun eta movo de la mano jen:


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

Turniĝas en ĉi tio:


$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 eligo estas la sama kiel Get-AdvancedOptions, sed ĝi funkcias multajn fojojn pli rapide. 

Nun al Get-VM. Ĝi ne estas rapida ĉar ĝi traktas kompleksajn objektojn. Logika demando ekestas: kial ni bezonas kromajn informojn kaj monstran PSObject en ĉi tiu kazo, kiam ni nur bezonas la nomon de la VM, ĝia stato kaj la valoro de delikata atributo?  

Krome, la malhelpo en la formo de Get-AdvancedOptions estis forigita de la skripto. Uzi Runspace Pools nun ŝajnas troege ĉar ne plu necesas paraleligi malrapidan taskon trans kaŭritaj fadenoj dum transdono de sesio. La ilo estas bona, sed ne por ĉi tiu kazo. 

Ni rigardu la eligon de ExtensionData: ĝi estas nenio pli ol Get-View objekto. 

Ni voku la antikvan teknikon de la PowerShell-majstroj: unu linio uzante filtrilojn, ordigon kaj grupigon. La tuta antaŭa hororo estas elegante kolapsita en unu linion kaj efektivigita en unu sesio:


$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

Ni mezuras tempon:

Kiel konstrui raket-akcelilon por PowerCLI-skriptoj

9 sekundoj por preskaŭ 10k objektoj kun filtrado laŭ la dezirata kondiĉo. Bonege!

Anstataŭ konkludo

Akceptebla rezulto rekte dependas de la elekto de ilo. Ofte estas malfacile diri certe, kion ĝuste oni elektu por atingi ĝin. Ĉiu el la listigitaj metodoj por akceli skriptojn estas bona en la limoj de sia aplikebleco. Mi esperas, ke ĉi tiu artikolo helpos vin en la malfacila tasko kompreni la bazojn de proceza aŭtomatigo kaj optimumigo en via infrastrukturo.

PS: La aŭtoro dankas ĉiujn komunumajn membrojn pro ilia helpo kaj subteno en la preparado de la artikolo. Eĉ tiuj kun piedoj. Kaj eĉ tiuj, kiuj ne havas krurojn, kiel boao.

fonto: www.habr.com

Aldoni komenton