Deploy Azure VM Windows Server and beyond, with Terraform

In the previous blogs , we have seen an example how-to deploy a infrastructure in Azure for the network part. Now a step further in the deployment process we will add a Windows Server 2016 to the environment. 

In this blog we will show you following examples:

  • Deploy  a virtual machine with the Azure RM module “compute”
  • Deploy a virtual machine with “azurerm_virtual_machine”
  • List the pro and contra
  • Use the extensions to
    • Add the virtual machine to a Windows Domain
    • Add desktop background information

Design

Design with two servers

Deploy Azure Virtual Machine with Azure RM module “Compute”

Download ./os!!

The download of the module is not working correctly when doing [Terraform Init].

module.windowsservers.os
Getting source “./os”
Error downloading modules: Error loading modules: error downloading

When we look on Github, we found that there is a BUG in the module – chase is still open: GITHUB Issue

Also work around to delete the terraform folder .terraform, did not solve the issue of the ./os download. It seems not to be a provider issue but an Terraform Core issue.

Based on above remark, I could not complete the deployment and this pushed me back to the “azurerm_virtual_machine” Code that was used to deploy a virtual machine with the “compute”- module: 

resource "random_string" "AccID" {
  length=5
  special=false
  number=true
  min_numeric=5
}
#Create local administrator Account ID - Example ID: AccID23756
locals {
    VarAccID="AccID-${random_string.AccID.result}"
}

/*
resource "aws_db_instance" "example" {
  password = "${random_string.password.result}"
}
*/
module "windowsservers" {
    source              = "Azure/compute/azurerm"
    location            = "${azurerm_resource_group.RGName.location}"
    vnet_subnet_id      = "${module.network.vnet_subnets[0]}"
    admin_password      = "${var.ARM_VAR_LocalSrvAccSecret}"
    admin_username      = "${local.VarAccID}"
    boot_diagnostics    = "enable"
    nb_instances        = "1"
    nb_public_ip        = "0"
    resource_group_name = "${azurerm_resource_group.RGName.name}"
    tags {
        Environment = "${var.TAGS_ENVPRD}"
        Departement = "${var.TAGS_DEPIT}"
    }
    vm_hostname         = "dcsazprois001"
    vm_os_simple        = "WindowsServer"
    vm_os_sku           = "2016-Datacenter"
    vm_size             = "Standard_DS2_V2"
}

Deploy Azure Virtual Machine with 
azurerm_virtual_machine

Deploy Virtual Machine – no extensions

To deploy windows servers we need a storage account. The storage account that I will deploy in the example has following characteristics. The storage account “azrmstgacc-stdssdlrs-001”:

  • Account tier: Premium 
  • Account kind: Storage V2
  • Access tier: Hot
  • Account replication Type: Local Redundant Storage (LRS)
  • Blob encryption: Enabled

Storage Code using “azurerm_Storage_account”, using the terraform file: “storage.tf”

resource "azurerm_storage_account" "azrmstgacc-stdssdlrs-001" {
    name = "${var.ARM_AZ_STGACC_STDSSD_NAME}"
    resource_group_name = "${azurerm_resource_group.RGName.name}"
    location = "${azurerm_resource_group.RGName.location}"
    account_tier = "Premium"
    #Storage V2 is the recommend storage type by Microsoft Azure (General purpose)
    account_kind = "StorageV2"
    #Storage Access tier = Hot / Cool
    access_tier = "Hot"
    /*
account replication type of the storage account
    LRS: Locally Redundant Storage (locally store 3 Copies of the data)
    ZRS: Zone Redundant Storage
    GRS: Geo Redundant Storage
    RAGRS: Read-Acces Geo Redundant Storage
    */
    account_replication_type = "LRS"
    enable_blob_encryption = "true"

    tags    = {
                Environment = "${var.TAGS_ENVPRD}"
                EnvType     = "${var.TAGS_EVNTYPE_STORAGE}"
                Departement = "${var.TAGS_DEPIT}"
            }
}
#Storage Container
resource "azurerm_storage_container" "vhds" {
  name                  = "${var.ARM_AZ_STGCONT_VHDS_NAME}"
  resource_group_name   = "${azurerm_resource_group.RGName.name}"
  storage_account_name  = "${azurerm_storage_account.azrmstgacc-stdssdlrs-001.name}"
  container_access_type = "private"
}
aStorage Account – Server VDH storage

The first deployment of a Windows Server 2016 data center is standard deployment with no extensions added. What has been included is the creation of a local administrator account that will be different for each created virtual machine. Included:

  • local administrator account – example AccID45874 (Random generated code of 5 numbers). The result will be included in the outputs.tf
  • Put the OS disk on the blob storage
  • Not included data disk on the server
  • Not included Public IP address to the server Network Interface Card
  • Currently also not included is a second Network Interface Card towards the back-end subnet network

Create following files:

  • compute.tf : configuration file that will be used to define the virtual machines
  • outputs.tf : configuration file that will be used to define output variables that need to be displayed at the end of the Terraform apply – process.
  • terraform.tfvars : configuration file that will contain variable that need to be excluded from the version repository (Github/DevOps) 
