Exemplo prático de um serviço web simples na Azure provisionado com Terraform, com promoção do ambiente de desenvolvimento para produção usando workspaces.
A diferença central em relação à AWS é que na Azure todo recurso precisa pertencer a um Resource Group, e a rede é declarada explicitamente (VNet → Subnet → Network Interface → VM).
Estrutura do projeto
infra/
├── backend.tf
├── main.tf
├── variables.tf
├── outputs.tf
├── dev.tfvars
└── prod.tfvars
Backend remoto (Azure Blob Storage)
# backend.tf
terraform {
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "meuprojtfstate"
container_name = "tfstate"
key = "app/terraform.tfstate"
}
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}A autenticação usa o az login (CLI) em desenvolvimento ou uma Service Principal via variáveis de ambiente em CI/CD:
export ARM_CLIENT_ID="<app-id>"
export ARM_CLIENT_SECRET="<secret>"
export ARM_SUBSCRIPTION_ID="<subscription-id>"
export ARM_TENANT_ID="<tenant-id>"Variáveis
# variables.tf
variable "localizacao" {
type = string
default = "brazilsouth"
}
variable "ambiente" {
type = string
}
variable "vm_size" {
type = string
}
variable "projeto" {
type = string
default = "meu-projeto"
}# dev.tfvars
ambiente = "dev"
vm_size = "Standard_B1s"
# prod.tfvars
ambiente = "prod"
vm_size = "Standard_B2s"Infraestrutura: VM Linux com NGINX
# main.tf
locals {
nome = "${var.projeto}-${var.ambiente}"
tags = {
Projeto = var.projeto
Ambiente = var.ambiente
ManagedBy = "terraform"
}
}
# Resource Group, obrigatório na Azure
resource "azurerm_resource_group" "app" {
name = "${local.nome}-rg"
location = var.localizacao
tags = local.tags
}
# Rede virtual
resource "azurerm_virtual_network" "app" {
name = "${local.nome}-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
tags = local.tags
}
resource "azurerm_subnet" "app" {
name = "${local.nome}-subnet"
resource_group_name = azurerm_resource_group.app.name
virtual_network_name = azurerm_virtual_network.app.name
address_prefixes = ["10.0.1.0/24"]
}
# IP público
resource "azurerm_public_ip" "app" {
name = "${local.nome}-pip"
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
allocation_method = "Static"
tags = local.tags
}
# Network Security Group, equivalente ao Security Group da AWS
resource "azurerm_network_security_group" "app" {
name = "${local.nome}-nsg"
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
tags = local.tags
security_rule {
name = "HTTP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "SSH"
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# Network Interface
resource "azurerm_network_interface" "app" {
name = "${local.nome}-nic"
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
tags = local.tags
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.app.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.app.id
}
}
resource "azurerm_network_interface_security_group_association" "app" {
network_interface_id = azurerm_network_interface.app.id
network_security_group_id = azurerm_network_security_group.app.id
}
# VM Linux
resource "azurerm_linux_virtual_machine" "app" {
name = "${local.nome}-vm"
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
size = var.vm_size
admin_username = "adminuser"
tags = local.tags
network_interface_ids = [azurerm_network_interface.app.id]
admin_ssh_key {
username = "adminuser"
public_key = file("~/.ssh/id_rsa.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"
}
custom_data = base64encode(<<-EOF
#!/bin/bash
apt-get update -y
apt-get install -y nginx
systemctl enable nginx
systemctl start nginx
echo "<h1>${local.nome}</h1>" > /var/www/html/index.html
EOF
)
}# outputs.tf
output "ip_publico" {
value = azurerm_public_ip.app.ip_address
}
output "url" {
value = "http://${azurerm_public_ip.app.ip_address}"
}Fluxo dev → prod
# 1. Subir ambiente de dev
terraform workspace new dev
terraform workspace select dev
terraform init
terraform plan -var-file="dev.tfvars"
terraform apply -var-file="dev.tfvars"
# Valida no IP exibido pelo output "url"...
# 2. Promover para prod
terraform workspace new prod
terraform workspace select prod
terraform plan -var-file="prod.tfvars"
terraform apply -var-file="prod.tfvars"
# 3. Destruir dev após validação
terraform workspace select dev
terraform destroy -var-file="dev.tfvars"Diferenças em relação à AWS
| Conceito | AWS | Azure |
|---|---|---|
| Agrupamento lógico | Tags / Accounts | Resource Group (obrigatório) |
| Backend de state | S3 + DynamoDB | Azure Blob Storage |
| VM | aws_instance | azurerm_linux_virtual_machine |
| Firewall | Security Group (inline) | NSG + associação separada |
| Imagem | AMI (data source) | source_image_reference inline |
Ver também: terraform-state | terraform-variaveis | terraform-providers | terraform | terraform-cloud-aws