Terraform with Azure? Sure!

While it is possible to use a proprietary Azure resource manager template in JSON format to define your Azure infrastructure or an AWS JSON CloudFormation template to define AWS infrastructure, using a tool like Terraform allows your company to standardize on one coding language for all your cloud infrastructure.  Terraform supports many providers including AWS, Google Cloud, and Microsoft Azure.  You can define, document, and roll out infrastructure to multiple providers with one set of configuration files.  However, this post will focus on how you can quickly define your Azure infrastructure with Terraform utilizing sample code for a typical deployment.

Terraform Resource names are unique across all providers so each defined resource knows which cloud is the intended destination.  For example, if you already have terraform code for your AWS infrastructure, you could add cloud redundancy or disaster recovery by adding an Azure provider with some Azure resources.   You do not have to separate your resources for different providers since all resource names are unique.

Terraform supports both the new Azure resource manager API as well as the classic Azure service management API.  I would recommend utilizing the Azure resource manager provider since this is the method of the future for Azure.  Although Terraform does not support all Azure resources, I found that it supports enough to deploy the majority of base infrastructure.

Below is a sample Azure infrastructure configured with a web tier, application tier, data tier, an infrastructure subnet, a management subnet, as well as a VPN gateway providing access the corporate network.  I was able to deploy all of this infrastructure besides the VPN gateway with Terraform resources.  The resources needed to deploy an Azure VPN gateway appear almost ready according to an open Hashicorp ticket.  By the time you read this, they may be available.  The resources will be called “asurerm_virtual_network_gateway” and “azure_virtual_network_gateway_connection.”

 

While not required, typically the first step to deploying your Azure Terraform infrastructure is do declare variables:


variable "subscription_id" {}
variable "client_id" {}
variable "client_secret" {}
variable "tenant_id" {}
variable "resource_group_id" {}
variable "resource_group_name" {}
variable "region" {
  default = "West US"
}
variable “VnetId” {}
variable “VnetName” {}
variable “EnvironmentTag” {}
variable “AdminUser” {}
variable “AdminPW” {}
variable “ServerName” {}

The first step is to supply the authentication required to address the Azure API by defining a Microsoft Azure Provider including subscription id, client id, client secret, and tenant id as defined in the variables.  


provider "azurerm" {
  subscription_id = "${var.subscription_id}"
  client_id       = "${var.client_id}"
  client_secret   = "${var.client_secret}"
  tenant_id       = "${var.tenant_id}"
}

After providing access, the Azure resource group is typically defined to contain the remaining resources.  An Azure resource group typically defines a set of infrastructure resources that will share the same lifecycle which means they will be deployed, updated, and ultimately deleted at the same time.  Resources that do not share the same lifecycle should be deployed in a separate resource group.  The resource group variables and the region variable are used to define the id, name and region for the Azure resources.


resource "azurerm_resource_group" "${var.resource_group_id}" {
    name     = "${var.resource_group_name}"
    location = "${var.region}"
}

Once you have your resource group, we can now define all of the resources for your base infrastructure.  A virtual network (VNet) is the base technology for your own isolated network in the cloud, so it should be defined first as well as it subnets.


resource "azurerm_virtual_network" "${var.VnetId}" {
  name                = "${var.VnetName}"
  address_space       = ["10.1.0.0/16"]
  location            = "${var.region}"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_env}".name}"
  subnet {
    name           = "MgmtSubnet"
    address_prefix = "10.1.32.0/19"
  }
  subnet {
    name           = "WebSubnet"
    address_prefix = "10.1.64.0/19"
    security_group = "${azurerm_network_security_group.WebAccess.id}"
  }
  subnet {
    name           = "AppSubnet"
    address_prefix = "10.1.96.0/19"
  }
  subnet {
    name           = "DataSubnet"
    address_prefix = "10.1.128.0/19"
  }
  subnet {
    name           = "ADDSSubnet"
    address_prefix = "10.1.160.0/19"
  }
  subnet {
    name           = "GatewaySubnet"
    address_prefix = "10.1.255.224/27"

  }
}

 

Azure provide basic routing between subnets, the Internet and across a configured VPN gateway automatically with their default system routes.  User-defined routes are only necessary if you want to alter the default behavior to force traffic to a virtual appliance or force tunneling to the Internet across the VPN gateway.  Below is a sample user-defined route.


resource "azurerm_route_table" "ADDS_Subnet" {
  name                = "AD DS Routing"
  location            = "${var.region}"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_env}".name}"
  route {
    name           = "routeVG"
    address_prefix = "10.1.160.0/19"
    next_hop_type  = "VirtualNetworkGateway
  }
  tags {
    environment = “EnvironmentTag” {}
  }
}

Azure network security groups (NSG) contain a list of security rules that deny or allow traffic to resources in an Azure virtual network.  NSGs can be associated with subnets or network interfaces attached to VMs.  Inbound traffic is first examined by NSGs attached to the subnet and then by a NSG attached to the NIC.  Outbound traffic is examined by the NSGs in reverse order.  Here is a sample NSG resource:


resource "azurerm_network_security_group" "WebAccess" {
  name                = "Web Access"
  location            = "${var.region}"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_env}".name}"
  security_rule {
    name                       = "Allow80"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "Internet"
    destination_address_prefix = "*"
  }
  tags {
    environment = “EnvironmentTag” {}  
  }
}

