Како да се изгради ракетен засилувач за PowerCLI скрипти 

Порано или подоцна, секој системски администратор на VMware доаѓа да ги автоматизира рутинските задачи. Сè започнува со командната линија, а потоа доаѓа PowerShell или VMware PowerCLI.

Да речеме дека сте го совладале PowerShell малку подалеку од лансирање на ISE и користење на стандардни cmdlet од модули кои работат поради „некаков вид на магија“. Кога ќе почнете да броите виртуелни машини во стотици, ќе откриете дека скриптите кои помагаат во мал обем работат значително побавно во голем обем. 

Во оваа ситуација, 2 алатки ќе помогнат:

  • PowerShell Runspaces – пристап кој ви овозможува да го паралелизирате извршувањето на процесите во посебни нишки; 
  • Земи-Прикажи – основна функција PowerCLI, аналог на Get-WMIObject во Windows. Овој cmdlet не ги повлекува објектите што ги придружуваат ентитетите, туку прима информации во форма на едноставен објект со едноставни типови на податоци. Во многу случаи излегува побрзо.

Следно, накратко ќе зборувам за секоја алатка и ќе покажам примери за употреба. Ајде да анализираме конкретни скрипти и да видиме кога едното работи подобро од другото. Оди!

Како да се изгради ракетен засилувач за PowerCLI скрипти

Прва фаза: Runspace

Значи, Runspace е дизајниран за паралелна обработка на задачи надвор од главниот модул. Се разбира, можете да стартувате друг процес кој ќе изеде малку меморија, процесор итн. Ако вашата скрипта работи за неколку минути и троши еден гигабајт меморија, најверојатно нема да ви треба Runspace. Но, за скрипти за десетици илјади објекти тоа е потребно.

Можете да започнете да учите овде: 
Почетна употреба на PowerShell Runspaces: Дел 1

Што дава користењето на Runspace:

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

Еве пример од Интернет кога Runspace помага:

„Спорот за складирање е една од најтешките метрики за следење во vSphere. Внатре во vCenter, не можете само да одите и да видите кој VM троши повеќе ресурси за складирање. За среќа, можете да ги соберете овие податоци за неколку минути благодарение на PowerShell.
Ќе споделам скрипта што ќе им овозможи на системските администратори на VMware брзо да пребаруваат низ vCenter и да добиваат листа на VM со податоци за нивната просечна потрошувачка.  
Скриптата користи патеки на PowerShell за да му овозможи на секој ESXi-домаќин да собира информации за потрошувачката од сопствените VM-и во посебен Runspace и веднаш да го пријави завршувањето. Ова му овозможува на PowerShell веднаш да ги затвори работните места, наместо да се повторува преку хостовите и да чека секој од нив да го заврши своето барање“.

Извор: Како да прикажете влез/излез на виртуелна машина на контролната табла ESXi

Во случајот подолу, Runspace веќе не е корисен:

„Се обидувам да напишам скрипта што собира многу податоци од VM и пишува нови податоци кога е потребно. Проблемот е што има доста VM-и, а 5-8 секунди се трошат на една машина“. 

Извор: Multithreading PowerCLI со RunspacePool

Овде ќе ви треба Get-View, ајде да продолжиме на него. 

Втора фаза: Get-View

За да разберете зошто Get-View е корисен, вреди да се потсетиме како функционираат cmdlets воопшто. 

Потребни се Cmdlets за практично да се добијат информации без потреба да се проучуваат референтните книги на API и повторно да се измисли следното тркало. Она што во старите денови бараше сто или две линии код, PowerShell ви овозможува да го направите со една команда. Ние плаќаме за оваа погодност со брзина. Во самите cmdlet нема магија: истото сценарио, но на пониско ниво, напишано од вештите раце на мајстор од сончевата Индија.

Сега, за споредба со Get-View, да го земеме cmdlet-от Get-VM: тој пристапува до виртуелната машина и враќа композитен објект, односно прикачува други поврзани објекти на него: VMHost, Datastore итн.  

Get-View на негово место не додава ништо непотребно на вратениот објект. Покрај тоа, ни овозможува строго да одредиме кои информации ни се потребни, што ќе го олесни излезниот објект. Во Windows Server воопшто и во Hyper-V особено, cmdlet-от Get-WMIObject е директен аналог - идејата е сосема иста.

Get-View е незгодно за рутински операции на точки објекти. Но, кога станува збор за илјадници и десетици илјади предмети, тоа нема цена.

Можете да прочитате повеќе на блогот VMware: Вовед во Get-View

