Managing Terraform Drift in Azure: A Step-by-Step Guide to Sync Resources

·

4 min read

Cover Image for Managing Terraform Drift in Azure: A Step-by-Step Guide to Sync Resources

Syncing Terraform with Azure: Handling Manual Changes to Resources

Terraform is a powerful Infrastructure-as-Code (IaC) tool that allows you to manage your cloud infrastructure declaratively. However, scenarios can arise where resources created with Terraform are manually modified in the Azure Portal or via other means. This can lead to a mismatch, or "drift," between Terraform’s state and the actual infrastructure.

In this blog, we’ll explore how to handle such situations effectively. Let’s consider a scenario where a Virtual Machine (VM) and a Network Security Group (NSG) were created using Terraform but were later manually modified in Azure. For example, additional rules were added to the NSG.

Infrastructure Deployed:

I deployed Linux VM and associated nsg to the VM-Nic.

resource "azurerm_virtual_network" "vnet-clouddevinsights" {
  name                = var.virtual_network_name
  address_space       = var.address_space
  location            = var.resource_group_location
  resource_group_name = var.resource_group_name
}

resource "azurerm_subnet" "vnet-clouddevinsights-subnet" {
  name                 = var.subnet_name
  resource_group_name  = azurerm_resource_group.clouddevinsights.name
  virtual_network_name = azurerm_virtual_network.vnet-clouddevinsights.name
  address_prefixes     = var.subnet_address_prefix
}

resource "azurerm_network_security_group" "nsg-clouddevinsights-nsg" {
  name                = var.network_security_group_name
  location            = var.resource_group_location
  resource_group_name = azurerm_resource_group.clouddevinsights.name

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

  security_rule {
    name                       = "Allow-HTTP"
    priority                   = 1002
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "Allow-HTTPS"
    priority                   = 1003
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_interface" "vm-nic" {
  name                = var.vm-nic-name
  location            = azurerm_resource_group.clouddevinsights.location
  resource_group_name = azurerm_resource_group.clouddevinsights.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.vnet-clouddevinsights-subnet.id
    private_ip_address_allocation = "Dynamic"

  }

}

resource "azurerm_network_interface_security_group_association" "nsg-association" {
  network_interface_id      = azurerm_network_interface.vm-nic.id
  network_security_group_id = azurerm_network_security_group.nsg-clouddevinsights-nsg.id
}

resource "azurerm_linux_virtual_machine" "linux-vm" {
  name                = var.vm-name
  resource_group_name = azurerm_resource_group.clouddevinsights.name
  location            = var.resource_group_location
  size                = "Standard_B1s"
  admin_username      = "adminuser"
  admin_password = "Password1234!"
  disable_password_authentication = "false"

  network_interface_ids = [
    azurerm_network_interface.vm-nic.id,
  ]

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

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

Make Changes in Azure:

I will add a NSG rule manually using Azure Portal. I will allow all inbound traffic from internet.


Steps to Sync Terraform with Azure

Step 1: Update Terraform’s State File

Terraform’s state file does not automatically reflect changes made directly in Azure. To synchronize the state file with the actual infrastructure, use the following command:

terraform refresh

This command fetches the latest state of the resources from Azure and updates the local state file. For example, if you added new rules to the NSG or changed the VM size, these changes will now be reflected in Terraform’s state.


Step 2: Detect Drift Using terraform plan

After refreshing the state, run the terraform plan command to identify any differences between the actual resources in Azure and the desired configuration defined in your .tf files:

terraform plan

Terraform will analyze the current state and the configuration files to detect any drift. It will display a plan of actions needed to bring the infrastructure back in line with the desired state. For example, it might show that the VM size or NSG rules differ from the configuration. Please see the image below to see the drift.


Step 3: Apply Changes to Sync Resources

If drift is detected, you can apply the necessary changes to align the resources with your Terraform configuration. Use the following command:

terraform apply

Terraform will prompt you to confirm the changes. Once confirmed, it will update the Azure resources to match the desired configuration. For example, it might:

  • Remove any manually added rules in the NSG that are not in the Terraform configuration.

Special Case: Both Terraform and Azure Modify Resources

If both Terraform and Azure have modified the same resources, it’s crucial to ensure that Terraform’s state file is up-to-date before applying changes. Here’s why:

  1. Terraform Updates the State File: When Terraform applies changes, it updates the state file to reflect the new state of the resources.

  2. Manual Changes in Azure: If resources are manually modified after Terraform’s state file has been updated, Terraform may overwrite those changes during the next apply operation.

To avoid unintentional overwrites:

  • Always run terraform refresh and terraform plan before applying changes.

  • Communicate with your team to establish clear guidelines for managing resources.


Key Takeaways

  • Avoid Manual Changes: The best practice is to avoid manual changes to resources managed by Terraform. This ensures consistency and reduces the risk of drift.

  • Refresh State Regularly: Use terraform refresh to keep the state file in sync with the actual infrastructure.

  • Detect and Resolve Drift: Use terraform plan to detect drift and terraform apply to resolve it.

  • Establish Governance: Set clear policies to manage resources and avoid conflicts between manual changes and Terraform.

By following these steps, you can ensure that your infrastructure remains consistent, predictable, and aligned with your Terraform configurations. Handling drift effectively is a critical skill for any cloud professional, and it ensures that your IaC workflows remain robust and reliable.