Having recently gone through getting Terraform to deploy a virtual machine and a VM extension to register Desired State Configuration (DSC) with Azure Automation, I thought I’d note the method and code here for future reference.
This presumes a functioning Azure Automation account with a DSC configuration and generated node configurations.
First, I specify my variables in a file “Variables.tf”:
// For all VMs variable "subscription" { type = "string" } variable "location" { type= "string" } variable "vmsize" { type = "map" } variable "username" { type = "string" } variable "clientcode" { type = "string" } variable "password" { type = "map" } variable "networkipaddress" { type = "map" } variable "serveripaddress" { type = "map" } variable "dnsservers" { type = "list" } variable "dcdnsservers" { type = "list" } //DSC related # The key for the Azure Automation account variable "dsc_key" { type = "string" } # Endpoint, also referred to as the Registration URL variable "dsc_endpoint" { type = "string" } # This can be ApplyAndMonitor, ApplyandAutoCorrect, among others variable "dsc_mode" { type = "string" } # Heres where you define the node configuration that you actually want to apply to the VM variable "dsc_nodeconfigname" { type = "map" } variable "dsc_configfrequency" { type = "string" } variable "dsc_refreshfrequency" { type = "string" } |
Then I deploy the VM, along with the required dependencies:
# Initial Resource Group resource "azurerm_resource_group" "Default" { name = "az${var.clientcode}" location = "${var.location}" } resource "azurerm_virtual_network" "VirtualNetwork" { name = "azcx${var.clientcode}" address_space = ["${lookup(var.networkipaddress, "VMNet")}"] location = "${var.location}" resource_group_name = "${azurerm_resource_group.Default.name}" dns_servers = "${var.dnsservers}" } resource "azurerm_subnet" "subnet-lan" { name = "az${var.clientcode}-lan" resource_group_name = "${azurerm_resource_group.Default.name}" virtual_network_name = "${azurerm_virtual_network.VirtualNetwork.name}" address_prefix = "${lookup(var.networkipaddress, "subnet-lan")}" } resource "azurerm_network_interface" "dc1nic1" { name = "az${var.clientcode}1nic1" location = "${var.location}" resource_group_name = "${azurerm_resource_group.Default.name}" # reverse DNS for the domain controller dns_servers = "${var.dcdnsservers}" ip_configuration { name = "ipconfig1" subnet_id = "${azurerm_subnet.subnet-lan.id}" private_ip_address_allocation = "static" private_ip_address = "${lookup(var.serveripaddress, "az${var.clientcode}1")}" } } resource "azurerm_virtual_machine" "dc1" { name = "az${var.clientcode}1" location = "${var.location}" resource_group_name = "${azurerm_resource_group.Default.name}" network_interface_ids = ["${azurerm_network_interface.dc1nic1.id}"] vm_size = "${lookup(var.vmsize, "az${var.clientcode}1")}" delete_os_disk_on_termination = true delete_data_disks_on_termination = true storage_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2012-R2-Datacenter" version = "latest" } storage_os_disk { name = "az${var.clientcode}1_c" caching = "ReadWrite" create_option = "FromImage" managed_disk_type = "Standard_LRS" disk_size_gb = "128" } storage_data_disk { name = "az${var.clientcode}1_d" managed_disk_type = "Standard_LRS" caching = "None" create_option = "Empty" lun = 0 disk_size_gb = "64" } os_profile { computer_name = "az${var.clientcode}1" admin_username = "${var.username}" admin_password = "${lookup(var.password, "az${var.clientcode}1")}" } os_profile_windows_config { provision_vm_agent = true enable_automatic_upgrades = false } } |
Now with the VM deployed, the Extension can be applied. This will pass in the proper variables defined, install the VM extension, and register with the Azure Automation account to begin the initial DSC deployment. Note, due to WordPress formatting, the line below that says “setting = SETTINGS” should look like this: “setting = <<SETTINGS”. The double bracket should exist for the PROTECTED_SETTINGS line too.
resource "azurerm_virtual_machine_extension" "dc1-dsc" { name = "Microsoft.Powershell.DSC" location = "${var.location}" resource_group_name = "${azurerm_resource_group.Default.name}" virtual_machine_name = "az${var.clientcode}1" publisher = "Microsoft.Powershell" type = "DSC" auto_upgrade_minor_version = true type_handler_version = "2.76" depends_on = ["azurerm_virtual_machine.dc1"] settings = SETTINGS { "WmfVersion": "latest", "advancedOptions": { "forcePullAndApply": true }, "Properties": { "RegistrationKey": { "UserName": "PLACEHOLDER_DONOTUSE", "Password": "PrivateSettingsRef:registrationKeyPrivate" }, "RegistrationUrl": "${var.dsc_endpoint}", "NodeConfigurationName": "${lookup(var.dsc_nodeconfigname, "dc1")}", "ConfigurationMode": "${var.dsc_mode}", "ConfigurationModeFrequencyMins": ${var.dsc_configfrequency}, "RefreshFrequencyMins": ${var.dsc_refreshfrequency}, "RebootNodeIfNeeded": true, "ActionAfterReboot": "continueConfiguration", "AllowModuleOverwrite": true } } SETTINGS protected_settings = PROTECTED_SETTINGS { "Items": { "registrationKeyPrivate" : "${var.dsc_key}" } } PROTECTED_SETTINGS } |
For passing in the variable values, I use two files. One titled inputs.auto.tfvars with the following:
# This file contains input values for variables defined in "CLIENT_Variables.tf" # As this file doesnt contain secrets, it can be committed to source control. subscription = "bc5242b8" location = "eastus2" vmsize = { "azclient1" = "Standard_A1_v2" "azclientweb" = "Standard_A1_v2" } username = "admin" clientcode = "client" serveripaddress = { "azclient1" = "10.0.0.211" "azclientweb" = "10.0.0.71" } networkipaddress = { "VMNet" = "10.0.0.0/23" "subnet-lan" = "10.0.0.0/26" } resource_group_name = "azclient" dnsservers = ["10.0.0.10", "10.0.0.111"] dcdnsservers = ["10.0.0.111","10.0.0.10"] #This is the registration URL of Azure Automation dsc_endpoint = "https://eus2-agentservice-prod-1.azure-automation.net/accounts/guid" dsc_mode = "ApplyandAutoCorrect" dsc_configfrequency = "240" dsc_refreshfrequency = "720" dsc_nodeconfigname = { "dc1" = "deploymentconfig.domaincontroller" "web1" = "deploymentconfig.webserver" } |
And then another titled secrets.auto.tfvars which does not get uploaded to source control:
password = { "azclient1" = "password" "azclientweb" = "password" } dsc_key = "insert key here" |