Terraform.tfvars

The file “terraform.tfvars” would be loaded automatically to populate the variables defined.

############################
# Authentication variables #
############################
#example data: this data was generated in previous blog.
ARM_CLIENT_ID="a00f125f-54ef-8a25-a87t-f514ag2541s8"
ARM_SUBSCRIPTION_ID="d1ge7156-1g55-4d44-5f22-7vdg21d542s3"
ARM_CLIENT_SECRET="d1ge7156-1g55-4d44-5f22-7vdg21d542s3"
ARM_TENANT_ID="d1ge7156-1g55-4d44-5f22-7vdg21d542s3"
##################
# Domain Account #
##################
#example data
ARM_VAR_LocalSrvAccSecret="<Password>" 
#The password that will be used by the local administrator account.
compute.tf

compute.tf – file will contain the information to deploy a virtual machine. Special will be here the random string that will be generated to create the local administrator account for the virtual machine. The string will be a numeric number of 5 numbers.

#########################
# COMPUTE CONFIGURATION #
#########################
/*
Generated random admin account
*/
resource "random_string" "AccID" {
  length=5
  special=false
  number=true
  min_numeric=5
}
#Create local administrator Account ID - Example ID: AccID23756
locals {
    VarAccID="AccID-${random_string.AccID.result}"
}
#https://www.terraform.io/docs/providers/azurerm/r/network_interface.html
 
resource "azurerm_network_interface" "nic" {
    name                = "primaryNic${count.index}"
    location            = "${azurerm_resource_group.RGName.location}"
    resource_group_name = "${azurerm_resource_group.RGName.name}"
    ip_configuration {
        name                          = "ipconfig00${count.index}"
        subnet_id                     = "${module.network.vnet_subnets[0]}"
        private_ip_address_allocation = "dynamic"
    }
}

#https://www.terraform.io/docs/providers/azurerm/r/virtual_machine.html 
resource "azurerm_virtual_machine" "InfrastructureServer" {
    name="dcsazprois002"
    resource_group_name="${azurerm_resource_group.RGName.name}"
    location="${azurerm_resource_group.RGName.location}"
    vm_size="Standard_B2s" #Information about the Virtual Machines Sizes: https://docs.microsoft.com/nl-be/azure/virtual-machines/windows/sizes-general
    network_interface_ids=["${azurerm_network_interface.nic.id}"] #Front-End Network

    os_profile_windows_config {
        provision_vm_agent=true
        timezone="Romance Standard Time" 
    }
    #OS Image selection: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/cli-ps-findimage
    storage_image_reference  {
        publisher="MicrosoftWindowsServer"
        offer="WindowsServer"
        sku="2016-Datacenter"
        version="latest"
    }

    storage_os_disk {
        name          = "dcsazprois002-os"
        vhd_uri       = "${azurerm_storage_account.azrmstgacc-stdssdlrs-001.primary_blob_endpoint}${azurerm_storage_container.vhds.name}/dcsazprois002-os.vhd"
        caching       = "ReadWrite"
        create_option = "FromImage"
        os_type       = "Windows"
    }

    os_profile {
        computer_name  = "dcsazprois002"
        admin_username = "${local.VarAccID}"
        admin_password = "${var.ARM_VAR_LocalSrvAccSecret}"
    }

    tags    = {
            Environment = "${var.TAGS_ENVPRD}"
            EnvType     = "${var.TAGS_EVNTYPE_COMPUTE}"
            Departement = "${var.TAGS_DEPIT}"
        }
}
Used commands to Deploy the virtual machine, from virtual studio code.
Terraform init
Terraform plan
Terraform apply

After deployment has been completed you will see the machine appear in Azure.
Virtual machine after deployment

Deploy Virtual Machine – with extensions

Deploying a second virtual machine with extra virtual machine extensions:

  • BGInfo Extension
  • Add server to Active Directory
  • Add extra network card towards the back-end subnet (no extension, extra information how-to add a second Network Interface Card)

More information about extensions.

The setup will be the same as previous, but extensions will be added. Creating Storage, Network Interface card and virtual machine. After the virtual machine has been deployed the extensions will be added to the virtual machine. Following extensions will be added:

  • BGInfo: This will display information about the virtual machine on the desktop background.
  • Adding the VM to Active Directory – JsonADDomainExtension (You need to have an Active Directory server in Azure or a VPN connection to your on-premises infrastructure)

Extension BGInfo.

Getting the publisher, type and version can be found by using the following one liner in Azure Shell.

Get-AzureRmVmImagePublisher -Location “WestEurope” | `
Get-AzureRmVMExtensionImageType | `
Get-AzureRmVMExtensionImage | Select Type, Version

Or


Get-AzureRmVmImagePublisher -Location “WestEurope” | `
Get-AzureRmVMExtensionImageType | `
Get-AzureRmVMExtensionImage | where-object-property Type -EQ -value “BGInfo”

