迟早任何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时可以这样做)。 显而易见的解决方案是:- 获取所有虚拟机的列表。
- 以某种方式解析列表。
原始版本是如此简单的脚本:function Get-CloudUUID1 {
$vms = Get-VM
$report = @()
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 $_
}
}
}
现在,我们通过运行空间池实现多线程。 算法如下:- 获取所有虚拟机的列表。
- 在并行线程中,我们得到cloud.uuid。
- 我们将数据从流收集到一个对象中。
- 我们通过按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 = {
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."
}
$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。