Како направити ракетни појачивач за ПоверЦЛИ скрипте 

Пре или касније, сваки администратор система ВМваре долази да аутоматизује рутинске задатке. Све почиње са командном линијом, затим долази ПоверСхелл или ВМваре ПоверЦЛИ.

Рецимо да сте савладали ПоверСхелл мало даље од покретања ИСЕ-а и коришћења стандардних цмдлета из модула који раде захваљујући „некој врсти магије“. Када почнете да бројите виртуелне машине у стотинама, видећете да скрипте које помажу у малој мери раде приметно спорије у великим размерама. 

У овој ситуацији, 2 алата ће помоћи:

  • ПоверСхелл Рунспацес – приступ који вам омогућава да паралелизујете извршавање процеса у одвојеним нитима; 
  • Гет-Виев – основна функција ПоверЦЛИ, аналог Гет-ВМИОбјецт у Виндовс-у. Овај цмдлет не повлачи објекте који прате ентитете, већ прима информације у облику једноставног објекта са једноставним типовима података. У многим случајевима излази брже.

Затим ћу укратко говорити о сваком алату и показати примере употребе. Хајде да анализирамо одређене скрипте и видимо када једна ради боље од друге. Иди!

Како направити ракетни појачивач за ПоверЦЛИ скрипте

Прва фаза: Рунспаце

Дакле, Рунспаце је дизајниран за паралелну обраду задатака изван главног модула. Наравно, можете покренути други процес који ће појести нешто меморије, процесора итд. Ако се ваша скрипта покрене за неколико минута и потроши гигабајт меморије, највероватније вам неће требати Рунспаце. Али за скрипте за десетине хиљада објеката то је потребно.

Можете почети да учите овде: 
Почетак коришћења ПоверСхелл рунспаце-а: 1. део

Шта даје коришћење Рунспаце-а:

  • брзину ограничавањем листе извршених команди,
  • паралелно извршавање задатака,
  • сигурност.

Ево примера са Интернета када Рунспаце помаже:

„Контрола за складиштење је једна од најтежих метрика за праћење у вСпхере. Унутар вЦентер-а, не можете само да одете и видите који ВМ троши више ресурса за складиштење. На срећу, ове податке можете прикупити за неколико минута захваљујући ПоверСхелл-у.
Поделићу скрипту која ће омогућити ВМваре систем администраторима да брзо претражују по вЦентер-у и добију листу ВМ-ова са подацима о њиховој просечној потрошњи.  
Скрипта користи ПоверСхелл рунспацес како би омогућила сваком ЕСКСи хосту да прикупи информације о потрошњи са сопствених ВМ-ова у засебном Рунспаце-у и одмах пријави завршетак. Ово омогућава ПоверСхелл-у да одмах затвори послове, уместо да се понавља кроз хостове и чека да сваки од њих заврши свој захтев.”

Извор: Како приказати И/О виртуелне машине на ЕСКСи контролној табли

У случају испод, Рунспаце више није користан:

„Покушавам да напишем скрипту која прикупља много података са ВМ-а и пише нове податке када је то потребно. Проблем је што има доста ВМ-ова и 5-8 секунди се троши на једну машину. 

Извор: Мултитхреадинг ПоверЦЛИ са РунспацеПоол

Овде ће вам требати Гет-Виев, пређимо на њега. 

Друга фаза: Гет-Виев

Да бисте разумели зашто је Гет-Виев користан, вреди се сетити како цмдлет команди уопште функционишу. 

Команде су потребне за практично добијање информација без потребе за проучавањем АПИ референтних књига и измишљањем следећег точка. Оно што је у старим данима требало сто или две линије кода, ПоверСхелл вам омогућава да урадите са једном командом. Ову погодност плаћамо брзином. У самим цмдлетима нема магије: иста скрипта, али на нижем нивоу, написана вештим рукама мајстора из сунчане Индије.

Сада, за поређење са Гет-Виев-ом, узмимо Гет-ВМ цмдлет: он приступа виртуелној машини и враћа композитни објекат, то јест, припаја му друге повезане објекте: ВМХост, Датасторе, итд.  

Гет-Виев уместо њега не додаје ништа непотребно враћеном објекту. Штавише, омогућава нам да стриктно одредимо које информације су нам потребне, што ће олакшати излазни објекат. У Виндовс Сервер-у уопште, а посебно у Хипер-В-у, Гет-ВМИОбјецт цмдлет је директан аналог - идеја је потпуно иста.

Гет-Виев је незгодан за рутинске операције на тачкастим објектима. Али када су у питању хиљаде и десетине хиљада објеката, то нема цену.

Више можете прочитати на ВМваре блогу: Увод у Гет-Виев

Сада ћу вам показати све користећи прави случај. 

Писање скрипте за истовар ВМ

Једног дана ме је колега замолио да оптимизујем његов сценарио. Задатак је уобичајена рутина: пронађите све ВМ-ове са дуплираним параметром цлоуд.ууид (да, ово је могуће када клонирате ВМ у вЦлоуд Дирецтор). 

