چگونه یک تقویت کننده موشک برای اسکریپت های PowerCLI بسازیم 

دیر یا زود، هر مدیر سیستم VMware برای خودکارسازی کارهای روتین می آید. همه چیز با خط فرمان شروع می شود، سپس PowerShell یا VMware PowerCLI می آید.

فرض کنید شما کمی بیشتر از راه اندازی ISE و استفاده از cmdlet های استاندارد از ماژول هایی که به دلیل "نوعی جادو" کار می کنند، به PowerShell تسلط دارید. وقتی شروع به شمارش ماشین‌های مجازی به صدها می‌کنید، متوجه می‌شوید که اسکریپت‌هایی که در مقیاس کوچک کمک می‌کنند، در مقیاس بزرگ به‌طور محسوسی کندتر اجرا می‌شوند. 

در این شرایط، 2 ابزار کمک خواهد کرد:

  • PowerShell Runspaces - رویکردی که به شما امکان می دهد اجرای فرآیندها را در موضوعات جداگانه موازی کنید. 
  • دریافت-نمایش – یک تابع پایه PowerCLI، آنالوگ Get-WMIObject در ویندوز. این cmdlet اشیاء موجودات همراه را نمی کشد، بلکه اطلاعات را به شکل یک شی ساده با انواع داده های ساده دریافت می کند. در بسیاری از موارد سریعتر بیرون می آید.

در مرحله بعد، من به طور خلاصه در مورد هر ابزار صحبت می کنم و نمونه هایی از استفاده را نشان می دهم. بیایید اسکریپت های خاص را تجزیه و تحلیل کنیم و ببینیم چه زمانی یکی بهتر از دیگری کار می کند. برو!

چگونه یک تقویت کننده موشک برای اسکریپت های PowerCLI بسازیم

مرحله اول: Runspace

بنابراین، Runspace برای پردازش موازی وظایف خارج از ماژول اصلی طراحی شده است. البته، می‌توانید فرآیند دیگری را راه‌اندازی کنید که مقداری از حافظه، پردازنده و غیره را می‌خورد. اگر اسکریپت شما در عرض چند دقیقه اجرا شود و یک گیگابایت حافظه مصرف کند، به احتمال زیاد به Runspace نیازی نخواهید داشت. اما برای اسکریپت های ده ها هزار شی مورد نیاز است.

می توانید از اینجا شروع به یادگیری کنید: 
شروع استفاده از PowerShell Runspaces: قسمت 1

استفاده از Runspace چه می دهد:

  • سرعت با محدود کردن لیست دستورات اجرا شده،
  • اجرای موازی وظایف،
  • امنیت.

در اینجا یک مثال از اینترنت است که Runspace کمک می کند:

«مشاهده فضای ذخیره‌سازی یکی از سخت‌ترین معیارها برای ردیابی در vSphere است. در داخل vCenter، نمی‌توانید بروید و ببینید کدام VM منابع ذخیره‌سازی بیشتری مصرف می‌کند. خوشبختانه، به لطف PowerShell می توانید این داده ها را در چند دقیقه جمع آوری کنید.
من اسکریپتی را به اشتراک خواهم گذاشت که به مدیران سیستم VMware اجازه می دهد تا به سرعت در سرتاسر vCenter جستجو کنند و لیستی از VM ها را با داده های میانگین مصرف آنها دریافت کنند.  
این اسکریپت از فضاهای اجرا PowerShell استفاده می کند تا به هر میزبان ESXi اجازه دهد تا اطلاعات مصرف را از ماشین های مجازی خود در یک Runspace جداگانه جمع آوری کند و بلافاصله تکمیل را گزارش کند. این به PowerShell اجازه می‌دهد تا فوراً کارها را ببندد، نه اینکه از طریق هاست‌ها تکرار شود و منتظر بماند تا هر یک درخواست خود را تکمیل کند.

منبع: نحوه نمایش ورودی/خروجی ماشین مجازی در داشبورد ESXi

در مورد زیر، Runspace دیگر مفید نیست:

من سعی می‌کنم اسکریپتی بنویسم که داده‌های زیادی را از VM جمع‌آوری کند و در صورت لزوم داده‌های جدیدی بنویسد. مشکل این است که ماشین های مجازی بسیار زیاد هستند و 5 تا 8 ثانیه روی یک ماشین صرف می شود. 

منبع: Multithreading PowerCLI با RunspacePool

در اینجا به Get-View نیاز دارید، بیایید به آن برویم. 

