Як пабудаваць ракетны паскаральнік для скрыптоў PowerCLI
Рана ці позна любы сістэмны адміністратар VMware даходзіць да аўтаматызацыі руцінных задач. Пачынаецца ўсё з каманднага радка, потым ідзе PowerShell ці VMware PowerCLI.
Дапушчальны, вы асвоілі PowerShell ледзь далей запуску ISE і выкарыстанні стандартных камандлетаў з модуляў, якія працуюць за рахунак «нейкай магіі». Калі вы пачняце лічыць віртуальныя машыны сотнямі, то выявіце, што скрыпты, якія выбаўлялі на малых маштабах, працуюць прыкметна павольней на вялікіх.
У гэтай сітуацыі выбавяць 2 прылады:
PowerShell Runspaces - падыход, які дазваляе распаралеліць выкананне працэсаў у асобных патоках;
Get-View – базавая функцыя PowerCLI, аналаг Get-WMIObject у Windows. Гэты камандлет не цягне за сабой спадарожныя сутнасці аб'екты, а атрымлівае інфармацыю ў выглядзе простага аб'екта з простымі тыпамі даных. У многіх выпадках выходзіць хутчэй.
Далей коратка раскажу пра кожную прыладу і пакажу прыклады выкарыстання. Разбяром пэўныя скрыпты і паглядзім, калі лепш працуе адзін, калі другі. Паехалі!
Першая ступень: Runspace
Такім чынам, Runspace прызначаны для раўналежнай апрацоўкі задач па-за асноўным модулем. Вядома, можна запусціць яшчэ адзін працэс, які з'есць колькі-то памяці, працэсара і т. д. Калі ваш скрыпт адпрацоўвае за пару хвілін і марнуе гігабайт памяці, хутчэй за ўсё, Runspace вам не запатрабуецца. А вось для скрыптоў на дзясяткі тысяч аб'ектаў ён патрэбны.
хуткасць за кошт абмежавання спісу выкананых каманд,
паралельнае выкананне задач,
бяспеку.
Вось прыклад з інтэрнэту, калі Runspace дапамагае:
«Канкурэнцыя за рэсурсы сховішчы - адна з метрык, якія складана адсочваць у vSphere. Усярэдзіне vCenter нельга проста ўзяць і паглядзець, якая ВМ спажывае больш рэсурсаў сховішчы. На шчасце, сабраць гэтыя дадзеныя можна за хвіліны дзякуючы PowerShell.
Падзялюся скрыптам, які дазволіць сістэмным адміністратарам VMware хутка выконваць пошук па ўсім vCenter і атрымліваць ліст ВМ з дадзенымі па іх сярэднім спажыванні.
Скрыпт выкарыстоўвае PowerShell runspaces, каб кожны хост ESXi збіраў інфармацыю па спажыванні яго ўласных ВМ у асобным Runspace і адразу паведамляў аб завяршэнні. Гэта дазваляе PowerShell адразу зачыняць джобы, а не перабіраць паслядоўна хасты і не чакаць, пакуль кожны завершыць свой запыт».
«Спрабую напісаць скрыпт, які збірае шмат дадзеных з ВМ і пры неабходнасці запісвае новыя дадзеныя. Праблема ў тым, што ВМ дастаткова шмат, і на адну машыну марнуецца па 5-8 секунд».
Каб разабрацца, чым карысны Get-View, варта ўспомніць, як працуюць камандлеты ўвогуле.
Камандлеты патрэбныя для зручнага атрымання інфармацыі без неабходнасці штудзіраваць даведнікі па API і вынаходзіць чарговы ровар. Тое, што ў старыя часы распісвалася ў сотню-іншую радкоў кода, PowerShell дазваляе зрабіць адной камандай. За гэтую зручнасць мы плацім хуткасцю. Унутры саміх камандлетаў ніякай магіі няма: той жа скрыпт, але ніжэйшага ўзроўню, напісаны ўмелымі рукамі майстра з сонечнай Індыі.
Зараз для параўнання з Get-View возьмем камандлет Get-VM: ён звяртаецца да віртуальнай машыны і вяртае складовай аб'ект, гэта значыць прыкладае да яго іншыя спадарожныя аб'екты: VMHost, Datastore і т. д.
Get-View на яго месцы не прыкручвае ў які вяртаецца аб'ект нічога лішняга. Больш за тое, ён дазваляе жорстка паказаць, якая менавіта інфармацыя нам патрэбная, што аблегчыць аб'ект на выхадзе. У Windows Server у цэлым і ў Hyper-V, у прыватнасці, прамым аналогам з'яўляецца камандлет Get-WMIObject - ідэя абсалютна тая ж.
Get-View няёмкі ў руцінных аперацыях над кропкавымі аб'ектамі. Але калі гаворка заходзіць аб тысячах і дзясятках тысяч аб'ектаў, яму няма кошту.
Аднойчы мой калега папрасіў мяне аптымізаваць ягоны скрыпт. Задача - звычайная руціна: знайсці ўсё ВМ з дублюючым параметрам cloud.uuid (так, такое магчыма пры кланаванні ВМ у vCloud Director).
Відавочны варыянт рашэння, які прыходзіць на розум:
Атрымаць спіс усіх ВМ.
Нейкім чынам распарсіць спіс.
Зыходным варыянтам быў такі немудрагелісты скрыпт:
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
}
# Далее РУКАМИ парсим полученный результат
Усё лімітава проста і зразумела. Пішацца за пару хвілін з перапынкам на каву. Прыкруціць фільтраванне, і справа зроблена.
Але замерым час:
2 хвіліны 47 секунд пры апрацоўцы амаль 10k ВМ. Бонусам - адсутнасць фільтраў і неабходнасць уручную сартаваць вынік. Відавочна, што скрыпт патрабуе аптымізацыі.
Ранспейсы першымі прыходзяць на дапамогу, калі трэба адначасова атрымаць метрыкі хастоў з 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 (Connect-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 $_
}
}
}
Цяпер рэалізуем мультыструменнасць праз Runspace Pools.
Алгарытм наступны:
Атрымліваем спіс усіх ВМ.
У паралельных патоках атрымліваем cloud.uuid.
Дадзеныя з плыняў збіраем у адзін аб'ект.
Фільтруем аб'ект праз групоўку па значэнні поля CloudUUID: тыя, дзе колькасць унікальных значэнняў большая за 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
}
Хараство гэтага скрыпту ў тым, што яго можна выкарыстоўваць і ў іншых падобных выпадках, проста замяніўшы ScriptBlock і параметры, якія будуць перададзены ў струмень. Exploit it!
Замяраем час:
55 секунд. Ужо лепш, але ўсё роўна мага хутчэй.
Пераходзім на другую хуткасць: GetView
Высвятляем, што не так.
Першае і відавочнае: камандлет Get-VM выконваецца доўга.
Другое: камандлет Get-AdvancedOptions выконваецца яшчэ даўжэй.
Спачатку разбяромся з другім.
Get-AdvancedOptions зручны на асобных аб'ектах ВМ, але вельмі непаваротлівы пры працы са мноствам аб'ектаў. Тую ж самую інфармацыю мы можам атрымаць з самага аб'екта віртуальнай машыны (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}}
Выснова тая ж, што і ў Get-AdvancedOptions, але працуе ў разы хутчэй.
Цяпер да Get-VM. Ён выконваецца не хутка, бо мае справу са складанымі аб'ектамі. Устае лагічнае пытанне: а навошта нам у дадзеным выпадку лішняя інфармацыя і монструозны PSObject, калі нам усяго-то трэба імя ВМ, яе стан і значэнне хітрага атрыбуту?
Да таго ж, са скрыпту сышоў тормаз у асобе Get-AdvancedOptions. Ужыванне Runspace Pools зараз выглядае празмернасцю, бо больш няма неабходнасці ў распаралельванні павольнай задачы ў струменях з прысяданнямі пры перадачы сесіі. Інструмент добры, але не для гэтага кейса.
Глядзім на выснову ExtensionData: гэта не што іншае, як аб'ект Get-View.
Паклічам старажытную тэхніку майстроў PowerShell: one line з ужываннем фільтраў, сартаванняў і групоўкі. Увесь папярэдні жах элегантна схлопваецца ў адзін радок і выконваецца ў адной сесіі:
9 секунд для амаль 10k аб'ектаў з фільтраваннем па патрэбнай умове. Выдатна!
замест заключэння
Прымальны вынік напрамую залежыць ад выбару прылады. Часцяком складана сказаць напэўна, што менавіта трэба абраць для яго дасягнення. Кожны з пералічаных метадаў паскарэння скрыптоў добры ў межах сваёй дастасавальнасці. Спадзяюся, дадзены артыкул дапаможа вам у нялёгкай справе зразумення асноў аўтаматызацыі працэсаў і іх аптымізацыі ў вашай інфраструктуры.
PS: Аўтар дзякуе ўсім удзельнікам камуны за дапамогу і падтрымку пры падрыхтоўцы артыкула. Нават тых, у каго лапкі. І нават у каго лапак няма, як у ўдава.