Infraestrutura

O que é Terraform?

Infraestrutura como código com Terraform. Provisione e gerencie servidores, bancos de dados e redes na nuvem de forma declarativa e versionada.

O que é Terraform?

Terraform é uma ferramenta de Infraestrutura como Código (IaC) criada pela HashiCorp. Com ela, você descreve em arquivos de texto toda a infraestrutura que seu projeto precisa — servidores, bancos de dados, redes, DNS, certificados SSL — e o Terraform cria, atualiza e destrói esses recursos automaticamente.

Em vez de clicar em painéis de controle e correr o risco de esquecer uma configuração, você versiona tudo no Git e recria o ambiente idêntico quando precisar. Isso elimina o famoso problema de "funcionava na minha máquina" para infraestrutura: com Terraform, dev, staging e produção são criados com o mesmo código.

Por que usar Terraform?

  • Reprodutibilidade: recrie seu ambiente de produção em minutos com um comando
  • Versionamento: veja no git quem mudou o quê na infraestrutura e quando
  • Multi-cloud: gerencie Hetzner, DigitalOcean, GCP com a mesma ferramenta e workflow
  • Previsibilidade: terraform plan mostra exatamente o que vai mudar antes de aplicar
  • Módulos reutilizáveis: crie padrões que funcionam em dev, staging e prod
  • Estado declarativo: você descreve o estado desejado, o Terraform descobre o que precisa mudar
  • Destroy seguro: desfaça tudo com um único comando, sem recursos órfãos

Instalação

# Linux (Ubuntu/Debian) — via repositório oficial HashiCorp
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | \
  sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

# macOS via Homebrew:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Verificar instalação:
terraform version
# Saída esperada: Terraform v1.x.x

Conceitos fundamentais

  • Provider: plugin que conecta o Terraform a um serviço (Hetzner, AWS, GCP, Cloudflare...)
  • Resource: um recurso de infraestrutura (servidor, banco, rede, DNS record)
  • Data Source: dados externos que o Terraform lê mas não gerencia (ex: imagem de SO disponível)
  • Variable: parâmetros configuráveis para tornar o código reutilizável
  • Output: valores expostos após o apply (IPs, IDs, URLs)
  • State: arquivo que registra o estado atual da infraestrutura gerenciada
  • Module: agrupamento de recursos reutilizáveis
  • Workspace: ambientes isolados (dev/staging/prod) com o mesmo código

Exemplo completo: Servidor na Hetzner com Firewall

# main.tf

terraform {
  required_version = ">= 1.5"
  required_providers {
    hcloud = {
      source  = "hetznercloud/hcloud"
      version = "~> 1.45"
    }
  }
}

provider "hcloud" {
  token = var.hcloud_token
}

# Chave SSH
resource "hcloud_ssh_key" "deploy" {
  name       = "deploy-key-${var.environment}"
  public_key = var.ssh_public_key
}

# Firewall
resource "hcloud_firewall" "django_app" {
  name = "django-app-${var.environment}"

  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "22"
    source_ips = var.admin_ips
    description = "SSH apenas IPs autorizados"
  }

  rule {
    direction  = "in"
    protocol   = "tcp"
    port       = "80"
    source_ips = ["0.0.0.0/0", "::/0"]
    description = "HTTP público"
  }

  rule {
    direction  = "in"
    protocol   = "tcp"
    port       = "443"
    source_ips = ["0.0.0.0/0", "::/0"]
    description = "HTTPS público"
  }
}

# Servidor principal
resource "hcloud_server" "web" {
  name        = "django-${var.environment}"
  image       = "ubuntu-24.04"
  server_type = var.server_type   # cx22 = 2vCPU 4GB (~€4/mês)
  location    = var.location      # nbg1, fsn1, hel1, ash (EUA)

  ssh_keys     = [hcloud_ssh_key.deploy.id]
  firewall_ids = [hcloud_firewall.django_app.id]

  labels = {
    environment = var.environment
    app         = "django"
    managed_by  = "terraform"
  }

  lifecycle {
    prevent_destroy = true   # protege contra terraform destroy acidental
    ignore_changes  = [image]  # não recria ao mudar versão de imagem base
  }
}

# Volume para backups e uploads (opcional)
resource "hcloud_volume" "data" {
  count    = var.environment == "production" ? 1 : 0
  name     = "django-data-${var.environment}"
  size     = 50   # 50 GB
  location = var.location
  format   = "ext4"
}