مرحله دوم: Get-View

برای درک اینکه چرا Get-View مفید است، باید به یاد داشته باشید که cmdlet ها به طور کلی چگونه کار می کنند. 

Cmdlet ها برای به دست آوردن راحت اطلاعات بدون نیاز به مطالعه کتاب های مرجع API و اختراع مجدد چرخ بعدی مورد نیاز هستند. چیزی که در قدیم صد یا دو خط کد می گرفت، PowerShell به شما اجازه می دهد با یک دستور انجام دهید. ما هزینه این راحتی را با سرعت پرداخت می کنیم. هیچ جادوی درون خود cmdlet ها وجود ندارد: همان اسکریپت، اما در سطح پایین تر، نوشته شده توسط دستان ماهر استادی از هند آفتابی.

اکنون برای مقایسه با Get-View، بیایید cmdlet Get-VM را در نظر بگیریم: به ماشین مجازی دسترسی پیدا می کند و یک شی ترکیبی را برمی گرداند، یعنی سایر اشیاء مرتبط را به آن متصل می کند: VMHost، Datastore و غیره.  

Get-View در جای خود هیچ چیز غیر ضروری به شیء برگشتی اضافه نمی کند. علاوه بر این، به ما این امکان را می دهد که دقیقاً مشخص کنیم به چه اطلاعاتی نیاز داریم، که شی خروجی را آسان تر می کند. در ویندوز سرور به طور کلی و در Hyper-V به طور خاص، cmdlet Get-WMIObject یک آنالوگ مستقیم است - ایده دقیقاً یکسان است.

Get-View برای عملیات روتین روی اشیاء نقطه ناخوشایند است. اما وقتی صحبت از هزاران و ده ها هزار شی می شود، قیمتی ندارد.

می توانید در وبلاگ VMware بیشتر بخوانید: مقدمه ای بر Get-View

اکنون همه چیز را با استفاده از یک کیس واقعی به شما نشان خواهم داد. 

نوشتن یک اسکریپت برای تخلیه ماشین مجازی

یک روز همکارم از من خواست فیلمنامه اش را بهینه کنم. این کار یک روال معمول است: همه ماشین های مجازی را با یک پارامتر تکراری cloud.uuid پیدا کنید (بله، این امکان هنگام شبیه سازی یک ماشین مجازی در vCloud Director وجود دارد). 

راه حل واضحی که به ذهن می رسد این است:

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

همه چیز بسیار ساده و واضح است. می توان آن را در چند دقیقه با یک استراحت قهوه نوشت. فیلتر را پیچ کنید و تمام شد.

اما بیایید زمان را اندازه گیری کنیم:

چگونه یک تقویت کننده موشک برای اسکریپت های PowerCLI بسازیم

چگونه یک تقویت کننده موشک برای اسکریپت های PowerCLI بسازیم

2 دقیقه 47 ثانیه هنگام پردازش تقریباً 10k VM. یک امتیاز عدم وجود فیلتر و نیاز به مرتب سازی دستی نتایج است. بدیهی است که اسکریپت نیاز به بهینه سازی دارد.

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 را دور بزنیم (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 $_
           }
       }
   }

حال بیایید multithreading را از طریق Runspace Pools پیاده سازی کنیم.  

الگوریتم به شرح زیر است:

  1. ما لیستی از تمام ماشین های مجازی دریافت می کنیم.
  2. در جریان های موازی، cloud.uuid را دریافت می کنیم.
  3. ما داده ها را از جریان ها در یک شی جمع آوری می کنیم.
  4. ما شی را با گروه بندی آن بر اساس مقدار فیلد 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 و پارامترهایی که به استریم منتقل می شود استفاده کرد. از آن بهره برداری کنید!

زمان را اندازه گیری می کنیم:

چگونه یک تقویت کننده موشک برای اسکریپت های PowerCLI بسازیم

55 СЃРμРєСѓРЅРґ. بهتر است، اما هنوز هم می تواند سریعتر باشد. 

بیایید به سرعت دوم برویم: GetView

بیایید بفهمیم چه مشکلی دارد.
اول و مهمتر از همه، اجرای cmdlet Get-VM زمان زیادی نیاز دارد.
دوم، تکمیل 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 بروید. سریع نیست زیرا با اشیاء پیچیده سروکار دارد. یک سوال منطقی مطرح می شود: چرا در این مورد به اطلاعات اضافی و یک PSObject هیولا نیاز داریم، در حالی که فقط به نام 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

اضافه کردن نظر