Voltar ao início
Azure 2 abril, 2026 8 min de leitura

Arquitetura IaC Multi-Região com Terraform: Deploy Seguro no Azure

Como estruturei uma infraestrutura Terraform modular para orquestrar múltiplos serviços Azure — de AI Foundry a Container Apps com ingress privado — e consegui fazer deploy em diferentes regiões e subscriptions com um único codebase.

Como o Terraform funciona

Visão geral da solução

Nos últimos meses, mergulhei fundo na construção de uma infraestrutura como código para um projeto Azure que foi crescendo conforme as necessidades. Começou com um módulo Terraform simples e foi evoluindo: AI Foundry com GPT-5.1, Virtual Network isolada, Key Vault, Managed Identities e, mais recentemente, Azure Container Apps com ingress privado.

O maior desafio era permitir deploy em diferentes regiões e subscriptions sem transformar a configuração num labirinto. A resposta que encontrei foi um módulo Terraform modular, com valores padrão sensatos, mas totalmente flexível via variáveis. Resultado: um único codebase, múltiplos ambientes.

Como estruturei o projeto em camadas modulares

Criei uma hierarquia de módulos que separa bem as responsabilidades:

foundry-platform/
├── main.tf
├── variables.tf
├── outputs.tf
└── resources/
    ├── networking/
    ├── keyvault/
    ├── identity-rbac/
    └── container-apps/

Cada módulo é independente, com suas próprias variáveis, recursos e outputs. O módulo pai (foundry-platform) orquestra tudo, passando valores conforme necessário.

A grande vantagem dessa abordagem? Quando precisei adicionar Container Apps, bastou criar um novo submódulo sem tocar em nada do que já existia. Zero refatoração.

Fluxo dos módulos (em português)

Para facilitar a manutenção, organizei o fluxo em uma sequência simples e previsível. Assim, cada camada recebe o que precisa da camada anterior e entrega apenas o necessário para a próxima:

[templates e variáveis]
      ↓
    [módulo pai: foundry-platform]
      ↓
    [networking] → [identity-rbac] → [keyvault]
      ↓
    [container-apps com ingress privado]
      ↓
    [aplicação publicada com acesso interno e autenticação segura no registry]

Na prática, isso me permite evoluir a plataforma por blocos: adiciono um módulo novo, conecto os inputs e outputs e sigo sem quebrar o restante do ambiente.

Adicionando Container Apps com ingress privado

Quando surgiu a necessidade de rodar contêineres com imagem privada no ACR, a estrutura modular mostrou todo o seu valor. Resolvi tudo em três partes.

1. Subnet delegada para Container Apps

O primeiro passo foi estender o módulo de networking para criar uma subnet com delegação correta. Sem isso, o Container Apps não consegue se comunicar com a VNet:

resource "azurerm_subnet" "container_apps" {
  name                 = "snet-container-apps"
  resource_group_name  = var.resource_group_name
  virtual_network_name = var.vnet_name
  address_prefixes     = [var.container_apps_subnet_cidr]

  delegation {
    name = "delegation"
    service_delegation {
      name    = "Microsoft.App/environments"
      actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
    }
  }
}

2. Container Apps Environment com load balancer interno

Criei o submódulo em resources/container-apps/ para provisionar o ambiente. O ponto chave aqui é o internal_load_balancer_enabled = true, que garante que o ingress fica privado e sem exposição direta à internet:

resource "azurerm_container_app_environment" "this" {
  name                           = var.environment_name
  location                       = var.location
  resource_group_name            = var.resource_group_name
  log_analytics_workspace_id     = var.log_analytics_workspace_id
  infrastructure_subnet_id       = var.subnet_id
  internal_load_balancer_enabled = true
}

3. Container App com autenticação no ACR via Managed Identity

A identidade gerenciada criada no submódulo identity-rbac é reaproveitada para puxar a imagem do ACR com segurança — sem senhas guardadas em lugar nenhum:

resource "azurerm_container_app" "this" {
  name                         = var.app_name
  container_app_environment_id = azurerm_container_app_environment.this.id
  resource_group_name          = var.resource_group_name
  revision_mode                = "Single"

  identity {
    type         = "UserAssigned"
    identity_ids = [var.managed_identity_id]
  }

  registry {
    server   = var.acr_login_server
    identity = var.managed_identity_id
  }

  ingress {
    external_enabled = false
    target_port      = var.container_port
    traffic_weight {
      latest_revision = true
      percentage      = 100
    }
  }

  template {
    container {
      name   = var.app_name
      image  = var.container_image
      cpu    = var.cpu
      memory = var.memory

      env {
        name  = "ENVIRONMENT"
        value = var.environment
      }
    }
    min_replicas = var.min_replicas
    max_replicas = var.max_replicas
  }
}

Deploy em múltiplas subscriptions

Um dos requisitos era permitir deploy em regiões e subscriptions distintas com o mínimo de configuração. A solução ficou bem limpa: cada ambiente tem seu próprio arquivo .tfvars, com os valores específicos daquela subscription:

location            = "brazilsouth"
environment         = "dev"
resource_group_name = "rg-platform-dev"
vnet_address_space  = "10.0.0.0/16"

Para trocar de subscription antes do deploy, executo este comando no terminal:

az account set --subscription "subscription-alvo"

Com isso, não preciso alterar o código Terraform. Só mudo o contexto do Azure CLI e sigo para o plan.

Validação e segurança antes do apply

Antes de qualquer terraform apply, sempre passo pelo pipeline de validação. Esses três comandos me salvaram várias vezes de subir configuração com erro de sintaxe ou recurso mal referenciado:

terraform fmt -recursive
terraform validate
terraform plan -var-file="dev.tfvars" -out="tfplan"

Para garantir permissão de pull no ACR, adicionei um role_assignment condicional. O count garante que o recurso só é criado quando a referência do ACR é informada; se não for, esse bloco é ignorado sem impacto no restante do ambiente:

resource "azurerm_role_assignment" "acr_pull" {
        count                = var.acr_reference != "" ? 1 : 0
        scope                = var.acr_reference
  role_definition_name = "AcrPull"
        principal_id         = var.managed_identity_principal
}

Conclusão

Trabalhar com Terraform modular no Azure me trouxe resultados bem concretos:

A chave está na separação de responsabilidades: cada módulo faz uma coisa bem feita, e o módulo pai junta tudo. Quando você precisa de algo novo, é só estender — não refatorar o que já existe.

Referências