Microsoft Azure provides redundancy for multiple servers providing the same service with Availability Sets.  Azure hardware clusters are divided into update domains and fault domains representing update cycles and physical infrastructure including power and networking.  Azure will distribute virtual machines within an availability set across domains to provide availability and fault tolerance.  For the sample design above, we will need four availability sets.  Here is an example for the web tier:


resource "azurerm_availability_set" "WebTierAS" {
  name                = "Web Tier Availability Set"
  location            = "${var.region}"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_id}".name}"
  tags {
    environment = “${var.EnvironmentTag}"
  }
}

An Azure storage account provides storage within your Azure infrastructure.  Your storage account creates a unique namespace to store and retrieve your data.


resource "azurerm_storage_account" "StorIT" {
  name                = "StorageAccount"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_id}".name}"
  location            = "${var.region}"
  account_type        = "Standard_LRS"
  tags {
    environment = “EnvironmentTag” {}
  }
}

resource "azurerm_storage_container" "ContainIT" {
  name                  = "vhds"
  resource_group_name   = "${azurerm_resource_group."${var.resource_group_id}".name}"
  storage_account_name  = "${azurerm_storage_account.StorIT.name}"
  container_access_type = "private"
}

Below is a sample virtual machine definition.  A network interface is defined and assigned to the virtual machine.  The virtual machine definition also specifies the VM size, the operating system with user information, and the storage account/container.


resource "azurerm_network_interface" "webserver1nic" {
  name                = "WebServer"
  location            = "${var.region}"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_id}".name}"
  ip_configuration {
    name                          = "webIP1"
    subnet_id                     = "${azurerm_subnet. WebSubnet.id}"
    private_ip_address_allocation = "dynamic"
  }
}

resource "azurerm_virtual_machine" "webserver1" {
  name                  = "webserver1"
  location              = "${var.region}"
  resource_group_name   = "${azurerm_resource_group."${var.resource_group_id}".name}"
  network_interface_ids = ["${azurerm_network_interface.webserver1nic.id}"]
  vm_size               = "Standard_A1"
  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2012-R2-Datacenter"
    version   = "latest"
  }
  storage_os_disk {
    name          = "myOSdisk"
    vhd_uri       = "${azurerm_storage_account.StorIT.primary_blob_endpoint}${azurerm_storage_          container.ContainIT.name}/myOSdisk.vhd"
    caching       = "ReadWrite"
    create_option = "FromImage"
  }
  os_profile {
    computer_name  = "${var.ServerName}"
    admin_username = "${var.AdminUser}"
    admin_password = "${var.AdminPW}"
  }
  tags {
    environment = “${var.EnvironmentTag}"
  }
}

Azure has three separate services for distributing traffic including the Azure Load Balancer which works at layer 4, an Application Gateway which operates at layer 7 and acts as a reverse-proxy, and Traffic Manager which work at the DNS level.  This example template is deploying the Azure Load Balancer.  There is an external load balancer for the web tier and internal load balancers for the application/data tiers.  Below is a sample of code for the web tier including a public IP, the load balancer, front-end configuration, a web balancing rule, a health check probe, and a backend web server address pool.


resource "azurerm_public_ip" "WebTierLBIP" {
  name                         = "PublicIPForLB"
  location                     = "${var.region}"
  resource_group_name          = "${azurerm_resource_group."${var.resource_group_id}".name}"
  public_ip_address_allocation = "static"
}
resource "azurerm_lb" "WebTierLB" {
  name                = "WebTierLoadBalancer"
  location            = "${var.region}"
  resource_group_name = "${azurerm_resource_group."${var.resource_group_id}".name}"
  frontend_ip_configuration {
    name                 = "WebTierIP"
    public_ip_address_id = "${azurerm_public_ip.WebTierLBIP.id}"
  }
}
resource "azurerm_lb_rule" "WebRule" {
  resource_group_name            = "${azurerm_resource_group."${var.resource_group_id}".name}"
  loadbalancer_id                = "${azurerm_lb.WebTierLB.id}"
  name                           = "WebLBRule"
  protocol                       = "Tcp"
  frontend_port                  = 80
  backend_port                   = 80
  frontend_ip_configuration_name = "WebTierIP"
}
resource "azurerm_lb_probe" "WebCheck" {
  resource_group_name = "${azurerm_resource_group."${var.resource_group_id}".name}"
  loadbalancer_id     = "${azurerm_lb.WebTierLB.id}"
  name                = "Port80responce"
  port                = 80
}
resource "azurerm_lb_backend_address_pool" "WebServers {
  resource_group_name = "${azurerm_resource_group."${var.resource_group_id}".name}"
  loadbalancer_id     = "${azurerm_lb.WebTierLB.id}"
  name                = "BackEndWebServers"
}

While not all Azure resources may be deployed with Terraform, many of the important ones are available including several not covered here.  There are definitely enough to supply your base infrastructure in Azure with or without base infrastructure for other providers as well.

 

The power of infrastructure-as-code really comes to life with a tool such as terraform especially with the potential of deploying infrastructure to multiple providers.  Utilizing terraform code similar to what I have shown in this post, you can quickly deploy an Azure resource group with a virtual network, route tables, network security groups, storage accounts, availability sets, virtual machines, and load balancers.  If you already have Terraform code for another provider, deploying Azure resources as shown in this post will allow you to quickly deploy a multi-cloud infrastructure.

 

Posted in General

Leave a Reply

Your email address will not be published. Required fields are marked *

*