resource "hcloud_volume_attachment" "data" {
  count     = var.environment == "production" ? 1 : 0
  volume_id = hcloud_volume.data[0].id
  server_id = hcloud_server.web.id
  automount = true
}

Variáveis e tfvars

# variables.tf
variable "hcloud_token" {
  description = "Token da API do Hetzner"
  type        = string
  sensitive   = true   # não exibe no output
}

variable "environment" {
  description = "Ambiente (dev, staging, production)"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Ambiente deve ser dev, staging ou production."
  }
}

variable "server_type" {
  description = "Tipo de servidor Hetzner"
  type        = string
  default     = "cx22"
}

variable "location" {
  description = "Localização do datacenter"
  type        = string
  default     = "nbg1"
}

variable "ssh_public_key" {
  description = "Chave SSH pública para acesso ao servidor"
  type        = string
}

variable "admin_ips" {
  description = "IPs permitidos para acesso SSH"
  type        = list(string)
  default     = []
}

# outputs.tf
output "server_ip" {
  description = "IP público do servidor"
  value       = hcloud_server.web.ipv4_address
}

output "server_id" {
  description = "ID do servidor no Hetzner"
  value       = hcloud_server.web.id
}

# terraform.tfvars (NÃO comite no git!)
hcloud_token   = "seu-token-aqui"
environment    = "production"
server_type    = "cx22"
location       = "nbg1"
ssh_public_key = "ssh-ed25519 AAAA..."
admin_ips      = ["seu.ip.aqui/32"]

Remote State — estado em produção de equipe

Em times ou em produção, o state nunca deve ficar local. Configure um backend remoto para compartilhar o estado e evitar conflitos:

# backend.tf — usando Terraform Cloud (gratuito para times pequenos)
terraform {
  cloud {
    organization = "minha-empresa"
    workspaces {
      name = "django-production"
    }
  }
}

# OU usando um bucket S3-compatible (ex: Hetzner Object Storage, Cloudflare R2):
terraform {
  backend "s3" {
    bucket                      = "meu-terraform-state"
    key                         = "django/production/terraform.tfstate"
    region                      = "us-east-1"
    endpoint                    = "https://s3.us-east-1.amazonaws.com"

    # Para Hetzner Object Storage:
    # endpoint = "https://fsn1.your-objectstorage.com"
    # region   = "us-east-1"  # valor fictício, obrigatório para S3 compat.

    # Previne leitura de state region da AWS
    skip_region_validation      = true
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    use_path_style              = true
  }
}

Fluxo de trabalho completo

  1. Escreva os arquivos .tf e preencha o terraform.tfvars
  2. terraform fmt — formata os arquivos automaticamente
  3. terraform validate — valida a sintaxe e lógica sem contatar a API
  4. terraform init — baixa os providers e configura o backend
  5. terraform plan -out=plano.bin — gera e salva o plano de execução
  6. Revise o plano com atenção — especialmente as linhas com - (destruição)
  7. terraform apply plano.bin — aplica exatamente o plano revisado
  8. terraform show — mostra o estado atual da infraestrutura
  9. terraform output — exibe os outputs (IPs, etc.)
# Comandos essenciais do dia a dia
terraform fmt            # formata .tf (execute sempre antes de commit)
terraform validate       # valida sem chamar a API
terraform init           # inicializa, baixa providers
terraform plan           # previsão do que vai mudar
terraform apply          # aplica as mudanças
terraform destroy        # remove TODA a infraestrutura (cuidado!)
terraform state list     # lista todos os recursos gerenciados
terraform state show hcloud_server.web  # detalhes de um recurso
terraform import hcloud_server.web 12345678  # importa recurso existente
terraform output server_ip               # exibe o IP do servidor

Estrutura recomendada para projetos Django

infra/
├── main.tf              # recursos principais
├── variables.tf         # declaração de variáveis com tipos e validação
├── outputs.tf           # outputs (IPs, IDs, connection strings)
├── backend.tf           # configuração do backend remoto
├── terraform.tfvars     # valores das variáveis (adicione ao .gitignore!)
├── terraform.tfvars.example  # exemplo sem valores reais (comite este)
└── modules/
    ├── server/          # módulo de servidor
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    ├── database/        # módulo de banco de dados managed
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    ├── firewall/        # módulo de regras de firewall
    │   ├── main.tf
    │   └── variables.tf
    └── dns/             # módulo de DNS (ex: Cloudflare)
        ├── main.tf
        └── variables.tf
