Terraform - Deploy End-End Azure Virtual Machine (VNET + IP + RDP + WinRM) - Part Three

You already got an idea of the use case of Terraform and also about how we can connect to Azure using terraform. If not, please refer to my first two articles:
So far we are able to connect to Azure and now, we will deploy some serious entities inside Azure using terraform. In this article, I thought to proceed with an Azure virtual machine deployment. We are expected to get the below entities inside Azure after this proposed Terraform deployment.
  1. A Windows 2016 server
  2. VNet with a dynamic public IP, Subnet, NSG, NIC etc...
  3. Connection ports open for HTTPS, WinRM and RDP. Able to connect using both RDP and WinRM
  4. VMAgent to run automatically
Terraform configuration(azurevm.tf) for this deployment has been uploaded here and only use that version for your reference. Below, I have explained the sections of interest so that you can refer to the configuration with a better understanding, rather than just following it. 
  1. provider "azurerm" {}  
  2.   
  3. variable "location" {  
  4.   default = "Southeast Asia"  
  5. }  
  6.   
  7. variable "username" {  
  8.   default = "jaishmathews"  
  9. }  
  10.   
  11. variable "password" {  
  12.   default = "jaishmathews$1234"  
  13. }  
  14.   
  15. resource "azurerm_resource_group" "resourceGroup" {  
  16.   name     = "MindcrackerResourceGroup"  
  17.   location = "${var.location}"  
  18. }  
         
  1. resource "azurerm_public_ip" "publicip" {  //Here defined the public IP
  2.   name                         = "mindcrackpublicip"  
  3.   location                     = "${var.location}"  
  4.   resource_group_name          = "${azurerm_resource_group.resourceGroup.name}"  
  5.   public_ip_address_allocation = "Dynamic"  
  6.   idle_timeout_in_minutes      = 30  
  7.   domain_name_label            = "mindcrackvm"  //Here defined the dns name
  8.   
  9.   tags {  
  10.     environment = "test"  
  11.   }  
  12. }  
  13.   
  14. resource "azurerm_virtual_network" "vnet" {   //Here defined the virtual network
  15.   name                = "mindcracknetwork"  
  16.   address_space       = ["10.0.0.0/16"]  
  17.   location            = "${var.location}"  
  18.   resource_group_name = "${azurerm_resource_group.resourceGroup.name}"  
  19. }  
  20.   
  21. resource "azurerm_network_security_group" "nsg" {  //Here defined the network secrity group
  22.   name                = "mindcracknsg"  
  23.   location            = "${var.location}"  
  24.   resource_group_name = "${azurerm_resource_group.resourceGroup.name}"  
  25.   
  26.   security_rule {  //Here opened https port
  27.     name                       = "HTTPS"  
  28.     priority                   = 1000  
  29.     direction                  = "Inbound"  
  30.     access                     = "Allow"  
  31.     protocol                   = "Tcp"  
  32.     source_port_range          = "*"  
  33.     destination_port_range     = "443"  
  34.     source_address_prefix      = "*"  
  35.     destination_address_prefix = "*"  
  36.   }  
  37.   security_rule {   //Here opened WinRMport
  38.     name                       = "winrm"  
  39.     priority                   = 1010  
  40.     direction                  = "Inbound"  
  41.     access                     = "Allow"  
  42.     protocol                   = "Tcp"  
  43.     source_port_range          = "*"  
  44.     destination_port_range     = "5985"  
  45.     source_address_prefix      = "*"  
  46.     destination_address_prefix = "*"  
  47.   }  
  48.   security_rule {   //Here opened https port for outbound
  49.     name                       = "winrm-out"  
  50.     priority                   = 100  
  51.     direction                  = "Outbound"  
  52.     access                     = "Allow"  
  53.     protocol                   = "*"  
  54.     source_port_range          = "*"  
  55.     destination_port_range     = "5985"  
  56.     source_address_prefix      = "*"  
  57.     destination_address_prefix = "*"  
  58.   }  
  59.   security_rule {   //Here opened remote desktop port
  60.     name                       = "RDP"  
  61.     priority                   = 110  
  62.     direction                  = "Inbound"  
  63.     access                     = "Allow"  
  64.     protocol                   = "Tcp"  
  65.     source_port_range          = "*"  
  66.     destination_port_range     = "3389"  
  67.     source_address_prefix      = "*"  
  68.     destination_address_prefix = "*"  
  69.   }  
  70.   tags {  
  71.     environment = "test"  
  72.   }  
  73. }  
  74.   
  75. resource "azurerm_subnet" "subnet" {   //Here defined subnet
  76.   name                 = "mindcracksubnet"  
  77.   resource_group_name  = "${azurerm_resource_group.resourceGroup.name}"  
  78.   virtual_network_name = "${azurerm_virtual_network.vnet.name}"  
  79.   address_prefix       = "10.0.2.0/24"  
  80. }  
  81.   
  82. resource "azurerm_network_interface" "nic" {  //Here defined network interface
  83.   name                      = "mindcracknic"  
  84.   location                  = "${var.location}"  
  85.   resource_group_name       = "${azurerm_resource_group.resourceGroup.name}"  
  86.   network_security_group_id = "${azurerm_network_security_group.nsg.id}"  
  87.   
  88.   ip_configuration {  
  89.     name                          = "mindcrackconfiguration"  
  90.     subnet_id                     = "${azurerm_subnet.subnet.id}"  
  91.     private_ip_address_allocation = "dynamic"  
  92.     public_ip_address_id          = "${azurerm_public_ip.publicip.id}"  
  93.   }  
  94. }  
  95.   
  96. resource "azurerm_storage_account" "storageacc" {  //Here defined a storage account for disk
  97.   name                     = "mindcrackstoacc"  
  98.   resource_group_name      = "${azurerm_resource_group.resourceGroup.name}"  
  99.   location                 = "${var.location}"  
  100.   account_tier             = "Standard"  
  101.   account_replication_type = "GRS"  
  102. }  
  103.   
  104. resource "azurerm_storage_container" "storagecont" {  //Here defined a storage account container for disk
  105.   name                  = "mindcrackstoragecont"  
  106.   resource_group_name   = "${azurerm_resource_group.resourceGroup.name}"  
  107.   storage_account_name  = "${azurerm_storage_account.storageacc.name}"  
  108.   container_access_type = "private"  
  109. }  
  110.   
  111. resource "azurerm_managed_disk" "datadisk" {  //Here defined data disk structure
  112.   name                 = "mindcrackdatadisk"  
  113.   location             = "${var.location}"  
  114.   resource_group_name  = "${azurerm_resource_group.resourceGroup.name}"  
  115.   storage_account_type = "Standard_LRS"  
  116.   create_option        = "Empty"  
  117.   disk_size_gb         = "1023"  
  118. }  
  119.   
  120. resource "azurerm_virtual_machine" "vm" {  //Here defined virtual machine
  121.   name                  = "mindcrackvm"  
  122.   location              = "${var.location}"  
  123.   resource_group_name   = "${azurerm_resource_group.resourceGroup.name}"  
  124.   network_interface_ids = ["${azurerm_network_interface.nic.id}"]  
  125.   vm_size               = "Standard_A2"  //Here defined virtual machine size
  126.   
  127.   storage_image_reference {  //Here defined virtual machine OS
  128.     publisher = "MicrosoftWindowsServer"  
  129.     offer     = "WindowsServer"  
  130.     sku       = "2016-Datacenter"  
  131.     version   = "latest"  
  132.   }  
  133.   
  134.   storage_os_disk {  //Here defined OS disk
  135.     name              = "mindcrackosdisk"  
  136.     caching           = "ReadWrite"  
  137.     create_option     = "FromImage"  
  138.     managed_disk_type = "Standard_LRS"  
  139.   }  
  140.   
  141.   storage_data_disk {  //Here defined actual data disk by referring to above structure
  142.     name            = "${azurerm_managed_disk.datadisk.name}"  
  143.     managed_disk_id = "${azurerm_managed_disk.datadisk.id}"  
  144.     create_option   = "Attach"  
  145.     lun             = 1  
  146.     disk_size_gb    = "${azurerm_managed_disk.datadisk.disk_size_gb}"  
  147.   }  
  148.   
  149.   os_profile {  //Here defined admin uid/pwd and also comupter name
  150.     computer_name  = "mindcrackhost"  
  151.     admin_username = "${var.username}"  
  152.     admin_password = "${var.password}"  
  153.   }  
  154.   
  155.   os_profile_windows_config {  //Here defined autoupdate config and also vm agent config
  156.     enable_automatic_upgrades = true  
  157.     provision_vm_agent        = true  
  158.   
  159.     winrm = {  //Here defined WinRM connectivity config
  160.       protocol = "http"  
  161.     }  
  162.   }  
  163. }  
