如何为PowerCLI脚本构建火箭加速器 

迟早任何VMware系统管理员都可以自动执行日常任务。所有这些都从命令行开始,然后是PowerShell或VMware PowerCLI。

假设您掌握了PowerShell的知识,而不是启动ISE并使用了由于某种魔术而起作用的模块中的标准cmdlet。当您开始数以百计的虚拟机时,您会发现小规模帮助的脚本在大规模脚本上的运行明显变慢。 

在这种情况下,有2种工具会有所帮助:

  • PowerShell Runspaces –一种使您可以并行执行单独线程中进程的方法; 
  • Get-View是基本的PowerCLI函数,类似于Windows上的Get-WMIObject。此cmdlet不会拖动与其相关的对象,而是以具有简单数据类型的简单对象的形式接收信息。在许多情况下,它的发布速度更快。

接下来,我将简要讨论每种工具并显示使用示例。我们将分析特定的脚本,看看哪种脚本效果最好,什么时候效果最好。走!



第一阶段:运行空间


因此,Runspace设计用于并行处理主模块之外的任务。当然,您可以启动另一个进程,该进程将占用一些内存,处理器等。如果您的脚本在几分钟内运行并花费了1GB的内存,则可能不需要Runspace。但是对于包含成千上万个对象的脚本来说,这是必需的。
您可以从此处开始开发: 
开始使用PowerShell运行空间:第1部分

使用运行空间的原因是什么:

  • 通过限制可执行命令列表来提高速度,
  • 并行任务
  • 安全。

这是Runspace在互联网上提供帮助的示例:
« – , vSphere. vCenter , . , PowerShell.
, VMware vCenter .  
PowerShell runspaces, ESXi Runspace . PowerShell , , ».

: How to Show Virtual Machine I/O on an ESXi Dashboard

在以下情况下,Runspace不再营业:
“我正在尝试编写一个从VM收集大量数据的脚本,并在必要时写入新数据。问题是虚拟机很多,一台机器需要5到8秒。” 

来源:具有RunspacePool的多线程PowerCLI

这里需要获取视图,让我们继续前进。 

第二阶段:获取视图


若要了解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 Blog上了解更多信息:Get-View简介

现在,我将在真实案例中展示所有内容。 

我们编写了一个脚本来卸载虚拟机


一次,我的同事要我优化他的脚本。该任务是一个常规例程:查找具有重复cloud.uuid参数的所有VM(是的,在vCloud Director中克隆VM时可以这样做)。 

显而易见的解决方案是:

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

一切都非常简单明了。它是在几分钟内写完,然后喝咖啡休息一下。拧上过滤器,然后完成。

但是测量时间:处理近10k VM时为





2分47秒。一个好处是缺少过滤器,并且需要手动对结果进行排序。显然,脚本正在要求优化。

当您需要一次通过vCenter获取主机指标,或者需要处理成千上万个对象时,Ranepses是第一个抢救的对象。让我们看看这种方法会带来什么。

我们以最快的速度启动: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的对象。让我们通过这样的会话来绕过$全局对象: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和将要传输到流中的参数即可。利用它!

我们测量时间:



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相同,但是它的运行速度快了很多倍。 

现在到Get-VM。由于它处理复杂的对象,因此无法快速执行。出现一个逻辑问题:在这种情况下,当我们只需要VM的名称,状态和棘手属性的值时,为什么需要额外的信息和庞大的PSObject?  

另外,面对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

我们测量时间:将近10,000个对象需要



9秒,并根据所需条件进行过滤。精细!

代替结论

可接受的结果直接取决于工具的选择。通常很难确定要实现该目标究竟应该选择什么。在其适用范围内,列出的每种脚本加速方法都不错。我希望本文能帮助您完成艰巨的任务,以了解流程自动化及其基础架构中的优化的基础知识。

PS:作者感谢公社的所有成员在撰写本文时的帮助和支持。即使是有爪子的人。甚至没有腿的人,例如蟒蛇con。

All Articles