Очигледно решење које ми пада на памет је:

  1. Добијте листу свих ВМ-ова.
  2. Некако анализирајте листу.

Оригинална верзија је била ова једноставна скрипта:

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

Све је крајње једноставно и јасно. Може се написати за пар минута уз паузу за кафу. Заврните филтрацију и готово је.

Али хајде да измеримо време:

Како направити ракетни појачивач за ПоверЦЛИ скрипте

Како направити ракетни појачивач за ПоверЦЛИ скрипте

КСНУМКС минута КСНУМКС секунди приликом обраде скоро 10к ВМ-а. Бонус је одсуство филтера и потреба за ручним сортирањем резултата. Очигледно, скрипта захтева оптимизацију.

Рунспаце-ови су први који прискачу у помоћ када морате истовремено да добијете метрику хоста од вЦентер-а или морате да обрадите десетине хиљада објеката. Хајде да видимо шта овај приступ доноси.

Укључите прву брзину: ПоверСхелл Рунспацес

Прва ствар која вам пада на памет за ову скрипту је да изврши петљу не секвенцијално, већ у паралелним нитима, прикупи све податке у један објекат и филтрира их. 

Али постоји проблем: ПоверЦЛИ нам неће дозволити да отворимо многе независне сесије за вЦентер и избациће смешну грешку:

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.

Да бисте ово решили, прво морате да проследите информације о сесији унутар тока. Подсетимо се да ПоверСхелл ради са објектима који се могу пренети као параметар у функцију или у СцриптБлоцк. Хајде да проследимо сесију у облику таквог објекта, заобилазећи $глобал:ДефаултВИСерверс (Цоннецт-ВИСервер са кључем -НотДефаулт):

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

Хајде сада да имплементирамо вишенитност кроз Рунспаце Поолс.  

Алгоритам је следећи:

  1. Добијамо листу свих ВМ-ова.
  2. У паралелним токовима добијамо облак.ууид.
  3. Прикупљамо податке из токова у један објекат.
  4. Филтрирамо објекат тако што га групишемо према вредности поља ЦлоудУУИД: они код којих је број јединствених вредности већи од 1 су ВМ-ови које тражимо.

Као резултат, добијамо скрипту:


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
}

Лепота ове скрипте је у томе што се може користити у другим сличним случајевима једноставном заменом СцриптБлоцк-а и параметара који ће бити прослеђени стриму. Искористите то!

Меримо време:

Како направити ракетни појачивач за ПоверЦЛИ скрипте

КСНУМКС секунди. Боље је, али ипак може бити брже. 

Пређимо на другу брзину: ГетВиев

Хајде да сазнамо шта није у реду.
Прво и најважније, Гет-ВМ цмдлет-у је потребно много времена да се изврши.
Друго, цмдлет команди Гет-АдванцедОптионс треба још дуже да се заврши.
Хајде да се прво позабавимо другом. 

Гет-АдванцедОптионс је погодан за појединачне ВМ објекте, али је веома неспретан када радите са много објеката. Исте информације можемо добити од самог објекта виртуелне машине (Гет-ВМ). Једноставно је добро закопан у објекту ЕктенсионДата. Наоружани филтрирањем, убрзавамо процес добијања потребних података.

Лаганим покретом руке ово је:


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

Претвара се у ово:


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

Излаз је исти као Гет-АдванцедОптионс, али ради много пута брже. 

Сада на Гет-ВМ. Није брз јер се бави сложеним објектима. Поставља се логично питање: зашто су нам у овом случају потребне додатне информације и монструозни ПСОбјецт, када нам је потребно само име ВМ-а, његово стање и вредност шкакљивог атрибута?  

Поред тога, из скрипте је уклоњена препрека у облику Гет-АдванцедОптионс. Коришћење Рунспаце Пулова сада изгледа као претерано, јер више нема потребе да се спори задатак паралелизује преко скуат нити приликом предаје сесије. Алат је добар, али није за овај случај. 

Хајде да погледамо излаз ЕктенсионДата: то није ништа друго до Гет-Виев објекат. 

Хајде да се позовемо на древну технику ПоверСхелл мајстора: једна линија помоћу филтера, сортирања и груписања. Сав претходни хорор је елегантно скупљен у једну линију и изведен у једној сесији:


$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

Меримо време:

Како направити ракетни појачивач за ПоверЦЛИ скрипте

КСНУМКС секунди за скоро 10к објеката са филтрирањем по жељеном стању. Велики!

Уместо закључка

Прихватљив резултат директно зависи од избора алата. Често је тешко са сигурношћу рећи шта тачно треба изабрати да се то постигне. Свака од наведених метода за убрзавање скрипти је добра у границама своје применљивости. Надам се да ће вам овај чланак помоћи у тешком задатку разумевања основа аутоматизације процеса и оптимизације ваше инфраструктуре.

ПС: Аутор се захваљује свим члановима заједнице на помоћи и подршци у припреми чланка. Чак и оне са шапама. Па чак и они који немају ноге, попут боа констриктора.

Извор: ввв.хабр.цом

Додај коментар