Por Luke Orellana

La contención de recursos de almacenamiento es una de las métricas de recursos más difíciles de rastrear en vSphere. No hay una manera fácil de saber en vCenter nativo qué MV en qué host ESXi está consumiendo gran cantidad de E/S de almacenamiento. Por suerte, ¡podemos reunir esta métrica sobre la marcha en cuestión de minutos con PowerShell! Voy a compartir un script que creé que permite a los administradores de VMware buscar en todo su entorno vCenter y generar una lista de todas las máquinas virtuales y sus respectivas E/S promedio. También aprovecha los espacios de ejecución de PowerShell para que cada host ESXi recopile datos de E/S de sus propias máquinas virtuales en su propio espacio de ejecución independiente y pase la información informe una vez realizado. Esto permite que PowerShell simplemente inicie cada tarea sin tener que esperar a que cada host ESXi complete su consulta antes de pasar al siguiente host ESXi. Permite escalar el script a un nivel mucho mayor para aquellos entornos vSphere más grandes.

Cómo ejecutar el script

Para comenzar a ejecutar el script en tu entorno, asegúrate de que PowerCLI esté instalado (instalación alternativa para Linux aquí) en el punto final que vaya a ejecutar el script. Instala PowerCli abriendo una consola administrativa de PowerShell y ejecuta lo siguiente o echa un vistazo a los enlaces que se acaban de mencionar si necesitas un análisis más detallado.

Install-Module -Name VMware.PowerCLI

A continuación, copia el código siguiente en un editor de texto (como el bloc de notas) o en las herramientas de edición de scripts (como PowerShell ISE o VSCode) y guárdalo como .ps1:

 [Parameter(Mandatory=$True,ValueFromPipeline=$true,ParameterSetName="Session")]$session,

   [Parameter(Mandatory=$True,ParameterSetName="Server")]     
   [String]$Server,     

   [Parameter(Mandatory=$True,ParameterSetName="Server")] 
   [System.Management.Automation.PSCredential]$Credential,     

   [Parameter(ParameterSetName="Session")]     
   [parameter(ParameterSetName = "Server")]     
   [String]$Minutes,   
  
   [Parameter(ParameterSetName="Session")]     
   [parameter(ParameterSetName = "Server")]     
   [String]$Hours     

   ) 
Begin{

    #Create Start Time
    if ($minutes){ $starttime = (get-date).AddMinutes(0-$Minutes)}
    if ($hours) { $starttime = (get-date).AddHours(0-$Hours)}
    if ($null -eq $Minutes -and $null -eq $hours) {$starttime = (get-date).AddMinutes(-5)}

    #connect to host
    If ($server){$Session= connect-viserver -server $server -credential $credential -NotDefault}
    
    #Get list of ESXi Hosts in VCenter
    $ESXiHosts = Get-vmhost * -server $Session | Where {$_.PowerState -eq "PoweredOn"}

}
Process{
     
    #Set up Runspace increase Max Threads to increase number of runspaces running at a time
    $MaxThreads = 10
    $RunspacePool = [RunspaceFactory ]::CreateRunspacePool(1, $MaxThreads)
    $RunspacePool.Open()
    $Jobs = @()

    #Get I/O for all VMs on each Host
    Foreach ($ESXihost in $ESXiHosts){

        #Create scriptblock to run for each ESXi host, parameters are passed through to script
        $scriptblock = {

           #Get list of VMs
            $VMs = Get-vm * -server $args[1] -location $args[0] | Where {$_.PowerState -eq "PoweredOn"}
            Foreach($VM in $VMs){
                Try{
                    #Gather I/O statistics for the VM based on the StartTime parameter
                    $numberwrite = get-stat -realtime -entity (get-vm $vm -server $args[1]) -stat disk.numberWriteAveraged.average -start $args[2] -finish (get-date) -ErrorAction Stop
                    $numberread = get-stat -realtime -entity (get-vm $vm -server $args[1]) -stat disk.numberReadAveraged.average -start $args[2] -finish (get-date) -ErrorAction Stop

Foreach ($writesample in $numberwrite){[array]$writedata += $writesample.value} Foreach ($readsample in $numberread){[array]$readdata += $readsample.value}

$averageread = [int]($readdata | measure-object -average).average $averagewrite = [int]($writedata | measure-object -average).average $writedata = $null $readdata = $null } Catch{ #If metrics are not available set them to 0 $averageread = 0 $averagewrite = 0 }
                #Set up Array for reporting I/O stats
                $name = $vm.name
                $ESXiHost = $vm.vmhost
                $status = New-Object System.Object
                $status | Add-Member -type NoteProperty -name VM -value $name
                $status | Add-Member -type NoteProperty -name AvgReadIO -value $averageread
                $status | Add-Member -type NoteProperty -name AvgWriteIO -value $averagewrite
               $status | Add-Member -type NoteProperty -name AvgTotalIO -value ($averagewrite + $averageread)
                $status | Add-Member -type NoteProperty -name ESXHost -value $ESXiHost
                [array]$VMreport += $status
            }
            #Output Report
            $VMreport
      }
       #Run the script block in a separate runspace for the ESXi Host and pass parameter's through
        $VMinfoJob = [powershell ]::Create().AddScript($ScriptBlock).AddArgument($ESXihost).AddArgument($Session).AddArgument($starttime)
        $VMinfoJob.RunspacePool = $RunspacePool
        $Jobs += New-Object PSObject -Property @{
                                Pipe = $VMinfoJob
                                Result = $VMinfoJob.BeginInvoke()
                                JobName = "ESXi-$esxihost"
                                }
    }

#Wait until jobs are complete Do {Start-sleep -Milliseconds 300 } While ( $Jobs.Result.IsCompleted -contains $false) #Display output from each Runspace Job for status ForEach ($Job in $Jobs){
        $info= $Job.Pipe.EndInvoke($Job.Result)
        [array]$report += $info
    }
    
    $report 

}

