Featured image of post Your First Terraform Project on Azure: Step-by-Step Guide

Your First Terraform Project on Azure: Step-by-Step Guide

Learn how to create, modify, and destroy Azure resources using Terraform in this comprehensive guide. Perfect for beginners, this step-by-step tutorial covers the full lifecycle of infrastructure as code on Azure.

Introduction

Hey there Everyone! 🌿 Welcome back to our Terraform on Azure series. If you’ve been following along, you’re in for a treat today. We’re about to embark on an exciting journey – creating your very first full-fledged Terraform project on Azure!

Where We’ve Been

Let’s quickly recap our adventure so far:

  1. In our first article, we explored the fundamentals of Terraform and why it’s such a game-changer for managing Azure infrastructure.
  2. In the second article, we got our hands dirty setting up our local Terraform environment, complete with Azure CLI, Terraform installation, and a shiny VS Code setup.

If you haven’t read these articles yet, no worries! You might want to give them a quick squiz to make sure you’re all set up and ready to go.

What We’re Building Today

Today, we’re taking off the training wheels and building a real Azure infrastructure using Terraform. We’re not just talking about a lone resource group anymore – we’re going full steam ahead! πŸš‚

Here’s a sneak peek of what we’ll be creating:

  • A Resource Group to keep everything tidy
  • A Virtual Network to provide connectivity
  • A Subnet to organize our network
  • A Network Security Group to keep things locked down
  • And the piΓ¨ce de rΓ©sistance – a Virtual Machine to run our workloads

By the end of this guide, you’ll have a fully functional Azure environment, all created and managed with Terraform. How choice is that?

What You’ll Learn

This hands-on project will teach you:

  • How to plan your Azure infrastructure before writing any code
  • The art of defining multiple, interconnected Azure resources in Terraform
  • How to make your configuration flexible (we’ll dip our toes into variables, but don’t worry, we won’t go too deep just yet)
  • The full Terraform workflow – from initialization to destruction
  • How to verify your Azure resources and make changes to your infrastructure

Why This Matters

Creating a multi-resource project is a big step up from managing single resources. It’s like going from building with blocks to constructing a full-on Lego city! This project will give you a taste of real-world infrastructure management, preparing you for more complex scenarios you’ll encounter in your cloud journey.

So, are you ready to level up your Terraform skills and breathe life into your first Azure project? Grab your favorite beverage β˜•, fire up your code editor, and let’s dive in!

Planning Our Azure Infrastructure

Before we start slinging Terraform code, let’s take a moment to plan out our Azure infrastructure. As the saying goes, “Measure twice, cut once” – or in our case, “Plan thoroughly, Terraform once!” πŸ“βœ¨

Our Infrastructure Blueprint

We’re going to create a basic but realistic Azure environment. Here’s what we’re aiming for:

  1. Resource Group πŸ“¦

    • Think of this as a container for all our Azure resources. It helps us organize and manage related resources as a unit.
  2. Virtual Network (VNet) 🌐

    • This is our own private network in Azure. It’s like setting up your own little internet within the Azure cloud.
  3. Subnet πŸ”Ή

    • A subnet is a range of IP addresses in your VNet. We’ll use this to organize and manage network traffic.
  4. Network Security Group (NSG) πŸ›‘οΈ

    • This is our virtual firewall. It controls inbound and outbound traffic, keeping our resources safe.
  5. Virtual Machine (VM) πŸ’»

    • Finally, we’ll create a VM where we can run our applications.

How These Components Interact

Let’s break down how these pieces fit together:

  1. Everything lives inside our Resource Group. It’s like a folder that keeps all our Azure resources neatly organized.

  2. The Virtual Network provides the networking foundation. It’s where all our network-related resources will reside.

  3. We’ll create a Subnet within our VNet. This gives us a specific range of IP addresses to use for our resources.

  4. The Network Security Group will be associated with our subnet. It acts as a gatekeeper, controlling what traffic is allowed in and out of our subnet.

  5. Finally, our Virtual Machine will be placed in the subnet and protected by the NSG. It will have a network interface that connects it to the VNet, allowing it to communicate with other resources and the internet (if we allow it).

Here’s a simple diagram of how it all fits together:

1
2
3
4
5
6
7
8
9
Resource Group
β”‚
└─── Virtual Network
    β”‚
    └─── Subnet
        β”‚
        β”œβ”€β”€β”€ Network Security Group
        β”‚
        └─── Virtual Machine

Why This Structure?

This setup gives us a solid, secure foundation for running applications in Azure:

  • The Resource Group helps with organization and management.
  • The VNet and Subnet provide network isolation.
  • The NSG adds a layer of security.
  • The VM gives us a place to run our applications.

It’s a common pattern in Azure and a great starting point for more complex infrastructures.

Planning for Terraform

As we plan our Terraform configuration, we’ll need to consider:

  • The names and locations for our resources
  • The address space for our VNet and Subnet
  • The inbound and outbound rules for our NSG
  • The size and OS for our VM

Don’t worry if this seems like a lot – we’ll tackle each piece step by step in our Terraform configuration.

Now that we have our blueprint, are you ready to start building? In the next section, we’ll set up our Terraform configuration and start bringing this plan to life! Let’s get cracking! πŸ—οΈ

Setting Up Our Terraform Configuration