Verification of this deployment includes the below steps. I am not mentioning about how to run a terraform configuration file as it has been mentioned in my previous articles.
  • Successful Deployment
    Please verify that you got something like below on command prompt before anything else.

    terraform
     
  • Verify Azure Portal
    You should see  something like the below image,

    terraform

  • Test RDP
    You should be able to login with uid/pwd inside the configuration file. Below is the screenshot from my side after login using the IP showing in the Azure portal
 terraform 
  • Verify WinRM
    After your successful login above, open a command prompt as Admin in the same logged machine. Then run the below command to get the details of an HTTP listener for WinRM connection, as mentioned in our configuration.
terraform
  • Verify Running VMAgent
    In the same logged machine, open task manager and click more details option on the bottom left side of the task manager window. Then make sure that the below task is running.
 terraform
  • Verify Remote WinRM Connection
    We should be able to connect to our new VM using WinRM remotely. So, again come back to your local machine from where you are running terraform. Open PowershellISE as Admin. Then try the below 4 commands. The first three commands are to establish a WinRM connection to our new VM and the fourth command is a normal command to make sure that the connected machine is our new VM.
terraform
 
That's all there is to show here. Please refer to the attached configuration file. In this series, next, I will show how to run certain bootstrap scripts during this VM deployment as part of Terraform. Until then, cheers.