Building Azure Infrastructure with Terraform and Configuring It with Ansible
This article explores how to combine Terraform and Ansible to create and configure an Azure environment seamlessly. We'll provision the infrastructure, deploy virtual machines (VMs), and configure them with NGINX using Ansible. The solution is ideal for automating deployments while ensuring consistency and scalability.
Overview of the Project
Tools and Frameworks:
Terraform: Automates infrastructure provisioning.
Ansible: Manages configuration and deployments.
Steps:
Use Terraform to provision:
Virtual Network (VNet).
Subnets.
Virtual Machines with Public IPs.
Secure configurations.
Use Ansible to:
Install NGINX.
Deploy a sample webpage.
Terraform Configuration
1. The vm.tf
File
The vm.tf
file provisions the virtual network, subnets, public IPs, and virtual machines based on the environment (dev, staging, or production). Here's the breakdown:
# Define Resource Group
data "azurerm_resource_group" "test" {
name = "tfmulti"
}
# Define Location Dynamically Based on Environment
locals {
region = var.environment == "prd" ? "East US" : var.environment == "stg" ? "West US" : var.environment == "dev" ? "Central US" : "East US"
}
# Provision Virtual Network
resource "azurerm_virtual_network" "tfvnetmulti" {
name = "${var.environment}-tfvnet"
address_space = ["10.0.0.0/16"]
location = local.region
resource_group_name = data.azurerm_resource_group.test.name
}
# Provision Subnet
resource "azurerm_subnet" "tfvnetsub" {
name = "internal"
resource_group_name = data.azurerm_resource_group.test.name
virtual_network_name = azurerm_virtual_network.tfvnetmulti.name
address_prefixes = ["10.0.2.0/24"]
}
# Provision Public IPs for Each VM
resource "azurerm_public_ip" "testpubid" {
count = var.instance_count
name = "${var.environment}-tflinuxpublicip-${count.index + 1}"
location = azurerm_virtual_network.tfvnetmulti.location
resource_group_name = data.azurerm_resource_group.test.name
allocation_method = "Static"
}
# Provision Network Interfaces
resource "azurerm_network_interface" "testnic" {
count = var.instance_count
name = "${var.environment}-tflinuxnic-${count.index + 1}"
location = azurerm_virtual_network.tfvnetmulti.location
resource_group_name = data.azurerm_resource_group.test.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.tfvnetsub.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.testpubid[count.index].id
}
}
# Provision Virtual Machines
resource "azurerm_linux_virtual_machine" "testvm" {
count = var.instance_count
name = "${var.environment}-tflinuxvm-${count.index + 1}"
resource_group_name = data.azurerm_resource_group.test.name
location = azurerm_virtual_network.tfvnetmulti.location
size = "Standard_F2"
admin_username = "adminuser"
network_interface_ids = [
azurerm_network_interface.testnic[count.index].id,
]
admin_ssh_key {
username = "adminuser"
public_key = file("tf-key.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
tags = {
Environment = var.environment
Project = "TerraformDemo"
}
}
2. The main.tf
File
The main.tf
file uses Terraform modules to define the number of instances and environments:
module "dev_infra" {
source = "./infra"
instance_count = 2
environment = "dev"
}
module "stg_infra" {
source = "./infra"
instance_count = 2
environment = "stg"
}
module "prd_infra" {
source = "./infra"
instance_count = 1
environment = "prd"
}
output "dev_infra_vmpubip" {
value = module.dev_infra.vmpubips
}
output "stg_infra_vmpubip" {
value = module.stg_infra.vmpubips
}
output "prd_infra_vmpubip" {
value = module.prd_infra.vmpubips
}
Ansible Configuration
Once the infrastructure is provisioned, Ansible is used to install and configure NGINX on the VMs.
Tasks File for NGINX Role
# tasks file for nginx-role
---
- name: Install nginx
apt:
name: nginx
state: latest
- name: Enable nginx
service:
name: nginx
enabled: yes
- name: Deploy webpage
copy:
src: index.html
dest: /var/www/html
Automation Workflow
Provision Infrastructure:
Run Terraform to create the infrastructure:
terraform init terraform apply -auto-approve
Extract Terraform Outputs:
Use the script to dynamically update Ansible inventory with public IPs:
#!/bin/bash # Paths to the inventory files INVENTORY_DEV="/Users/snehsrivastava/terraform_practice/ansible/inventories/dev" INVENTORY_STG="/Users/snehsrivastava/terraform_practice/ansible/inventories/stg" INVENTORY_PRD="/Users/snehsrivastava/terraform_practice/ansible/inventories/prd" # Terraform output JSON file TERRAFORM_OUTPUT="terraform_output.json" # Step 1: Fetch Terraform outputs in JSON format terraform output -json > "$TERRAFORM_OUTPUT" if [ $? -ne 0 ]; then echo "Error: Failed to fetch Terraform outputs. Ensure Terraform has been applied successfully." exit 1 fi echo "Terraform outputs saved to $TERRAFORM_OUTPUT" # Step 2: Function to update inventory file generate_inventory() { local env="$1" # Environment (e.g., dev, stg, prd) local inventory_file="$2" # Path to the inventory file # Extract the IP addresses from Terraform outputs ips=$(jq -r ".${env}_infra_vmpubip.value[]" "$TERRAFORM_OUTPUT") # Check if there are any IPs for this environment if [ -z "$ips" ]; then echo "No IPs found for environment: $env" return fi # Start generating the inventory file content echo "[${env}servers]" > "$inventory_file" server_index=1 for ip in $ips; do echo "server${server_index} ansible_host=${ip}" >> "$inventory_file" server_index=$((server_index + 1)) done # Add group variables echo -e "\n[${env}servers:vars]" >> "$inventory_file" echo "ansible_user=adminuser" >> "$inventory_file" echo "ansible_ssh_private_key_file=/Users/snehsrivastava/terraform_practice/secrets/tf-key" >> "$inventory_file" echo "ansible_python_interpreter=/usr/bin/python3" >> "$inventory_file" echo "Updated inventory for $env: $inventory_file" } # Step 3: Update inventory files for each environment generate_inventory "dev" "$INVENTORY_DEV" generate_inventory "stg" "$INVENTORY_STG" generate_inventory "prd" "$INVENTORY_PRD" # Cleanup rm -f "$TERRAFORM_OUTPUT" echo "Terraform output file removed. All inventory files updated successfully."
Run Ansible Playbook:
Configure the VMs:
ansible-playbook -i inventories/dev nginx-playbook.yml
Challenges and Resolutions
Dynamic Inventory:
- Parsing Terraform outputs into Ansible inventory was automated using a Python script integrated into the shell workflow.
Terraform Module Management:
- Separate modules were used for dev, staging, and production environments for better scalability.
Firewall Rules:
- Ensured port 22 (SSH) and port 80 (HTTP) were open in NSGs.
Configuration Drift:
- Ansible was used to standardize configuration across VMs.
Diagram of the Architecture
Terraform-Provisioned Azure Infrastructure:
Virtual Network with Subnets.
Public IPs and Network Interfaces.
Linux VMs tagged by environment.
NGINX Deployment via Ansible:
- Automated installation and configuration on provisioned VMs.
Conclusion: This project demonstrates how Terraform and Ansible can work together to create a highly automated and scalable infrastructure on Azure. Terraform handles the provisioning, while Ansible ensures consistent configurations across environments.
Feel free to explore the GitHub repository for the complete code!