Terraform para Azure: Guia Completo de Deploy
Por Vivo de Código | Atualizado em 2024
Introdução
Se você já precisou criar manualmente recursos na Azure pelo portal, sabe o quanto isso é frágil, lento e propenso a erros. Agora imagine replicar essa mesma infraestrutura em três ambientes diferentes: desenvolvimento, homologação e produção. O trabalho manual se torna insustentável.
É aqui que o Terraform entra como peça fundamental. Desenvolvido pela HashiCorp, o Terraform permite que você defina toda a sua infraestrutura em arquivos declarativos, versionáveis e reproduzíveis. Quando combinado com a Azure, você ganha o poder de provisionar desde um simples Resource Group até arquiteturas complexas com AKS, Application Gateway e bancos gerenciados, tudo com um único comando.
Neste guia, vou compartilhar o que aprendi em anos de trabalho com Terraform na Azure em cenários reais de produção. Vamos cobrir desde a configuração inicial até boas práticas que vão evitar muita dor de cabeça no futuro.
Por que usar Terraform com Azure
Vantagens da infraestrutura como código na nuvem
A Azure oferece seu próprio mecanismo de IaC com ARM Templates e o mais recente Bicep. Então por que escolher Terraform? Existem razões técnicas sólidas para essa decisão.
Primeiro: Terraform é multi-cloud. Se a sua empresa utiliza Azure como cloud principal mas tem serviços na AWS ou GCP, você mantém uma única linguagem (HCL) para toda a infraestrutura. Isso reduz a curva de aprendizado da equipe e padroniza os processos de deploy.
Segundo: o estado (state) do Terraform funciona como uma fonte da verdade. Ele sabe exatamente o que existe na nuvem e calcula o plano de execução antes de aplicar qualquer mudança. Isso dá uma previsibilidade que ARM Templates simplesmente não oferecem da mesma forma.
Terceiro: o ecossistema de módulos é gigantesco. O Terraform Registry possui centenas de módulos prontos e testados para Azure, acelerando o desenvolvimento e promovendo reutilização.
Casos de uso reais onde essa combinação brilha
Na prática, a combinação Terraform e Azure se destaca em cenários como: provisionamento de clusters AKS com políticas de rede configuradas, criação de ambientes efêmeros para testes de integração (criando e destruindo infraestrutura automaticamente em pipelines de CI/CD), e gestão de múltiplas subscriptions com governança centralizada usando módulos compartilhados. Outro caso clássico é a migração gradual de infraestrutura legada, onde o Terraform permite importar recursos existentes e passar a gerenciá-los como código.
Setup inicial do Terraform com Azure
Instalando e configurando o ambiente
Antes de escrever qualquer linha de HCL, você precisa configurar três coisas: o Terraform CLI, o Azure CLI e a autenticação via Service Principal.
Instale o Terraform e o Azure CLI:
// Script de setup automatizado (Node.js/TypeScript)
// Arquivo: scripts/setup-azure-terraform.ts
import { execSync } from "child_process";
interface AzureConfig {
subscriptionId: string;
servicePrincipalName: string;
resourceGroup: string;
storageAccount: string;
containerName: string;
}
function executeCommand(command: string, description: string): string {
console.log(`[SETUP] ${description}...`);
try {
const result = execSync(command, { encoding: "utf-8" });
console.log(`[OK] ${description} concluído.`);
return result.trim();
} catch (error) {
console.error(`[ERRO] Falha ao executar: ${description}`);
throw error;
}
}
function createServicePrincipal(config: AzureConfig): void {
// Define a subscription ativa
executeCommand(
`az account set --subscription ${config.subscriptionId}`,
"Definindo subscription"
);
// Cria o Service Principal com role de Contributor
const spOutput = executeCommand(
`az ad sp create-for-rbac --name "${config.servicePrincipalName}" --role Contributor --scopes /subscriptions/${config.subscriptionId} --sdk-auth`,
"Criando Service Principal"
);
console.log("Guarde estas credenciais com segurança:");
console.log(spOutput);
}
function createRemoteBackend(config: AzureConfig): void {
// Cria o Resource Group para o state
executeCommand(
`az group create --name ${config.resourceGroup} --location brazilsouth`,
"Criando Resource Group para backend"
);
// Cria a Storage Account
executeCommand(
`az storage account create --name ${config.storageAccount} --resource-group ${config.resourceGroup} --sku Standard_LRS --encryption-services blob`,
"Criando Storage Account"
);
// Cria o container para o state do Terraform
executeCommand(
`az storage container create --name ${config.containerName} --account-name ${config.storageAccount}`,
"Criando container para Terraform state"
);
}
// Execução principal
const config: AzureConfig = {
subscriptionId: "sua-subscription-id-aqui",
servicePrincipalName: "sp-terraform-prod",
resourceGroup: "rg-terraform-state",
storageAccount: "stterraformstateprod",
containerName: "tfstate",
};
createServicePrincipal(config);
createRemoteBackend(config);Agora, os comandos CLI que você vai executar diretamente no terminal:
# Login na Azure
az login
# Verificar a subscription ativa
az account show
# Verificar a instalação do Terraform
terraform versionConfigurando o backend remoto
Nunca armazene o state do Terraform localmente em produção. Use sempre um backend remoto. Na Azure, a solução padrão é o Azure Blob Storage. Crie o arquivo de configuração principal:
# providers.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.85.0"
}
}
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstateprod"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
provider "azurerm" {
features {}
}Exemplo prático: provisionando infraestrutura completa
Criando uma aplicação web com banco de dados na Azure
Vamos criar um cenário real: uma aplicação web rodando em App Service com um banco PostgreSQL gerenciado e Application Insights para monitoramento.
# variables.tf
variable "environment" {
type = string
description = "Ambiente de deploy (dev, staging, prod)"
default = "dev"
}
variable "location" {
type = string
description = "Região da Azure"
default = "Brazil South"
}
variable "project_name" {
type = string
description = "Nome do projeto"
default = "vivoapp"
}# main.tf
locals {
resource_suffix = "${var.project_name}-${var.environment}"
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}
resource "azurerm_resource_group" "main" {
name = "rg-${local.resource_suffix}"
location = var.location
tags = local.common_tags
}
resource "azurerm_service_plan" "main" {
name = "asp-${local.resource_suffix}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
os_type = "Linux"
sku_name = var.environment == "prod" ? "P1v3" : "B1"
tags = local.common_tags
}
resource "azurerm_linux_web_app" "main" {
name = "app-${local.resource_suffix}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
service_plan_id = azurerm_service_plan.main.id
site_config {
application_stack {
node_version = "18-lts"
}
always_on = var.environment == "prod" ? true : false
health_check_path = "/health"
}
app_settings = {
"DATABASE_URL" = azurerm_postgresql_flexible_server.main.fqdn
"APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.main.connection_string
"NODE_ENV" = var.environment
}
tags = local.common_tags
}
resource "azurerm_postgresql_flexible_server" "main" {
name = "psql-${local.resource_suffix}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "15"
administrator_login = "psqladmin"
administrator_password = var.db_password
storage_mb = 32768
sku_name = var.environment == "prod" ? "GP_Standard_D2s_v3" : "B_Standard_B1ms"
zone = "1"
tags = local.common_tags
}
resource "azurerm_postgresql_flexible_server_database" "main" {
name = "appdb"
server_id = azurerm_postgresql_flexible_server.main.id
collation = "en_US.utf8"
charset = "utf8"
}
resource "azurerm_application_insights" "main" {
name = "appi-${local.resource_suffix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
application_type = "Node.JS"
tags = local.common_tags
}Para aplicar essa infraestrutura:
# Inicializar o Terraform
terraform init
# Ver o plano de execução
terraform plan -var="environment=dev" -var="db_password=SuaSenhaSegura123!"
# Aplicar as mudanças
terraform apply -var="environment=dev" -var="db_password=SuaSenhaSegura123!" -auto-approveAutomatizando validações com TypeScript
Em projetos maduros, é comum ter scripts de validação que rodam antes e depois do Terraform. Este segundo exemplo mostra como automatizar a verificação da infraestrutura provisionada:
// scripts/validate-infrastructure.ts
import { DefaultAzureCredential } from "@azure/identity";
import { WebSiteManagementClient } from "@azure/arm-appservice";
import { PostgreSQLManagementFlexibleServerClient } from "@azure/arm-postgresql-flexible";
interface ValidationResult {
resource: string;
status: "ok" | "erro";
details: string;
}
async function validateInfrastructure(
subscriptionId: string,
resourceGroup: string,
environment: string
): Promise<ValidationResult[]> {
const credential = new DefaultAzureCredential();
const results: ValidationResult[] = [];
// Valida o App Service
const webClient = new WebSiteManagementClient(credential, subscriptionId);
try {
const appName = `app-vivoapp-${environment}`;
const app = await webClient.webApps.get(resourceGroup, appName);
results.push({
resource: "App Service",
status: app.state === "Running" ? "ok" : "erro",
details: `Estado: ${app.state} | Runtime: ${app.siteConfig?.linuxFxVersion}`,
});
// Verifica se o health check está configurado
const healthCheckPath = app.siteConfig?.healthCheckPath;
results.push({
resource: "Health Check",
status: healthCheckPath ? "ok" : "erro",
details: healthCheckPath
? `Path configurado: ${healthCheckPath}`
: "Health check não configurado",
});
} catch (error) {
results.push({
resource: "App Service",
status: "erro",
details: `Recurso não encontrado: ${error}`,
});
}
// Valida o PostgreSQL
const pgClient = new PostgreSQLManagementFlexibleServerClient(
credential,
subscriptionId
);
try {
const serverName = `psql-vivoapp-${environment}`;
const server = await pgClient.servers.get(resourceGroup, serverName);
results.push({
resource: "PostgreSQL Flexible Server",
status: server.state === "Ready" ? "ok" : "erro",
details: `Estado: ${server.state} | Versão: ${server.version} | SKU: ${server.sku?.name}`,
});
// Valida as databases
const databases = pgClient.databases.listByServer(
resourceGroup,
serverName
);
let dbCount = 0;
for await (const db of databases) {
dbCount++;
if (db.name === "appdb") {
results.push({
resource: "Database (appdb)",
status: "ok",
details: `Charset: ${db.charset} | Collation: ${db.collation}`,
});
}
}
} catch (error) {
results.push({
resource: "PostgreSQL",
status: "erro",
details: `Recurso não encontrado: ${error}`,
});
}
return results;
}
// Execução
async function main(): Promise<void> {
const subscriptionId = process.env.AZURE_SUBSCRIPTION_ID || "";
const environment = process.env.ENVIRONMENT || "dev";
const resourceGroup = `rg-vivoapp-${environment}`;
console.log(`Validando infraestrutura no ambiente: ${environment}\n`);
const results = await validateInfrastructure(
subscriptionId,
resourceGroup,
environment
);
let hasErrors = false;
for (const result of results) {
const indicator = result.status === "ok" ? "[OK]" : "[ERRO]";
console.log(`${indicator} ${result.resource}: ${result.details}`);
if (result.status === "erro") hasErrors = true;
}
if (hasErrors) {
console.log("\nValidação finalizada com erros. Verifique os itens acima.");
process.exit(1);
}
console.log("\nTodos os recursos validados com sucesso.");
}
main().catch(console.error);Boas práticas de Terraform na Azure
Organização de arquivos e módulos
Evite colocar tudo em um único arquivo. Uma estrutura recomendada para projetos de médio e grande porte:
infrastructure/
modules/
app-service/
main.tf
variables.tf
outputs.tf
database/
main.tf
variables.tf
outputs.tf
environments/
dev/
main.tf
terraform.tfvarsGuias Relacionados
Explore mais guias técnicos
Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.
Ver todos os guias