Cách xây dựng bộ tăng tốc tên lửa cho tập lệnh PowerCLI 

Sớm hay muộn, bất kỳ quản trị viên hệ thống VMware nào cũng sẽ tự động hóa các tác vụ thông thường. Tất cả bắt đầu bằng dòng lệnh, sau đó đến PowerShell hoặc VMware PowerCLI.

Giả sử bạn đã thành thạo PowerShell hơn một chút ngoài việc khởi chạy ISE và sử dụng các lệnh ghép ngắn tiêu chuẩn từ các mô-đun hoạt động nhờ “một loại phép thuật nào đó”. Khi bắt đầu đếm số máy ảo lên hàng trăm, bạn sẽ thấy rằng các tập lệnh trợ giúp ở quy mô nhỏ chạy chậm hơn đáng kể trên quy mô lớn. 

Trong tình huống này, 2 công cụ sẽ giúp ích:

  • Không gian chạy PowerShell – một cách tiếp cận cho phép bạn song song hóa việc thực thi các tiến trình trong các luồng riêng biệt; 
  • Nhận-Xem – chức năng PowerCLI cơ bản, tương tự như Get-WMIObject trong Windows. Lệnh ghép ngắn này không kéo các đối tượng đi kèm với các thực thể mà nhận thông tin dưới dạng một đối tượng đơn giản với các kiểu dữ liệu đơn giản. Trong nhiều trường hợp nó xuất hiện nhanh hơn.

Tiếp theo, tôi sẽ nói ngắn gọn về từng công cụ và đưa ra các ví dụ về cách sử dụng. Hãy phân tích các tập lệnh cụ thể và xem khi nào tập lệnh này hoạt động tốt hơn tập lệnh khác. Đi!

Cách xây dựng bộ tăng tốc tên lửa cho tập lệnh PowerCLI

Giai đoạn đầu tiên: Runspace

Vì vậy, Runspace được thiết kế để xử lý song song các tác vụ bên ngoài mô-đun chính. Tất nhiên, bạn có thể khởi chạy một quy trình khác sẽ ngốn một số bộ nhớ, bộ xử lý, v.v. Nếu tập lệnh của bạn chạy sau vài phút và tiêu tốn một gigabyte bộ nhớ, rất có thể bạn sẽ không cần Runspace. Nhưng đối với các tập lệnh cho hàng chục nghìn đối tượng thì điều đó là cần thiết.

Bạn có thể bắt đầu học tại đây: 
Bắt đầu sử dụng PowerShell Runspaces: Phần 1

Việc sử dụng Runspace mang lại điều gì:

  • tốc độ bằng cách giới hạn danh sách các lệnh được thực thi,
  • thực hiện song song các nhiệm vụ,
  • sự an toàn.

Đây là một ví dụ từ Internet khi Runspace trợ giúp:

“Tranh chấp bộ nhớ là một trong những số liệu khó theo dõi nhất trong vSphere. Bên trong vCenter, bạn không thể chỉ xem VM nào đang tiêu tốn nhiều tài nguyên lưu trữ hơn. May mắn thay, bạn có thể thu thập dữ liệu này sau vài phút nhờ PowerShell.
Tôi sẽ chia sẻ một tập lệnh cho phép quản trị viên hệ thống VMware nhanh chóng tìm kiếm trên vCenter và nhận danh sách các máy ảo có dữ liệu về mức tiêu thụ trung bình của chúng.  
Tập lệnh sử dụng các không gian chạy PowerShell để cho phép mỗi máy chủ ESXi thu thập thông tin tiêu thụ từ các máy ảo của chính nó trong một Runspace riêng biệt và báo cáo hoàn thành ngay lập tức. Điều này cho phép PowerShell đóng công việc ngay lập tức, thay vì lặp lại qua các máy chủ và đợi từng máy hoàn thành yêu cầu của nó.”

Nguồn: Cách hiển thị I/O máy ảo trên bảng điều khiển ESXi

Trong trường hợp bên dưới, Runspace không còn hữu ích nữa:

“Tôi đang cố gắng viết một tập lệnh thu thập nhiều dữ liệu từ máy ảo và ghi dữ liệu mới khi cần thiết. Vấn đề là có khá nhiều máy ảo và chỉ dành 5-8 giây cho một máy.” 

Nguồn: PowerCLI đa luồng với RunspacePool

Ở đây bạn sẽ cần Get-View, hãy chuyển sang nó. 

Giai đoạn thứ hai: Xem

Để hiểu tại sao Get-View lại hữu ích, cần nhớ cách hoạt động của các lệnh ghép ngắn nói chung. 