Сега ќе ви покажам сè користејќи вистинска кутија. 

Пишување скрипта за растоварување на VM

Еден ден мојот колега ме замоли да го оптимизирам неговото сценарио. Задачата е вообичаена рутина: најдете ги сите VM со дупликат cloud.uuid параметар (да, ова е можно при клонирање на VM во vCloud Director). 

Очигледното решение што ми паѓа на ум е:

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

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

Но, ајде да го измериме времето:

Како да се изгради ракетен засилувач за PowerCLI скрипти

Како да се изгради ракетен засилувач за PowerCLI скрипти

2 минути 47 секунди при обработка на речиси 10k VMs. Бонус е отсуството на филтри и потребата за рачно сортирање на резултатите. Очигледно, сценариото бара оптимизација.

Runspaces се првите кои доаѓаат на помош кога треба истовремено да ги добиете метриките на домаќинот од vCenter или треба да обработите десетици илјади објекти. Ајде да видиме што носи овој пристап.

Вклучете ја првата брзина: PowerShell Runspaces

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

Но, има проблем: PowerCLI нема да ни дозволи да отвориме многу независни сесии на vCenter и ќе фрли смешна грешка:

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.

За да го решите ова, прво мора да ги пренесете информациите за сесијата внатре во преносот. Да се ​​потсетиме дека PowerShell работи со објекти кои може да се пренесат како параметар или на функција или на ScriptBlock. Ајде да ја пренесеме сесијата во форма на таков објект, заобиколувајќи $global:DefaultVIServers (Поврзи-VIServer со копчето -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 $_
           }
       }
   }

Сега да го имплементираме multithreading преку Runspace Pools.  

Алгоритмот е како што следува:

  1. Добиваме листа на сите VM.
  2. Во паралелните текови добиваме облак.uuid.
  3. Ние собираме податоци од потоци во еден објект.
  4. Ние го филтрираме објектот групирајќи го по вредноста на полето CloudUUID: оние каде што бројот на единствени вредности е поголем од 1 се VM што ги бараме.

Како резултат, го добиваме сценариото:


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
}

Убавината на оваа скрипта е што може да се користи во други слични случаи со едноставно замена на ScriptBlock и параметрите што ќе се пренесат на стримот. Искористете го!

Го мериме времето:

Како да се изгради ракетен засилувач за PowerCLI скрипти

55 секунди. Подобро е, но сепак може да биде побрзо. 

Да преминеме на втората брзина: GetView

Ајде да дознаеме што не е во ред.
Прво и основно, на Get-VM cmdlet му треба долго време за да се изврши.
Второ, cmdlet-от Get-AdvancedOptions трае уште подолго.
Ајде прво да се справиме со второто. 

Get-AdvancedOptions е погодно за поединечни VM објекти, но многу несмасно кога работите со многу објекти. Можеме да ги добиеме истите информации од самиот објект на виртуелната машина (Get-VM). Само добро е закопано во објектот ExtensionData. Вооружени со филтрирање, го забрзуваме процесот на добивање на потребните податоци.

Со мало движење на раката, ова е:


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

Излезот е ист како Get-AdvancedOptions, но работи многу пати побрзо. 

Сега на Get-VM. Не е брз бидејќи се занимава со сложени предмети. Се поставува логично прашање: зошто ни се потребни дополнителни информации и монструозен PSO-објект во овој случај, кога ни треба само името на VM, неговата состојба и вредноста на незгодниот атрибут?  

Покрај тоа, пречката во форма на Get-AdvancedOptions е отстранета од сценариото. Користењето на Runspace Pools сега изгледа како претерано, бидејќи повеќе нема потреба да се паралелизира бавната задача преку нишките за сквотови кога се предава сесијата. Алатката е добра, но не за овој случај. 

Ајде да го погледнеме излезот на ExtensionData: тоа не е ништо повеќе од објект Get-View. 

Ајде да ја повикаме древната техника на мајсторите на PowerShell: една линија користејќи филтри, сортирање и групирање. Целиот претходен хорор е елегантно склопен во една линија и извршен во една сесија:


$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

Го мериме времето:

Како да се изгради ракетен засилувач за PowerCLI скрипти

9 секунди за речиси 10 илјади објекти со филтрирање според саканата состојба. Одлично!

Наместо заклучок

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

PS: Авторот им се заблагодарува на сите членови на заедницата за помошта и поддршката во подготовката на статијата. Дури и оние со шепи. Па дури и оние кои немаат нозе, како боа констриктор.

Извор: www.habr.com

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