Alright, team! It’s time to roll up our sleeves and start setting up our Terraform configuration. We’re going to create a new project directory and initialize our Terraform workspace. Let’s dive in! πŸŠβ€β™‚οΈ

Creating the Project Directory

First things first, let’s create a new directory for our project:

  1. Open your terminal or command prompt.
  2. Navigate to where you want to create your project.
  3. Create a new directory and move into it:
1
2
mkdir azure-terraform-project
cd azure-terraform-project

Good on ya! You’ve just created a home for your Terraform project. 🏠

Creating Our Main Configuration File

Now, let’s create our main Terraform configuration file:

  1. In your project directory, create a new file named main.tf.
  2. Open this file in your favorite text editor (remember that snazzy VS Code setup we did earlier?).

Configuring the Azure Provider

In our main.tf file, we’ll start by configuring the Azure provider. This tells Terraform that we’re working with Azure and sets up some basic parameters.

Add the following code to your main.tf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.104"
    }
  }
}

provider "azurerm" {
  features {}
}

# We'll define our Azure resources here

Let’s break this down:

  • The terraform block specifies that we’re using the Azure Resource Manager (azurerm) provider.
  • The provider block configures the Azure provider. We’re using a simple configuration here, but you can add more options as needed.

Initializing Our Terraform Working Directory

Now that we have our basic configuration set up, it’s time to initialize our Terraform working directory:

  1. In your terminal, make sure you’re in your project directory.
  2. Run the following command:
1
terraform init

This command does a few important things:

  • Downloads the Azure provider plugin
  • Sets up the directory for use with Terraform
  • Creates a .terraform directory to store the provider plugin and other necessary files

If everything goes well, you should see a message saying “Terraform has been successfully initialized!”

Congratulations! πŸŽ‰ You’ve just set up and initialized your Terraform configuration for Azure. We’re ready to start defining our resources.

In the next section, we’ll start adding the Azure resources we planned earlier to our Terraform configuration. We’ll also introduce some basic ways to make our configuration more flexible, including specifying our deployment region. Ready to see your infrastructure start taking shape? Let’s keep the momentum going! πŸš€

Defining Azure Resources in Terraform

Now that we’ve got our Terraform configuration initialized, it’s time to breathe life into our Azure infrastructure. We’re going to define each of the resources we planned earlier, right here in our main.tf file. Exciting stuff!

Resource Blocks in Terraform

In Terraform, we define Azure resources using resource blocks. The basic structure looks like this:

1
2
3
resource "type" "name" {
  # Resource configuration goes here
}
  • type is the type of Azure resource (like azurerm_resource_group)
  • name is a unique identifier for this resource in your Terraform config

Let’s get cracking and define our resources one by one!

1. Resource Group

First up, let’s create our resource group. Remember, this is like a container for all our other Azure resources. Add this to your main.tf:

1
2
3
4
resource "azurerm_resource_group" "rg" {
  name     = "myTFResourceGroup"
  location = "Australia East"
}

Here, we’re creating a resource group named “myTFResourceGroup” in the Australia East region. Choice location, mate! 🦘

2. Virtual Network (VNet)

Next, let’s set up our virtual network. This will be our private network in Azure. Add this after your resource group definition:

1
2
3
4
5
6
resource "azurerm_virtual_network" "vnet" {
  name                = "myTFVNet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

Notice how we’re referencing the resource group we just created? That’s the power of Terraform – resources can reference each other!

3. Subnet

Now, let’s carve out a subnet within our VNet:

1
2
3
4
5
6
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"]
}

This creates a subnet with a range of IP addresses for our resources to use.

4. Network Security Group (NSG)

Time to add some security! Let’s create a network security group:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
resource "azurerm_network_security_group" "nsg" {
  name                = "myTFNSG"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

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

This NSG allows inbound RDP traffic (port 3389). In a real-world scenario, you’d want to restrict the source address prefix for better security.

5. Network Interface Card (NIC)

Before we create our VM, we need to set up a network interface for it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
resource "azurerm_network_interface" "nic" {
  name                = "myTFNIC"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
  }
}

This NIC will allow our VM to communicate on the network.

6. Virtual Machine

Last but not least, let’s create our Windows virtual machine:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
resource "azurerm_windows_virtual_machine" "vm" {
  name                = "myTFVM"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  size                = "Standard_F2"
  admin_username      = "adminuser"
  admin_password      = "P@$$w0rd1234!"
  network_interface_ids = [
    azurerm_network_interface.nic.id,
  ]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }
}

Note: In a real-world scenario, you’d want to use a more secure method for handling passwords, like using Azure Key Vault or environment variables. We’re using a plain text password here for simplicity, but remember - never commit passwords to version control!

Wahoo! We’ve now defined all our planned Azure resources in Terraform. πŸŽ‰

In the next section, we’ll look at ways to make our configuration more flexible and reusable. We’re making great progress, team! Keep up the great work! πŸ’ͺ

Making Our Configuration Flexible

We’ve got our resources defined, but let’s take it up a notch. It’s time to make our configuration more flexible and reusable. We’re going to dip our toes into the world of Terraform variables. Don’t worry, we won’t go too deep – we’re just getting a taste today!

Why Use Variables?

