Post

Estação de Trabalho como Código (Parte 13): Infraestrutura no Azure com Terraform

Aprenda a provisionar infraestrutura no Microsoft Azure usando Terraform. Este tutorial cobre configuração do Azure CLI, criação de Service Principal, Resource Groups, redes virtuais, máquinas virtuais, módulos Terraform e como organizar seu código para múltiplos ambientes.

Estação de Trabalho como Código (Parte 13): Infraestrutura no Azure com Terraform

Tutorial Anterior: Estação de Trabalho como Código (Parte 12): Infraestrutura na AWS com Terraform

Introdução

O Microsoft Azure é o segundo maior provedor de nuvem do mercado, com forte presença em ambientes corporativos, especialmente entre empresas que usam tecnologias Microsoft. O Azure oferece integração profunda com ferramentas como Active Directory, Office 365 e SQL Server, além de serviços de IA e análise de dados de classe mundial.

Neste tutorial, vamos aprender a provisionar infraestrutura no Azure usando Terraform, cobrindo conceitos como Resource Groups, Virtual Networks, Virtual Machines, módulos reutilizáveis e como organizar seu código para múltiplos ambientes.

Observação: Esta parte foca em Terraform (recomendado). Se preferir usar Azure CLI manualmente, consulte a documentação oficial. No entanto, recomendamos Terraform para automação profissional.

Objetivos desta Parte

  • Instalar e configurar o Azure CLI
  • Criar um Service Principal para acesso via Terraform
  • Configurar o provider Azure no Terraform
  • Criar Resource Groups e redes virtuais
  • Provisionar máquinas virtuais no Azure
  • Configurar grupos de segurança de rede (NSGs)
  • Organizar código em módulos reutilizáveis
  • Usar variáveis para flexibilidade

Pré-requisitos

  • Conclusão da Parte 12 desta série
  • Conta Azure criada (pode usar free tier ou créditos)
  • Terraform instalado (Parte 7)
  • Azure CLI instalado (Parte 11)

A quem se destina

Este tutorial é ideal para:

  • DevOps Engineers: Que precisam provisionar infraestrutura no Azure
  • Arquitetos de Nuvem: Que querem usar IaC para Azure
  • SysAdmins: Que querem expandir para a nuvem
  • Profissionais de TI: Que querem implementar IaC em Azure

Pré-conhecimento: Conhecimento de Terraform, Azure e networking (coberto nas partes anteriores) é recomendado.

Tempo Estimado

150-180 minutos

Isso inclui:

  • Leitura e compreensão: ~25 min
  • Configuração Azure CLI: ~15 min
  • Criação de Service Principal: ~10 min
  • Criação de estrutura Terraform: ~20 min
  • Desenvolvimento de módulos: ~50 min
  • Execução e testes: ~30 min
  • Troubleshooting: ~15 min

Dica Útil: Use o free tier do Azure para experimentar sem custos. Você tem créditos de teste.


Entendendo Azure

Antes de começar, é importante entender os conceitos fundamentais.

O que é Azure?

Azure (Microsoft Azure) é uma plataforma de computação em nuvem que oferece mais de 200 serviços diferentes. Os principais são:

  • Computação: Virtual Machines, App Service, Functions
  • Armazenamento: Blob Storage, File Shares, Disk Storage
  • Banco de Dados: SQL Database, Cosmos DB, PostgreSQL
  • Rede: Virtual Networks, Load Balancer, Application Gateway
  • Segurança: Azure AD, Key Vault, Security Center

Conceitos Principais

ConceitoDescrição
SubscriptionContrato de faturamento com a Microsoft
Resource GroupContêiner para recursos relacionados
RegionLocalização geográfica com múltiplos datacenters
Availability ZoneDatacenter isolado dentro de uma região
VNetRede virtual isolada onde você executa recursos
SubnetSegmento de rede dentro de uma VNet
VMMáquina virtual no Azure
NSGFirewall virtual para controlar tráfego
Service PrincipalIdentidade para automação e Terraform

Verificando Pré-Requisitos

