如何為 PowerCLI 腳本建立火箭助推器 

遲早,任何 VMware 系統管理員都會自動執行日常任務。 這一切都從命令列開始,然後是 PowerShell 或 VMware PowerCLI。

假設您對 PowerShell 的掌握程度比啟動 ISE 和使用因「某種魔力」而起作用的模組中的標準 cmdlet 更進一步。 當您開始計算數百個虛擬機器時,您會發現在小規模上提供幫助的腳本在大規模上運行速度明顯較慢。 

在這種情況下,有兩個工具可以提供幫助:

  • PowerShell 運作空間 – 一種允許您在單獨的執行緒中並行執行進程的方法; 
  • 取得視圖 – 基本的 PowerCLI 函數,類似於 Windows 中的 Get-WMIObject。 此 cmdlet 不會提取伴隨實體的對象,而是接收具有簡單資料類型的簡單對象形式的資訊。 在許多情況下,它的結果會更快。

接下來,我將簡要介紹每個工具並展示使用範例。 讓我們分析一下具體的腳本,看看一個腳本何時比另一個腳本效果更好。 去!

如何為 PowerCLI 腳本建立火箭助推器

第一階段:運行空間

因此,Runspace 是為主模組之外的任務的並行處理而設計的。 當然,您可以啟動另一個進程,該進程會消耗一些內存、處理器等。如果您的腳本運行幾分鐘並消耗一千兆字節的內存,那麼您很可能不需要運行空間。 但對於數以萬計的物件的腳本來說,這是需要的。

您可以從這裡開始學習: 
開始使用 PowerShell 運作空間:第 1 部分

使用 Runspace 可以帶來什麼:

  • 透過限制執行命令的清單來提高速度,
  • 並行執行任務,
  • 安全。

以下是 Runspace 提供協助時來自網際網路的範例:

「儲存爭用是 vSphere 中最難追蹤的指標之一。 在 vCenter 內部,您不能只查看哪個虛擬機器消耗了更多儲存資源。 幸運的是,借助 PowerShell,您可以在幾分鐘內收集這些數據。
我將分享一個腳本,該腳本允許 VMware 系統管理員快速搜尋整個 vCenter,並接收包含平均消耗資料的虛擬機器清單。  
該腳本使用 PowerShell 運行空間,允許每個 ESXi 主機從單獨的運行空間中自己的虛擬機收集消耗信息,並立即報告完成情況。 這允許 PowerShell 立即關閉作業,而不是遍歷主機並等待每個主機完成其請求。”

來源: 如何在 ESXi 儀表板上顯示虛擬機器 I/O

在以下情況下,Runspace 不再有用:

「我正在嘗試編寫一個腳本,從虛擬機器收集大量資料並在必要時寫入新資料。 問題是虛擬機相當多,一台機器上要花5-8秒。” 

來源: 帶有 RunspacePool 的多執行緒 PowerCLI

這裡你需要 Get-View,讓我們繼續吧。 

第二階段:取得視圖

要了解 Get-View 為何有用,有必要記住 cmdlet 的一般工作原理。 

需要cmdlet來方便地獲取訊息,而不需要研究API參考書和重新發明下一個輪子。 過去需要一百兩行程式碼才能完成的工作,現在 PowerShell 可以讓您用一條指令來完成。 我們為這種便利付出了速度的代價。 這些 cmdlet 本身並沒有什麼魔力:相同的腳本,但等級較低,由來自陽光明媚的印度的大師的巧手編寫。

現在,為了與 Get-View 進行比較,我們以 Get-VM cmdlet 為例:它存取虛擬機器並傳回一個複合對象,也就是說,它附加了其他相關對象:VMHost、Datastore 等。  

Get-View 不會為傳回的物件新增任何不必要的內容。 而且,它允許我們嚴格指定我們需要什麼訊息,這將使輸出物件變得更容易。 在一般的 Windows Server 中,特別是在 Hyper-V 中,Get-WMIObject cmdlet 是一個直接的模擬 - 想法完全相同。

Get-View對於點物件的常規操作不方便。 但到了幾千萬件的時候,就沒有價格了。

您可以在 VMware 部落格上閱讀更多內容: 取得視圖簡介

現在我用一個真實的案例來告訴你一切。 

編寫腳本來卸載虛擬機

有一天,我的同事讓我優化他的腳本。 這個任務是一個常見的例程:尋找具有重複 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 個虛擬機器時。 好處是沒有過濾器並且需要手動對結果進行排序。 顯然,該腳本需要優化。

當您需要同時從 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(使用 -NotDefault 鍵的 Connect-VIServer):

$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. 在並行流中,我們得到cloud.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 需要很長時間才能執行。
其次,Get-AdvancedOptions cmdlet 需要更長的時間才能完成。
我們先處理第二個問題。 

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 相同,但運行速度快很多倍。 

現在取得VM。 它並不快,因為它處理複雜的物件。 一個邏輯問題出現了:為什麼在這種情況下我們需要額外的資訊和一個巨大的 PSObject,而我們只需要虛擬機器的名稱、其狀態和一個棘手屬性的值?  

此外,Get-AdvancedOptions 形式的障礙已從腳本中刪除。 現在使用運行空間池似乎有點大材小用,因為在移交會話時不再需要跨蹲執行緒並行化緩慢的任務。 該工具很好,但不適合這種情況。 

讓我們來看看 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秒 對於近 10k 個對象,並依所需條件進行篩選。 偉大的!

取而代之的是結論

可接受的結果直接取決於工具的選擇。 通常很難確定到底該選擇什麼來實現這一目標。 列出的每種加速腳本的方法在其適用範圍內都是很好的。 我希望本文能幫助您完成了解基礎架構中的流程自動化和最佳化基礎知識的艱鉅任務。

PS: 作者感謝所有社區成員在準備本文時提供的幫助和支持。 即使是那些有爪子的人。 甚至那些沒有腿的動物,像是蟒蛇。

來源: www.habr.com

添加評論