Imagine you want to create similar infrastructure in different environments, or you need to change a value that’s used in multiple places. Without variables, you’d have to manually update each instance. That’s not very efficient, is it? Variables help us avoid this hassle.

Introducing Variables

Let’s start by creating a new file called variables.tf in the same directory as our main.tf. This is where we’ll define our variables.

Open variables.tf and add the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
variable "resource_group_name" {
  default = "myTFResourceGroup"
}

variable "location" {
  default = "Australia East"
}

variable "vnet_address_space" {
  default = ["10.0.0.0/16"]
}

variable "subnet_address_prefix" {
  default = ["10.0.1.0/24"]
}

variable "vm_size" {
  default = "Standard_F2"
}

These variables define default values for some of the parameters we’ve been using in our configuration.

Using Variables in Our Configuration

Now, let’s update our main.tf to use these variables. We’ll replace hard-coded values with references to our variables.

Update your resource group definition in main.tf:

1
2
3
4
resource "azurerm_resource_group" "rg" {
  name     = var.resource_group_name
  location = var.location
}

Do the same for the virtual network:

1
2
3
4
5
6
resource "azurerm_virtual_network" "vnet" {
  name                = "myTFVNet"
  address_space       = var.vnet_address_space
  location            = var.location
  resource_group_name = var.resource_group_name
}

And for the subnet:

1
2
3
4
5
6
resource "azurerm_subnet" "subnet" {
  name                 = "myTFSubnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = var.subnet_address_prefix
}

Finally, update the VM size in the virtual machine resource:

1
2
3
4
5
resource "azurerm_windows_virtual_machine" "vm" {
  # ... other configuration ...
  size                = var.vm_size
  # ... rest of the configuration ...
}

The Benefits of Using Variables

By using variables, we’ve made our configuration more flexible:

  1. Easy Updates: If we need to change the resource group name or location, we only need to update it in one place.
  2. Reusability: We can now easily use this configuration for different environments by providing different variable values.
  3. Readability: Our main configuration is now cleaner and easier to understand.

A Sneak Peek at Variable Overrides

While we’re using default values here, Terraform allows you to override these values when you run your commands. For example:

1
terraform apply -var="resource_group_name=myNewRG" -var="location=Australia Southeast"

This would use the new values instead of the defaults. Neat, eh?

We’ve just scratched the surface of variables here. There’s a lot more to learn, but don’t worry – we’ll dive deeper in a future article dedicated to Terraform variables.

Great job, team! πŸŽ‰ We’ve added flexibility to our configuration. In the next section, we’ll look at creating output values to easily retrieve important information about our infrastructure. Ready to keep rolling? Let’s go! πŸš€

Creating Output Values

We’re making great progress. Now, let’s talk about a crucial feature of Terraform: output values. These are especially important for retrieving information that isn’t known until after Terraform has done its work.

Why Outputs Are Essential

Imagine this scenario: You’ve set up a Virtual Machine, but you don’t know what IP address it will be assigned until it’s actually created. That’s where outputs come in handy! They allow us to capture and display information that’s only available after Terraform has finished applying the configuration.

Outputs are super useful for:

  1. Retrieving values that are generated during the creation of resources (like IP addresses, generated names, etc.)
  2. Passing information to other parts of your infrastructure or to other systems
  3. Providing data for scripts or other tools that might use your Terraform configuration

Think of outputs as Terraform’s way of saying, “Here’s what I’ve created for you!” πŸ“’

Defining Output Values

Let’s create a new file called outputs.tf in the same directory as our main.tf and variables.tf. We’ll define some useful outputs, including one for that dynamically assigned IP address we talked about.

Open outputs.tf and add the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
output "resource_group_name" {
  value = azurerm_resource_group.rg.name
}

output "virtual_machine_name" {
  value = azurerm_windows_virtual_machine.vm.name
}

output "virtual_machine_private_ip_address" {
  value = azurerm_network_interface.nic.private_ip_address
}

Let’s break this down:

  1. We’re outputting the name of our resource group.
  2. We’re outputting the name of our virtual machine.
  3. Most importantly, we’re outputting the private IP address of our VM’s network interface. This value isn’t known until Azure assigns it during the creation of the VM!

Why That Last Output is So Important

The virtual_machine_private_ip_address output is capturing a value that we couldn’t have known beforehand. Azure dynamically assigns this IP address when it creates the network interface for our VM. By defining this as an output, we’re telling Terraform to fetch this value once it’s available and make it easily accessible to us.

This is incredibly useful in real-world scenarios. For example:

  • You might need this IP address to configure other resources that need to communicate with this VM.
  • You could use this in a script that automatically updates DNS records with the new VM’s IP.
  • It could be used in subsequent Terraform runs or other automation tools that need to know how to reach this VM.

Viewing and Using Outputs

After you run terraform apply, Terraform will display the outputs at the end of its run. You can also view the outputs at any time by running:

1
terraform output

Or for a specific output:

1
terraform output virtual_machine_private_ip_address

In automation scenarios, you could capture these outputs and use them as inputs for other processes. For instance, a CI/CD pipeline might use the VM’s IP address to deploy an application to it after Terraform has finished creating the infrastructure.

By using outputs, especially for values that are only known after resource creation, we’re making our Terraform configuration more powerful and enabling smoother automation and integration with other systems. It’s like leaving breadcrumbs for our future selves or other parts of our infrastructure to follow! 🍞