# .gitignore para projetos Terraform
*.tfvars                  # contém tokens e senhas
!*.tfvars.example         # mas comita os exemplos
.terraform/               # diretório de providers (grande)
.terraform.lock.hcl       # pode comitar — fixa versões dos providers
terraform.tfstate         # state local (use remote state em produção)
terraform.tfstate.backup
*.tfplan                  # planos gerados

Data Sources — lendo recursos existentes

Data sources permitem referenciar recursos que existem fora do Terraform ou foram criados separadamente:

# Ler imagem disponível no Hetzner
data "hcloud_image" "ubuntu" {
  name             = "ubuntu-24.04"
  type             = "system"
  architecture     = "x86"
  most_recent      = true
}

# Usar no resource:
resource "hcloud_server" "web" {
  image = data.hcloud_image.ubuntu.id
  ...
}

# Ler chave SSH já existente
data "hcloud_ssh_key" "existing" {
  name = "minha-chave-ja-cadastrada"
}

# Ler registros DNS do Cloudflare para adicionar novos
data "cloudflare_zone" "meusite" {
  name = "meusite.com"
}

resource "cloudflare_record" "web" {
  zone_id = data.cloudflare_zone.meusite.id
  name    = "@"
  value   = hcloud_server.web.ipv4_address
  type    = "A"
  proxied = true   # ativa CDN + DDoS protection do Cloudflare
}

Perguntas Frequentes

Terraform é só para AWS?

Não! Terraform é multi-cloud. Nos tutoriais do canal, usamos principalmente com Hetzner, DigitalOcean e GCP. O mesmo workflow funciona para qualquer provedor que tenha um provider Terraform — são mais de 3000 providers disponíveis no Terraform Registry.

Preciso saber uma linguagem de programação para usar Terraform?

Não no sentido tradicional. Terraform usa HCL (HashiCorp Configuration Language), que é uma linguagem declarativa bem simples de aprender. Em poucas horas você já consegue criar infraestrutura básica. Você descreve o que quer, não como criar.

O que acontece se eu rodar terraform destroy por engano?

O terraform destroy sempre pede confirmação digitando "yes" antes de deletar recursos. Em produção, adicione lifecycle { prevent_destroy = true } nos recursos críticos para que o Terraform recuse destruí-los mesmo com destroy.

Terraform é gratuito?

O Terraform open-source é gratuito. A HashiCorp mudou a licença em 2023 para BSL (Business Source License), mas para uso pessoal e projetos não-competitivos continua gratuito. Existe também o OpenTofu, fork open-source mantido pela comunidade.

Como organizo os arquivos Terraform para um projeto Django?

A estrutura que usamos no canal: environments/ (dev, staging, prod) com módulos reutilizáveis em modules/ (vpc, server, database, dns). Cada environment tem seus próprios tfvars e é um workspace independente.

O que é o Terraform State e onde armazená-lo?

O state (terraform.tfstate) é o arquivo que registra o estado atual da infraestrutura gerenciada. Em desenvolvimento, fica localmente. Em produção em equipe, SEMPRE use remote state — S3 (AWS), GCS (Google), Terraform Cloud ou o próprio bucket do provedor. Nunca comite o tfstate no git.

Qual a diferença entre terraform plan e terraform apply?

O plan mostra uma prévia do que será criado, modificado ou destruído — sem fazer nada de fato. O apply executa as mudanças. Sempre revise o plan antes do apply, especialmente em produção. Use "terraform plan -out=plano.bin && terraform apply plano.bin" para garantir que aplica exatamente o que foi revisado.

Como gerenciar segredos (senhas, tokens) no Terraform?

Nunca coloque segredos hardcoded nos arquivos .tf. Use variáveis (variable) com valores passados via terraform.tfvars (não comitado no git), variáveis de ambiente TF_VAR_nome, ou integração com Vault/AWS Secrets Manager via data sources.

Qual a diferença entre Terraform e Ansible?

Terraform é para provisionar infraestrutura (criar servidores, redes, bancos). Ansible é para configurar servidores já criados (instalar software, configurar nginx, fazer deploy). São complementares: Terraform cria o servidor, Ansible o configura.