Cần có các lệnh ghép ngắn để lấy thông tin một cách thuận tiện mà không cần phải nghiên cứu sách tham khảo API và phát minh lại bánh xe tiếp theo. Những gì ngày xưa cần tới một trăm hoặc hai dòng mã thì PowerShell cho phép bạn thực hiện chỉ bằng một lệnh. Chúng tôi trả tiền cho sự thuận tiện này bằng tốc độ. Không có phép thuật nào bên trong các lệnh ghép ngắn: cùng một chữ viết, nhưng ở cấp độ thấp hơn, được viết bởi bàn tay khéo léo của một bậc thầy đến từ Ấn Độ đầy nắng.

Bây giờ, để so sánh với Get-View, hãy lấy lệnh ghép ngắn Get-VM: nó truy cập vào máy ảo và trả về một đối tượng tổng hợp, nghĩa là nó gắn các đối tượng liên quan khác vào nó: VMHost, Datastore, v.v.  

Get-View ở vị trí của nó không thêm bất kỳ thứ gì không cần thiết vào đối tượng được trả về. Hơn nữa, nó cho phép chúng tôi chỉ định nghiêm ngặt những thông tin chúng tôi cần, điều này sẽ giúp đối tượng đầu ra trở nên dễ dàng hơn. Trong Windows Server nói chung và trong Hyper-V nói riêng, lệnh ghép ngắn Get-WMIObject là một lệnh tương tự trực tiếp - ý tưởng hoàn toàn giống nhau.

Get-View không thuận tiện cho các thao tác thông thường trên các đối tượng điểm. Nhưng khi nói tới hàng nghìn, hàng chục nghìn đồ vật thì nó không có giá cả.

Bạn có thể đọc thêm trên blog VMware: Giới thiệu về Get-View

Bây giờ tôi sẽ chỉ cho bạn mọi thứ bằng cách sử dụng một chiếc ốp lưng thực tế. 

Viết tập lệnh để dỡ bỏ VM

Một ngày nọ, đồng nghiệp của tôi yêu cầu tôi tối ưu hóa kịch bản của anh ấy. Nhiệm vụ này là một quy trình phổ biến: tìm tất cả các máy ảo có tham số cloud.uuid trùng lặp (vâng, điều này có thể thực hiện được khi sao chép máy ảo trong vCloud Director). 

Giải pháp rõ ràng xuất hiện trong đầu là:

  1. Nhận danh sách tất cả các máy ảo.
  2. Phân tích danh sách bằng cách nào đó.

Phiên bản gốc là tập lệnh đơn giản này:

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
}
# Далее РУКАМИ парсим полученный результат

Mọi thứ đều cực kỳ đơn giản và rõ ràng. Nó có thể được viết trong vài phút sau giờ giải lao uống cà phê. Vặn bộ lọc vào là xong.

Nhưng hãy đo thời gian:

Cách xây dựng bộ tăng tốc tên lửa cho tập lệnh PowerCLI

Cách xây dựng bộ tăng tốc tên lửa cho tập lệnh PowerCLI

Số phút 2 47 giây khi xử lý gần 10k máy ảo. Một phần thưởng là không có bộ lọc và cần phải sắp xếp kết quả theo cách thủ công. Rõ ràng, kịch bản yêu cầu tối ưu hóa.

Runspaces là giải pháp đầu tiên được giải quyết khi bạn cần lấy đồng thời số liệu máy chủ từ vCenter hoặc cần xử lý hàng chục nghìn đối tượng. Hãy xem cách tiếp cận này mang lại điều gì.

Bật tốc độ đầu tiên: PowerShell Runspaces

Điều đầu tiên bạn nghĩ đến đối với tập lệnh này là thực thi vòng lặp không tuần tự mà theo các luồng song song, thu thập tất cả dữ liệu vào một đối tượng và lọc nó. 

Nhưng có một vấn đề: PowerCLI sẽ không cho phép chúng ta mở nhiều phiên độc lập trên vCenter và sẽ đưa ra một lỗi buồn cười:

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.

Để giải quyết vấn đề này, trước tiên bạn phải chuyển thông tin phiên bên trong luồng. Chúng ta hãy nhớ rằng PowerShell hoạt động với các đối tượng có thể được truyền dưới dạng tham số cho hàm hoặc cho ScriptBlock. Hãy vượt qua phiên ở dạng một đối tượng như vậy, bỏ qua $global:DefaultVIServers (Connect-VIServer với khóa -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 $_
           }
       }
   }

Bây giờ hãy triển khai đa luồng thông qua Runspace Pools.  