#Extension BGInfo
resource "azurerm_virtual_machine_extension" "BGInfo" {
  name                 = "${local.VarSrvName}-BGInfo"
  location             = "${azurerm_resource_group.RGName.location}"
  resource_group_name  = "${azurerm_resource_group.RGName.name}"
  virtual_machine_name = "${azurerm_virtual_machine.IS01.name}"
  publisher            = "Microsoft.Compute"
  type                 = "BGInfo"
  type_handler_version = "2.1"
}

Extension add server to domain.

#Extension: add server to domain
resource "azurerm_virtual_machine_extension" "ADD2AD" {
    name                 = "${local.VarSrvName}-ADD2AD"
    location             = "${azurerm_resource_group.RGName.location}"
    resource_group_name  = "${azurerm_resource_group.RGName.name}"
    virtual_machine_name = "${azurerm_virtual_machine.IS01.name}"
    publisher            = "Microsoft.Compute"
    type                 = "JsonADDomainExtension"
    type_handler_version = "1.3"
    # What the settings means: https://docs.microsoft.com/en-us/windows/desktop/api/lmjoin/nf-lmjoin-netjoindomain

    settings = <<SETTINGS
        {
            "Name": "<NAME>",
            "OUPath": "OU=AZURE_SERVERS,DC=<DOMAIN>,DC=LOCAL",
            "User": "DOMAIN\\USERNAME",
            "Restart": "true",
            "Options": "3"
        }
SETTINGS
  protected_settings = <<PROTECTED_SETTINGS
    {
      "Password": "<Password>"
    }
  PROTECTED_SETTINGS
  depends_on = ["azurerm_virtual_machine.IS01"]
}

Server will be added to your active directory domain. Some pictures.

Next step will be calling custom PowerShell Scripts.
Terraform code for the virtual machine.

resource "random_string" "AccID2" {
  length=5
  special=false
  number=true
  min_numeric=5
}
#Create local administrator Account ID - Example ID: AccID23756
locals {
    VarAccID2="AccID-${random_string.AccID2.result}"
    VarSrvName="${var.ARM_AZ_TEMP_ISSRV}1"
    VarNIC-FE-Name="${local.VarSrvName}-NIC-FE"
    VarNIC-BE-Name="${local.VarSrvName}-NIC-BE"
    varIPNIC-FE-Name="${local.VarSrvName}-IPConf-FE"
    varIPNIC-BE-Name="${local.VarSrvName}-IPConf-BE"
}


resource "azurerm_network_interface" "NIC-FE" {
    name                = "${local.VarNIC-FE-Name}"
    location            = "${azurerm_resource_group.RGName.location}"
    resource_group_name = "${azurerm_resource_group.RGName.name}"
    ip_configuration {
        name                          = "${local.varIPNIC-FE-Name}"
        subnet_id                     = "${module.network.vnet_subnets[0]}" #[0] based on the network subnets order creation
       private_ip_address_allocation = "dynamic"
    }
}

resource "azurerm_network_interface" "NIC-BE" {
    name                = "${local.VarNIC-BE-Name}"
    location            = "${azurerm_resource_group.RGName.location}"
    resource_group_name = "${azurerm_resource_group.RGName.name}"
    ip_configuration {
        name                          = "${local.varIPNIC-BE-Name}"
        subnet_id                     = "${module.network.vnet_subnets[1]}"
        private_ip_address_allocation = "dynamic"
    }
}
#https://www.terraform.io/docs/providers/azurerm/r/virtual_machine.html 
resource "azurerm_virtual_machine" "IS01" {
    name="${local.VarSrvName}"
    resource_group_name="${azurerm_resource_group.RGName.name}"
    location="${azurerm_resource_group.RGName.location}"
    #Information about the Virtual Machines Sizes: https://docs.microsoft.com/nl-be/azure/virtual-machines/windows/sizes-general
    vm_size="Standard_B2s" 
    network_interface_ids=["${azurerm_network_interface.NIC-FE.id}","${azurerm_network_interface.NIC-BE.id}"] #Front-End/Back-End Network
    primary_network_interface_id="${azurerm_network_interface.NIC-FE.id}"

    os_profile_windows_config {
        provision_vm_agent=true
        timezone="Romance Standard Time"
    }
    #OS Image selection: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/cli-ps-findimage
    storage_image_reference  {
        publisher="MicrosoftWindowsServer"
        offer="WindowsServer"
        sku="2016-Datacenter"
        version="latest"
    }

    storage_os_disk {
        name          = "${local.VarSrvName}-os"
        vhd_uri       = "${azurerm_storage_account.azrmstgacc-stdssdlrs-001.primary_blob_endpoint}${azurerm_storage_container.vhds.name}/${local.VarSrvName}-os.vhd"
        caching       = "ReadWrite"
        create_option = "FromImage"
        os_type       = "Windows"
    }

    os_profile {
        computer_name  = "${local.VarSrvName}"
        admin_username = "${local.VarAccID}"
        admin_password = "${var.ARM_VAR_LocalSrvAccSecret}"
    }

    tags    = {
            Environment = "${var.TAGS_ENVPRD}"
            EnvType     = "${var.TAGS_EVNTYPE_COMPUTE}"
            Departement = "${var.TAGS_DEPIT}"
        }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.