Mais cedo ou mais tarde, qualquer administrador de sistema VMware consegue automatizar tarefas de rotina. Tudo começa na linha de comando e, em seguida, vem o PowerShell ou o VMware PowerCLI.Suponha que você tenha dominado o PowerShell um pouco mais do que iniciar o ISE e usar cmdlets padrão de módulos que funcionam com algum tipo de mágica. Quando você começa a contar centenas de máquinas virtuais, verá que os scripts que ajudaram em pequenas escalas funcionam visivelmente mais devagar em grandes. Nesta situação, duas ferramentas ajudarão:- Espaços de execução do PowerShell - uma abordagem que permite paralelizar a execução de processos em threads separados;
- O Get-View é uma função básica do PowerCLI, um análogo do Get-WMIObject no Windows. Esse cmdlet não arrasta objetos relacionados a ele, mas recebe informações na forma de um objeto simples com tipos de dados simples. Em muitos casos, sai mais rápido.
A seguir, falarei brevemente sobre cada ferramenta e mostrarei exemplos de uso. Analisaremos scripts específicos e veremos quando um funciona melhor, quando o segundo. Vai!
Primeira Etapa: Runspace
Portanto, o Runspace foi projetado para processamento paralelo de tarefas fora do módulo principal. Obviamente, você pode iniciar outro processo que consumirá memória, processador etc. Se o script for executado em alguns minutos e gasta um gigabyte de memória, provavelmente você não precisará do Runspace. Mas, para scripts em dezenas de milhares de objetos, é necessário.Você pode iniciar o desenvolvimento a partir daqui:
Iniciando o uso dos espaços de execução do PowerShell: Parte 1
O que dá uso ao Runspace:- velocidade limitando a lista de comandos executáveis,
- tarefas paralelas
- segurança.
Aqui está um exemplo da Internet quando o Runspace ajuda:« – , vSphere. vCenter , . , PowerShell.
, VMware vCenter .
PowerShell runspaces, ESXi Runspace . PowerShell , , ».
: How to Show Virtual Machine I/O on an ESXi Dashboard
No caso abaixo, a Runspace não está mais no negócio:“Estou tentando escrever um script que coleta muitos dados da VM e, se necessário, grava novos dados. O problema é que existem muitas VMs e leva de 5 a 8 segundos para uma máquina. ”
Fonte: PowerCLI multithreading com RunspacePool
O Get-View é necessário aqui, vamos prosseguir. Segunda etapa: Get-View
Para entender a utilidade do Get-View, lembre-se de como os cmdlets funcionam em geral. Os cmdlets são necessários para obter informações convenientemente sem a necessidade de estudar os livros de referência da API e reinventar a roda. O que antigamente era escrito em uma centena ou duas linhas de código, o PowerShell permite que você execute um comando. Por essa conveniência, pagamos velocidade. Dentro dos próprios cmdlets, não há mágica: o mesmo script, mas de nível inferior, escrito pelas mãos hábeis de um mestre da ensolarada Índia.Agora, para comparação com o Get-View, use o cmdlet Get-VM: ele acessa a máquina virtual e retorna um objeto composto, ou seja, anexa outros objetos relacionados a ela: VMHost, Datastore etc. O Get-View em seu lugar não estraga nada extra no objeto retornado. Além disso, permite que você indique rigidamente exatamente quais informações precisamos, o que facilitará o objeto na saída. No Windows Server em geral, e no Hyper-V em particular, o cmdlet Get-WMIObject é um análogo direto - a idéia é exatamente a mesma.O Get-View é inconveniente em operações de rotina em recursos de pontos. Mas quando se trata de milhares e dezenas de milhares de objetos, ele não tem preço.Leia mais no Blog da VMware: Introdução ao Get-View
Agora vou mostrar tudo em um caso real. Escrevemos um script para descarregar uma VM
Uma vez, meu colega me pediu para otimizar seu script. A tarefa é uma rotina normal: encontre todas as VMs com um parâmetro cloud.uuid duplicado (sim, isso é possível ao clonar uma VM no vCloud Director). A solução óbvia que vem à mente:- Obtenha uma lista de todas as VMs.
- De alguma forma, analise a lista.
A versão original era um script tão simples: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
}
Tudo é extremamente simples e claro. Está escrito em alguns minutos com uma pausa para o café. Enrosque o filtro e pronto.Mas meça o tempo:
2 minutos e 47 segundos ao processar quase 10k VM. Um bônus é a falta de filtros e a necessidade de classificar manualmente o resultado. Obviamente, o script está pedindo otimização.Ransepses são os primeiros a serem resgatados quando você precisa obter métricas de host com o vCenter de uma só vez ou precisa processar dezenas de milhares de objetos. Vamos ver o que essa abordagem dará.Ativamos a primeira velocidade: PowerShell RunspacesA primeira coisa que vem à mente para esse script é executar o loop não em série, mas em threads paralelos, colete todos os dados em um objeto e os filtre. Mas há um problema: o PowerCLI não nos permitirá abrir muitas sessões independentes para o vCenter e lançará um erro engraçado: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.
Para resolvê-lo, você deve primeiro passar as informações da sessão para o fluxo. Lembramos que o PowerShell trabalha com objetos que podem ser passados como parâmetro para pelo menos uma função, pelo menos para ScriptBlock. Vamos passar a sessão como um objeto ignorando $ global: DefaultVIServers (Connect-VIServer com a tecla -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 $_
}
}
}
Agora, implementamos multithreading por meio de pools de Runspace. O algoritmo é o seguinte:- Obtenha uma lista de todas as VMs.
- Em threads paralelos, obtemos cloud.uuid.
- Coletamos dados de fluxos em um objeto.
- Nós filtramos o objeto agrupando pelo valor do campo CloudUUID: aqueles em que o número de valores exclusivos é maior que 1 e existem as VMs desejadas.
Como resultado, obtemos o script:
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
}
A beleza desse script é que ele pode ser usado em outros casos semelhantes, simplesmente substituindo o ScriptBlock e os parâmetros que serão transferidos para o fluxo. Explorar!Medimos o tempo:
55 segundos. Já é melhor, mas ainda mais rápido. Passamos para a segunda velocidade: GetViewDescobrimos o que está errado.Primeiro e óbvio: o cmdlet Get-VM leva muito tempo para ser concluído.Segundo: o cmdlet Get-AdvancedOptions executa ainda mais.Primeiro, vamos lidar com o segundo. Get-AdvancedOptions é conveniente em objetos individuais da VM, mas muito lento ao trabalhar com muitos objetos. Podemos obter as mesmas informações do próprio objeto da máquina virtual (Get-VM). É só que está bem enterrado no objeto ExtensionData. Armado com a filtragem, aceleramos o processo de obtenção dos dados necessários.Com um movimento do pulso, isto é:
VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}
Se transforma nisso:
$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}}
A conclusão é a mesma que Get-AdvancedOptions, mas funciona muitas vezes mais rápido. Agora para Get-VM. Não é executado rapidamente, pois lida com objetos complexos. Uma questão lógica surge: por que precisamos de informações extras e um PSObject monstruoso nesse caso, quando precisamos apenas do nome da VM, seu estado e valor do atributo complicado? Além disso, o freio diante de Get-AdvancedOptions deixou o script. O uso de Pools de Runspace agora parece um exagero, pois não há mais necessidade de paralelizar uma tarefa lenta em fluxos com agachamentos ao transferir uma sessão. A ferramenta é boa, mas não para este caso. Observamos a saída de ExtensionData: não é nada além de um objeto Get-View. Vamos chamar a técnica antiga dos mestres do PowerShell: uma linha usando filtros, classificação e agrupamento. Todo o horror anterior se quebra elegantemente em uma linha e é executado em uma sessão:
$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
Medimos o tempo:
9 segundos para quase 10 mil objetos com filtragem de acordo com a condição desejada. Bem!Em vez de uma conclusão Umresultado aceitável depende diretamente da escolha da ferramenta. Muitas vezes é difícil dizer com certeza o que exatamente deve ser escolhido para alcançá-lo. Cada um dos métodos de aceleração de script listados é bom dentro dos limites de sua aplicabilidade. Espero que este artigo o ajude na difícil tarefa de entender os conceitos básicos da automação de processos e sua otimização em sua infraestrutura.PS: O autor agradece a todos os membros da comuna por sua ajuda e apoio na preparação do artigo. Mesmo aqueles com patas. E mesmo quem não tem pernas, como uma jibóia.