This example shows the use of 'loops' in terraform to create three linux servers, each with its private IP and its public IP, the three servers belonging to a 10.0.0.0/28 network segment that belongs to a 10.0 network .0.0/24. The 'loop' will be used to generate three elements of the components public ip, network card of each server and linux server.

A 'loop' in terraform is created with the variable 'count' inside an element.

In the 'providers'tf' file we specify that we will use a version of 'hashicorp/azurerm' provider '3.48.0', and the data of the tenant ID and Azure subscription where we will deploy the infrastructure of this example:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.48.0"
    }
  }
}

provider "azurerm" { 
  features {} 

  subscription_id = "aad4e527-3ef6-4378-a599-61f803834bce"
  tenant_id       = "7a292158-a1c7-4f67-99f9-435174e561b1"

} 


We are going to use the use of loop in two ways in the 'resources.tf' file:

  • Using a count with an explicitly assigned value, example 'count = 3'
  • Using a count from a list that we have in the 'variables.tf' file, example 'count = length(var.azurerm_network_interface__names)'
resource "azurerm_resource_group" "myrg" { 
  name     = "my-resourcegroup" 
  location = "uksouth" 
} 

resource "azurerm_virtual_network" "myvn" { 
  name                = var.azurerm_virtual_network__name
  resource_group_name = azurerm_resource_group.myrg.name
  location            = azurerm_resource_group.myrg.location
  address_space       = ["10.0.0.0/24"]
} 

resource "azurerm_subnet" "mysn" { 
  name                 = var.azurerm_subnet__name 
  resource_group_name  = azurerm_resource_group.myrg.name 
  virtual_network_name = azurerm_virtual_network.myvn.name 
  address_prefixes     = ["10.0.0.0/28"] 
} 

resource "azurerm_public_ip" "mypi" { 
  count               = 3
  name                = "my-publicip-${count.index + 1}"
  location            = "uksouth" 
  resource_group_name = azurerm_resource_group.myrg.name 
  allocation_method   = "Dynamic" 
  sku                 = "Basic" 
} 

resource "azurerm_network_interface" "myni"   { 
  count               = length(var.azurerm_network_interface__names)
  name                = var.azurerm_network_interface__names[count.index]
  location            = "uksouth" 
  resource_group_name = azurerm_resource_group.myrg.name 

  ip_configuration { 
    name                          = "ipconfig-main" 
    subnet_id                     = azurerm_subnet.mysn.id 
    private_ip_address_allocation = "Static" 
    private_ip_address            = var.private_ips[count.index]
    public_ip_address_id          = azurerm_public_ip.mypi[count.index].id
  } 
} 