Reviewing Our Terraform Configuration

We’ve come a long way in building our Azure infrastructure with Terraform. Now, let’s take a step back and look at the big picture. We’ll review all the files we’ve created and see how they work together to define our infrastructure.

Our Terraform File Structure

First, let’s recap the files we’ve created:

  1. main.tf: Our primary configuration file
  2. variables.tf: Where we define our input variables
  3. outputs.tf: Where we specify our output values

This structure helps keep our code organized and easy to maintain. Let’s dive into each file:

main.tf: The Heart of Our Configuration

Our main.tf file is where the magic happens. Here’s a quick rundown of what we’ve defined:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# Provider configuration
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 2.65"
    }
  }
}

provider "azurerm" {
  features {}
}

# Resource Group
resource "azurerm_resource_group" "rg" {
  name     = var.resource_group_name
  location = var.location
}

# Virtual Network
resource "azurerm_virtual_network" "vnet" {
  name                = "myTFVNet"
  address_space       = var.vnet_address_space
  location            = var.location
  resource_group_name = var.resource_group_name
}

# Subnet
resource "azurerm_subnet" "subnet" {
  name                 = "myTFSubnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = var.subnet_address_prefix
}

# Network Security Group
resource "azurerm_network_security_group" "nsg" {
  name                = "myTFNSG"
  location            = var.location
  resource_group_name = var.resource_group_name

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

# Network Interface
resource "azurerm_network_interface" "nic" {
  name                = "myTFNIC"
  location            = var.location
  resource_group_name = var.resource_group_name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
  }
}

# Virtual Machine
resource "azurerm_windows_virtual_machine" "vm" {
  name                = "myTFVM"
  resource_group_name = var.resource_group_name
  location            = var.location
  size                = var.vm_size
  admin_username      = "adminuser"
  admin_password      = "P@$$w0rd1234!"
  network_interface_ids = [
    azurerm_network_interface.nic.id,
  ]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }
}

This file defines all our Azure resources, from the resource group to the virtual machine. Notice how we’re using variables (var.something) throughout the configuration. This makes our code more flexible and reusable.

variables.tf: Defining Our Input Variables

In variables.tf, we’ve defined variables that make our configuration flexible:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
variable "resource_group_name" {
  default = "myTFResourceGroup"
}

variable "location" {
  default = "Australia East"
}

variable "vnet_address_space" {
  default = ["10.0.0.0/16"]
}

variable "subnet_address_prefix" {
  default = ["10.0.1.0/24"]
}

variable "vm_size" {
  default = "Standard_F2"
}

These variables allow us to easily change key aspects of our infrastructure without modifying the main configuration.

outputs.tf: Specifying Our Output Values

Finally, in outputs.tf, we’ve defined outputs to easily retrieve important information:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
output "resource_group_name" {
  value = azurerm_resource_group.rg.name
}

output "virtual_machine_name" {
  value = azurerm_windows_virtual_machine.vm.name
}

output "virtual_machine_private_ip_address" {
  value = azurerm_network_interface.nic.private_ip_address
}

These outputs will provide us with key information after Terraform creates our resources, including that all-important dynamically assigned private IP address.

How It All Fits Together

  1. Terraform reads the configuration in main.tf.
  2. It uses the values from variables.tf (or values we provide at runtime) to fill in the blanks.
  3. It determines the required actions to create or modify the infrastructure.
  4. After applying the configuration, it presents the information we’ve requested in outputs.tf.

This structure allows us to maintain a clean, organized, and flexible Terraform configuration. We can easily modify variables to create different environments, and we have easy access to important information about our infrastructure through outputs.

Awesome work, team! πŸŽ‰ We’ve built a robust, flexible Terraform configuration for our Azure infrastructure. In the next section, we’ll learn how to initialize our Terraform project and validate our configuration. Ready to bring our infrastructure to life? Let’s go! πŸš€

Initializing and Validating Our Terraform Project

Now that we’ve got our configuration files all sorted, it’s time to take our first steps towards bringing our infrastructure to life. We’ll start by initializing our Terraform project and then validate our configuration to make sure everything is in great shape.

Initializing Our Terraform Project

First things first, we need to initialize our Terraform working directory. This step downloads the necessary provider plugins and sets up the backend. It’s like prepping our kitchen before we start cooking!

  1. Open your terminal or command prompt.
  2. Navigate to the directory containing your Terraform files.
  3. Run the following command:
1
terraform init

You should see output similar to this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 3.104"...
- Installing hashicorp/azurerm v3.104.0...
- Installed hashicorp/azurerm v3.104.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

What just happened? πŸ€”

  • Terraform downloaded the Azure provider plugin.
  • It set up the backend (where Terraform stores its state).
  • It created a .terraform directory to store these components.

Think of this step as Terraform unpacking its toolbox and getting ready to work!

Validating Our Terraform Configuration

Now that we’re initialized, let’s make sure our configuration is valid. Terraform provides a handy command for this:

1
terraform validate

This command checks your configuration files for syntax errors and internal consistency. It’s like a spell-check for your Terraform code!

If everything is correct, you should see:

1
Success! The configuration is valid.

Wahoo! πŸŽ‰ Your configuration passed the check.

