Cara membangun akselerator roket untuk skrip PowerCLI 

Cepat atau lambat, administrator sistem VMware dapat mengotomatiskan tugas-tugas rutin. Semuanya dimulai dari baris perintah, kemudian datang PowerShell atau VMware PowerCLI.

Misalkan Anda sudah menguasai PowerShell sedikit lebih jauh daripada memulai ISE dan menggunakan cmdlet standar dari modul yang bekerja dengan semacam sihir. Ketika Anda mulai menghitung ratusan mesin virtual, Anda akan menemukan bahwa skrip yang membantu pada skala kecil bekerja terasa lebih lambat pada yang besar. 

Dalam situasi ini, 2 alat akan membantu:

  • PowerShell Runspaces - sebuah pendekatan yang memungkinkan Anda untuk memparalelasikan eksekusi proses di utas terpisah; 
  • Get-View adalah fungsi dasar PowerCLI, analog dari Get-WMIObject di Windows. Cmdlet ini tidak menyeret objek yang terkait dengannya, tetapi menerima informasi dalam bentuk objek sederhana dengan tipe data sederhana. Dalam banyak kasus, itu keluar lebih cepat.

Selanjutnya, saya akan berbicara secara singkat tentang setiap alat dan menunjukkan contoh penggunaan. Kami akan menganalisis skrip tertentu dan melihat kapan seseorang bekerja paling baik, kapan yang kedua. Pergilah!



Tahap Pertama: Runspace


Jadi, Runspace dirancang untuk pemrosesan tugas paralel di luar modul utama. Tentu saja, Anda dapat memulai proses lain yang akan memakan sebagian memori, prosesor, dll. Jika skrip Anda berjalan dalam beberapa menit dan menghabiskan satu gigabyte memori, Anda mungkin tidak akan memerlukan Runspace. Tetapi untuk skrip pada puluhan ribu objek itu diperlukan.
Anda dapat memulai pengembangan dari sini: 
Mulai Menggunakan PowerShell Runspaces: Bagian 1

Apa yang memberi manfaat bagi Runspace:

  • mempercepat dengan membatasi daftar perintah yang dapat dieksekusi,
  • tugas paralel
  • keamanan.

Ini adalah contoh dari internet ketika Runspace membantu:
« – , vSphere. vCenter , . , PowerShell.
, VMware vCenter .  
PowerShell runspaces, ESXi Runspace . PowerShell , , ».

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

Dalam kasus di bawah ini, Runspace tidak lagi dalam bisnis:
“Saya mencoba menulis skrip yang mengumpulkan banyak data dari VM dan, jika perlu, menulis data baru. Masalahnya adalah ada banyak VM, dan butuh 5-8 detik untuk satu mesin. ” 

Sumber: Multithreading PowerCLI dengan RunspacePool

Get-View diperlukan di sini, mari beralih ke sana. 

Tahap kedua: Get-View


Untuk memahami manfaat Get-View, ingat bagaimana cmdlet bekerja secara umum. 

Cmdlet diperlukan untuk mendapatkan informasi dengan mudah tanpa harus mempelajari buku referensi API dan menemukan kembali roda. Apa yang di masa lalu ditulis dalam seratus atau dua baris kode, PowerShell memungkinkan Anda untuk melakukan satu perintah. Untuk kenyamanan ini kami membayar kecepatan. Di dalam cmdlet itu sendiri, tidak ada sihir: naskah yang sama, tetapi dari tingkat yang lebih rendah, ditulis oleh tangan-tangan terampil seorang master dari India yang cerah.

Sekarang, untuk perbandingan dengan Get-View, ambil cmdlet Get-VM: ia mengakses mesin virtual dan mengembalikan objek komposit, yaitu, melampirkan objek terkait lainnya ke dalamnya: VMHost, Datastore, dll.  

Get-View di tempatnya tidak mengacaukan apa pun ke objek yang dikembalikan. Selain itu, ini memungkinkan Anda untuk secara kaku menunjukkan informasi apa yang kami butuhkan, yang akan memfasilitasi objek pada output. Pada Windows Server pada umumnya, dan pada Hyper-V pada khususnya, cmdlet Get-WMIObject adalah analog langsung - idenya persis sama.

Get-View tidak nyaman dalam operasi rutin pada fitur titik. Tetapi ketika menyangkut ribuan dan puluhan ribu objek, ia tidak memiliki harga.
Baca lebih lanjut di Blog VMware: Pengantar Get-View

Sekarang saya akan menunjukkan semuanya pada kasus nyata. 

Kami menulis skrip untuk menurunkan VM


Suatu kali kolega saya meminta saya untuk mengoptimalkan skripnya. Tugasnya adalah rutinitas normal: temukan semua VM dengan parameter duplikat cloud.uuid (ya, ini dimungkinkan ketika kloning VM di vCloud Director). 

Solusi jelas yang terlintas dalam pikiran:

  1. Dapatkan daftar semua VM.
  2. Entah bagaimana menguraikan daftar.

Versi aslinya adalah skrip sederhana:

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

Semuanya sangat sederhana dan jelas. Itu ditulis dalam beberapa menit dengan rehat kopi. Kencangkan filternya, dan selesai.

Tetapi mengukur waktu:





2 menit 47 detik saat memproses hampir 10k VM. Bonus adalah kurangnya filter dan kebutuhan untuk mengurutkan hasilnya secara manual. Jelas, skrip meminta optimasi.

Ransepses adalah yang pertama datang untuk menyelamatkan ketika Anda perlu mendapatkan metrik host dengan vCenter pada satu waktu atau Anda perlu memproses puluhan ribu objek. Mari kita lihat apa yang akan diberikan oleh pendekatan ini.

Kami mengaktifkan kecepatan pertama: PowerShell Runspaces

Hal pertama yang terlintas dalam pikiran untuk skrip ini adalah untuk mengeksekusi loop tidak secara seri, tetapi dalam utas paralel, mengumpulkan semua data dalam satu objek dan memfilternya. 

Tetapi ada masalah: PowerCLI tidak akan mengizinkan kami membuka banyak sesi independen ke vCenter dan akan membuat kesalahan lucu:

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.

Untuk mengatasinya, Anda harus terlebih dahulu meneruskan informasi sesi ke aliran. Kami ingat bahwa PowerShell berfungsi dengan objek yang dapat dikirimkan sebagai parameter ke setidaknya fungsi, setidaknya ke ScriptBlock. Mari kita lewati sesi sebagai objek yang melewati $ global: DefaultVIServers (Connect-VIServer dengan kunci -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 $_
           }
       }
   }

Sekarang kami menerapkan multithreading melalui Runspace Pools.  

Algoritma adalah sebagai berikut:

  1. Dapatkan daftar semua VM.
  2. Dalam utas paralel kami mendapatkan cloud.uuid.
  3. Kami mengumpulkan data dari stream ke satu objek.
  4. Kami memfilter objek melalui pengelompokan berdasarkan nilai bidang CloudUUID: yang di mana jumlah nilai unik lebih dari 1, dan ada VM yang diinginkan.

Hasilnya, kami mendapatkan skrip:


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
}

Keindahan skrip ini adalah dapat digunakan dalam kasus serupa lainnya, cukup dengan mengganti ScriptBlock dan parameter yang akan ditransfer ke aliran. Memanfaatkannya!

Kami mengukur waktu:



55 detik. Sudah lebih baik, tetapi masih lebih cepat. 

Kami beralih ke kecepatan kedua: GetView

Kami mencari tahu apa yang salah.
Pertama dan jelas: cmdlet Get-VM membutuhkan waktu lama untuk diselesaikan.
Kedua: Get-AdvancedOptions cmdlet berjalan lebih lama.
Pertama, mari kita berurusan dengan yang kedua. 

Get-AdvancedOptions mudah digunakan pada objek VM individual, tetapi sangat lambat saat bekerja dengan banyak objek. Kita bisa mendapatkan informasi yang sama dari objek mesin virtual itu sendiri (Get-VM). Hanya saja itu terkubur dengan baik di objek ExtensionData. Berbekal penyaringan, kami mempercepat proses mendapatkan data yang diperlukan.

Dengan gerakan pergelangan tangan, ini adalah:


VM | Get-AdvancedSetting -Name Cloud.uuid -Server $ConnectionString | Select-Object @{N="VMName";E={$_.Entity.Name}},@{N="CloudUUID";E={$_.Value}},@{N="PowerState";E={$_.Entity.PowerState}}

Berubah menjadi ini:


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

Kesimpulannya sama dengan Get-AdvancedOptions, tetapi kerjanya berkali-kali lebih cepat. 

Sekarang ke Get-VM. Ini tidak dieksekusi dengan cepat, karena berurusan dengan objek yang kompleks. Sebuah pertanyaan logis muncul: mengapa kita membutuhkan informasi tambahan dan sebuah PSObject mengerikan dalam kasus ini, ketika kita hanya perlu nama VM, status dan nilai atribut rumitnya?  

Selain itu, rem di muka Get-AdvancedOptions telah meninggalkan skrip. Menggunakan Runspace Pools sekarang tampaknya berlebihan, karena tidak ada lagi kebutuhan untuk memparalelkan tugas lambat dalam aliran dengan squat saat mentransfer sesi. Alatnya bagus, tetapi tidak untuk kasus ini. 

Kami melihat output dari ExtensionData: tidak lain adalah objek Get-View. 

Mari kita sebut teknik kuno dari master PowerShell: satu baris menggunakan filter, macam dan pengelompokan. Semua horor sebelumnya runtuh secara elegan menjadi satu baris dan dieksekusi dalam satu sesi:


$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

Kami mengukur waktu:



9 detik untuk hampir 10k objek dengan penyaringan sesuai dengan kondisi yang diinginkan. Baik!

Alih-alih kesimpulan.

Hasil yang dapat diterima secara langsung tergantung pada pilihan alat. Seringkali sulit untuk mengatakan dengan pasti apa yang harus dipilih untuk mencapainya. Setiap metode akselerasi skrip yang terdaftar baik dalam batas penerapannya. Saya harap artikel ini akan membantu Anda dalam tugas yang sulit untuk memahami dasar-dasar otomatisasi proses dan pengoptimalannya dalam infrastruktur Anda.

PS: Penulis berterima kasih kepada semua anggota komune atas bantuan dan dukungan mereka dalam mempersiapkan artikel. Bahkan mereka yang cakarnya. Dan bahkan yang tidak memiliki kaki, seperti boa constrictor.

All Articles