Setting up Azure Virtual Machines with Terraform
Learn how to provision and manage Azure VMs using Terraform, including networking, scaling, and best practices
Setting up Azure Virtual Machines with Terraform
Azure Virtual Machines (VMs) provide scalable computing resources in the cloud. This guide demonstrates how to set up and manage Azure VMs using Terraform.
Video Tutorial
Learn more about managing Azure Virtual Machines with Terraform in this comprehensive video tutorial:
Prerequisites
- Azure CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Basic understanding of Azure networking concepts
- Resource group created
Project Structure
terraform-azure-vm/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── vm/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── scripts/
└── setup.sh
Virtual Machine Configuration
Create modules/vm/main.tf
:
# Resource Group
resource "azurerm_resource_group" "main" {
name = "${var.project_name}-rg"
location = var.location
tags = var.tags
}
# Virtual Network
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tags = var.tags
}
# Subnet
resource "azurerm_subnet" "main" {
name = "${var.project_name}-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
}
# Public IP
resource "azurerm_public_ip" "main" {
name = "${var.project_name}-pip"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
tags = var.tags
}
# Network Security Group
resource "azurerm_network_security_group" "main" {
name = "${var.project_name}-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = var.allowed_ssh_cidr
destination_address_prefix = "*"
}
tags = var.tags
}
# Network Interface
resource "azurerm_network_interface" "main" {
name = "${var.project_name}-nic"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.main.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.main.id
}
tags = var.tags
}
# Connect NSG to NIC
resource "azurerm_network_interface_security_group_association" "main" {
network_interface_id = azurerm_network_interface.main.id
network_security_group_id = azurerm_network_security_group.main.id
}
# Virtual Machine
resource "azurerm_linux_virtual_machine" "main" {
name = "${var.project_name}-vm"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = var.vm_size
admin_username = var.admin_username
network_interface_ids = [
azurerm_network_interface.main.id
]
admin_ssh_key {
username = var.admin_username
public_key = file(var.ssh_public_key_path)
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
boot_diagnostics {
storage_account_uri = azurerm_storage_account.diagnostics.primary_blob_endpoint
}
identity {
type = "SystemAssigned"
}
tags = var.tags
}
# Storage Account for Boot Diagnostics
resource "azurerm_storage_account" "diagnostics" {
name = "${lower(var.project_name)}diag"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
tags = var.tags
}
# Managed Disk
resource "azurerm_managed_disk" "data" {
name = "${var.project_name}-data-disk"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
storage_account_type = "Premium_LRS"
create_option = "Empty"
disk_size_gb = var.data_disk_size_gb
tags = var.tags
}
# Attach Data Disk
resource "azurerm_virtual_machine_data_disk_attachment" "data" {
managed_disk_id = azurerm_managed_disk.data.id
virtual_machine_id = azurerm_linux_virtual_machine.main.id
lun = "0"
caching = "ReadWrite"
}
Monitoring and Alerts
- Azure Monitor Configuration
resource "azurerm_monitor_diagnostic_setting" "vm" {
name = "${var.project_name}-vm-diag"
target_resource_id = azurerm_linux_virtual_machine.main.id
storage_account_id = azurerm_storage_account.diagnostics.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
metric {
category = "AllMetrics"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
}
resource "azurerm_monitor_metric_alert" "cpu" {
name = "${var.project_name}-cpu-alert"
resource_group_name = azurerm_resource_group.main.name
scopes = [azurerm_linux_virtual_machine.main.id]
description = "Alert when CPU usage exceeds threshold"
criteria {
metric_namespace = "Microsoft.Compute/virtualMachines"
metric_name = "Percentage CPU"
aggregation = "Average"
operator = "GreaterThan"
threshold = 80
}
action {
action_group_id = azurerm_monitor_action_group.main.id
}
}
Auto-scaling Configuration
- Scale Set Configuration
resource "azurerm_virtual_machine_scale_set" "main" {
name = "${var.project_name}-vmss"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
upgrade_policy_mode = "Manual"
sku {
name = var.vm_size
tier = "Standard"
capacity = var.instance_count
}
storage_profile_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
storage_profile_os_disk {
name = ""
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Premium_LRS"
}
os_profile {
computer_name_prefix = var.project_name
admin_username = var.admin_username
}
os_profile_linux_config {
disable_password_authentication = true
ssh_keys {
path = "/home/${var.admin_username}/.ssh/authorized_keys"
key_data = file(var.ssh_public_key_path)
}
}
network_profile {
name = "networkprofile"
primary = true
ip_configuration {
name = "ipconfig"
primary = true
subnet_id = azurerm_subnet.main.id
load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.main.id]
}
}
}
resource "azurerm_monitor_autoscale_setting" "main" {
name = "${var.project_name}-autoscale"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
target_resource_id = azurerm_virtual_machine_scale_set.main.id
profile {
name = "defaultProfile"
capacity {
default = var.instance_count
minimum = var.min_instances
maximum = var.max_instances
}
rule {
metric_trigger {
metric_name = "Percentage CPU"
metric_resource_id = azurerm_virtual_machine_scale_set.main.id
time_grain = "PT1M"
statistic = "Average"
time_window = "PT5M"
time_aggregation = "Average"
operator = "GreaterThan"
threshold = 75
}
scale_action {
direction = "Increase"
type = "ChangeCount"
value = "1"
cooldown = "PT5M"
}
}
rule {
metric_trigger {
metric_name = "Percentage CPU"
metric_resource_id = azurerm_virtual_machine_scale_set.main.id
time_grain = "PT1M"
statistic = "Average"
time_window = "PT5M"
time_aggregation = "Average"
operator = "LessThan"
threshold = 25
}
scale_action {
direction = "Decrease"
type = "ChangeCount"
value = "1"
cooldown = "PT5M"
}
}
}
}
Backup Configuration
- Recovery Services Vault
resource "azurerm_recovery_services_vault" "main" {
name = "${var.project_name}-vault"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
sku = "Standard"
soft_delete_enabled = true
}
resource "azurerm_backup_policy_vm" "main" {
name = "${var.project_name}-backup-policy"
resource_group_name = azurerm_resource_group.main.name
recovery_vault_name = azurerm_recovery_services_vault.main.name
backup {
frequency = "Daily"
time = "23:00"
}
retention_daily {
count = 7
}
retention_weekly {
count = 4
weekdays = ["Sunday"]
}
retention_monthly {
count = 12
weekdays = ["Sunday"]
weeks = ["First"]
}
}
resource "azurerm_backup_protected_vm" "main" {
resource_group_name = azurerm_resource_group.main.name
recovery_vault_name = azurerm_recovery_services_vault.main.name
source_vm_id = azurerm_linux_virtual_machine.main.id
backup_policy_id = azurerm_backup_policy_vm.main.id
}
Best Practices
-
Security
- Use managed identities
- Implement network security groups
- Enable disk encryption
- Regular security updates
-
High Availability
- Use availability sets
- Implement load balancing
- Configure auto-scaling
- Regular backups
-
Performance
- Choose appropriate VM sizes
- Use premium storage
- Monitor performance
- Optimize networking
-
Cost Optimization
- Use reserved instances
- Implement auto-shutdown
- Monitor usage
- Right-size VMs
Advanced Features
- Custom Script Extension
resource "azurerm_virtual_machine_extension" "custom_script" {
name = "custom-script"
virtual_machine_id = azurerm_linux_virtual_machine.main.id
publisher = "Microsoft.Azure.Extensions"
type = "CustomScript"
type_handler_version = "2.0"
settings = <<SETTINGS
{
"script": "${base64encode(file("${path.module}/scripts/setup.sh"))}"
}
SETTINGS
}
- Azure Policy Assignment
resource "azurerm_policy_assignment" "tag_governance" {
name = "tag-governance"
scope = azurerm_resource_group.main.id
policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/1e30110a-5ceb-460c-a204-c1c3969c6d62"
description = "Enforce tag and its value on resources"
display_name = "Require tag on resources"
parameters = <<PARAMETERS
{
"tagName": {
"value": "Environment"
},
"tagValue": {
"value": "${var.environment}"
}
}
PARAMETERS
}
Conclusion
You’ve learned how to set up and manage Azure Virtual Machines using Terraform. This setup provides:
- Secure VM deployment
- Auto-scaling capabilities
- Monitoring and alerts
- Backup and recovery
Remember to:
- Monitor VM performance
- Implement security best practices
- Optimize costs
- Maintain regular backups