resource "azurerm_linux_virtual_machine" "mylvm" {
  count                 = length(var.azurerm_linux_virtual_machine__names)
  name                  = var.azurerm_linux_virtual_machine__names[count.index]
  location              = azurerm_resource_group.myrg.location
  resource_group_name   = azurerm_resource_group.myrg.name
  network_interface_ids = [azurerm_network_interface.myni[count.index].id]
  size                  = "Standard_DS1_v2"
  admin_username        = var.username

  admin_ssh_key {
    username   = var.username
    public_key = file("id_rsa_operatorinfra.pub")
  }

  os_disk {
    name                 = "hdd${count.index + 1}"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  tags = {
    environment = "env"
  }

}

In the 'variables.tf' file, lists of values ​​for the private IPs are defined, and for example for the names of the servers:

variable "azurerm_virtual_network__name" {
  type    = string
  default = "my-resourcegroup"
}

variable "azurerm_subnet__name" {
  type    = string
  default = "my-subnet"
}

variable "azurerm_network_interface__names" {
  type    = list(string)
  default = ["my-ni-1", "my-ni-2", "my-ni-3"]
}

variable "private_ips" {
  type    = list(string)
  default = ["10.0.0.9", "10.0.0.10", "10.0.0.11"]
}

variable "azurerm_linux_virtual_machine__names" {
  type    = list(string)
  default = ["my-lvm-1-pele" , "my-lvm-2-romario", "my-lvm-3En -ronaldo"]
}


variable "username" {
  type    = string
  default = "operatorinfra"
}


In 'output.tf' we show the private IPs and public IPs of the three servers in different ways: with a * to show the data from a list of resources (we do it in two different ways as well) or by specifying the 'index' of a specific list of resources:

output "azurerm_private_ip__ip_address_vms" {
  value = "${azurerm_network_interface.myni.*.private_ip_address}"
  description = "IP private all vms"
}

output "azurerm_public_ip__ip_address_vm1" {
  value = azurerm_public_ip.mypi[0].ip_address
  description = "IP public vm1"
}

output "azurerm_public_ip__ip_address_vm2" {
  value = azurerm_public_ip.mypi[1].ip_address
  description = "IP public vm2"
}

output "azurerm_public_ip__ip_address_vm3" {
  value = azurerm_public_ip.mypi[2].ip_address
  description = "IP public vm3"
}

output "azurerm_public_ip__ip_address_vms" {
  value = azurerm_public_ip.mypi[*].ip_address
  description = "IP public all vms"
}

Once we have all the code assembled, it is available at:

https://github.com/jlsvfeitam/terraform-azure-linux-three-servers-creation


, we execute the following command sequence in the project directory where these four files that are detailed in are located:

terraform - first steps: creating a linux virtual server en Azure


Note: do not forget to generate the key pair to be able to authenticate to the three servers.

terraform init
terraform validate
terraform apply -auto-approve


And with this, the three servers must have been created, each one with its private IP and its public IP, the subnet and the network, all within the 'resource group'.


The ouput of the 'terraform apply -auto-approve' command should give us the information of the creation of all the components and the private IPs at the end, the public IPs are not provided:

...
azurerm_linux_virtual_machine.mylvm[0]: Creation complete after 48s [id=/subscriptions/aad4e527-3ef6-4378-a599-61f803834bce/resourceGroups/my-resourcegroup/providers/Microsoft.Compute/virtualMachines/my-lvm-1-pele]
azurerm_linux_virtual_machine.mylvm[1]: Creation complete after 48s [id=/subscriptions/aad4e527-3ef6-4378-a599-61f803834bce/resourceGroups/my-resourcegroup/providers/Microsoft.Compute/virtualMachines/my-lvm-2-romario]

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

Outputs:

azurerm_private_ip__ip_address_vms = [
  "10.0.0.9",
  "10.0.0.10",
  "10.0.0.11",
]
azurerm_public_ip__ip_address_vm1 = ""
azurerm_public_ip__ip_address_vm2 = ""
azurerm_public_ip__ip_address_vm3 = ""
azurerm_public_ip__ip_address_vms = [
  "",
  "",
  "",
]


To see the public IPs you must execute:

terraform apply -auto-approve -refresh-only


And returns the public IPs:

...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

azurerm_private_ip__ip_address_vms = [
  "10.0.0.9",
  "10.0.0.10",
  "10.0.0.11",
]
azurerm_public_ip__ip_address_vm1 = "13.87.74.40"
azurerm_public_ip__ip_address_vm2 = "13.87.74.52"
azurerm_public_ip__ip_address_vm3 = "20.108.41.225"
azurerm_public_ip__ip_address_vms = [
  "13.87.74.40",
  "13.87.74.52",
  "20.108.41.225",
]


With the public IPs and the created private key you can connect to the servers with:

ssh -i id_rsa_operatorinfra operatorinfra@13.87.74.40
[operatorfeitam@dev08 terraform-azure-linux-three-servers-creation]$ ssh -i id_rsa_operatorinfra operatorinfra@13.87.74.40
The authenticity of host '13.87.74.40 (13.87.74.40)' can't be established.
ECDSA key fingerprint is SHA256:GnN4rzuPaX1s0CntRVas6jyl3MKM4R32N3yDb8K/Vf8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '13.87.74.40' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-1104-azure x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Thu Mar 23 21:36:14 UTC 2023

  System load:  0.03              Processes:           111
  Usage of /:   4.5% of 28.89GB   Users logged in:     0
  Memory usage: 5%                IP address for eth0: 10.0.0.9
  Swap usage:   0%

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

operatorinfra@my-lvm-1-pele:~$ 


Don't forget to destroy everything with:

terraform destroy -auto-approve

or from the Azure console