Menu

Virtual Geek

Tales from real IT system administrators world and non-production environment

Create an Azure virtual machine scale set and load balancer using Terraform

While working on one of the client requirement I got below assignement which wanted to be demonstrated either with Terraform or ARM. Although I am very good at writing script for both languages, I chose Terraform, as there was another requirement for other projects where I am already working on Terraform. As I was running short on the time, wrote this script in very short time of span but I will revise it later to get more proper input from user when they deploy it.

Updated Version: Azure Terraform fixed Availibility Zones on Virtual Machine Scale Set

My requirement is as below:

Assignment

1.1 OBJECT GOAL
This is small project, Deploy a simple web site in a load balanced, highly available and zone-redundant manner using automation and Azure best practices.

1.2 SPECIFICATIONS
      •	Virtual Network with a private subnet and all required dependent infrastructure
      •	Azure Load Balancer with web server instances in the backend pool
              o	Incorporate a simple health probe to make sure the web servers in the backend pool are responding and healthy
              o	The health probe should automatically supersede instances if they are unhealthy, and the
      •	instances should come back into service on their own
      •	Virtual Machine Scale Set that launches Virtual Machines and registers them to the backend pool
              o	Set up a VMSS minimum and desired server count meeting Highly Available and zone
              o	redundant requirement
              o	Establish a VMSS maximum
              o	Establish an Auto scale rule that scales up/down based on a metric of your choice (and be able to exhibit a scaling event)
      •	Network Security Group allowing HTTP traffic to load balancer from anywhere (not directly to the instance(s))
      •	Network Security Group allowing only HTTP traffic from the load balancer to the instance(s)
      •	Remote management ports such as SSH and RDP must not be open to the world
      •	Some kind of automation or scripting that achieves the following:
              o	Install a web server (your choice – Apache, Nginx, and IIS are common examples)
              o	Deploys a simple “hello world” page for the web server to serve up
              o	May be written in the language of your choice (HTML, PHP, etc)
              o	May be sourced from the location of your choice (Git, cookbook file/ template, etc)
              o	Must include the server’s hostname in the “hello world” presented to the user
      •	All Azure resources must be created using Terraform or Azure Resource Manager (Do not use or download Terraform modules from other users All this should be from scrach)
      •	No resources may be created or managed by hand in the portal, the Azure CLI, or with Powershell

Download the complete terraform script here, or it also available on github.com. Below are the necessary tf files

main.tf
This file uses the Microsoft Azure Provider plugin from hasicorp.

1
2
3
4
# Configure the Microsoft Azure Provider
provider "azurerm" {
  features {}
}

variables.tf
In this file I have mentioned what the common configuration information names will be when deploying Azure Resources. Example: Location, Resource Group Name, VMSS name etc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
variable "location" {
 description = "The location where resources will be created"
 default     = "East Us"
 type = string
}

variable "tags" {
 description = "A map of the tags to use for the resources that are deployed"
 type        = map(string)

 default = {
   environment = "codelab"
 }
}

variable "resource_group_name" {
 description = "The name of the resource group in which the resources will be created"
 default     = "VMSS"
 type = string
}

locals {
  regions_with_availability_zones = ["eastus"] #["centralus","eastus2","eastus","westus"]
  zones = contains(local.regions_with_availability_zones, var.location) ? list("1","2","3") : null
}

/*
output "zones" {
  value = local.zones
}
*/

variable "azurerm_virtual_network" {
 description = "The name of the virtual network in which the resources will be created"
 default     = "VMSSnet"
 type = string
}

variable "azurerm_virtual_machine_scale_set" {
 description = "The name of the virtual network in which the resources will be created"
 default     = "VMScaleSet"
 type = string
}

variable "availability_zone_names" {
 description = "The name of the virtual network in which the resources will be created"
 default     = ["eastus"]
 type    = list(string)
}

vmss.tf
In this terraform tf configuration file I will deploy vNet (Azure Virtual Network), VMSS (Azure Virtual Machine Scale Set), LB (Azure Machine Load balancer) they are all configured with each other as a part the requirement given by client.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
resource "azurerm_resource_group" "vmss" {
 name     = var.resource_group_name
 location = var.location
 tags     = var.tags
}

resource "random_string" "fqdn" {
 length  = 6
 special = false
 upper   = false
 number  = false
}

resource "azurerm_virtual_network" "vmss" {
 name                = var.azurerm_virtual_network
 address_space       = ["10.0.0.0/16"]
 location            = var.location
 resource_group_name = azurerm_resource_group.vmss.name
 tags                = var.tags
}

resource "azurerm_subnet" "vmss" {
 name                 = "vmss-subnet"
 resource_group_name  = azurerm_resource_group.vmss.name
 virtual_network_name = azurerm_virtual_network.vmss.name
 address_prefix       = "10.0.2.0/24"
}

