Pular para o conteúdo

Testando infraestrutura

Avançado16 min de leituraiac

Código de infraestrutura tem uma propriedade desagradável: o erro só aparece quando já mudou alguma coisa real. Não existe “rodar localmente e ver se funciona” para uma regra de firewall. Por isso o teste aqui é organizado por quanto custa descobrir o problema, do mais barato para o mais caro.

Camada Custo O que pega Quando roda
Formato e lint segundos erro de sintaxe, antipadrão, provider desatualizado a cada commit
Teste de plano segundos valores errados, recurso ausente, padrão inseguro a cada PR
Política sobre o plano segundos violação de regra da organização a cada PR
Estimativa de custo segundos surpresa de fatura a cada PR
Integração real minutos a horas o que só a nuvem revela merge, ou por noite

A base larga é onde está quase todo o retorno. Muita gente pula direto para o teste de integração, gasta caro e mantém pouco.

Janela do terminal
terraform fmt -check -recursive # falha se alguém não formatou
terraform init -backend=false # sem tocar no state
terraform validate # sintaxe e coerência de tipos
tflint --recursive # antipadrões e regras do provider
trivy config . # configuração insegura conhecida

Coloque isso em pre-commit e no CI. No pre-commit dá feedback em segundos; no CI garante que ninguém pulou o hook.

O terraform test nativo executa o plano em memória e afirma sobre o resultado — rápido o bastante para rodar em todo pull request e suficiente para a maioria dos erros de módulo.

tests/padroes.tftest.hcl
run "bucket_privado_por_padrao" {
command = plan
variables { nome = "teste", ambiente = "dev" }
assert {
condition = aws_s3_bucket_public_access_block.este.block_public_acls
error_message = "Bucket não pode permitir ACL pública por padrão."
}
assert {
condition = aws_s3_bucket_server_side_encryption_configuration.este != null
error_message = "Criptografia em repouso é obrigatória."
}
}
run "ambiente_invalido_falha" {
command = plan
variables { nome = "teste", ambiente = "producao" } # valor fora do enum
expect_failures = [var.ambiente]
}

Teste o caminho de erro também: validação que ninguém exercita é validação que pode estar quebrada.

O que vale para toda a organização não deve ser copiado em cada módulo — vira política central aplicada sobre o JSON do plano:

Janela do terminal
terraform plan -out=plano.bin
terraform show -json plano.bin > plano.json
conftest test plano.json --policy politicas/ # OPA/Rego

Regras que valem o esforço: nada público sem exceção aprovada, criptografia obrigatória, tag de dono e centro de custo, retenção mínima de backup, proibição de recurso fora das regiões permitidas. Comece em modo aviso e só depois bloqueie — a mesma sequência da página de política como código.

E um teste específico de infraestrutura que salva o dia: falhe o pull request quando o plano destruir recurso com estado, exigindo aprovação explícita.

Janela do terminal
jq -e '[.resource_changes[]
| select(.change.actions | index("delete"))
| select(.type | test("db_instance|s3_bucket|efs_file_system"))] | length == 0' plano.json

Alguma coisa só aparece na nuvem: limite de cota, comportamento real de IAM, tempo de provisionamento, incompatibilidade entre versões. Para isso, provisione de verdade em conta isolada e destrua no fim.

// Terratest — cria, verifica, destrói
func TestBucketPrivado(t *testing.T) {
opts := &terraform.Options{TerraformDir: "../exemplos/basico"}
defer terraform.Destroy(t, opts) // sempre com defer, mesmo se falhar
terraform.InitAndApply(t, opts)
arn := terraform.Output(t, opts, "bucket_arn")
assert.NotEmpty(t, arn)
aws.AssertS3BucketPolicyDenyPublic(t, "sa-east-1", nomeDoBucket)
}

Regras para isso não virar um problema próprio: conta separada e descartável, nomes com sufixo aleatório, limpeza agendada para o que escapar (o destroy vai falhar algum dia), e execução por noite ou no merge — não em cada push, ou o feedback fica lento e caro.

Teste de integração cobre módulo compartilhado e caminhos críticos. Não tente cobrir toda a infraestrutura assim: o custo cresce mais rápido que o valor.

Infraestrutura tem uma dimensão que aplicação não tem: cada linha aprovada é uma fatura mensal. Traga o número para a revisão.

Janela do terminal
infracost breakdown --path plano.json
infracost diff --path plano.json --format github-comment

Combine com uma política simples: acima de um limite mensal, exige aprovação de mais uma pessoa. Isso transforma discussão de fim de mês em decisão consciente no momento certo.

PR: fmt → validate → tflint → trivy config → terraform test → plan
→ conftest → infracost → comentário com o plano no PR
Merge: plan salvo → aprovação → apply do plano aprovado → testes de integração
Diário: plan -detailed-exitcode em todos os ambientes → alerta de drift

O item diário costuma ser o mais esquecido e é o que mantém o código honesto: sem detecção de drift, o repositório vira ficção em alguns meses.

Próximo passo: você fechou a trilha de IaC. Leve as decisões para o provedor em Nuvem e veja o custo no PR em Custo no pull request.