还是决定写一下这个话题,Powershell 非我的强项,本文大部分内容也来自于其他博客,就当是知识收藏。
大概10月份左右去做一个项目,看到以前同事留下的资料中包含很多 PowerCLI 的脚本,有去批量做配置的,有批量收集信息的,于是花时间整理了一套适合自己的,但总有一个问题不能很好地解决:Powershell 脚本是顺序执行的,面对大批量的数据收集速度很慢,通常会耗费 1 小时以上才能收集完信息,于是花了些时间尝试性能调优,将优化经验整理发出来。
0x00. 关于 PowerCLI 及 Powershell
Powercli 是 VMware 出品的一系列 Microsoft Powershell 模块,通过这些模块,用于可以非常快捷的利用 Powershell 对 vCenter、NSX、SRM 等资源进行批量操作,例如批量修改 ESXI 主机的配置、列举虚拟机清单等等。国外的 vSphere 管理员用 PowerCLI 非常多,社区中积累了大量的 PowerCLI 脚本,只要能想到的操作都能找到相关资源。
0x01. 优化原则
目前我使用过的优化方式有三种:
1、优化代码,提升单行单码的运行效率
2、异步执行命令
3、多线程
以下章节逐一讲解每个部分
0x02. 优化代码
查看瓶颈在哪里
Powershell 有个命令叫 Measure-Command ,通过此命令可以查看一段代码的运行时间,其使用方法如下:
Measure-Command{ 命令内容 }
例如,测试 Get-Process 的运行时间:
将过滤左移
一个通用的优化方式如下:假如你要先收集大量的数据,应当尽早对数据进行过滤,以缩减这部分数据的大小,再进行其他二次过滤。或者按照原作者的话,将过滤往代码的左边移动。
例如:
想要通过 get-process 获取 svchost 这个进程的 CPU 信息,可以通过以下两组代码实现:
Get-Process -Name 'Dock' | Where-Object {$_.CPU -ne $null} | Select-Object CPU
Get-Process | Where-Object {$_.ProcessName -eq 'Dock'} | Select-Object CPU | Where-Object {$_.CPU -ne $null}
性能对比如下:
可以看到前者运行速度比后者要快一倍多,如果是执行大量的任务,这一点点差距累计起来的差距是很大的。
PowerCLI 代码性能优化
笔者对 PowerCLI 的脚本进行了类似测试发现随着写法不同,代码执行速度差异很大,如下例:获取主机所在集群信息:
Get-VMHost $vmHost | Get-cluster
Get-Cluster -vmhost $vmHost
又如对 ESXi 本地盘数量的统计:
虽然 esxcli 看似步骤更多,代码更多,但使用 esxcli 的效率明显高于 Get-scsilun 的效率:
$ESXCLI = Get-EsxCli -VMHost $VMHost $luncount=$ESXCLI.storage.core.device.list() | select DeviceType | where DeviceType -eq "Direct-Access"write-host "Count is" $luncount.Count -ForegroundColor Green
$luncount=get-vmhost $VMhost| Get-ScsiLun -LunType disk write-host "Count is" $luncount.Count -ForegroundColor Green
遇到大量代码时,需要反复测试不同写法带来的速度差异,选择最优的。
赋值的效率远远高于执行一个查询
另外测试发现,所有赋值的操作速度都低于 1ms,所以这类操作并不影响性能。具体到代码优化上,应该尽可能减少与 vCenter 的交互。例如获取主机所在集群:
Get-VMHost $vmHost|Get-cluster
Get-Cluster -vmhost $vmHost
$info.cluster=$vmhost.Parent.name
直接的赋值远比执行一条查询快。三条命令执行速度分别如下:
0x03. 异步任务
PowerCLI 有相当一部分操作可以使用 -RunAsync
参数,添加此参数后,此命令在执行后只通过 vCenter 创建任务,并不监控任务执行情况。
在执行某些批量任务的时候,例如虚拟机开关机、虚拟机创建会非常有用。
0x04. 使用多线程
Powershell 有多种方式来创建多线程任务,时间原因我只测试使用了 PSJob ,除此之外还有 Runspace 和 RSJob ,有兴趣的可以自行研究。
PSJob 使用条件
如果要使用 PSJob 创建多线程任务,前提条件有:
- Powershell 3.0 以上版本
- 宿主机的内存和CPU足够多
- 确保程序可以多线程运行
注意:在进行多线程改造前,需要确保多线程运行时,多个线程间不会冲突。例如多个进程同时写入文件便会冲突,但是同时读取一个文件就没问题。
PSJobs 的 cmdlet 内置于 Microsoft.PowerShell.Core 中,可以使用下列命令查看此 cmdlet 下的命令:
Get-Command *-Job
PSJobs 共有 8 种状态,最常用的如下:
- Completed :Job 已经完成,可以获取 job 输出的信息,或者 job 可以被安全移除。
- Running:Job 正在运行,无法停止(除非强制停止),无法获取输出信息。
- Blocked:Job 正在运行,但是系统提示要求输入信息才能继续。
- Failed:执行 Job 时出错。
start-job 开始任务
使用 start-job
可以创建一个新的任务,如果使用 measure-command 查看任务执行时间,可以看到远远小于命令真实的执行时间。
get-job 获取 job 状态
通过 get-job
命令可以查看 Job 的运行情况。
PS51> Start-Job -Scriptblock {Start-Sleep 5}
PS51> Get-Job
上例中,如果任务还在运行,则 State 显示 Running,如果运行完毕,State 为 Completed。
另外在运行完后 HasMoreData 值为 False,表示此 Job 没有输出。
下图是其他状态的截图,使用的命令见 Command 列。
receive-job 获取任务输出
通过 receive-job
命令可以查看 Job 的输出信息(使用 write-output
输出),注意一旦输出后 Job 中就不会再有这些信息了,因此建议等 Job 完成后再完整收集信息。
$Job = Start-Job -ScriptBlock {Write-Output 'Hello World'}
Receive-Job $Job
参数传递
默认 start-job 生产的任务无法使用全局定义的变量,需要 ArgumentList 进行变量的传递。
Start-Job -Scriptblock {param ($Text) Write-Output $Text} -ArgumentList "Hello world!"
任务执行完毕后的操作
在做一些批量收集的时候,我们通过 start-job
来创建了大量的 Job,但这只是下发了任务,我们还需要在任务执行完毕后读取并处理数据,在此我使用 for 循环定期检查 Job 是不是已经完成,只有等待完成后才批量读取数据,具体操作如下:
$tasklist=@(
"task1"
"task2"
"task3"
)
$jobs=@()
Foreach ($task in $tasklist) {
write-host "Starting Job..."
$jobs += start-job -ScriptBlock {
param ($taskname)
sleep 5
Write-Output "Task Finished : "$taskname
} -ArgumentList $task}while($jobs.state -contains "Running")
{write-host "still running!" -ForegroundColor Yellow
sleep 1
}
write-host "Finished!" -ForegroundColor Greenreceive-job $jobs
输出优化
上面提到可以在 job 内使用 Write-Output
输出信息 ,job 外使用receive-job
来接受信息,
在实际测试时发现 Write-Output
对于一个比较大的变量效率非常低(例如我尝试输出 Get-vmhost 的结果,几十分钟没有反应),低到怀疑人生,因此最佳方法是在 Job 内完成该有的过滤。
例如:
Measure-Command{
write-host "Starting Job..."
$job = start-job -ScriptBlock { $debuginfo=Connect-VIServer -Server "192.168.1.1" -User 'administrator@vsphere.local' -Password 'VMware1!' -WarningAction Ignore $vmHost =get-vmhost "esx06.vsphere.local" Write-Output $vmHost Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false}while($job.state -contains "Running") { write-host "still running!" -ForegroundColor Yellow sleep 1
}
write-host "Finished!" -ForegroundColor Greenreceive-job $job}
与
Measure-Command{ $job = start-job -ScriptBlock { $debuginfo=Connect-VIServer -Server "192.168.1.1" -User 'administrator@vsphere.local' -Password 'VMware1!' -WarningAction Ignore $vmHost =get-vmhost "esx06.vsphere.local"| select name,Version Write-Output $vmHost Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false } while($job.state -contains "Running") { write-host "still running!" -ForegroundColor Yellow sleep 1 } write-host "Finished!" -ForegroundColor Greenreceive-job $job}
后面的命令只是在输出时增加了 select name,Version
,执行时间只有 7 秒,而前面的程序几十分钟了还没反应。
PSJob 应用于 PowerCLI
在应用于 PowerCLI 时,发现每个 job 都必须单独连接 vCenter,好在这个操作可以将密码写入代码,无需交互:
$debuginfo=Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false
$debuginfo=Connect-VIServer -Server "192.168.1.1" -User 'administrator@vsphere.local' -Password 'VMware1!' -WarningAction Ignore
在通过以上各种手段处理后,原本收集100台主机的信息需要十几分钟,现在缩减到了两分多钟:
不过对应的,电脑风扇开始狂响。。
参考资料:
https://adamtheautomator.com/powershell-multithreading/#runspace-vs-psjobs
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_jobs?view=powershell-7