resource "azurerm_public_ip" "vmss" {
 name                         = "vmss-public-ip"
 location                     = var.location
 resource_group_name          = azurerm_resource_group.vmss.name
 allocation_method = "Static"
 domain_name_label            = random_string.fqdn.result
 tags                         = var.tags
}

resource "azurerm_lb" "vmss" {
 name                = "vmss-lb"
 location            = var.location
 resource_group_name = azurerm_resource_group.vmss.name

 frontend_ip_configuration {
   name                 = "PublicIPAddress"
   public_ip_address_id = azurerm_public_ip.vmss.id
 }

 tags = var.tags
}

resource "azurerm_lb_backend_address_pool" "bpepool" {
 resource_group_name = azurerm_resource_group.vmss.name
 loadbalancer_id     = azurerm_lb.vmss.id
 name                = "BackEndAddressPool"
}

resource "azurerm_lb_probe" "vmss" {
 resource_group_name = azurerm_resource_group.vmss.name
 loadbalancer_id     = azurerm_lb.vmss.id
 name                = "ssh-running-probe"
 port                = var.application_port
}

resource "azurerm_lb_rule" "lbnatrule" {
   resource_group_name            = azurerm_resource_group.vmss.name
   loadbalancer_id                = azurerm_lb.vmss.id
   name                           = "http"
   protocol                       = "Tcp"
   frontend_port                  = var.application_port
   backend_port                   = var.application_port
   backend_address_pool_id        = azurerm_lb_backend_address_pool.bpepool.id
   frontend_ip_configuration_name = "PublicIPAddress"
   probe_id                       = azurerm_lb_probe.vmss.id
}

resource "azurerm_virtual_machine_scale_set" "vmss" {
 name                = var.azurerm_virtual_machine_scale_set
 location            = var.location
 resource_group_name = azurerm_resource_group.vmss.name
 upgrade_policy_mode = "Manual"

 zones = local.zones

 sku {
   name     = "Standard_B1S"
   tier     = "Standard"
   capacity = 2
 }

 storage_profile_image_reference {
   publisher = "Canonical"
   offer     = "UbuntuServer"
   sku       = "16.04-LTS"
   version   = "latest"
 }

 storage_profile_os_disk {
   name              = ""
   caching           = "ReadWrite"
   create_option     = "FromImage"
   managed_disk_type = "Standard_LRS"
 }

 storage_profile_data_disk {
   lun          = 0
   caching        = "ReadWrite"
   create_option  = "Empty"
   disk_size_gb   = 10
 }

 os_profile {
   computer_name_prefix = "vmlab"
   admin_username       = var.admin_user
   admin_password       = var.admin_password
   custom_data          = file("web.conf")
 }

 os_profile_linux_config {
   disable_password_authentication = false
 }

 network_profile {
   name    = "terraformnetworkprofile"
   primary = true

   ip_configuration {
     name                                   = "IPConfiguration"
     subnet_id                              = azurerm_subnet.vmss.id
     load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.bpepool.id]
     primary = true
   }
 }

 tags = var.tags

}

/*
resource "azurerm_virtual_machine_scale_set_extension" "example" {
  name                         = "example"
  virtual_machine_scale_set_id =  azurerm_virtual_machine_scale_set.vmss.id
  publisher                    = "Microsoft.Azure.Extensions"
  type                         = "CustomScript"
  type_handler_version         = "2.0"
  settings = jsonencode({
    "commandToExecute" = "echo Hello World $HOSTNAME "
  })
}
*/