If there are any issues, Terraform will provide helpful error messages. For example:

1
2
3
4
5
6
Error: Missing name for resource

  on main.tf line 15:
  15: resource "azurerm_resource_group" {

All resource blocks must have 2 labels (type, name).

This error would indicate that we forgot to give a name to our resource group resource. Terraform’s error messages are usually quite clear about what’s wrong and where to find the issue.

What If There Are Errors?

Don’t worry if you see errors – they’re a normal part of the development process. Here’s what to do:

  1. Read the error message carefully. It usually points to the exact file and line where the problem is.
  2. Check that file and line for typos or missing brackets.
  3. Make sure all your resource blocks have both a type and a name.
  4. Verify that all your variables are defined and used correctly.
  5. After making changes, run terraform validate again.

Remember, it’s much better to catch these errors now than when we try to apply our configuration!

Best Practices

  • Run terraform init whenever you add new providers or modules to your configuration.
  • Use terraform validate frequently as you write your configuration. It can save you a lot of time by catching errors early.
  • If you’re using version control (which you should be!), run terraform validate before committing your changes. It’s a great way to catch errors before they make it into your repository.

Great work, team! πŸ‘ We’ve initialized our project and validated our configuration. We’re now ready to take the next big step – planning our infrastructure deployment. Excited to see what Terraform is going to do? Let’s keep rolling! πŸš€

Planning Our Infrastructure Deployment

Now that we’ve initialized our project and validated our configuration, it’s time for one of the most exciting parts of working with Terraform: planning our infrastructure deployment. This is where we get to see a preview of what Terraform is going to do before it actually does it. It’s like a dress rehearsal for our infrastructure! 🎭

What is Terraform Plan?

The terraform plan command is a crucial step in the Terraform workflow. It does a few important things:

  1. It checks your configuration against the current state of your infrastructure.
  2. It determines what changes need to be made to achieve the desired state described in your configuration.
  3. It presents a detailed plan of what actions Terraform will take when you apply your configuration.

Think of it as Terraform saying, “Here’s what I’m planning to do. Does this look good to you?” πŸ€”

Running Terraform Plan

Let’s run the plan command and see what Terraform has in store for us:

  1. Make sure you’re in the directory containing your Terraform files.
  2. Run the following command:
1
terraform plan

Interpreting the Plan Output

The output of terraform plan can be quite lengthy, especially for larger infrastructures. Here’s a breakdown of what you’ll see:

  1. Refreshing State: Terraform checks the current state of any existing resources.

  2. Planned Changes: This is the main part of the output. It shows what Terraform is planning to do:

    • + means Terraform will create this resource
    • - means Terraform will destroy this resource
    • ~ means Terraform will modify this resource
  3. Plan Summary: At the end, you’ll see a summary of the planned changes.

Here’s an example of what you might see:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Terraform will perform the following actions:

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "australiaeast"
      + name     = "myTFResourceGroup"
    }

  # azurerm_virtual_network.vnet will be created
  + resource "azurerm_virtual_network" "vnet" {
      + address_space       = [
          + "10.0.0.0/16",
        ]
      + guid                = (known after apply)
      + location            = "australiaeast"
      + name                = "myTFVNet"
      + resource_group_name = "myTFResourceGroup"
    }

  # ... (more resources)

Plan: 7 to add, 0 to change, 0 to destroy.

In this example, Terraform is planning to create 7 new resources, including a resource group and a virtual network.

Understanding the Plan

Let’s break down what we’re seeing:

  • The + signs indicate that all these resources will be created.
  • Some values, like id, show “(known after apply)”. This means Terraform can’t know these values until the resources are actually created.
  • The plan shows the exact values that will be used for each resource attribute.

What to Look for in the Plan

When reviewing the plan, pay attention to:

  1. Are all the expected resources there?
  2. Are the resource names and attributes correct?
  3. Are there any unexpected changes or deletions?

The plan is your chance to catch any mistakes before Terraform makes changes to your real infrastructure. It’s like a safety net for your deployment! πŸ₯…

Best Practices

  • Always run terraform plan before applying changes.
  • Review the plan output carefully, especially for large or critical infrastructures.
  • If something doesn’t look right, stop and review your configuration before proceeding.
  • Consider saving the plan output to a file for review or approval in team settings:
    1
    
    terraform plan -out=tfplan
    
    This creates a file named tfplan that you can later apply with terraform apply tfplan.

Beautiful work, team! πŸ‘ We’ve now seen exactly what Terraform is planning to do with our infrastructure. In the next section, we’ll take the exciting step of applying our configuration and watching our Azure resources come to life. Ready to make it happen? Let’s go! πŸš€

Applying Our Terraform Configuration

We’ve planned, we’ve prepared, and now it’s time for the main event - applying our Terraform configuration! This is where the magic happens, where our code transforms into real, living infrastructure in Azure. Excited? Let’s dive in!

What is Terraform Apply?

The terraform apply command is where Terraform takes the plans we’ve made and turns them into reality. It’s like pressing the “Make it so!” button on your infrastructure. πŸš€

Here’s what happens when you run terraform apply:

  1. Terraform refreshes the state to make sure it has the most up-to-date information about your existing infrastructure.
  2. It creates a new plan (just like terraform plan did).
  3. It asks for your confirmation.
  4. If you confirm, it goes ahead and makes the necessary changes to your infrastructure.

