Skip to content

Commit 4dab108

Browse files
committed
initial file creation
1 parent 48a25a1 commit 4dab108

16 files changed

Lines changed: 880 additions & 0 deletions

File tree

.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"markdownlint.config": {
3+
"MD028": false,
4+
"MD025": {
5+
"front_matter_title": ""
6+
}
7+
}
8+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
## Terraform for Azure Virtual Desktop
2+
3+
The purpose of this repository is to demonstrate using Terraform to deploy a simple Azure Virtual Desktop environment. For Classic Azure Virtual Desktop click [here](https://github.com/Azure/RDS-Templates/tree/master/wvd-sh/terraform-azurerm-windowsvirtualdesktop).
4+
5+
## Requirements and limitations
6+
* Ensure that you meet the [requirements for Azure Virtual Desktop](https://docs.microsoft.com/en-us/azure/virtual-desktop/overview#requirements)
7+
* Terraform must be installed and configured as outlined [here](https://docs.microsoft.com/en-us/azure/developer/terraform/get-started-cloud-shell)
8+
* Active Directory already in place in this example, we are using AD in it’s own VNet.
9+
* Users in AAD that will be given access to AVD
10+
* This demo does not support Azure ADDS only deployment
11+
* Destroy could produce errors deleting subnet due to resources associated. Manually delete resources within the subnet before running destroy
12+
13+
## Components
14+
15+
* Azure Virtual Desktop Environment
16+
* Networking Infrastructure
17+
* Session Hosts
18+
* Profile Storage
19+
* Role Based Access Control
20+
21+
## Features
22+
23+
This directory contains the various components for building out Azure Virtual Desktop.
24+
* `main.tf`
25+
deploys a new workspace, hostpool, application group with associations
26+
* `networking.tf`
27+
deploys a new vnet, subnet, nsg and peering to AD vnet
28+
* `host.tf`
29+
deploys new session host from the marketplace build and join to domain
30+
* `afstorage.tf`
31+
deploys Azure Files storage for profiles and creates file share with RBAC permissions for the users group ([NTFS permissions will need to be configured](https://docs.microsoft.com/en-us/azure/virtual-desktop/create-file-share))
32+
* `rbac.tf`
33+
deploys rbac assignment for the users group
34+
* `variables.tf`
35+
Input variables
36+
* `defaults.tfvars`
37+
declares the actual input values (keep security in mind if you are putting confidential data)
38+
* `provider.tf`
39+
Azure RM and Azure AD provider configuation
40+
* `outputs.tf`
41+
defines the outputs that will be displayed on deployment
42+
* `netappstorage.tf`
43+
as an alternate to Azure Files storage this deploys NetApp Files storage for profiles in a dedicated subnet (access needs to be granted to the ANF service) [Set up Azure NetApp Files](https://docs.microsoft.com/en-us/azure/azure-netapp-files/azure-netapp-files-quickstart-set-up-account-create-volumes?tabs=azure-portal)
44+
45+
## Varialble Inputs
46+
47+
| Name | Description | Default |
48+
|:---|:---|:---|
49+
| `rg_name` | Name of the Resource Group in which to deploy these resources | `AVD-TF` |
50+
| `deploy_location` | Region in which to deploy these resources | - |
51+
| `hostpool` | Name of the Azure Virtual Desktop host pool | `AVD-TF-HP` |
52+
| `ad_vnet` | Name of domain controller VNet | - |
53+
| `dns_servers` | Custom DNS configuration | - |
54+
| `vnet_range` | Address range for deployment VNet | - |
55+
| `subnet_range` | Address range for session host subnet | - |
56+
| `avd_users` | The resource group for AD VM | `[]` |
57+
| `aad_group_name` | Azure Active Directory Group for AVD users | - |
58+
| `rdsh_count` | Number of AVD machines to deploy | 2 |
59+
| `prefix` | Prefix of the name of the AVD machine(s) | - |
60+
| `domain_name` | Name of the domain to join | - |
61+
| `domain_user_upn` | Username for domain join (do not include domain name as this is appended | - |
62+
| `domain_password` | Password of the user to authenticate with the domain | - |
63+
| `vm_size` | Size of the machine to deploy | `Standard_DS2_v2` |
64+
| `ou_path` | The ou path for AD | `""` |
65+
| `local_admin_username` | The local admin username for the VM | - |
66+
| `local_admin_password` | The local admin password for the VM | - |
67+
| `netapp_acct_name` | The NetApp account name | `AVD_NetApp` |
68+
| `netapp_pool_name` | The NetApp pool name | `AVD_NetApp_pool` |
69+
| `netapp_volume_name` | The NetApp volume name | `AVD_NetApp_volume` |
70+
| `netapp_smb_name` | The NetApp smb name | `AVDNetApp` |
71+
| `netapp_volume_path` | The NetApp volume path | `AVDNetAppVolume` |
72+
| `netapp_subnet_name` | The NetApp subnet name | `NetAppSubnet` |
73+
| `netapp_address` | The Address range for NetApp Subnet | - |
74+
75+
## Deploy
76+
If you’ve not previously setup terraform, check out this article to get it installed [Quickstart - Configure Terraform using Azure Cloud Shell](https://docs.microsoft.com/en-us/azure/developer/terraform/get-started-cloud-shell)
77+
78+
You can review our sample configuration video here
79+
80+
Once Terraform is setup and you have created your Terraform templates, the first step is to initialize Terraform. This step ensures that Terraform has all the prerequisites to build your template in Azure.
81+
82+
```
83+
terraform init
84+
```
85+
86+
The next step is to have Terraform review and validate the template. An execution plan is generated and stored in the file specified by the -out parameter.
87+
88+
We also need to pass our variable definitions file during the plan. We can either load it automatically by renaming env.tfvars as terraform.tfvars OR env.auto.tfvars, in which case we will use the following to create the execution plan:
89+
90+
```bash
91+
terraform plan -out terraform_azure.tfplan
92+
```
93+
94+
When you're ready to build the infrastructure in Azure, apply the execution plan:
95+
96+
```bash
97+
terraform apply terraform_azure.tfplan
98+
```
99+
100+
## Final Configuration
101+
102+
You’ll notice we didn’t actually configure the session hosts to use our profile storage at any point. There is an assumption that we are using GPO to manage FSLogix across our host pools as documented here: [Use FSLogix Group Policy Template Files - FSLogix](https://docs.microsoft.com/en-us/fslogix/use-group-policy-templates-ht).
103+
104+
At a minimum you’ll need to configure the registry keys to enable FSLogix and configure the VHD Location to the NetApp Share URI: [Profile Container registry configuration settings - FSLogix](https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#enabled)
105+
106+
## Troubleshooting Terraform deployment
107+
<details>
108+
<summary>Click to expand</summary>
109+
Terraform deployment can fail in two main categories:
110+
111+
Issues with Terraform code
112+
1. [Issues with Desired State Configuration (DSC)](#issues-with-desired-state-configuration-dsc)
113+
2. [Issues with Terraform code](#issues-with-desired-state-configuration-dsc)
114+
115+
While it is rare to have issues with the Terraform code it is still possible, however most often errors are due to bad input in variables.tf.
116+
117+
* If there are errors in the Terraform code, please file a GitHub issue.
118+
* If there are warning in the Terraform code feel free to ignore or address for your own instance of that code.
119+
* Using Terraform error messages it's a good starting point towards identifying issues with input variables
120+
121+
### Issues with Desired State Configuration (DSC)
122+
123+
To troubleshoot this type of issue, navigate to the Azure portal and if needed reset the password on the VM that failed DSC. Once you are able to log in to the VM review the log files in the following two folders:
124+
</details>
125+
126+
## Additional References
127+
<details>
128+
<summary>Click to expand</summary>
129+
130+
- [Terraform Download](https://www.terraform.io/downloads.html)
131+
- [Visual Code Download](https://code.visualstudio.com/Download)
132+
- [Powershell VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell)
133+
- [HashiCorp Terraform VS Code Extension](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform)
134+
- [Azure Terraform VS Code Extension Name](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureterraform)
135+
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli)
136+
- [Configure the Azure Terraform Visual Studio Code extension](https://docs.microsoft.com/en-us/azure/developer/terraform/configure-vs-code-extension-for-terraform)
137+
- [Setup video](https://youtu.be/YmbmpGdhI6w)
138+
</details>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## Create a Resource Group for Storage
2+
resource "azurerm_resource_group" "rg_storage" {
3+
location = "east us"
4+
name = "af-storage-rg"
5+
}
6+
7+
# generate a random string (consisting of four characters)
8+
# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string
9+
resource "random_string" "random" {
10+
length = 4
11+
upper = false
12+
special = false
13+
}
14+
15+
## Azure Storage Accounts requires a globally unique names
16+
## https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview
17+
## Create a File Storage Account
18+
resource "azurerm_storage_account" "storage" {
19+
name = "stor${random_string.random.id}"
20+
resource_group_name = azurerm_resource_group.rg_storage.name
21+
location = azurerm_resource_group.rg_storage.location
22+
account_tier = "Premium"
23+
account_replication_type = "LRS"
24+
account_kind = "FileStorage"
25+
}
26+
27+
resource "azurerm_storage_share" "FSShare" {
28+
name = "fslogix"
29+
storage_account_name = azurerm_storage_account.storage.name
30+
depends_on = [azurerm_storage_account.storage]
31+
}
32+
33+
## Azure built-in roles
34+
## https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
35+
data "azurerm_role_definition" "storage_role" {
36+
name = "Storage File Data SMB Share Contributor"
37+
}
38+
39+
resource "azurerm_role_assignment" "af_role" {
40+
scope = azurerm_storage_account.storage.id
41+
role_definition_id = data.azurerm_role_definition.storage_role.id
42+
principal_id = azuread_group.aad_group.id
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Customized the sample values below for your environment and either rename to terraform.tfvars or env.auto.tfvars
2+
3+
deploy_location = "west europe"
4+
rg_name = "avd-resources-rg"
5+
prefix = "avdtf"
6+
local_admin_username = "localadm"
7+
local_admin_password = "ChangeMe123$"
8+
vnet_range = ["10.1.0.0/16"]
9+
subnet_range = ["10.1.0.0/24"]
10+
dns_servers = ["10.0.1.4", "168.63.129.16"]
11+
aad_group_name = "AVDUsers"
12+
domain_name = "infra.local"
13+
domain_user_upn = "admin" # do not include domain name as this is appended
14+
domain_password = "ChangeMe123!"
15+
ad_vnet = "infra-network"
16+
ad_rg = "infra-rg"
17+
avd_users = [
18+
"avduser01@infra.local",
19+
"avduser01@infra.local"
20+
]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
locals {
2+
registration_token = azurerm_virtual_desktop_host_pool.hostpool.registration_info[0].token
3+
}
4+
5+
resource "random_string" "AVD_local_password" {
6+
count = var.rdsh_count
7+
length = 16
8+
special = true
9+
min_special = 2
10+
override_special = "*!@#?"
11+
}
12+
13+
resource "azurerm_network_interface" "avd_vm_nic" {
14+
count = var.rdsh_count
15+
name = "${var.prefix}-${count.index + 1}-nic"
16+
resource_group_name = var.rg_name
17+
location = var.deploy_location
18+
19+
ip_configuration {
20+
name = "nic${count.index + 1}_config"
21+
subnet_id = azurerm_subnet.subnet.id
22+
private_ip_address_allocation = "dynamic"
23+
}
24+
25+
depends_on = [
26+
azurerm_resource_group.rg
27+
]
28+
}
29+
30+
resource "azurerm_windows_virtual_machine" "avd_vm" {
31+
count = var.rdsh_count
32+
name = "${var.prefix}-${count.index + 1}"
33+
resource_group_name = var.rg_name
34+
location = var.deploy_location
35+
size = var.vm_size
36+
network_interface_ids = ["${azurerm_network_interface.avd_vm_nic.*.id[count.index]}"]
37+
provision_vm_agent = true
38+
admin_username = var.local_admin_username
39+
admin_password = var.local_admin_password
40+
41+
os_disk {
42+
name = "${lower(var.prefix)}-${count.index + 1}"
43+
caching = "ReadWrite"
44+
storage_account_type = "Standard_LRS"
45+
}
46+
47+
source_image_reference {
48+
publisher = "MicrosoftWindowsDesktop"
49+
offer = "Windows-10"
50+
sku = "20h2-evd"
51+
version = "latest"
52+
}
53+
54+
depends_on = [
55+
azurerm_resource_group.rg,
56+
azurerm_network_interface.avd_vm_nic
57+
]
58+
}
59+
60+
resource "azurerm_virtual_machine_extension" "domain_join" {
61+
count = var.rdsh_count
62+
name = "${var.prefix}-${count.index + 1}-domainJoin"
63+
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
64+
publisher = "Microsoft.Compute"
65+
type = "JsonADDomainExtension"
66+
type_handler_version = "1.3"
67+
auto_upgrade_minor_version = true
68+
69+
settings = <<SETTINGS
70+
{
71+
"Name": "${var.domain_name}",
72+
"OUPath": "${var.ou_path}",
73+
"User": "${var.domain_user_upn}@${var.domain_name}",
74+
"Restart": "true",
75+
"Options": "3"
76+
}
77+
SETTINGS
78+
79+
protected_settings = <<PROTECTED_SETTINGS
80+
{
81+
"Password": "${var.domain_password}"
82+
}
83+
PROTECTED_SETTINGS
84+
85+
lifecycle {
86+
ignore_changes = [settings, protected_settings]
87+
}
88+
89+
depends_on = [
90+
azurerm_virtual_network_peering.peer1,
91+
azurerm_virtual_network_peering.peer2
92+
]
93+
}
94+
95+
resource "azurerm_virtual_machine_extension" "vmext_dsc" {
96+
count = var.rdsh_count
97+
name = "${var.prefix}${count.index + 1}-avd_dsc"
98+
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
99+
publisher = "Microsoft.Powershell"
100+
type = "DSC"
101+
type_handler_version = "2.73"
102+
auto_upgrade_minor_version = true
103+
104+
settings = <<-SETTINGS
105+
{
106+
"modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_3-10-2021.zip",
107+
"configurationFunction": "Configuration.ps1\\AddSessionHost",
108+
"properties": {
109+
"HostPoolName":"${azurerm_virtual_desktop_host_pool.hostpool.name}"
110+
}
111+
}
112+
SETTINGS
113+
114+
protected_settings = <<PROTECTED_SETTINGS
115+
{
116+
"properties": {
117+
"registrationInfoToken": "${local.registration_token}"
118+
}
119+
}
120+
PROTECTED_SETTINGS
121+
122+
depends_on = [
123+
azurerm_virtual_machine_extension.domain_join,
124+
azurerm_virtual_desktop_host_pool.hostpool
125+
]
126+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
resource "azurerm_resource_group" "log" {
2+
name = "${var.shared}-resources"
3+
location = var.deploy_location
4+
}
5+
6+
# Creates Log Anaylytics Workspace
7+
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace
8+
resource "azurerm_log_analytics_workspace" "law" {
9+
name = "log${random_string.random.id}"
10+
location = azurerm_resource_group.log.location
11+
resource_group_name = azurerm_resource_group.log.name
12+
sku = "PerGB2018"
13+
retention_in_days = 30
14+
}

0 commit comments

Comments
 (0)