Thuật toán như sau:

  1. Chúng tôi nhận được một danh sách tất cả các máy ảo.
  2. Trong các luồng song song, chúng tôi nhận được cloud.uuid.
  3. Chúng tôi thu thập dữ liệu từ các luồng vào một đối tượng.
  4. Chúng tôi lọc đối tượng bằng cách nhóm nó theo giá trị của trường CloudUUID: những đối tượng có số lượng giá trị duy nhất lớn hơn 1 là các VM mà chúng tôi đang tìm kiếm.

Kết quả là chúng ta nhận được kịch bản:


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
}

Cái hay của tập lệnh này là nó có thể được sử dụng trong các trường hợp tương tự khác bằng cách thay thế ScriptBlock và các tham số sẽ được chuyển vào luồng. Khai thác nó!

Chúng tôi đo thời gian:

Cách xây dựng bộ tăng tốc tên lửa cho tập lệnh PowerCLI

55 giây. Nó tốt hơn, nhưng nó vẫn có thể nhanh hơn. 

Hãy chuyển sang tốc độ thứ hai: GetView

Chúng ta hãy tìm hiểu những gì sai.
Đầu tiên và quan trọng nhất, lệnh ghép ngắn Get-VM mất nhiều thời gian để thực thi.
Thứ hai, lệnh ghép ngắn Get-AdvancedOptions thậm chí còn mất nhiều thời gian hơn để hoàn thành.
Trước tiên hãy giải quyết vấn đề thứ hai. 

Get-AdvancedOptions thuận tiện cho các đối tượng VM riêng lẻ, nhưng lại rất vụng về khi làm việc với nhiều đối tượng. Chúng ta có thể lấy thông tin tương tự từ chính đối tượng máy ảo (Get-VM). Nó được chôn kỹ trong đối tượng ExtensionData. Được trang bị tính năng lọc, chúng tôi tăng tốc quá trình thu thập dữ liệu cần thiết.

Với một cử động nhẹ của bàn tay, đây là:


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

Biến thành thế này:


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

Đầu ra giống như Get-AdvancedOptions, nhưng nó hoạt động nhanh hơn nhiều lần. 

Bây giờ đến Get-VM. Nó không nhanh vì nó xử lý các đối tượng phức tạp. Một câu hỏi hợp lý được đặt ra: tại sao chúng ta cần thêm thông tin và một PSObject khổng lồ trong trường hợp này, khi chúng ta chỉ cần tên của VM, trạng thái của nó và giá trị của một thuộc tính phức tạp?  

Ngoài ra, chướng ngại vật ở dạng Get-AdvancedOptions đã bị xóa khỏi tập lệnh. Việc sử dụng Runspace Pools giờ đây có vẻ như là quá mức cần thiết vì không còn cần phải thực hiện song song một tác vụ chậm trên các luồng squat khi chuyển phiên. Công cụ này tốt, nhưng không dành cho trường hợp này. 

Hãy xem đầu ra của ExtensionData: nó không gì khác hơn là một đối tượng Get-View. 

Hãy sử dụng kỹ thuật cổ xưa của các bậc thầy PowerShell: một dòng sử dụng các bộ lọc, sắp xếp và nhóm. Tất cả nỗi kinh hoàng trước đó được thu gọn một cách tao nhã thành một dòng và được thực hiện trong một phiên:


$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

Chúng tôi đo thời gian:

Cách xây dựng bộ tăng tốc tên lửa cho tập lệnh PowerCLI

9 giây cho gần 10k đối tượng với tính năng lọc theo điều kiện mong muốn. Tuyệt vời!

Thay vì một kết luận

Một kết quả chấp nhận được trực tiếp phụ thuộc vào việc lựa chọn công cụ. Thường rất khó để nói chắc chắn chính xác nên chọn gì để đạt được mục tiêu đó. Mỗi phương pháp tăng tốc tập lệnh được liệt kê đều phù hợp trong giới hạn khả năng áp dụng của nó. Tôi hy vọng bài viết này sẽ giúp bạn trong nhiệm vụ khó khăn là tìm hiểu những kiến ​​thức cơ bản về tự động hóa và tối ưu hóa quy trình trong cơ sở hạ tầng của bạn.

PS: Tác giả cảm ơn tất cả các thành viên cộng đồng vì sự giúp đỡ và hỗ trợ của họ trong việc chuẩn bị bài viết. Ngay cả những người có bàn chân. Và ngay cả những người không có chân cũng giống như con trăn.

Nguồn: www.habr.com

Thêm một lời nhận xét