Antes de começar, certifique-se de que seu ambiente está pronto.

Passo 1: Verificar Terraform

1
2
3
4
5
# Verifique se Terraform está instalado
$ terraform --version

# Você deve ver:
# Terraform v1.14.5

Passo 2: Verificar Azure CLI

1
2
3
4
5
6
# Verifique se Azure CLI está instalado
$ az --version

# Você deve ver:
# azure-cli                         2.83.0
# core                              2.83.0

Passo 3: Verificar Login no Azure

1
2
3
4
# Verifique se está logado no Azure
$ az account show

# Você deve ver informações de sua conta

Configurando Azure CLI

Passo 1: Instalar Azure CLI

1
2
3
4
5
# Instale Azure CLI
$ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Verifique
$ az --version

Passo 2: Fazer Login

1
2
3
4
5
# Faça login no Azure
$ az login --use-device-code

# Você será solicitado a abrir um link e digitar um código
# Siga as instruções na tela

Passo 3: Verificar Conta

1
2
3
4
5
6
7
8
9
10
# Verifique sua conta
$ az account show

# Você deve ver:
# {
#   "id": "0273e9e7-269c-4199-a62c-4f84da6e12e4",
#   "name": "Azure subscription 1",
#   "tenantId": "813ccd62-e6a9-4405-9a4d-dd7fad0d386a",
#   ...
# }

Criando Service Principal

Passo 1: Obter Subscription ID

1
2
3
4
5
# Obtenha o Subscription ID
$ az account show --query id -o tsv

# Você verá:
# 0273e9e7-269c-4199-a62c-4f84da6e12e4

Passo 2: Criar Service Principal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Crie um Service Principal
$ az ad sp create-for-rbac \
  --role="Contributor" \
  --scopes="/subscriptions/$(az account show --query id -o tsv)"

# Você verá:
# {
#   "appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "displayName": "azure-cli-2026-02-19-12-34-56",
#   "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
#   "tenant": "813ccd62-e6a9-4405-9a4d-dd7fad0d386a"
# }

# Salve essas informações em um local seguro

Passo 3: Configurar Variáveis de Ambiente

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Configure variáveis de ambiente para Terraform
$ export ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ export ARM_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$ export ARM_SUBSCRIPTION_ID="0273e9e7-269c-4199-a62c-4f84da6e12e4"
$ export ARM_TENANT_ID="813ccd62-e6a9-4405-9a4d-dd7fad0d386a"

# Ou adicione ao ~/.bashrc para persistência
$ cat >> ~/.bashrc << 'EOF'
export ARM_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export ARM_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export ARM_SUBSCRIPTION_ID="0273e9e7-269c-4199-a62c-4f84da6e12e4"
export ARM_TENANT_ID="813ccd62-e6a9-4405-9a4d-dd7fad0d386a"
EOF

# Recarregue
$ source ~/.bashrc

Criando Estrutura Terraform para Azure

Passo 1: Criar Diretórios

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
# Crie a estrutura de diretórios
$ mkdir -p ~/workspace-as-code/terraform/providers/azure/{modules,environments}
$ cd ~/workspace-as-code/terraform/providers/azure

# Estrutura esperada:
# azure/
# ├── modules/
# │   ├── resource_group/
# │   │   ├── main.tf
# │   │   ├── variables.tf
# │   │   └── outputs.tf
# │   ├── network/
# │   │   ├── main.tf
# │   │   ├── variables.tf
# │   │   └── outputs.tf
# │   └── vm/
# │       ├── main.tf
# │       ├── variables.tf
# │       └── outputs.tf
# ├── environments/
# │   ├── dev/
# │   │   ├── main.tf
# │   │   ├── terraform.tfvars
# │   │   └── backend.tf
# │   ├── staging/
# │   └── prod/
# ├── main.tf
# ├── variables.tf
# ├── outputs.tf
# └── provider.tf