Running Terraform Apply

Alright, let’s make it happen:

  1. Make sure you’re in the directory containing your Terraform files.
  2. Run the following command:
1
terraform apply

What to Expect

After running the command, you’ll see output similar to what you saw with terraform plan. Terraform will show you the planned changes and ask for confirmation:

1
2
3
4
5
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

This is your last chance to review the changes before they’re applied. Take a deep breath, double-check that everything looks good, and if you’re ready, type yes and hit Enter.

Watching the Magic Happen

Once you confirm, Terraform will start creating your resources. You’ll see output like this:

1
2
3
4
5
azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 2s [id=/subscriptions/.../resourceGroups/myTFResourceGroup]
azurerm_virtual_network.vnet: Creating...
azurerm_virtual_network.vnet: Creation complete after 5s [id=/subscriptions/.../virtualNetworks/myTFVNet]
...

Terraform creates resources in the order determined by its dependency graph, so you might see some resources being created before others.

Completion and Outputs

Once all resources are created, you’ll see a completion message:

1
2
3
4
5
6
7
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Outputs:

resource_group_name = "myTFResourceGroup"
virtual_machine_name = "myTFVM"
virtual_machine_private_ip_address = "10.0.1.4"

The outputs we defined earlier are displayed here, giving us easy access to important information about our new infrastructure.

What Just Happened?

Let’s take a moment to appreciate what we’ve accomplished:

  1. We’ve created a resource group in Azure.
  2. Inside that resource group, we’ve set up a virtual network with a subnet.
  3. We’ve created a network security group with a rule allowing RDP access.
  4. We’ve provisioned a Windows virtual machine, complete with a network interface.

All of this from just a few configuration files and a single command. How good is that? πŸ‘

Best Practices

  • Always run terraform plan before terraform apply to avoid surprises.
  • In production environments, consider using the -auto-approve flag with caution, or better yet, integrate Terraform with a CI/CD pipeline for controlled, automated deployments.
  • After applying, take a moment to verify your resources in the Azure portal or using Azure CLI.
  • Keep your state file safe - it’s crucial for Terraform to keep track of your infrastructure.

Troubleshooting

If something goes wrong during the apply process, don’t panic! Terraform will output error messages that can help you identify and fix the issue. Common problems include:

  • Insufficient permissions in your Azure subscription
  • Resource name conflicts (if resources already exist)
  • Exceeded Azure quotas or limits

If you encounter any issues, review the error message, make necessary adjustments to your configuration or Azure settings, and try again.

Congratulations, team! πŸŽ‰ You’ve just deployed real infrastructure to Azure using Terraform. Take a moment to let that sink in - you’re now officially infrastructure-as-code practitioners!

In our next section, we’ll verify our newly created Azure resources and make sure everything is set up correctly. Ready to admire your handiwork? Let’s keep going! πŸš€

Verifying Our Azure Resources

We’ve just conjured up some Azure resources using Terraform, and now it’s time to make sure everything appeared exactly as we intended. It’s like checking your work after a spell - always a good idea! Let’s explore two ways to verify our newly created Azure resources.

Method 1: Using the Azure Portal

The Azure Portal provides a visual way to inspect our resources. Here’s how to do it:

  1. Open your web browser and navigate to https://portal.azure.com
  2. Log in with your Azure credentials
  3. In the search bar at the top, type “Resource groups” and select it from the dropdown
  4. Look for the resource group we created (remember, we named it “myTFResourceGroup”)
  5. Click on the resource group to see all the resources inside it

You should see something like this:

  • Resource group: myTFResourceGroup
  • Virtual network: myTFVNet
  • Network security group: myTFNSG
  • Network interface: myTFNIC
  • Virtual machine: myTFVM

Click on each resource to inspect its details. Make sure everything matches what we specified in our Terraform configuration.

Method 2: Using Azure CLI

For those who prefer the command line (and it’s a good skill to have!), we can use Azure CLI to verify our resources. First, make sure you’re logged in:

1
az login

Then, let’s check our resources:

  1. List the resource group:

    1
    
    az group show --name myTFResourceGroup
    
  2. List the virtual network:

    1
    
    az network vnet show --resource-group myTFResourceGroup --name myTFVNet
    
  3. Check the subnet:

    1
    
    az network vnet subnet show --resource-group myTFResourceGroup --vnet-name myTFVNet --name myTFSubnet
    
  4. Inspect the network security group:

    1
    
    az network nsg show --resource-group myTFResourceGroup --name myTFNSG
    
  5. View the virtual machine details:

    1
    
    az vm show --resource-group myTFResourceGroup --name myTFVM
    

These commands will output JSON-formatted information about each resource. It might look a bit overwhelming at first, but you can scan through it to verify key details like names, locations, and configurations.

What to Look For

When verifying your resources, pay attention to:

  1. Resource Names: Do they match what we specified in our Terraform config?
  2. Location: Are all resources in Australia East as we intended?
  3. Virtual Network and Subnet: Are the address spaces correct?
  4. Network Security Group: Is the RDP rule (port 3389) properly configured?
  5. Virtual Machine: Is it the correct size and using the right Windows image?

Terraform State

