Back in Action: Terraforming Azure

From Setup to Deployment

Hello Muser!

I wanted to apologize for the long delay on this edition of the newsletter. I had some personal events get in the way of the schedule but I’m back to writing!

Last time we did a deep dive into Terraform's vocabulary, getting acquainted with the syntax and components like providers, resources, and how the state is managed. We even took a peek at a simple AWS example to see how these pieces come together in a script. Now, we're all set to roll up our sleeves and get our hands dirty with a real-world Azure scenario.

If you’ve worked in Azure with Terraform before, please add some thoughts in the comments!

Azure Account Setup

  • First off, let’s get you set up on Azure. Head on over to the Azure Free Account Page and snag yourself a free account.

  • Azure generously offers a bunch of free services for 12 months plus a $200 credit to explore any Azure service for 30 days. It’s a sweet deal to kickstart your cloud journey!

----------

Terraform, Meet Azure

Before Terraform and Azure can start interacting, you'll need to install Azure CLI (Command-Line Interface) if you haven’t already. Here's a quick summary on getting it installed and setting things up:

Installing Azure CLI

  • For Windows, grab the installer and run it.

  • On macOS, a simple brew install azure-cli should do the trick.

  • Linux folks, run curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash.

Logging In

  • Once Azure CLI is installed, open your command line and enter az login. Your web browser will open for you to sign in to your Azure account.

Check Subscription

  • Once logged in, confirm you're on the right subscription with az account list. If you have multiple subscriptions, set the right one using az account set --subscription="SUBSCRIPTION_ID".

Setting Up Terraform

  • With Azure CLI installed and logged in, Terraform is now ready to interact with Azure. If you run into any trouble, you can refer to the Azure CLI guide. It's a straightforward walkthrough to get Terraform and Azure on speaking terms and recaps some of these steps we just did in more detail.

----------

Create a new GitHub repo for your Terraform configurations

  • Head over to GitHub and hit the New button.

  • Name your repo something like terraform-azure-hello-world.

  • Choose to make it public or private, and initialize it with a README.

----------

Terraform and Your Repo

  • Clone your repo, and pop in a main.tf file.

provider "azurerm" {
  features {}
}

provider "tls" {}

# Generate an RSA SSH key
resource "tls_private_key" "myprivatekey" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

# Save the private key locally
resource "local_file" "private_key" {
  content  = tls_private_key.myprivatekey.private_key_openssh
  filename = "${path.module}/private_key.openssh"
}

# Save the public key locally
resource "local_file" "public_key" {
  content  = tls_private_key.myprivatekey.public_key_openssh
  filename = "${path.module}/public_key.pub"
}

# Resource group
resource "azurerm_resource_group" "my_rg" {
  name     = "MyResourceGroup"
  location = "East US"
}

# Virtual network
resource "azurerm_virtual_network" "my_vnet" {
  name                = "MyVNet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.my_rg.location
  resource_group_name = azurerm_resource_group.my_rg.name

}

# Subnet
resource "azurerm_subnet" "my_subnet" {
  name                 = "MySubnet"
  resource_group_name  = azurerm_resource_group.my_rg.name
  virtual_network_name = azurerm_virtual_network.my_vnet.name
  address_prefixes     = ["10.0.2.0/24"]
}

# Network Security Group to allow SSH
resource "azurerm_network_security_group" "my_nsg" {
  name                = "MyNSG"
  location            = azurerm_resource_group.my_rg.location
  resource_group_name = azurerm_resource_group.my_rg.name

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Public IP address
resource "azurerm_public_ip" "my_public_ip" {
  name                = "MyPublicIP"
  location            = azurerm_resource_group.my_rg.location
  resource_group_name = azurerm_resource_group.my_rg.name
  allocation_method   = "Dynamic"
}

# Network Interface
resource "azurerm_network_interface" "my_nic" {
  name                = "MyNIC"
  location            = azurerm_resource_group.my_rg.location
  resource_group_name = azurerm_resource_group.my_rg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.my_subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.my_public_ip.id
  }
}

# Associate NSG to NIC

resource "azurerm_network_interface_security_group_association" "myprivatekey" {
  network_interface_id      = azurerm_network_interface.my_nic.id
  network_security_group_id = azurerm_network_security_group.my_nsg.id
}

# Virtual Machine
resource "azurerm_virtual_machine" "my_vm" {
  name                  = "MyVM"
  location              = azurerm_resource_group.my_rg.location
  resource_group_name   = azurerm_resource_group.my_rg.name
  network_interface_ids = [azurerm_network_interface.my_nic.id]
  vm_size               = "Standard_DS1_v2"

  # Create the VM with an image from the marketplace
  storage_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }

  # Define the OS disk
  storage_os_disk {
    name              = "myosdisk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  # Computer name, admin username
  os_profile {
    computer_name  = "myvm"
    admin_username = "adminuser"
  }

  # SSH Key for the VM
  os_profile_linux_config {
    disable_password_authentication = true
    ssh_keys {
      path     = "/home/adminuser/.ssh/authorized_keys"
      key_data = tls_private_key.myprivatekey.public_key_openssh
    }
  }
}

Here's what this does:

  • Sets Up Azure Provider

  • Generates an RSA SSH Key

  • Stores SSH Keys Locally

  • Creates a Resource Group

  • Deploys a Virtual Network

  • Configures a Subnet

  • Establishes Network Security

  • Assigns a Public IP

  • Sets Up a Network Interface

  • Associates NSG to NIC

  • Launches a Virtual Machine

  • Uses an Ubuntu Image

  • Configures OS and Disk

----------

Planning Our Azure Resources

  • cd into your git repo folder (if you haven't already) and kick things off with terraform init to prep your directory.

  • Run terraform plan to peek into Terraform’s playbook. You'll get an output like something below (this is a snippet):

# azurerm_virtual_machine.my_vm will be created
  + resource "azurerm_virtual_machine" "my_vm" {
      + availability_set_id              = (known after apply)
      + delete_data_disks_on_termination = false
      + delete_os_disk_on_termination    = false
      + id                               = (known after apply)
      + license_type                     = (known after apply)
      + location                         = "eastus"
      + name                             = "MyVM"
      + network_interface_ids            = (known after apply)
      + resource_group_name              = "MyResourceGroup"
      + vm_size                          = "Standard_DS1_v2"

      + os_profile {
          # At least one attribute in this block is (or was) sensitive,
          # so its contents will not be displayed.
        }

      + os_profile_linux_config {
          + disable_password_authentication = true
          + ssh_keys {
              + key_data = (known after apply)
              + path     = "/home/adminuser/.ssh/authorized_keys"
            }
        }

      + storage_image_reference {
          + offer     = "0001-com-ubuntu-server-jammy"
          + publisher = "Canonical"
          + sku       = "22_04-lts-gen2"
          + version   = "latest"
        }

      + storage_os_disk {
          + caching                   = "ReadWrite"
          + create_option             = "FromImage"
          + disk_size_gb              = (known after apply)
          + managed_disk_id           = (known after apply)
          + managed_disk_type         = "Standard_LRS"
          + name                      = "myosdisk"
          + os_type                   = (known after apply)
          + write_accelerator_enabled = false
        }

Since we're running the full plan, you'll have a lot more than this and it'll all be + (creations) to Terraform. If you got this far, you've successfully written your first functioning Terraform configuration! Congratulations!

I don’t want to take too much longer on this since the code makes this quite long in terms of word count. Next time we’ll apply and actually create the infrastructure in Azure! Don’t miss it! Subscribe now and share with your colleagues who might find this useful!

Keep learning and keep growing,

Darrell

Reply

or to participate.