使用 Terraform 配置 Azure 虚拟桌面会话主机

本文介绍如何使用 Terraform 生成会话主机并将其部署到 AVD 主机池。 本文假设你已部署 Azure 虚拟桌面基础结构

文章使用以下 Terraform 和 Terraform 提供程序版本进行测试:

详细了解如何在 Azure 中使用 Terraform

在本文中,学习如何:

  • 使用 Terraform 为每个会话主机创建 NIC
  • 使用 Terraform 为会话主机创建 VM
  • 将 VM 加入域
  • 将 VM 注册到 Azure 虚拟桌面
  • 使用变量文件

1.配置环境

  • Azure 订阅:如果没有 Azure 订阅,请在开始之前创建一个免费帐户

2. 实现 Terraform 代码

  1. 创建用于测试和运行示例 Terraform 代码的目录,并将其设为当前目录。

  2. 创建名为 providers.tf 的文件并插入下列代码。

    terraform {
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>2.0"
        }
        azuread = {
          source = "hashicorp/azuread"
        }
      }
    }
    
    provider "azurerm" {
      features {}
    }
    

    要点:

    • 使用 count 指示将创建多少个资源
    • 引用在生成基础结构时创建的资源,例如 azurerm_subnet.subnet.idazurerm_virtual_desktop_host_pool.hostpool.name。 如果从该部分更改了这些资源的名称,则还需要在此处更新引用。
  3. 创建名为 main.tf 的文件并插入下列代码:

    locals {
      registration_token = azurerm_virtual_desktop_host_pool_registration_info.registrationinfo.token
    }
    
    resource "random_string" "AVD_local_password" {
      count            = var.rdsh_count
      length           = 16
      special          = true
      min_special      = 2
      override_special = "*!@#?"
    }
    
    resource "azurerm_resource_group" "rg" {
      name     = var.rg
      location = var.resource_group_location
    }
    
    resource "azurerm_network_interface" "avd_vm_nic" {
      count               = var.rdsh_count
      name                = "${var.prefix}-${count.index + 1}-nic"
      resource_group_name = azurerm_resource_group.rg.name
      location            = azurerm_resource_group.rg.location
    
      ip_configuration {
        name                          = "nic${count.index + 1}_config"
        subnet_id                     = azurerm_subnet.subnet.id
        private_ip_address_allocation = "dynamic"
      }
    
      depends_on = [
        azurerm_resource_group.rg
      ]
    }
    
    resource "azurerm_windows_virtual_machine" "avd_vm" {
      count                 = var.rdsh_count
      name                  = "${var.prefix}-${count.index + 1}"
      resource_group_name   = azurerm_resource_group.rg.name
      location              = azurerm_resource_group.rg.location
      size                  = var.vm_size
      network_interface_ids = ["${azurerm_network_interface.avd_vm_nic.*.id[count.index]}"]
      provision_vm_agent    = true
      admin_username        = var.local_admin_username
      admin_password        = var.local_admin_password
    
      os_disk {
        name                 = "${lower(var.prefix)}-${count.index + 1}"
        caching              = "ReadWrite"
        storage_account_type = "Standard_LRS"
      }
    
      source_image_reference {
        publisher = "MicrosoftWindowsDesktop"
        offer     = "Windows-10"
        sku       = "20h2-evd"
        version   = "latest"
      }
    
      depends_on = [
        azurerm_resource_group.rg,
        azurerm_network_interface.avd_vm_nic
      ]
    }
    
    resource "azurerm_virtual_machine_extension" "domain_join" {
      count                      = var.rdsh_count
      name                       = "${var.prefix}-${count.index + 1}-domainJoin"
      virtual_machine_id         = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
      publisher                  = "Microsoft.Compute"
      type                       = "JsonADDomainExtension"
      type_handler_version       = "1.3"
      auto_upgrade_minor_version = true
    
      settings = <<SETTINGS
        {
          "Name": "${var.domain_name}",
          "OUPath": "${var.ou_path}",
          "User": "${var.domain_user_upn}@${var.domain_name}",
          "Restart": "true",
          "Options": "3"
        }
    SETTINGS
    
      protected_settings = <<PROTECTED_SETTINGS
        {
          "Password": "${var.domain_password}"
        }
    PROTECTED_SETTINGS
    
      lifecycle {
        ignore_changes = [settings, protected_settings]
      }
    
      depends_on = [
        azurerm_virtual_network_peering.peer1,
        azurerm_virtual_network_peering.peer2
      ]
    }
    
    resource "azurerm_virtual_machine_extension" "vmext_dsc" {
      count                      = var.rdsh_count
      name                       = "${var.prefix}${count.index + 1}-avd_dsc"
      virtual_machine_id         = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
      publisher                  = "Microsoft.Powershell"
      type                       = "DSC"
      type_handler_version       = "2.73"
      auto_upgrade_minor_version = true
    
      settings = <<-SETTINGS
        {
          "modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_09-08-2022.zip",
          "configurationFunction": "Configuration.ps1\\AddSessionHost",
          "properties": {
            "HostPoolName":"${azurerm_virtual_desktop_host_pool.hostpool.name}"
          }
        }
    SETTINGS
    
      protected_settings = <<PROTECTED_SETTINGS
      {
        "properties": {
          "registrationInfoToken": "${local.registration_token}"
        }
      }
    PROTECTED_SETTINGS
    
      depends_on = [
        azurerm_virtual_machine_extension.domain_join,
        azurerm_virtual_desktop_host_pool.hostpool
      ]
    }
    
  4. 创建名为 variables.tf 的文件并插入下列代码:

variable "resource_group_location" {
  default     = "eastus"
  description = "Location of the resource group."
}

variable "rg" {
  type        = string
  default     = "rg-avd-compute"
  description = "Name of the Resource group in which to deploy session host"
}

variable "rdsh_count" {
  description = "Number of AVD machines to deploy"
  default     = 2
}

variable "prefix" {
  type        = string
  default     = "avdtf"
  description = "Prefix of the name of the AVD machine(s)"
}

variable "domain_name" {
  type        = string
  default     = "infra.local"
  description = "Name of the domain to join"
}

variable "domain_user_upn" {
  type        = string
  default     = "domainjoineruser" # do not include domain name as this is appended
  description = "Username for domain join (do not include domain name as this is appended)"
}

variable "domain_password" {
  type        = string
  default     = "ChangeMe123!"
  description = "Password of the user to authenticate with the domain"
  sensitive   = true
}

variable "vm_size" {
  description = "Size of the machine to deploy"
  default     = "Standard_DS2_v2"
}

variable "ou_path" {
  default = ""
}

variable "local_admin_username" {
  type        = string
  default     = "localadm"
  description = "local admin username"
}

variable "local_admin_password" {
  type        = string
  default     = "ChangeMe123!"
  description = "local admin password"
  sensitive   = true
}
  1. 创建名为 output.tf 的文件并插入下列代码:
output "location" {
  description = "The Azure region"
  value       = azurerm_resource_group.rg.location
}

output "session_host_count" {
  description = "The number of VMs created"
  value       = var.rdsh_count
}

output "dnsservers" {
  description = "Custom DNS configuration"
  value       = azurerm_virtual_network.vnet.dns_servers
}

output "vnetrange" {
  description = "Address range for deployment vnet"
  value       = azurerm_virtual_network.vnet.address_space
}
  1. 创建名为 terraform.tfvars 的文件并插入下列代码:

    # Customized the sample values below for your environment and either rename to terraform.tfvars or env.auto.tfvars
    
    deploy_location      = "west europe"
    rg_name              = "avd-resources-rg"
    prefix               = "avdtf"
    local_admin_username = "localadm"
    local_admin_password = "ChangeMe123$"
    vnet_range           = ["10.1.0.0/16"]
    subnet_range         = ["10.1.0.0/24"]
    dns_servers          = ["10.0.1.4", "168.63.129.16"]
    aad_group_name       = "AVDUsers"
    domain_name          = "infra.local"
    domain_user_upn      = "admin"     # do not include domain name as this is appended
    domain_password      = "ChangeMe123!"
    ad_vnet              = "infra-network"
    ad_rg                = "infra-rg"
    avd_users = [
      "avduser01@infra.local",
      "avduser01@infra.local"
    ]
    

3. 初始化 Terraform

运行 terraform init,将 Terraform 部署进行初始化。 此命令将下载管理 Azure 资源所需的 Azure 提供程序。

terraform init -upgrade

要点:

  • 参数 -upgrade 将必要的提供程序插件升级到符合配置版本约束的最新版本。

4. 创建 Terraform 执行计划

运行 terraform plan 以创建执行计划。

terraform plan -out main.tfplan

要点:

  • terraform plan 命令将创建一个执行计划,但不会执行它。 它会确定创建配置文件中指定的配置需要执行哪些操作。 此模式允许你在对实际资源进行任何更改之前验证执行计划是否符合预期。
  • 使用可选 -out 参数可以为计划指定输出文件。 使用 -out 参数可以确保所查看的计划与所应用的计划完全一致。
  • 若要详细了解如何使执行计划和安全性持久化,请参阅安全警告一节

5. 应用 Terraform 执行计划

运行 terraform apply,将执行计划应用到云基础结构。

terraform apply main.tfplan

要点:

  • 上面的 terraform apply 命令假设之前运行了 terraform plan -out main.tfplan
  • 如果为 -out 参数指定了不同的文件名,请在对 terraform apply 的调用中使用该相同文件名。
  • 如果未使用 -out 参数,请调用不带任何参数的 terraform apply

6. 验证结果

  1. 在Azure 门户,选择“Azure 虚拟桌面”。
  2. 选择“ 主机池 ”,然后选择 池创建资源的名称
  3. 选择“ 会话主机 ”,然后验证是否已列出会话主机。

7.清理资源

不再需要通过 Terraform 创建的资源时,请执行以下步骤:

  1. 运行 terraform plan 并指定 destroy 标志。

    terraform plan -destroy -out main.destroy.tfplan
    

    要点:

    • terraform plan 命令将创建一个执行计划,但不会执行它。 它会确定创建配置文件中指定的配置需要执行哪些操作。 此模式允许你在对实际资源进行任何更改之前验证执行计划是否符合预期。
    • 使用可选 -out 参数可以为计划指定输出文件。 使用 -out 参数可以确保所查看的计划与所应用的计划完全一致。
    • 若要详细了解如何使执行计划和安全性持久化,请参阅安全警告一节
  2. 运行 terraform apply 以应用执行计划。

    terraform apply main.destroy.tfplan
    

Azure 上的 Terraform 故障排除

排查在 Azure 上使用 Terraform 时遇到的常见问题

后续步骤