resource "azurerm_monitor_autoscale_setting" "vmss" {
  name                = "AutoscaleSetting"
  resource_group_name = azurerm_resource_group.vmss.name
  location            = azurerm_resource_group.vmss.location
  target_resource_id  = azurerm_virtual_machine_scale_set.vmss.id

  profile {
    name = "defaultProfile"

    capacity {
      default = 2
      minimum = 2
      maximum = 10
    }

    rule {
      metric_trigger {
        metric_name        = "Percentage CPU"
        metric_resource_id = azurerm_virtual_machine_scale_set.vmss.id
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "GreaterThan"
        threshold          = 75
        metric_namespace   = "microsoft.compute/virtualmachinescalesets"
        dimensions {
          name     = "AppName"
          operator = "Equals"
          values   = ["App1"]
        }
      }

      scale_action {
        direction = "Increase"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }

    rule {
      metric_trigger {
        metric_name        = "Percentage CPU"
        metric_resource_id = azurerm_virtual_machine_scale_set.vmss.id
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "LessThan"
        threshold          = 25
      }

      scale_action {
        direction = "Decrease"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }
  }

  notification {
    email {
      send_to_subscription_administrator    = true
      send_to_subscription_co_administrator = true
      custom_emails                         = ["admin@contoso.com"]
    }
  }
}

web.conf
This is cloud Init configuration once the resources are deployed, This file will deploy the web server nginx, nodejs, npm application on the VMSS instances.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#cloud-config
package_upgrade: true
packages:
  - nginx
  - nodejs
  - npm
write_files:
  - owner: www-data:www-data
    path: /etc/nginx/sites-available/default
    content: |
      server {
        listen 80;
        location / {
          proxy_pass http://localhost:3000;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection keep-alive;
          proxy_set_header Host $host;
          proxy_cache_bypass $http_upgrade;
        }
      }
  - owner: azureuser:azureuser
    path: /home/azureuser/myapp/index.js
    content: |
      var express = require('express')
      var app = express()
      var os = require('os');
      app.get('/', function (req, res) {
        res.send('Hello World from host ' + os.hostname() + '!')
      })
      app.listen(3000, function () {
        console.log('Hello world app listening on port 3000!')
      })
runcmd:
  - service nginx restart
  - cd "/home/azureuser/myapp"
  - npm init
  - npm install express -y
  - nodejs index.js

output.tf

This file has few variables defined, which I am going update with few more information later.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
output "vmss_public_ip" {
    value = azurerm_public_ip.vmss.fqdn
}

variable "application_port" {
   description = "The port that you want to expose to the external load balancer"
   default     = 80
}

variable "admin_user" {
   description = "User name to use as the admin account on the VMs that will be part of the VM Scale Set"
   default     = "azureuser"
}

variable "admin_password" {
   description = "Default password for admin account"
   default     = "Computer@123"
}

Next to execute the files run terraform init, it downloads required azurerm plugins from HashiCorp. This command is used to initialize a working directory containing Terraform configuration files. This is the first command that should be run after writing a new Terraform configuration or cloning an existing one from version control. It is safe to run this command multiple times.

Microsoft Azure powershell terraform init hashcorp azurerm arm provider resource services vmss terraform load balancer virtual machine scalse set cloud init directory.png

Run terraform plan to previews the changes that will made to infrastructure. It also Reads the current state of any already-existing remote objects to make sure that the Terraform state is up-to-date.

Microsoft Azure terraform Plan vmss refressing state auzre_virtual_network virtual machine scale set load balancer cloud init nginx resource group high availability zones nsg public ip lb.png

The terraform apply command executes the actions proposed in a Terraform plan.

Microsoft Azure terroform arm automation apply -auto-approve refressing state provider azuer resource virtual machine scale set load balancer probe apply complete output variable tf file tfstate.png

Once terraform apply complete you can view the resources on Azure portal. I tested website its working fine.

Microsoft Azure portal Resource Groups VMSS virtual machine scaleset vmscaleset load balancer virtual public ip virtual network vnet cloud init ngix subscription id tags terraform automation.png

Useful Articles
PART 1 : MICROSOFT AZURE CREATION AND CONFIGURATION OF VPN TUNNEL SERIES
PART 2 : MICROSOFT AZURE CREATING RESOURCE GROUP 
PART 3 : MICROSOFT AZURE CREATING AND ADMINISTERING VIRTUAL NETWORK (VNET) 
PART 3.1 : MICROSOFT AZURE POWERSHELL CREATING AND ADMINISTERING VIRTUAL NETWORK (VNET)
PART 4 : MICROSOFT AZURE CREATING AND ADMINISTRATING LOCAL NETWORK GATEWAY VPN
PART 4.1 : MICROSOFT AZURE POWERSHELL CREATING AND ADMINISTRATING LOCAL NETWORK GATEWAY 

Microsoft azure virtual network vnet creation in resource group designing gateway

PART 5: VIRTUAL NETWORK GATEWAY DEPLOYMENT ON MICROSOFT AZURE 
PART 5.1: VIRTUAL NETWORK GATEWAY DEPLOYMENT USING MICROSOFT AZURE POWERSHELL
PART 6: INSTALLING ROUTING AND REMOTE ACCESS SERVER ROLE (MICROSOFT RRAS)
PART 6.1: CONFIGURING ROUTING AND REMOTE ACCESS SERVER DEMAND-DIAL (MICROSOFT RRAS AZURE VPN)
PART 6.2: CONFIGURING ROUTING AND REMOTE ACCESS SERVER ROUTER (MICROSOFT RRAS AZURE VPN)
References :  https://docs.microsoft.com/en-us/azure/developer/terraform/create-vm-scaleset-network-disks-hcl and https://docs.microsoft.com/en-us/azure/virtual-machines/linux/tutorial-automate-vm-deployment

Go Back

Comment

Blog Search

Page Views

5939704

Follow me on Blogarama