Passo 2: Criar Arquivo de Provider

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
# Crie arquivo provider.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/provider.tf << 'EOF'
terraform {
  required_version = "~> 1.14.5"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {
    virtual_machine {
      delete_os_disk_on_deletion            = true
      graceful_shutdown                     = false
      skip_shutdown_and_force_delete        = false
    }
  }

  skip_provider_registration = false
}
EOF

# Verifique
$ cat ~/workspace-as-code/terraform/providers/azure/provider.tf

Passo 3: Criar Variáveis Principais

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
# Crie arquivo variables.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/variables.tf << 'EOF'
variable "azure_region" {
  description = "Azure region"
  type        = string
  default     = "canadacentral"
}

variable "project_name" {
  description = "Project name"
  type        = string
  default     = "workspace-as-code"
}

variable "environment" {
  description = "Environment name (dev, staging, prod)"
  type        = string
  default     = "dev"
}

variable "vnet_cidr" {
  description = "CIDR block for Virtual Network"
  type        = string
  default     = "10.0.0.0/16"
}

variable "subnet_cidr" {
  description = "CIDR block for subnet"
  type        = string
  default     = "10.0.1.0/24"
}

variable "vm_size" {
  description = "Azure VM size"
  type        = string
  default     = "Standard_B1s"
}

variable "enable_public_ip" {
  description = "Enable public IP for VM"
  type        = bool
  default     = true
}
EOF

# Verifique
$ cat ~/workspace-as-code/terraform/providers/azure/variables.tf

Criando Módulos Terraform

Módulo 1: Resource Group

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
# Crie diretório do módulo
$ mkdir -p ~/workspace-as-code/terraform/providers/azure/modules/resource_group

# Crie arquivo main.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/resource_group/main.tf << 'EOF'
resource "azurerm_resource_group" "main" {
  name     = "${var.project_name}-${var.environment}-rg"
  location = var.azure_region

  tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}
EOF

# Crie arquivo variables.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/resource_group/variables.tf << 'EOF'
variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "azure_region" {
  description = "Azure region"
  type        = string
}
EOF

# Crie arquivo outputs.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/resource_group/outputs.tf << 'EOF'
output "resource_group_id" {
  description = "Resource Group ID"
  value       = azurerm_resource_group.main.id
}

output "resource_group_name" {
  description = "Resource Group name"
  value       = azurerm_resource_group.main.name
}
EOF

Módulo 2: Network (VNet + Subnet + NSG)

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# Crie diretório do módulo
$ mkdir -p ~/workspace-as-code/terraform/providers/azure/modules/network

# Crie arquivo main.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/network/main.tf << 'EOF'
# Virtual Network
resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-${var.environment}-vnet"
  address_space       = [var.vnet_cidr]
  location            = var.azure_region
  resource_group_name = var.resource_group_name

  tags = {
    Name = "${var.project_name}-vnet"
  }
}

# Subnet
resource "azurerm_subnet" "main" {
  name                 = "${var.project_name}-${var.environment}-subnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = [var.subnet_cidr]
}