En mi ejemplo siguiente, lo guardé en c:\ scripts\VMware y nombré el archivo Get-VMIO.ps1:

Ahora, abre una consola de Powershell administrativa y escribe la siguiente sintaxis para ejecutar el comando. Puedes usar el parámetro -hours o -minutes para especificar la antigüedad con la que desea las estadísticas. Es decir, si quisiéramos estadísticas de E/S de los últimos 5 minutos, usaríamos -minutes 5:

c:\scripts\vmware\Get-VMIO.ps1 -server 192.168.0.7 -minutes 5 | ft
Tablero de instrumentos de EXSi PowerShell

Podemos ver que el resultado es una tabla con las estadísticas de E/S promedio. No tengo un montón de E/S pesado en el momento de crear estas capturas de pantalla, por lo que los números van a ser bastante bajos.

Agregar la herramienta al tablero

Don Jones siempre dice: «Sé el fabricante de herramientas, no el usuario de la herramienta». Estas son sabias palabras, por lo que si vamos a crear un script que se pueda ejecutar desde la consola de PowerShell, también podemos asegurarnos de que nuestro script se pueda ejecutar desde un Panel de PowerShell. El truco consiste en permitir pasar un objeto de sesión de VIServer a través de un parámetro, así nuestro script puede usar esa sesión para ejecutar varios comandos PowerCLI. Este es el trozo que nos permite crear un parámetro que pasará por una sesión:

[CmdletBinding()]
param(
    [Parameter(Mandatory=$True,ValueFromPipeline=$true,ParameterSetName="Session")]$session,

En mis pruebas, descubrí que no especificar el tipo de objeto funciona mejor que especificar el nombre del tipo [VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl] dentro del parámetro. Así podemos ejecutar el script desde la consola. Establecemos nuestra conexión a VCenter como una variable y especificamos el parámetro -NotDefault. Esto es muy importante, ya que se encontrará con errores sobre la variable $global:defaultVIServer si no se especifica:

$newvcsession = connect-viserver -server 192.168.0.7 -notdefault

Ahora que tenemos una sesión, podemos pasarla a nuestro script con la siguiente sintaxis:

c:\scripts\vmware\Get-VMIO.ps1 -session $newvcsession -minutes 5 | ft

Puedes ver que obtenemos los mismos resultados que antes pero, en cambio, estamos usando una sesión de vCenter. Así es como funciona PowerShell Universal Dashboard, el mensaje de login inicial se autenticará en vCenter y almacenará en caché esa sesión en una variable guardada en memoria. Luego, se pasa esa variable entre cada página del panel, lo que permite a los usuarios ir clicando sin que se les solicite iniciar sesión cada vez. Lo bonito de esto es que, desde que escribimos nuestro script inicial con la capacidad de manejar objetos VISession, la integración de este script en nuestro panel de control se puede realizar en cuestión de minutos. Todo lo que tenemos que hacer es llamar a nuestro script desde la página del panel de control, como se muestra a continuación. Con solo unas pocas líneas para esta página. Solo estamos configurando una Tabla de cuadrícula y generando los datos de Get-VMIO.ps1 en una tabla. Establecí el intervalo de actualización en 120 segundos, lo que significa que el tablero se actualizará cada 2 minutos con el comando:

New-UDPage -Name "VMIO" -Content {
  
    #Create Rows
    New-UDRow {
        New-UdGrid -Title "VM Average IO" -Headers @("VM","AvgReadIO","AvgWriteIO","AvgTotalIO") -Properties @("VM","AvgReadIO","AvgWriteIO","AvgTotalIO") -Endpoint {
                    
                    $params = @{Session=$cache:VCSession;Minutes=2}
                    & "C:\ESXi Dashboard\Pages\Tools\Get-VMIO.ps1" @params  | Select-object VM,AvgReadIO,AVGWriteIO,AvgTotalIO | Out-UDGridData
                     
         }  -AutoRefresh -RefreshInterval 120       

    }



}

El resultado final se ve así:

VM Media I / O en un panel de control de ESXi

Para acabar

Voy a añadirlo como una herramienta (como a muchas otras en el futuro) en el proyecto ESXi Dashboard en la comunidad GitHub de Altaro. Si no has visto nuestra publicación sobre cómo crear su propio Tablero de ESXi con el módulo Tablero Universal, échale un vistazo. Como puedes ver si planeas que tus scripts se ejecuten desde varios entornos, puedes integrar rápidamente las herramientas que crea en los tableros con solo unas pocas líneas de código. Y además queda bastante bonito. Ya sabes, ahora puedes ganar puntos con tu jefe con un Tablero totalmente creado por ti