Azureコラム 第10回「Azure Virtual Desktop 運用自動化 Vol.1」
早いものでAzureコラム 第7回「Azure Virtual Desktop システム構成 Vol.3」から3年、細々と行ってきた運用に満を持して自動化のメスを入れることにしました。
Vol.1は月次更新運用の自動化をお届けします。
条件・制約
- ●!!!!! スクリプトの実行は自己責任でお願いします !!!!!
- ●PowerShellモジュールは2024年8月20日時点で最新化して動作確認を行っています。
自動化の効果
最後まで読んでいただいて「なんだその程度か」も申し訳ありませんので、冒頭に本稿の自動化の効果を記載しておきます。
- ●作業量:約30画面の操作 → 5つのスクリプト実行
- ●作業時間:約30分 → 5分 ※デプロイ待ち時間除く
- ●所感:既定値の入力をしなくて良くなるのが精神衛生的に効果あり!
月次更新フロー
月次更新は更新プログラム公開に合わせて行っており、毎月下記のフローを回しています。
Step | フロー | 補足 | |
---|---|---|---|
1 | マスターVMのデプロイ | 前回更新時に取得したスナップショットからマスターVMをデプロイ | |
2 | マスターVMの更新① | 1. 更新プログラムの適用 2. (あれば)FSLogixの更新 3. ディスククリーンアップ |
※ 本稿の自動化対象外 |
3 | スナップショット取得 | マスターVMのスナップショット取得 | |
4 | マスターVMの更新② | sysprep実行 | ※ 本稿の自動化対象外 |
5 | イメージのキャプチャ | マスターVMをギャラリーイメージにキャプチャ | |
6 | テストAVDのデプロイ | ギャラリーイメージからテストAVDをデプロイ | |
7 | テスト実施 | テストAVDにて接続テストを実施 | ※ 本稿の自動化対象外 |
8 | 本番AVDのデプロイ | ギャラリーイメージから本番AVDをデプロイ | |
9 | リソースクリーンアップ | 古いAVDなど不要なリソースをクリーンアップ |
それでは次セクションから各ステップの運用自動化スクリプトをご紹介していきます。
Step1:マスターVMのデプロイ
Step1ではスナップショットからマスターVMをデプロイします。
パラメーター:
パラメーター | 値 | 例 |
---|---|---|
$resourceGroupName | 展開先リソースグループ名 | avd-master |
$location | 展開先リージョン | japaneast |
$snapshotName | デプロイ元のスナップショット名 | AVDMaster_OSDisk_1_19000101 |
$storageType | ディスクSKU | Standard_LRS |
$diskSize | ディスクサイズ(GB) | 128 |
$subnetId | 展開先のサブネットID(リソースID形式) | /subscriptions/~/subnets/master |
$vmName | VM名 | AVDMaster |
$vmSize | VMサイズ | Standard_B2ls_v2 |
コマンド:
$ErrorActionPreference = "Stop"
$resourceGroupName = "avd-master"
$location = "japaneast"
$snapshotName = "AVDMaster_OSDisk_1_19000101"
$storageType="Standard_LRS"
$diskSize = 128
$subnetId = "/subscriptions/01234567-8901-2345-6789-012345678901/resourceGroups/avd/providers/Microsoft.Network/virtualNetworks/avd-nw/subnets/master"
$vmName = "AVDMaster"
$vmSize = "Standard_B2ls_v2"
# Connect to Azure
if (-not (Get-AzContext)) {Connect-AzAccount}
try {
# Create a new disk from the snapshot
Write-Output "Get the snapshot: ${snapshotName}"
$snapshot = Get-AzSnapshot -ResourceGroupName $resourceGroupName -SnapshotName $snapshotName
# Create a new disk
$osDiskName = "${vmName}_OSDisk_1"
Write-Output "Deploy the disk: ${osDiskName}"
$diskConfig = New-AzDiskConfig `
-SkuName $storageType `
-Location $location `
-CreateOption Copy `
-SourceResourceId $snapshot.Id `
-DiskSizeGB $diskSize
$osDisk = New-AzDisk `
-Disk $diskConfig `
-ResourceGroupName $resourceGroupName `
-DiskName $osDiskName
# Create a new NIC
$nicName = "${vmName}_NetworkInterface"
Write-Output "Deploy the nic: ${nicName}"
$nic = New-AzNetworkInterface `
-Name $nicName `
-ResourceGroupName $resourceGroupName `
-Location $location `
-SubnetId $subnetId
# Create a new VM
Write-Output "Deploy the vm: ${vmName}"
$vm = New-AzVMConfig -VMName $vmName -VMSize $vmSize
$vm = Set-AzVMOSDisk -VM $vm -ManagedDiskId $osDisk.Id -CreateOption Attach -DeleteOption Delete -Windows
$vm = Add-AzVMNetworkInterface -VM $vm -Id $nic.Id -DeleteOption Delete
$vm = Set-AzVMBootDiagnostic -VM $vm -Disable
New-AzVM `
-ResourceGroupName $resourceGroupName `
-Location $location `
-VM $vm `
-DisableBginfoExtension
Write-Output "Successfully deployed the vm: ${vmName}"
}
catch {
Write-Error "Failed to deploy the vm: $_"
}
補足:
- ・ディスクは {仮想マシン名}_OSDisk_1 という規則で命名
- ・ネットワークインターフェースは {仮想マシン名}_NetworkInterface という規則で命名
- ・ディスクとネットワークインターフェースはVMで強制削除
- ・ブート診断無効化
- ・BGInfo無効化
Step2:マスターVMの更新①
Step2はマスターVMにリモートデスクトップして手動で実施します。
- 1.更新プログラムの適用
- 2.(あれば)FSLogixの更新
- 3.ディスククリーンアップ
次のステップでスナップショットを取得しますので、このステップではまだsysprepは実行しません。
Step3:スナップショット取得
Step3ではマスターVMのスナップショットを取得します。
パラメーター:
パラメーター | 値 | 例 |
---|---|---|
$resourceGroupName | スナップショット取得先リソースグループ名 | avd-master |
$vmName | VM名 | AVDMaster |
$location | スナップショット取得先リージョン | japaneast |
コマンド:
$ErrorActionPreference = "Stop"
$resourceGroupName = "avd-master"
$vmName = "AVDMaster"
$location = "japaneast"
# Connect to Azure
if (-not (Get-AzContext)) {Connect-AzAccount}
try {
# Get the VM
Write-Output "Get the vm: ${vmName}"
$vm = Get-AzVM -ResourceGroupName $resourceGroupName -Name $vmName
# Create a snapshot
$osDiskId = $vm.StorageProfile.OsDisk.ManagedDisk.Id
$osDiskName = $vm.StorageProfile.OsDisk.Name
$date = Get-Date -Format "yyyyMMdd"
$snapshotName = "${osDiskName}_${date}"
Write-Output "Create the snapshot: ${snapshotName}"
$snapshot = New-AzSnapshotConfig `
-SourceUri $osDiskId `
-Location $location `
-CreateOption copy
New-AzSnapshot `
-Snapshot $snapshot `
-SnapshotName $snapshotName `
-ResourceGroupName $resourceGroupName
Write-Output "Successfully created the snapshot: ${snapshotName}"
}
catch {
Write-Error "Failed to create the snapshot: $_"
}
補足:
- ・スナップショットは {OSディスク名}_{yyyyMMdd} という規則で命名
Step4:マスターVMの更新②
再度マスターVMにリモートデスクトップしてsysprepを実行します。
Step5:イメージのキャプチャ
Step5ではマスターVMをギャラリーのイメージにキャプチャします。
パラメーター:
パラメーター | 値 | 例 |
---|---|---|
$vmResourceGroupName | VMのリソースグループ名 | avd-master |
$vmName | VM名 | AVDMaster |
$galleryName | イメージ格納先のギャラリー名 | AVDImage |
$imageDefinition | イメージ定義名 | AVDMaster |
$resourceGroupName | ギャラリーのリソースグループ名 | avd |
$location | リソースグループのリージョン | japaneast |
$targetRegion | イメージ格納先のリージョン | @{Name = 'japaneast'} ※配列 |
$replicaCount | イメージのレプリカ数 | 1 |
コマンド:
$ErrorActionPreference = "Stop"
$vmResourceGroupName = "avd-master"
$vmName = "AVDMaster"
$galleryName = "AVDImage"
$imageDefinition="AVDMaster"
$resourceGroupName = "avd"
$location = "japaneast"
$targetRegion = @{Name = 'japaneast'}
$replicaCount = 1
# Connect to Azure
if (-not (Get-AzContext)) {Connect-AzAccount}
try {
# Stop the VM and generalize it
Write-Output "Stop the vm and generalize it: ${vmName}"
$vm = Get-AzVM -ResourceGroupName $vmResourceGroupName -Name $vmName
Stop-AzVM -ResourceGroupName $vmResourceGroupName -Name $vmName -Force
Set-AzVM -ResourceGroupName $vmResourceGroupName -Name $vmName -Generalized
# Create a new image version
$imageVersion = (Get-Date).ToString("1.yyyy.MM")
Write-Output "Create a new image version: ${imageDefinition}/${imageVersion}"
New-AzGalleryImageVersion `
-GalleryName $galleryName `
-GalleryImageDefinitionName $imageDefinition `
-GalleryImageVersionName $imageVersion `
-ResourceGroupName $resourceGroupName `
-Location $location `
-TargetRegion $targetRegion `
-SourceImageVMId $vm.Id.ToString() `
-ReplicaCount $replicaCount
Write-Output "Successfully captured vm: ${vmName}"
}
catch {
Write-Error "Failed to capture the vm: $_"
}
補足:
- ・イメージバージョンは 1.{yyyy}.{MM} という規則で命名
Step6~8:テストAVDのデプロイ~本番AVDのデプロイ
テストAVDと本番AVDのデプロイは中身が同じですのでまとめて。
パラメーター:
パラメーター | 値 | 例 |
$resourceGroupName | ARMテンプレートのリソースグループ名 | avd-prod |
$hostpoolResourceGroup | AVDホストプールのリソースグループ名 | avd-prod |
$hostPoolName | AVDホストプール名 | AVDProd |
$vmResourceGroup | VM展開先のリソースグループ名 | avd-prod |
$vmLocation | VM展開先のリージョン | japaneast |
$vmImageType | イメージの種類 | CustomImage |
$vmCustomImageSourceId | イメージのリソースID | /subscriptions/~/images/AVDMaster/versions/1.0.0 |
$vmNamePrefix | VM名のプレフィックス | avdprode |
$vmInitialNumber | VM名連番の最初の番号 | 0 |
$vmNumberOfInstances | VM数 | 1 |
$availabilityOption | 可用性ゾーン設定 | NONE ※ゾーン冗長しない |
$vmSize | VMサイズ | Standard_B2ls_v2 |
$vmDiskType | ディスクSKU | StandardSSD_LRS |
$virtualNetworkResourceGroupName | 仮想ネットワークのリソースグループ名 | avd |
$existingVnetName | 仮想ネットワーク名 | avd-nw |
$existingSubnetName | サブネット名 | prod |
$administratorAccountUsername | ドメイン参加用の管理者名 | avdadmin@012345678901.onmicrosoft.com |
$administratorAccountPassword | ドメイン参加用の管理者パスワード | 012345678901 |
$vmAdministratorAccountUsername | VMのローカル管理者名 | avdadmin |
$vmAdministratorAccountPassword | VMのローカル管理者パスワード | 012345678901 |
$domain | 参加先ドメイン名 | 012345678901.onmicrosoft.com |
$shutdownStatus | 自動シャットダウン設定 | Enabled ※自動シャットダウン有効化 |
$shutdownTime | 自動シャットダウン時間 | 20:00 |
$shutdownTimezone | 自動シャットダウン時間タイムゾーン | Tokyo Standard Time |
コマンド:
$ErrorActionPreference = "Stop"
$resourceGroupName = "avd-prod"
$hostpoolResourceGroup = "avd-prod"
$hostPoolName = "AVDProd"
$vmResourceGroup = "avd-prod"
$vmLocation = "japaneast"
$vmImageType = "CustomImage"
$vmCustomImageSourceId = "/subscriptions/01234567-8901-2345-6789-012345678901/resourceGroups/avd/providers/Microsoft.Compute/galleries/AVDImage/images/AVDMaster/versions/1.0.0"
$vmNamePrefix = "AVDProd"
$vmInitialNumber = 0
$vmNumberOfInstances = 1
$availabilityOption = "None"
$vmSize = "Standard_B2ls_v2"
$vmDiskType = "StandardSSD_LRS"
$virtualNetworkResourceGroupName = "avd"
$existingVnetName = "avd-nw"
$existingSubnetName = "prod"
$administratorAccountUsername = "avdadmin@012345678901.onmicrosoft.com"
$administratorAccountPassword = "012345678901"
$vmAdministratorAccountUsername = "avdadmin"
$vmAdministratorAccountPassword = "012345678901"
$domain = "012345678901.onmicrosoft.com"
$shutdownStatus = "Enabled"
$shutdownTime = "20:00"
$shutdownTimezone = "Tokyo Standard Time"
# Connect to Azure
if (-not (Get-AzContext)) {Connect-AzAccount}
try {
# Get the host pool registration token
Write-Output "Get the host pool registration token: ${hostPoolName}"
$expirationTime = (Get-Date).ToString("yyyy-MM-ddT23:59:59Z")
$hostPool = New-AzWvdRegistrationInfo `
-ResourceGroupName $hostpoolResourceGroup `
-HostPoolName $hostPoolName `
-ExpirationTime $expirationTime
$secureToken = ConvertTo-SecureString -String $hostPool.Token -AsPlainText -Force
$administratorAccountPassword = ConvertTo-SecureString -String $administratorAccountPassword -AsPlainText -Force
$vmAdministratorAccountPassword = ConvertTo-SecureString -String $vmAdministratorAccountPassword -AsPlainText -Force
# Deploy the host
$deploymentId = New-Guid
Write-Output "Add host to hostpool: ${hostPoolName}"
$deployment = New-AzResourceGroupDeployment `
-ResourceGroupName $resourceGroupName `
-TemplateFile "../Common/Add_Host_template.json" `
-TemplateParameterFile "../Common/Add_Host_parameters.json" `
-hostpoolResourceGroup $hostpoolResourceGroup `
-hostPoolName $hostPoolName `
-hostpoolToken $secureToken `
-vmResourceGroup $vmResourceGroup `
-vmLocation $vmLocation `
-vmImageType $vmImageType `
-vmCustomImageSourceId $vmCustomImageSourceId `
-vmNamePrefix $vmNamePrefix `
-vmInitialNumber $vmInitialNumber `
-vmNumberOfInstances $vmNumberOfInstances `
-availabilityOption $availabilityOption `
-vmSize $vmSize `
-vmDiskType $vmDiskType `
-virtualNetworkResourceGroupName $virtualNetworkResourceGroupName `
-existingVnetName $existingVnetName `
-existingSubnetName $existingSubnetName `
-administratorAccountUsername $administratorAccountUsername `
-administratorAccountPassword $administratorAccountPassword `
-vmAdministratorAccountUsername $vmAdministratorAccountUsername `
-vmAdministratorAccountPassword $vmAdministratorAccountPassword `
-domain $domain `
-deploymentId $deploymentId
Write-Output "Successfully added host to hostpool: ${hostPoolName}"
# Set auto-shutdown for the VMs
$vms = $deployment.Outputs.rdshVmNamesObject.Value | ForEach-Object {$_.Value.ToString() | ConvertFrom-Json}
foreach ($vm in $vms) {
Write-Output "Setting auto-shutdown for VM: $($vm.name)"
New-AzResourceGroupDeployment `
-ResourceGroupName $resourceGroupName `
-TemplateFile "../Common/Set_AutoShutdown_template.json" `
-TemplateParameterFile "../Common/Set_AutoShutdown_parameters.json" `
-vmName $vm.name `
-shutdownStatus $shutdownStatus `
-shutdownTime $shutdownTime `
-shutdownTimezone $shutdownTimezone -ErrorAction Stop
}
Write-Output "Successfully set auto-shutdown for VMs"
} catch {
Write-Error "Failed to deploy the host: $_"
}
補足:
- ・コマンドの中で使用しているARMテンプレートは後述のGitHubから取得してください。
- ・すみません、可用性ゾーン展開などのカスタマイズパターンはテストできていません……。
Step9:リソースクリーンアップ
ご参考程度に、本稿では下記リソースのクリーンアップを行っています。
- ・マスター
- ➢VM
- ➢ディスク
- ➢ネットワークインターフェース
- ➢最も古い世代のスナップショット
- ➢最も古い世代のギャラリーイメージ
- ・古いAVD
- ➢セッションホスト
- ➢VM
- ➢ディスク
- ➢ネットワークインターフェース
コマンド:
$masterResourceGroupName = "avd-master"
$masterVMName = "AVDMaster"
$imageResourceGroupName = "avd"
$galleryName = "AVDImage"
$imageDefinitionName = "AVDMaster"
$testResourceGroupName = "avd-test"
$testHostPoolName = "AVDTest"
$testAVDName = "avdtest-0.012345678901.onmicrosoft.com"
$testVMName = "avdtest-0"
$prodResourceGroupName = "avd-prod"
$prodHostPoolName = "AVDProd"
$prodAVDNameSuffix = "avdprode"
$prodVMNameSuffix = " avdprode"
# Connect to Azure
if (-not (Get-AzContext)) {Connect-AzAccount}
# Master
# VM
Get-AzVM -ResourceGroupName $masterResourceGroupName -Name $masterVMName | Remove-AzVm -ForceDeletion $true
# Snapshot
Get-AzSnapshot -ResourceGroupName $masterResourceGroupName | `
Where-Object { $_.Name -like "${masterVMName}*" } | `
Sort-Object { $_.TimeCreated } | `
Select-Object -First 1 | `
Remove-AzSnapshot
# Image
Get-AzGalleryImageVersion -ResourceGroupName $imageResourceGroupName -GalleryName $galleryName -GalleryImageDefinitionName $imageDefinitionName | `
Sort-Object { $_.PublishingProfile.PublishedDate } | `
Select-Object -First 1 | `
Remove-AzGalleryImageVersion
# Test
# SessionHost
Remove-AzWvdSessionHost -ResourceGroupName $testResourceGroupName -HostPoolName $testHostPoolName -Name $testAVDName
# VM
$vmConfig = Get-AzVM -ResourceGroupName $testResourceGroupName -Name $testVMName
$vmConfig.StorageProfile.OsDisk.DeleteOption = 'Delete'
$vmConfig.StorageProfile.DataDisks | ForEach-Object { $_.DeleteOption = 'Delete' }
$vmConfig.NetworkProfile.NetworkInterfaces | ForEach-Object { $_.DeleteOption = 'Delete' }
$vmConfig | Update-AzVM
$vmConfig | Remove-AzVm -ForceDeletion $true
#Prod
# SessionHost
Get-AzWvdSessionHost -ResourceGroupName $prodResourceGroupName -HostPoolName $prodHostPoolName | `
Where-Object { $_.Name -like "*${prodAVDNameSuffix}*" } | `
Remove-AzWvdSessionHost
# VM
Get-AzVM -ResourceGroupName $prodResourceGroupName | `
Where-Object { $_.Name -like "${prodVMNameSuffix}*" } | `
ForEach-Object{
$_.StorageProfile.OsDisk.DeleteOption = 'Delete'
$_.StorageProfile.DataDisks | ForEach-Object { $_.DeleteOption = 'Delete' }
$_.NetworkProfile.NetworkInterfaces | ForEach-Object { $_.DeleteOption = 'Delete' }
$_ | Update-AzVM
$_ | Remove-AzVm -ForceDeletion $true
}
別添
ご紹介したスクリプトは下記のGitHubにて公開しておりますのでARMテンプレートと合わせてご参照ください。
https://github.com/mstechcenter/avd-operation
おわりに
Azure Virtual Desktop 運用自動化いかがでしたでしょうか?
まだパラメーターを修正してコマンドを叩くという手動の手順は残りますが、自動化によりマウス入力や画面遷移がなくなっただけでも結構な労力の削減になるということを実感しました。
Vol.2ではさらなる自動化を目指すべく、Azure DevOpsもしくはGitHubとの連携を進めていきたいと思います。
- ※文中の商品名、会社名、団体名は、一般に各社の商標または登録商標です。