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:
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