In this tutorial, you will learn how Terraform handles multiple resources and their relationships to one another. In the previous examples your configuration has only contained a single resource. Real infrastructure is a diverse collection of interdependent resources. Terraform configurations usually contain many resources.
When you change your infrastructure configuration, Terraform must execute these changes in a specific order determined by resource dependencies.
Implicit dependencies, which Terraform and the Azure provider determine automatically based on the configuration. For example, if a virtual machine (VM) resource references a network interface (NIC), Terraform creates the NIC before the virtual machine.
Explicit dependencies, which you define using the depends_on
meta-argument. Use this when you want to override the default execution plan.
»Create the required network resources
Azure requires the following underlying resources before you can deploy a virtual machine:
- Resource group
- Virtual network
- Subnet
- Network security group
- Network interface
Your deployment will also include a public IP address as well as an explicitly opened port 22 for SSH access.
In your main.tf
file, add the resource block below, which creates a virtual network for your virtual machine.
# Create a virtual network
resource "azurerm_virtual_network" "vnet" {
name = "myTFVnet"
address_space = ["10.0.0.0/16"]
location = "westus2"
resource_group_name = azurerm_resource_group.rg.name
}
To create a new Azure VNet, you have to specify the name of the resource group to contain the vnet. The value of the resource_group_name
attribute is an expression using Terraform interpolation.
Next, observe the value of address_space
. The address is surrounded by square brackets []
. Square brackets define lists. The list is a sequence of comma-separated values. When you see square brackets around an argument, it tells you that the argument accepts more than one value. In this example, we have specified one address space, but you can supply more than one.
»Add a resource with an implicit dependency
Terraform can infer when one resource depends on another by analyzing the resource attributes used in interpolation expressions. Referencing an attribute from a resource as an argument in another resource creates an implicit dependency. Terraform builds a dependency tree to establish the correct order of operations for these dependencies.
In your current configuration, the expression azurerm_resource_group.rg.name
creates the implicit dependency on the azurerm_resource_group
object named rg
.
Terraform's order of operations is not dependent on the resource placement in your configuration file, so if you create these resources in a different order, Terraform will still respect the implicit dependency.
Note on Explicit Dependencies Sometimes there are dependencies between resources that are not visible to Terraform. In these scenarios, use the depends_on meta-argument to explicitly define these dependencies. The depends_on
meta-argument accepts a list of resources.
»Apply your configuration
Copy and paste the remaining configuration blocks below to your main.tf
file. This configuration contains all of the underlying infrastructure for your virtual machine. Notice the dependencies and interpolation used throughout the remaining configuration.
variable "admin_username" {
type = string
description = "Administrator user name for virtual machine"
}
variable "admin_password" {
type = string
description = "Password must meet Azure complexity requirements"
}
# Create subnet
resource "azurerm_subnet" "subnet" {
name = "myTFSubnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
}
# Create public IP
resource "azurerm_public_ip" "publicip" {
name = "myTFPublicIP"
location = "westus2"
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Static"
}
# Create Network Security Group and rule
resource "azurerm_network_security_group" "nsg" {
name = "myTFNSG"
location = "westus2"
resource_group_name = azurerm_resource_group.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 = "*"
}
}
# Create network interface
resource "azurerm_network_interface" "nic" {
name = "myNIC"
location = "westus2"
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "myNICConfg"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "dynamic"
public_ip_address_id = azurerm_public_ip.publicip.id
}
}
# Create a Linux virtual machine
resource "azurerm_virtual_machine" "vm" {
name = "myTFVM"
location = "westus2"
resource_group_name = azurerm_resource_group.rg.name
network_interface_ids = [azurerm_network_interface.nic.id]
vm_size = "Standard_DS1_v2"
storage_os_disk {
name = "myOsDisk"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Premium_LRS"
}
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04.0-LTS"
version = "latest"
}
os_profile {
computer_name = "myTFVM"
admin_username = var.admin_username
admin_password = var.admin_password
}
os_profile_linux_config {
disable_password_authentication = false
}
}
data "azurerm_public_ip" "ip" {
name = azurerm_public_ip.publicip.name
resource_group_name = azurerm_virtual_machine.vm.resource_group_name
depends_on = [azurerm_virtual_machine.vm]
}
This configuration introduces the variable
block for your virtual machine's username and password. You will learn more about input variables and why you should not hardcode passwords in your Terraform configuration in the next tutorial.
Run terraform init
to initialize the working directory. Terraform will download plugins.
$ terraform init
Then, apply the changes.
$ terraform apply
You will be prompted to input a password and username for your virtual machine.
var.admin_password
Password must meet Azure complexity requirements
Enter a value: Password1234!
var.admin_username
Administrator user name for virtual machine
Enter a value: plankton
Terraform will generate a plan and output the proposed changes in your terminal. You will be prompted to approve the action. Type yes
and Terraform will execute those changes.
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
<= read (data resources)
Terraform will perform the following actions:
# data.azurerm_public_ip.ip will be read during apply
# (config refers to values not yet known)
<= data "azurerm_public_ip" "ip" {
+ allocation_method = (known after apply)
+ domain_name_label = (known after apply)
+ fqdn = (known after apply)
+ id = (known after apply)
+ idle_timeout_in_minutes = (known after apply)
+ ip_address = (known after apply)
+ ip_version = (known after apply)
+ location = (known after apply)
+ name = "myTFPublicIP"
+ resource_group_name = "myTFResourceGroup"
## ... Output Truncated ...
Plan: 7 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Congratulations, you have just deployed an Azure virtual machine using infrastructure as code!
»Destroy your configuration
The terraform destroy
command destroys the resources from your current state file. Because the admin username and password variables aren't defined in the configuration, Terraform will prompt you for them.
$ terraform destroy
var.admin_password
Password must meet Azure complexity requirements
Enter a value:
Enter Password1234!
for the password and plankton
for the username.
Enter a value: Password1234!
var.admin_username
Administrator user name for virtual machine
Enter a value: plankton
Terraform will then continue to generate a destruction plan. You will be prompted to approve the action. Type yes
and Terraform will destroy your infrastructure.