# Network Security Group
resource "azurerm_network_security_group" "main" {
  name                = "${var.project_name}-${var.environment}-nsg"
  location            = var.azure_region
  resource_group_name = var.resource_group_name

  # SSH Ingress
  security_rule {
    name                       = "AllowSSH"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  # HTTP Ingress
  security_rule {
    name                       = "AllowHTTP"
    priority                   = 200
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  # HTTPS Ingress
  security_rule {
    name                       = "AllowHTTPS"
    priority                   = 300
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  tags = {
    Name = "${var.project_name}-nsg"
  }
}

# Associate NSG with Subnet
resource "azurerm_subnet_network_security_group_association" "main" {
  subnet_id                 = azurerm_subnet.main.id
  network_security_group_id = azurerm_network_security_group.main.id
}

# Public IP
resource "azurerm_public_ip" "main" {
  name                = "${var.project_name}-${var.environment}-pip"
  location            = var.azure_region
  resource_group_name = var.resource_group_name
  allocation_method   = "Static"
  sku                 = "Standard"

  tags = {
    Name = "${var.project_name}-pip"
  }
}

# Network Interface
resource "azurerm_network_interface" "main" {
  name                = "${var.project_name}-${var.environment}-nic"
  location            = var.azure_region
  resource_group_name = var.resource_group_name

  ip_configuration {
    name                          = "testconfiguration1"
    subnet_id                     = azurerm_subnet.main.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.main.id
  }

  tags = {
    Name = "${var.project_name}-nic"
  }
}
EOF

# Crie arquivo variables.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/network/variables.tf << 'EOF'
variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "azure_region" {
  description = "Azure region"
  type        = string
}

variable "resource_group_name" {
  description = "Resource Group name"
  type        = string
}

variable "vnet_cidr" {
  description = "CIDR block for Virtual Network"
  type        = string
}

variable "subnet_cidr" {
  description = "CIDR block for subnet"
  type        = string
}
EOF

# Crie arquivo outputs.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/network/outputs.tf << 'EOF'
output "vnet_id" {
  description = "Virtual Network ID"
  value       = azurerm_virtual_network.main.id
}

output "subnet_id" {
  description = "Subnet ID"
  value       = azurerm_subnet.main.id
}

output "nsg_id" {
  description = "Network Security Group ID"
  value       = azurerm_network_security_group.main.id
}

output "nic_id" {
  description = "Network Interface ID"
  value       = azurerm_network_interface.main.id
}

output "public_ip" {
  description = "Public IP address"
  value       = azurerm_public_ip.main.ip_address
}
EOF

Módulo 3: 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
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
93
94
95
96
97
98
99
# Crie diretório do módulo
$ mkdir -p ~/workspace-as-code/terraform/providers/azure/modules/vm

# Crie arquivo main.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/vm/main.tf << 'EOF'
# Generate SSH key if not exists
resource "tls_private_key" "main" {
  algorithm = "Ed25519"
}

# Virtual Machine
resource "azurerm_linux_virtual_machine" "main" {
  name                = "${var.project_name}-${var.environment}-vm"
  location            = var.azure_region
  resource_group_name = var.resource_group_name
  size                = var.vm_size

  admin_username = "azureuser"

  admin_ssh_key {
    username   = "azureuser"
    public_key = tls_private_key.main.public_key_openssh
  }

  network_interface_ids = [
    var.network_interface_id,
  ]

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

  source_image_reference {
    publisher = "Debian"
    offer     = "debian-12"
    sku       = "12"
    version   = "latest"
  }

  tags = {
    Name = "${var.project_name}-vm"
  }
}

# Save private key locally
resource "local_file" "private_key" {
  content         = tls_private_key.main.private_key_openssh
  filename        = "${path.module}/../../${var.project_name}-${var.environment}-key.pem"
  file_permission = "0600"
}
EOF

# Crie arquivo variables.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/vm/variables.tf << 'EOF'
variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "azure_region" {
  description = "Azure region"
  type        = string
}

variable "resource_group_name" {
  description = "Resource Group name"
  type        = string
}

variable "vm_size" {
  description = "Azure VM size"
  type        = string
  default     = "Standard_B1s"
}

variable "network_interface_id" {
  description = "Network Interface ID"
  type        = string
}
EOF

# Crie arquivo outputs.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/modules/vm/outputs.tf << 'EOF'
output "vm_id" {
  description = "Virtual Machine ID"
  value       = azurerm_linux_virtual_machine.main.id
}

output "private_key_path" {
  description = "Path to private key file"
  value       = local_file.private_key.filename
}
EOF

Criando Configuração Principal

Passo 1: Arquivo Main

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
# Crie arquivo main.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/main.tf << 'EOF'
# Módulo Resource Group
module "resource_group" {
  source = "./modules/resource_group"

  project_name = var.project_name
  environment  = var.environment
  azure_region = var.azure_region
}

# Módulo Network
module "network" {
  source = "./modules/network"

  project_name        = var.project_name
  environment         = var.environment
  azure_region        = var.azure_region
  resource_group_name = module.resource_group.resource_group_name
  vnet_cidr           = var.vnet_cidr
  subnet_cidr         = var.subnet_cidr
}

# Módulo VM
module "vm" {
  source = "./modules/vm"

  project_name         = var.project_name
  environment          = var.environment
  azure_region         = var.azure_region
  resource_group_name  = module.resource_group.resource_group_name
  vm_size              = var.vm_size
  network_interface_id = module.network.nic_id
}
EOF

# Verifique
$ cat ~/workspace-as-code/terraform/providers/azure/main.tf

Passo 2: Arquivo Outputs

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
# Crie arquivo outputs.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/outputs.tf << 'EOF'
output "resource_group_id" {
  description = "Resource Group ID"
  value       = module.resource_group.resource_group_id
}

output "resource_group_name" {
  description = "Resource Group name"
  value       = module.resource_group.resource_group_name
}

output "vnet_id" {
  description = "Virtual Network ID"
  value       = module.network.vnet_id
}

output "subnet_id" {
  description = "Subnet ID"
  value       = module.network.subnet_id
}

output "nsg_id" {
  description = "Network Security Group ID"
  value       = module.network.nsg_id
}

output "vm_id" {
  description = "Virtual Machine ID"
  value       = module.vm.vm_id
}

output "vm_public_ip" {
  description = "VM Public IP"
  value       = module.network.public_ip
}

output "private_key_path" {
  description = "Path to private key file"
  value       = module.vm.private_key_path
}
EOF

# Verifique
$ cat ~/workspace-as-code/terraform/providers/azure/outputs.tf

Criando Ambientes

Passo 1: Ambiente Dev

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
# Crie diretório do ambiente
$ mkdir -p ~/workspace-as-code/terraform/providers/azure/environments/dev

# Crie arquivo terraform.tfvars
$ cat > ~/workspace-as-code/terraform/providers/azure/environments/dev/terraform.tfvars << 'EOF'
azure_region  = "canadacentral"
project_name  = "workspace-as-code"
environment   = "dev"
vnet_cidr     = "10.0.0.0/16"
subnet_cidr   = "10.0.1.0/24"
vm_size       = "Standard_B1s"
EOF

# Crie arquivo main.tf (referencia módulos)
$ cat > ~/workspace-as-code/terraform/providers/azure/environments/dev/main.tf << 'EOF'
terraform {
  required_version = "~> 1.14.5"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

module "azure_infrastructure" {
  source = "../../"

  azure_region  = var.azure_region
  project_name  = var.project_name
  environment   = var.environment
  vnet_cidr     = var.vnet_cidr
  subnet_cidr   = var.subnet_cidr
  vm_size       = var.vm_size
}
EOF

# Crie arquivo variables.tf
$ cat > ~/workspace-as-code/terraform/providers/azure/environments/dev/variables.tf << 'EOF'
variable "azure_region" {
  type = string
}

variable "project_name" {
  type = string
}

variable "environment" {
  type = string
}

variable "vnet_cidr" {
  type = string
}

variable "subnet_cidr" {
  type = string
}

variable "vm_size" {
  type = string
}
EOF

Executando Terraform

Passo 1: Inicializar Terraform

1
2
3
4
5
6
7
8
# Navegue para o diretório
$ cd ~/workspace-as-code/terraform/providers/azure

# Inicialize Terraform
$ terraform init

# Você deve ver:
# Terraform has been successfully configured!

Passo 2: Validar Configuração

1
2
3
4
5
# Valide a sintaxe
$ terraform validate

# Você deve ver:
# Success! The configuration is valid.

Passo 3: Formatar Código

1
2
3
4
# Formate o código
$ terraform fmt -recursive

# Isso vai formatar todos os arquivos .tf

Passo 4: Planejar Execução

1
2
3
4
# Crie um plano
$ terraform plan -out=tfplan

# Você verá as mudanças que serão feitas

Passo 5: Aplicar Configuração

1
2
3
4
5
# Aplique o plano
$ terraform apply tfplan

# Você verá:
# Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

Passo 6: Verificar Saída

1
2
3
4
5
6
7
# Veja os outputs
$ terraform output

# Você verá:
# vm_public_ip = "20.48.230.96"
# private_key_path = "..."
# ...

Acessando a Instância Azure

Passo 1: Obter IP Público

1
2
3
4
5
# Obtenha o IP público
$ terraform output vm_public_ip

# Você verá:
# "20.48.230.96"

Passo 2: Obter Caminho da Chave Privada

1
2
3
4
5
# Obtenha o caminho da chave privada
$ terraform output private_key_path

# Você verá:
# "~/workspace-as-code/terraform/providers/azure/workspace-as-code-dev-key.pem"

Passo 3: Conectar via SSH

1
2
3
4
5
6
7
8
9
10
# Conecte à instância
$ ssh -i $(terraform output -raw private_key_path) azureuser@$(terraform output -raw vm_public_ip)

# Você deve ver:
# The authenticity of host '20.48.230.96 (20.48.230.96)' can't be established.
# Are you sure you want to continue connecting (yes/no)?

# Digite: yes

# Você deve estar conectado à instância

Passo 4: Verificar Instância

1
2
3
4
# Verifique informações da instância
$ uname -a
$ cat /etc/os-release
$ df -h

Tabela de Módulos Terraform

MóduloDescriçãoRecursos
resource_groupCria Resource GroupResource Group
networkCria rede e segurançaVNet, Subnet, NSG, NIC, Public IP
vmCria máquina virtualLinux VM, SSH Key

Troubleshooting

Erro: “The client does not have authorization”

Problema: Service Principal sem permissões ou credenciais inválidas.

Solução:

1
2
3
4
5
6
# Verifique variáveis de ambiente
$ echo $ARM_CLIENT_ID
$ echo $ARM_SUBSCRIPTION_ID

# Ou recrie Service Principal
$ az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/$(az account show --query id -o tsv)"

Erro: “ResourceGroupNotFound”

Problema: Resource Group não existe.

Solução:

1
2
3
4
5
# Verifique Resource Groups
$ az group list --query "[].name" -o table

# Ou recrie com terraform apply
$ terraform apply

Erro: “VirtualNetworkNotFound”

Problema: Virtual Network não existe.

Solução:

1
2
3
4
5
# Verifique VNets
$ az network vnet list --query "[].name" -o table

# Ou recrie com terraform apply
$ terraform apply

Erro: “InvalidImageReference”

Problema: Imagem Debian não encontrada.

Solução:

1
2
3
4
5
# Verifique imagens disponíveis
$ az vm image list --publisher Debian --offer debian-12 -o table

# Ou use outra imagem
# Edite modules/vm/main.tf com nova imagem

Instância não responde a SSH

Problema: NSG bloqueia SSH.

Solução:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Verifique regras NSG
$ az network nsg rule list --resource-group <rg> --nsg-name <nsg>

# Ou adicione regra manualmente
$ az network nsg rule create \
  --resource-group <rg> \
  --nsg-name <nsg> \
  --name AllowSSH \
  --priority 100 \
  --source-address-prefixes '*' \
  --source-port-ranges '*' \
  --destination-address-prefixes '*' \
  --destination-port-ranges 22 \
  --access Allow \
  --protocol Tcp

Dicas e Boas Práticas

Dica 1: Use Variáveis para Flexibilidade

1
2
3
4
5
6
7
8
9
# Bom
variable "vm_size" {
  default = "Standard_B1s"
}

# Ruim
resource "azurerm_linux_virtual_machine" "main" {
  size = "Standard_B1s"
}

Dica 2: Organize em Módulos

1
2
3
4
modules/
├── resource_group/
├── network/
└── vm/

Dica 3: Use Outputs para Referências

1
2
3
output "vm_public_ip" {
  value = module.network.public_ip
}

Dica 4: Versione Tudo

1
2
3
$ git add .
$ git commit -m "feat: add Azure infrastructure"
$ git push origin main

Dica 5: Use Workspaces para Ambientes

1
2
3
$ terraform workspace new dev
$ terraform workspace new staging
$ terraform workspace new prod

Integrando com workspace-as-code

Passo 1: Organizar Estrutura

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Verifique a estrutura
$ tree ~/workspace-as-code/terraform/providers/azure/

# Você deve ver:
# azure/
# ├── modules/
# │   ├── resource_group/
# │   ├── network/
# │   └── vm/
# ├── environments/
# │   └── dev/
# ├── main.tf
# ├── variables.tf
# ├── outputs.tf
# └── provider.tf

Passo 2: Versionar no Git

1
2
3
4
5
6
7
8
# Adicione ao Git
$ cd ~/workspace-as-code
$ git add terraform/providers/azure/
$ git commit -m "feat: add Azure infrastructure with Terraform"
$ git push origin main

# Verifique
$ git log --oneline | head -5

Script de Validação

Para verificar se tudo foi configurado corretamente:

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
#!/bin/bash
# Script de validação de Azure Terraform

echo "Validando Azure Terraform..."

# Verificar Azure CLI
if command -v az &> /dev/null; then
    echo "✓ Azure CLI instalado"
else
    echo "✗ Azure CLI não instalado"
    exit 1
fi

# Verificar login
if az account show &> /dev/null; then
    echo "✓ Login Azure OK"
else
    echo "✗ Não está logado no Azure"
    exit 1
fi

# Verificar Terraform
if command -v terraform &> /dev/null; then
    echo "✓ Terraform instalado"
else
    echo "✗ Terraform não instalado"
    exit 1
fi

# Verificar estrutura
if [ -d "terraform/providers/azure/modules" ]; then
    echo "✓ Módulos existem"
else
    echo "✗ Módulos não existem"
    exit 1
fi

# Validar Terraform
cd terraform/providers/azure
if terraform validate &> /dev/null; then
    echo "✓ Terraform válido"
else
    echo "✗ Terraform inválido"
    exit 1
fi

echo ""
echo "Validação concluída com sucesso!"

Conclusão

Você aprendeu a provisionar infraestrutura no Azure usando Terraform. Com Terraform, você pode definir infraestrutura como código, versioná-la no Git e reutilizá-la em múltiplos ambientes.

O Que Você Alcançou

✓ Instalação e configuração do Azure CLI ✓ Criação de Service Principal ✓ Configuração do provider Azure no Terraform ✓ Criação de Resource Groups ✓ Criação de redes virtuais ✓ Provisão de máquinas virtuais ✓ Configuração de segurança (NSG) ✓ Organização em módulos reutilizáveis ✓ Uso de variáveis para flexibilidade

Próximos Passos Imediatos

  1. Configure Azure CLI:
    1
    
    $ az login --use-device-code
    
  2. Crie Service Principal:
    1
    
    $ az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/$(az account show --query id -o tsv)"
    
  3. Configure variáveis de ambiente:
    1
    2
    3
    4
    
    $ export ARM_CLIENT_ID="..."
    $ export ARM_CLIENT_SECRET="..."
    $ export ARM_SUBSCRIPTION_ID="..."
    $ export ARM_TENANT_ID="..."
    
  4. Crie estrutura Terraform:
    1
    
    $ mkdir -p ~/workspace-as-code/terraform/providers/azure/{modules,environments}
    
  5. Crie módulos:
    1
    
    $ mkdir -p ~/workspace-as-code/terraform/providers/azure/modules/{resource_group,network,vm}
    
  6. Copie arquivos dos exemplos acima

  7. Inicialize Terraform:
    1
    2
    
    $ cd ~/workspace-as-code/terraform/providers/azure
    $ terraform init
    
  8. Valide e aplique:
    1
    2
    3
    
    $ terraform validate
    $ terraform plan
    $ terraform apply
    
  9. Versione no Git:
    1
    2
    3
    
    $ git add terraform/
    $ git commit -m "feat: add Azure infrastructure"
    $ git push origin main
    

Próximo Tutorial

Com infraestrutura no Azure configurada, o próximo passo é aprender a usar GCP com Terraform.


Recursos Adicionais


Fim da Parte 13

Próxima: Infraestrutura no GCP com Terraform

This post is licensed under CC BY 4.0 by the author.