Hello and welcome to another blog about AVD and Terraform. We all know that the biggest cost for Azure Virtual Desktop is the virtual machine running cost. In the past we could enable autoscaling using an automation account and an Azure Logic App.
Recently Microsoft announced the public preview of the Scaling Plan feature. This enables us to create scaling plans using the Azure portal. In this blog I’ll show you how you can create a scaling plan using Terraform.
Auto Scale feature
The scaling feature is currently in preview and only available in certain regions. There are some limitations like:
- only 1 scaling plan per hostpool
- limited in region for the moment
- Auto Scale overides the load balancing settings on your hostpool
- Auto Scale overrides drain mode so make sure to use the exlusion tags
The official documentation can be found here
Steps in this blogpost
- Import Log Analytics Workspace
- Deploy AVD Pooled hostpool
- Create custom AAD role for AVD and assign it
- Deploy scaling plan
- Configure diagnostic settings for all resources
Import Log Analytics Workspace
The first thing we need is to tell Terraform the location and name of the Log Analytics Workspace that we will use to sent the logs to.
data "azurerm_log_analytics_workspace" "law" {
name = "law-hub-jvn-01"
resource_group_name = "rg-hub-jvn-law-01"
}
Deploy the hostpool
The next thing is the hostpool itself. In this case a Pooled hostpool. First I’ll create the resource groups for AVD. I create 1 for the hostpool, workspace and application group and 1 for the session hosts.
resource "azurerm_resource_group" "rg-backplane" {
name = "rg-prod-${var.prefix}-avd-backplane-01"
location = var.location
tags = {
"Location" = "Weu"
"Costcenter" = "IT"
}
}
##Create AVD Session Hosts resource Group
resource "azurerm_resource_group" "rg-sessionhosts" {
name = "rg-prod-${var.prefix}-avd-session-hosts-01"
location = var.location
tags = {
"Location" = "Weu"
"Costcenter" = "IT"
}
}
Now lets create the hostpool, workspace and the application group.
resource "azurerm_virtual_desktop_host_pool" "hp" {
resource_group_name = azurerm_resource_group.rg-backplane.name
name = "hp-prod-${var.prefix}-avd-weu-01"
location = azurerm_resource_group.rg-backplane.location
validate_environment = true
custom_rdp_properties = "audiocapturemode:i:1;audiomode:i:0;"
type = "Pooled"
maximum_sessions_allowed = 2
load_balancer_type = "BreadthFirst"
friendly_name = "AVD HostPool"
start_vm_on_connect = true
tags = {
"Location" = "Weu"
"Costcenter" = "IT"
}
}
resource "azurerm_virtual_desktop_workspace" "ws" {
name = "ws-prod-${var.prefix}-avd-weu-01"
resource_group_name = azurerm_resource_group.rg-backplane.name
location = azurerm_resource_group.rg-backplane.location
friendly_name = "avd Workspace"
description = "avd workspace"
tags = {
"Location" = "Weu"
"Costcenter" = "IT"
}
}
# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "fd" {
resource_group_name = azurerm_resource_group.rg-backplane.name
host_pool_id = azurerm_virtual_desktop_host_pool.hp.id
location = azurerm_resource_group.rg-backplane.location
type = "Desktop"
name = "fd-prod-${var.prefix}-avd-weu-01"
friendly_name = "AVD Full Desktop"
description = "AVD Full Desktop"
depends_on = [azurerm_virtual_desktop_host_pool.hp]
}
# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "fd" {
application_group_id = azurerm_virtual_desktop_application_group.fd.id
workspace_id = azurerm_virtual_desktop_workspace.ws.id
}
Because we want to be able to monitor it we need to turn on the diagnostic settings for all the AVD components.
resource "azurerm_monitor_diagnostic_setting" "avd-logs" {
name = "diag-prod-jvn-avd-hp"
target_resource_id = azurerm_virtual_desktop_host_pool.hp.id
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.law.id
depends_on = [azurerm_virtual_desktop_host_pool.hp]
log {
category = "Error"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "Checkpoint"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "Management"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "Connection"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "HostRegistration"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "AgentHealthStatus"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "NetworkData"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "SessionHostManagement"
enabled = true
retention_policy {
enabled = false
}
}
}
resource "azurerm_monitor_diagnostic_setting" "fd-logs" {
name = "diag-prod-jvn-avd-fd"
target_resource_id = azurerm_virtual_desktop_application_group.fd.id
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.law.id
depends_on = [
azurerm_virtual_desktop_application_group.fd
]
log {
category = "Checkpoint"
enabled = "true"
retention_policy {
enabled = false
}
}
log {
category = "Error"
enabled = "true"
retention_policy {
enabled = false
}
}
log {
category = "Management"
enabled = "true"
retention_policy {
enabled = false
}
}
}
resource "azurerm_monitor_diagnostic_setting" "avd-ws-logs" {
name = "diag-prod-jvn-avd-ws"
target_resource_id = azurerm_virtual_desktop_workspace.ws.id
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.law.id
depends_on = [azurerm_virtual_desktop_workspace.ws]
log {
category = "Error"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "Checkpoint"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "Management"
enabled = true
retention_policy {
enabled = false
}
}
log {
category = "Feed"
enabled = true
retention_policy {
enabled = false
}
}
}
Auto Scale Custom AAD role
For the autoscale to work we need a custom AAD role and assign it to the “Windows Virtual Desktop service. (Yes old name)
resource "azurerm_role_definition" "avd-autoscale" {
name = "AVD-Autoscale"
scope = "/subscriptions/dadb7fec-f397-4981-8ea7-9ba12934a0d0"
description = "AVD Autoscale Custom role"
permissions {
actions = [
"Microsoft.Insights/eventtypes/values/read",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Compute/virtualMachines/powerOff/action",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/read",
"Microsoft.DesktopVirtualization/hostpools/read",
"Microsoft.DesktopVirtualization/hostpools/write",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/read",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/write",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/delete",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/read",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/sendMessage/action",
"Microsoft.DesktopVirtualization/hostpools/sessionhosts/usersessions/read"
]
not_actions = []
}
assignable_scopes = [
"/subscriptions/subscriptionid",
]
}
data "azuread_service_principal" "avd-sp" {
display_name = "Windows Virtual Desktop"
}
resource "random_uuid" "avd-sp-custom-role" {
}
resource "azurerm_role_assignment" "avd-sp-custom-role" {
name = random_uuid.avd-sp-custom-role.result
scope = "/subscriptions/subscriptionid"
role_definition_id = azurerm_role_definition.avd-autoscale.role_definition_resource_id
principal_id = data.azuread_service_principal.avd-sp.id
skip_service_principal_aad_check = true
}
Auto Scale deployment
The final piece of code is to deploy the scaling plan itself. This is just an example of how you can configure it. It will depend on the customer environment.
resource "azurerm_virtual_desktop_scaling_plan" "avd-scalingplan" {
name = "sp-prod-jvn-avd-weu-01"
location = azurerm_resource_group.rg-backplane.location
resource_group_name = azurerm_resource_group.rg-backplane.name
friendly_name = "Production week days scaling plan"
description = "Production week days scaling plan"
depends_on = [
azurerm_virtual_desktop_host_pool.hp
]
time_zone = "Romance standard Time"
schedule {
name = "Weekdays"
days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
ramp_up_start_time = "07:00"
ramp_up_load_balancing_algorithm = "BreadthFirst"
ramp_up_minimum_hosts_percent = 10
ramp_up_capacity_threshold_percent = 10
peak_start_time = "09:00"
peak_load_balancing_algorithm = "BreadthFirst"
ramp_down_start_time = "19:00"
ramp_down_load_balancing_algorithm = "DepthFirst"
ramp_down_minimum_hosts_percent = 10
ramp_down_force_logoff_users = false
ramp_down_wait_time_minutes = 45
ramp_down_notification_message = "Please log off in the next 45 minutes..."
ramp_down_capacity_threshold_percent = 5
ramp_down_stop_hosts_when = "ZeroSessions"
off_peak_start_time = "22:00"
off_peak_load_balancing_algorithm = "DepthFirst"
}
host_pool {
hostpool_id = azurerm_virtual_desktop_host_pool.hp.id
scaling_plan_enabled = true
}
}
The last thing to do is to set the diagnostic settings for AVD scaling plan.
resource "azurerm_monitor_diagnostic_setting" "sp-logs" {
name = "diag-prod-jvn-avd-sp"
target_resource_id = azurerm_virtual_desktop_scaling_plan.avd-scalingplan.id
log_analytics_workspace_id = data.azurerm_log_analytics_workspace.law.id
depends_on = [
azurerm_virtual_desktop_scaling_plan.avd-scalingplan
]
log {
category = "Autoscale"
enabled = "true"
retention_policy {
enabled = false
}
}
}
There you go, a hostpool with all the components and Autoscale configured. Now only deploy some hosts and you are ready to use the Autoscale feature. The code for this can be found on my Github
In case you have any questions regarding this feel free to contact me on Linkedin or via my Twitter handle.