Remember, Terraform keeps track of the resources it manages in the state file. You can also use Terraform to show resource details:

1
terraform show

This command displays the current state of your infrastructure as Terraform sees it. It’s a great way to cross-reference with what you see in Azure.

What If Something’s Not Right?

If you spot any discrepancies between what you expected and what’s actually in Azure, don’t worry! Here’s what you can do:

  1. Double-check your Terraform configuration files for any mistakes
  2. Run terraform plan again to see if Terraform detects any differences
  3. If needed, make adjustments to your configuration and run terraform apply again

Remember, with infrastructure as code, it’s easy to make changes and keep your infrastructure in the desired state.

Beautiful work, team! πŸ‘ You’ve not only created Azure resources with Terraform but also verified that they’re set up correctly. That’s some top-notch infrastructure management right there!

In our next section, we’ll explore how to make changes to our infrastructure using Terraform. Ready to flex those modification muscles? Let’s keep the momentum going! πŸš€

Making Changes to Our Infrastructure

You’ve created your infrastructure, you’ve verified it’s all there - but what happens when you need to make a change? That’s where the real power of Infrastructure as Code shines. Let’s dive in and see how Terraform handles modifications to our existing setup.

The Scenario

Let’s say our application is becoming more popular (congrats!), and we need to beef up our VM a bit. We’re going to change the VM size from Standard_F2 to Standard_F4, giving us more compute power to handle the increased load.

Modifying Our Terraform Configuration

  1. Open your variables.tf file.
  2. Find the vm_size variable and change its default value:
1
2
3
variable "vm_size" {
  default = "Standard_F4"
}

That’s it! This simple change tells Terraform that we want a bigger VM.

Planning the Changes

Before we apply this change, let’s see what Terraform plans to do:

  1. Save your changes to variables.tf.
  2. In your terminal, run:
1
terraform plan

