🌊

Azure Resource Graph と ThreadJob を使って Azure PowerShell を高速化する

2024/04/06に公開

TL;DR

  • Azure PowerShell を使った処理を高速化し隊
  • Azure Resource Graph (ARG) はいいぞ
  • PowerShell の ThreadJob はいいぞ

はじめに

Azure PowerShell を使って何か作業の効率化をするにあたって、よくあるパターンとしては Get-AzVM してそれを foreach で回す、みたいなのがあると思います。
シンプルな実装で分かりやすいのも重要なのですが、数百台を超えてくるとそこそこ時間がかかるようになり、効率化が求められます。
ここでは、2 つの方法を使って効率化し、かつそれをスニペットというか、コード片として残しておきます。

コピペ用

完成したものはこちらです。
$scriptBlock$query を書き換えることで使いまわしができます。

PowerState/deallocated なマシンだけに対して処理をするようなイメージ
# Required Modules and Azure Login
Connect-AzAccount | Out-Null

# Define the script block for processing each VM
$scriptBlock = {
    param($resource)

    $VerbosePreference = 'Continue'

    try {
        Write-Host "Processing VM Resource ID: $($resource.resourceId)"
        # Split the ResourceID to get individual components
        $resourceIdParts = $resource.resourceId -split '/'
        $subscriptionId = $resourceIdParts[2]
        $resourceGroupName = $resourceIdParts[4]
        $resourceName = $resourceIdParts[-1]

        Write-Host "Azure VM: $($resourceName) in Resource Group: $($resourceGroupName) in Subscription: $($subscriptionId) is $($resource.powerState)"
    } catch {
        Write-Host "Error processing VM Resource ID: $($resource.resourceId). Error: $_"
    }
}

# Initialize an array to hold the thread jobs
$jobs = @()

# Record the start time
$startTime = Get-Date

# Use Azure Resource Graph to retrieve only stopped VMs in the specified resource group
$query = @"
resources
| where type == 'microsoft.compute/virtualmachines'
| extend powerState = properties.extended.instanceView.powerState.code
| project-away properties
| where powerState == 'PowerState/deallocated'
"@

$resources = Search-AzGraph -Query $query

# Process each VM Resource ID in parallel using thread jobs
foreach ($resource in $resources) {
    # Start a new thread job for each VM Resource ID with a throttle limit
    $job = Start-ThreadJob -ScriptBlock $scriptBlock -ArgumentList $resource -ThrottleLimit 20
    # Add the job to the jobs array
    $jobs += $job
}

# Wait for all jobs to complete
$jobs | Wait-Job | Out-Null

# Retrieve and display the output from each job
$jobs | Receive-Job

# Clean up the jobs
$jobs | Remove-Job

# Calculate and display the duration
$endTime = Get-Date
Write-Host "Duration: $(($endTime - $startTime).TotalSeconds) seconds"

Write-Host "Process completed."

Azure Resource Graph を使った絞り込み

Get-AzVM はシンプルでいいのですが、powerState が PowerState/deallocated などの VM だけを取得、、というのはできません。
これは、Azure Resource Graph を使うことで解決できます。

# Use Azure Resource Graph to retrieve only stopped VMs in the specified resource group
$query = @"
resources
| where type == 'microsoft.compute/virtualmachines'
| extend powerState = properties.extended.instanceView.powerState.code
| project-away properties
| where powerState == 'PowerState/deallocated'
"@

$resources = Search-AzGraph -Query $query

Azure Resource Graph は Kusto Query Language (KQL) を使うので、分からない部分は検索したり、Microsoft Copilot for Azure に聞いて解決できます。

ThreadJob を使った並列処理

対象を絞ったところで、逐次処理ではやはり時間がかかります。
ので、ThreadJob を使った並列処理を行います。
ThreadJob については PowerShell Job を使ってみる に書いてありますので、ここでは割愛します。

Azure Resource Graph で検索した結果はオブジェクトに入っているので、それをそのまま ThreadJob に渡すことができます。

# Process each VM Resource ID in parallel using thread jobs
foreach ($resource in $resources) {
    # Start a new thread job for each VM Resource ID with a throttle limit
    $job = Start-ThreadJob -ScriptBlock $scriptBlock -ArgumentList $resource -ThrottleLimit 20
    # Add the job to the jobs array
    $jobs += $job
}

渡された ThreadJob 側で、resourceId を分解して、Subscription の ID やリソース グループの名前などを取得して、続く処理を実行するイメージです。
ここでは、Azure VM に関して少し詳細な感じでメッセージを出していますが、あまり意味はありません。

# Define the script block for processing each VM
$scriptBlock = {
    param($resource)

    $VerbosePreference = 'Continue'

    try {
        Write-Host "Processing VM Resource ID: $($resource.resourceId)"
        # Split the ResourceID to get individual components
        $resourceIdParts = $resource.resourceId -split '/'
        $subscriptionId = $resourceIdParts[2]
        $resourceGroupName = $resourceIdParts[4]
        $resourceName = $resourceIdParts[-1]

        Write-Host "Azure VM: $($resourceName) in Resource Group: $($resourceGroupName) in Subscription: $($subscriptionId) is $($resource.powerState)"
    } catch {
        Write-Host "Error processing VM Resource ID: $($resource.resourceId). Error: $_"
    }
}

まとめ

ずーっと書きたかったのですが、Get-AzVM して foreach して、、、というみなさまの PowerShell script が一気に高速化できるのではないか、というお話でした。
Azure Resource Graph は ARM template とほぼ同じ情報を扱えるので、フィルターとしては十分すぎる表現が可能と考えています。
また、ThreadJob という、比較的使いやすい並列処理の仕組みを使って、さらに処理を高速化できます。

参考

  • PowerShell Job を使ってみる

https://zenn.dev/skmkzyk/articles/powershell-job

  • クイック スタート:初めての PowerShell クエリ - Azure Resource Graph

https://learn.microsoft.com/azure/governance/resource-graph/first-query-powershell?wt.mc_id=MVP_391314

  • Azure Resource Graph から変更履歴を検索する

https://zenn.dev/microsoft/articles/8640c6b351e110

  • Log AnalyticsからAzure Resource Graphを通してアラートを上げる

https://zenn.dev/microsoft/articles/00cf34cb7e53cd

Discussion