You should see output similar to this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Terraform will perform the following actions:

  # azurerm_windows_virtual_machine.vm will be updated in-place
  ~ resource "azurerm_windows_virtual_machine" "vm" {
        name                = "myTFVM"
      ~ size                = "Standard_F2" -> "Standard_F4"
        # (other unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

The ~ symbol indicates that Terraform will modify an existing resource rather than create a new one or destroy an old one.

Applying the Changes

If the plan looks good, let’s apply these changes:

1
terraform apply

Terraform will show you the plan again and ask for confirmation. Type yes and hit Enter.

You’ll see output showing Terraform updating the VM:

1
2
3
4
5
azurerm_windows_virtual_machine.vm: Modifying... [id=/subscriptions/.../virtualMachines/myTFVM]
azurerm_windows_virtual_machine.vm: Still modifying... [id=/subscriptions/.../virtualMachines/myTFVM, 10s elapsed]
azurerm_windows_virtual_machine.vm: Modifications complete after 15s [id=/subscriptions/.../virtualMachines/myTFVM]

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

Understanding What Just Happened

Let’s break down what Terraform did:

  1. It compared the desired state (our updated configuration) with the current state (the existing infrastructure).
  2. It identified that only the VM size needed to change.
  3. It updated the VM in place, without affecting other aspects of the VM or other resources.

This is the beauty of Terraform - it makes only the necessary changes to bring your infrastructure in line with your configuration.

Verifying the Change

Let’s double-check that our change took effect:

Using Azure CLI:

1
az vm show --resource-group myTFResourceGroup --name myTFVM --query hardwareProfile.vmSize

You should see “Standard_F4” as the output.

The Power of Infrastructure as Code

What we’ve just done demonstrates the real power of Infrastructure as Code:

  1. Predictability: We saw exactly what would change before applying it.
  2. Minimal Disruption: Only the necessary resource was modified.
  3. Version Control: If we’re using git, this change is now tracked in our repository.
  4. Repeatability: We can make this same change across multiple environments easily.

Best Practices for Making Changes

  1. Always run terraform plan before terraform apply to avoid surprises.
  2. Keep your Terraform configuration in version control to track changes over time.
  3. For larger changes, consider using Terraform workspaces or separate configuration sets for different environments.
  4. Regularly update your Terraform version and provider versions to access new features and bug fixes.

Congratulations, you infrastructure-modifying maestro! πŸŽ‰ You’ve successfully updated your Azure infrastructure using Terraform. This ability to easily and safely make changes is what makes Infrastructure as Code so powerful in managing cloud resources.

In our next and final section, we’ll look at how to clean up our resources when we’re done. Ready to wrap this up? Let’s go! πŸš€

Cleaning Up: Destroying Our Infrastructure

We’ve come to the final stage of our Terraform journey - cleaning up our resources. It’s like leaving a campsite cleaner than you found it, but in the cloud! Let’s learn how to responsibly tear down the infrastructure we’ve built.

Why Clean Up is Important

Cleaning up unused resources is crucial for several reasons:

  1. Cost Management: Azure charges for resources even when they’re not actively used. Deleting unused resources saves money.
  2. Security: Reducing your infrastructure footprint minimizes potential security risks.
  3. Resource Limits: Azure subscriptions have limits on the number of resources you can create. Cleaning up frees up your quota.
  4. Cleanliness: A tidy Azure subscription is easier to manage and understand.

The Terraform Destroy Command

Terraform makes it easy to remove all the resources we’ve created with a single command: terraform destroy. This command does exactly what it sounds like - it destroys all the resources defined in your Terraform configuration.

Here’s how to use it:

  1. Make sure you’re in the directory containing your Terraform files.
  2. Run the following command:
1
terraform destroy

What to Expect

When you run terraform destroy, here’s what happens:

  1. Terraform plans the destruction of resources, similar to how terraform plan works.
  2. It shows you a summary of what will be destroyed.
  3. It asks for your confirmation before proceeding.

You’ll see output like this:

1
2
3
4
5
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: 

This is your last chance to review what’s about to be deleted. Double-check that you’re in the right directory and that you’re okay with destroying these resources.

If you’re sure, type yes and hit Enter.

Watching the Cleanup

Terraform will then proceed to destroy your resources. You’ll see output like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
azurerm_windows_virtual_machine.vm: Destroying... [id=/subscriptions/.../virtualMachines/myTFVM]
azurerm_windows_virtual_machine.vm: Still destroying... [id=/subscriptions/.../virtualMachines/myTFVM, 10s elapsed]
azurerm_windows_virtual_machine.vm: Destruction complete after 45s
azurerm_network_interface.nic: Destroying... [id=/subscriptions/.../networkInterfaces/myTFNIC]
azurerm_network_interface.nic: Destruction complete after 5s
...
azurerm_resource_group.rg: Destroying... [id=/subscriptions/.../resourceGroups/myTFResourceGroup]
azurerm_resource_group.rg: Still destroying... [id=/subscriptions/.../resourceGroups/myTFResourceGroup, 30s elapsed]
azurerm_resource_group.rg: Destruction complete after 45s

Destroy complete! Resources: 7 destroyed.

Terraform destroys resources in the reverse order of their dependencies, ensuring everything is cleaned up properly.

Verifying the Cleanup

After Terraform finishes, it’s a good idea to double-check that everything was removed:

  1. Log into the Azure portal
  2. Look for the resource group we created (“myTFResourceGroup”)
  3. It should no longer exist

You can also use Azure CLI to verify:

1
az group show --name myTFResourceGroup

This should return an error saying the resource group wasn’t found.

Best Practices for Cleanup

  1. Always Review the Plan: Before confirming the destroy operation, carefully review what Terraform plans to delete.
  2. Use Targeted Destroy: If you only want to remove specific resources, you can use terraform destroy -target=RESOURCE_TYPE.NAME for more granular control.
  3. Version Control: Keep your Terraform configurations in version control. Even after destroying resources, you’ll have a record of what was created.
  4. Backup State: Before destroying, consider backing up your Terraform state file. This can be helpful for auditing or if you need to recreate the infrastructure later.

A Note on Terraform State

After destroying your resources, your Terraform state file (terraform.tfstate) will be updated to reflect that no resources are currently managed. If you run terraform show now, you should see no resources listed.

Wrapping Up

Beautiful work πŸ‘ You’ve now completed the full lifecycle of infrastructure management with Terraform:

  1. Writing configuration
  2. Planning changes
  3. Applying configuration
  4. Modifying resources
  5. And finally, cleaning everything up

You’ve got the skills to create, manage, and responsibly remove cloud infrastructure using code. That’s a powerful toolset for any cloud engineer!

Remember, in real-world scenarios, be extra cautious with destroy commands, especially in shared or production environments. Always double-check what you’re about to remove.

Congratulations on completing this Terraform journey! You’re well on your way to becoming a true Infrastructure as Code expert. Keep practicing, keep learning, and most importantly, have fun building amazing things in the cloud! πŸš€πŸŒŸ

Conclusion

Congratulations You’ve just completed your first end-to-end Terraform project on Azure. Let’s take a moment to appreciate how far you’ve come:

  1. You’ve set up a Terraform configuration for Azure resources
  2. Learned how to use variables to make your config flexible
  3. Planned and applied your infrastructure changes
  4. Modified existing resources using Terraform
  5. And responsibly cleaned up your resources

You’ve not just learned about Terraform - you’ve experienced the full lifecycle of infrastructure as code. That’s no small feat!

Key Takeaways

  • Infrastructure as Code makes managing cloud resources more efficient and repeatable
  • Terraform allows you to define, create, modify, and destroy Azure resources using code
  • Always plan before you apply to avoid surprises
  • Cleaning up unused resources is crucial for cost management and security

What’s Next?

But wait, there’s more! πŸŽ‰ As you’ve been working through this tutorial, you might have noticed Terraform referring to something called “state”. What is this mysterious state, and why is it so important?

In our next article, “Understanding Terraform State Management in Azure”, we’ll dive deep into this crucial concept. You’ll learn:

  • What Terraform state is and why it’s essential
  • How Terraform uses state to track your resources
  • Best practices for managing state in Azure
  • How to work with remote state for team collaboration

State management is a key part of working with Terraform, especially in team environments or when dealing with larger, more complex infrastructures. It’s the secret sauce that allows Terraform to know what it’s managing and how to make changes efficiently.

So, pat yourself on the back for a job well done, take a quick break, and when you’re ready, join us for the next exciting chapter in your Terraform journey. The world of infrastructure as code is at your fingertips, and you’re just getting started!

Keep calm and Terraform on! πŸš€πŸ’»πŸŒŸ