03 - Terraform Workspaces e Environments
video: https://youtu.be/... no frontmatter da nota.Terraform Workspaces e Environments
A pergunta estrutural central de qualquer operação de Terraform madura é como organizar o mesmo desenho de infraestrutura para dev, staging e produção sem duplicar código e sem acoplar ambientes que devem evoluir separados. Terraform oferece três respostas principais: workspaces, que isolam states sob o mesmo código; diretórios separados por ambiente, que copiam a raiz e divergem por arquivos; e módulos raiz por ambiente, que mantêm raízes finas consumindo módulos versionados com parâmetros. Nenhuma abordagem vence em todas as dimensões, e a escolha envolve trade-offs reais de isolamento, divergência de código, custo de coordenação e segurança de execução. A decisão importa mais do que parece: ela define como uma mudança é promovida entre ambientes, como uma variável diverge por ambiente, como um incidente em produção fica contido e como a recuperação de desastre executa. Este arquivo cobre o funcionamento dos workspaces, as três abordagens com suas consequências práticas, os critérios de trade-off e a estrutura de repositório que operações maduras costumam adotar.
Conceito: ambiente como unidade de isolamento
Ambiente, no contexto de IaC, é um conjunto de recursos com ciclo de vida próprio, own state, own credentials, own risk: dev se destrói e recria livremente, staging se recicla para testar promoção, produção tem acesso restrito e gate humano. O IaC precisa expressar "o mesmo desenho, ambientes diferentes", e a forma de expressar isso é exatamente a decisão deste arquivo.
Dois riscos organizam a avaliação das abordagens. O risco de acoplamento: quando dev e produção compartilham demais, uma mudança de dev aplica em produção por acidente, e a recuperação de produção depende de dev. O risco de divergência: quando os ambientes copiam código em vez de parametrizar, o desenho evolui em produção e staging fica para trás, e a promoção para de testar o que será aplicado. Toda abordagem é uma posição nesse eixo entre acoplamento e divergência.
A variável de calibragem é o porte da operação e o grau de separação exigido. Um time pequeno com um produto simples convive bem com workspaces; uma operação com múltiplos times, compliance e contas separadas raramente convive com menos que diretórios ou raízes por ambiente, e frequentemente com backends e pipelines separados também.
Vale fixar o vocabulário antes das comparações: state, o registro por ambiente; backend, o mecanismo remoto que guarda e tranca o state; raiz, o diretório de execução; e ambiente, a fronteira de risco. A discussão de workspaces e ambientes é, no fundo, uma discussão de quantas fronteiras o desenho precisa e onde cada uma vive.
Workspaces: isolamento de state no mesmo código
Um workspace é um state separado sob o mesmo código e o mesmo backend: terraform.workspace devolve o nome do workspace corrente, e a CLI gerencia a lista e a seleção. O mesmo código aplicado em dois workspaces cria dois conjuntos completos de recursos, com dois estados independentes e dois locks independentes. A promoção, nesse modelo, é literalmente trocar de workspace e aplicar, pois o código é idêntico por construção.
A abordagem tem um ganho imediato: divergência de desenho é impossível por construção, e o custo de adicionar ambiente é uma lista de valores por workspace. O preço vem em duas formas. A primeira é a lógica condicional que o workspace obriga a plantar no código: terraform.workspace == "producao" ? "t3.large" : "t3.small" escala mal e espalha decisão de ambiente pelo meio da implementação. A segunda é o isolamento de segurança fraco: mesmo código, mesmo backend e mesma pipeline significam que a credencial que aplica em dev frequentemente alcança produção.
O uso saudável dos workspaces é o de escala pequena e risco uniforme: ambientes efêmeros, ramificações de teste, regiões irmãs com o mesmo desenho. Como mecanismo principal de separação entre dev e produção em operação real, o padrão é considerado frágil, e a literatura operacional converge para diretórios ou raízes separadas quando há riscos e donos diferentes por ambiente.
# Ciclo de workspaces: mesmíssimo código, states independentes
terraform workspace list # lista os workspaces existentes
terraform workspace new staging # cria workspace com state próprio
terraform workspace select producao # troca o state corrente
terraform plan -var-file=env/producao.tfvars
terraform apply # aplica só sobre o state selecionado
Diretórios separados por ambiente
A segunda abordagem mantém uma pasta por ambiente, cada uma com a própria raiz, o próprio backend e o próprio arquivo de variáveis. O desenho é compartilhado por módulos versionados, e a pasta do ambiente é fina: chama módulos, passa parâmetros e conecta outputs. A divergência de desenho continua impossível, pois desenho vive em módulo; a divergência de parametrização é explícita e revisada em cada pasta.
Os ganhos são de operação e segurança: cada pasta tem backend e credenciais próprios, o que materializa a fronteira entre ambientes; pipelines separadas por pasta dão independência de execução e gate específico de produção; e o plano de cada ambiente é pequeno e legível. Os custos são de coordenação: uma mudança que atravessa ambientes abre vários pull requests ou um PR multilinha com arquivos por ambiente, e o risco de drift de versão de módulo entre pastas exige disciplina de bump coordenado.
O anti-padrão do modelo é a pasta gorda: ambiente que copia desenho em vez de consumir módulo. Quando a pasta de produção tem trezentas linhas de recursos próprios, a abordagem degenerou em cópia com pasta, e a divergência que os diretórios prometiam evitar voltou pela porta de trás. A regra é simples: pasta de ambiente é parametrização, nunca desenho.
No eixo de execução, o modelo também simplifica a automação: cada pasta é um job de pipeline com permissão e gate próprios, e a adição de ambiente é copiar a estrutura, ajustar backend e tfvars e abrir a pipeline correspondente. O custo de coordenação aparece nas mudanças transversais, e por isso times que adotam pastas costumam acompanhar com convenções de PR multilinha ou automação que propaga o bump de versão por todas as pastas de uma vez.
Módulos raiz por ambiente: a composição canônica
A terceira abordagem generaliza a segunda e é a convergência típica de operações grandes: um repositório de módulos versionado, e raízes por ambiente que compõem os módulos com parâmetros. A raiz é a unidade de execução e de state, e o conjunto de raízes é o mapa da infraestrutura. A promoção de código é a troca da tag de versão do módulo, primeiro em staging, validada, depois em produção, com planos provando a equivalência do desenho.
O modelo explicita as responsabilidades: o repositório de módulos é produto, com changelog, testes e dono; as raízes por ambiente são consumo, com política de versão e pipeline própria. O custo é o mais alto dos três modelos em coordenação, e o benefício é o mais forte em isolamento e auditabilidade, pois cada ambiente é uma fronteira física de backend, credencial e pipeline.
A migração para esse modelo a partir de qualquer dos outros segue o mesmo ritual: extrair o desenho das raízes para módulos, versionar, converter cada ambiente em raiz fina e validar com plan de equivalência em cada passo. É trabalho de refactor com produção em pé, executado por componente, e o plano sem diff é a prova de cada etapa.
A estrutura de repositório abaixo ilustra a composição típica, com biblioteca separada e raízes finas:
infra/
modules/ # biblioteca compartilhada, versionada por tag
vpc/ # desenho de rede completo e testado
cluster/ # desenho de computação com auto scaling
banco/ # desenho de dados com backup e réplica
envs/
dev/ # raiz fina: só parâmetros e composição
main.tf
backend.tf # backend próprio do ambiente
dev.tfvars
staging/
main.tf
backend.tf
staging.tfvars
producao/
main.tf
backend.tf # credencial e pipeline restritas
producao.tfvars
pipelines/ # definição de CI por raiz de ambiente
Trade-offs entre as abordagens
| Dimensão | Workspaces | Diretórios por ambiente | Raízes com módulos versionados |
|---|---|---|---|
| Divergência de desenho | Impossível por construção | Baixa, desenho em módulo | Baixa, desenho em módulo |
| Divergência de versão | Inexistente | Risco entre pastas | Controlada por bump de tag |
| Isolamento de segurança | Fraco, mesmo backend | Forte, backend por pasta | Forte, backend por raiz |
| Gate e pipeline | Compartilhados | Por pasta | Por raiz |
| Parametrização por ambiente | Condicionais no código | tfvars por pasta | tfvars e parâmetros na raiz |
| Custo de novo ambiente | Baixo | Médio | Médio-alto |
| Custo de coordenação | Baixo | Médio | Alto |
| Escala da operação | Times pequenos | Média | Grande, multi-time |
A leitura de decisão: comece simples e migre quando o custo do isolamento ficar menor que o custo dos incidentes. Workspaces servem enquanto os ambientes têm o mesmo dono e o mesmo risco; a primeira diferença séria de compliance, credencial ou ritmo de mudança entre ambientes é o gatilho clássico de migração para diretórios ou raízes separadas.
A migração entre abordagens é, ela mesma, um procedimento com plano: os recursos precisam ser movidos entre states sem serem recriados, e o teste de cada passo é um plan vazio, sem diff, sobre o ambiente migrado. Fazer essa migração com plano executável e checkpoints é o que a separa de uma reconstrução disfarçada.
Promoção de código entre ambientes
Promoção é o processo que leva o mesmo código do ambiente de menor risco ao de maior risco, e ela é a prova viva de que a abordagem escolhida funciona. O fluxo canônico: mudança entra por pull request com plano de dev anexado; aplicada em dev e validada, a mesma versão é aplicada em staging, com o plano provando que o desenho é idêntico; aprovada em staging, a produção recebe a mesma versão com gate humano. O que promove é a versão, não o copy de arquivo.
Com módulos versionados, a promoção é um bump de tag consumido por cada raiz, e a disciplina central é a janela: staging e produção não podem viver em versões distintas por muito tempo, pois a janela é exatamente o que a promoção existe para reduzir. Times maduros automatizam a promoção com pipeline que detecta a versão em staging e abre o PR de produção, e mantêm o gate humano no merge.
A promoção também é o momento de testar o rollback: a versão anterior permanece aplicável, e revertê-la em staging é parte do exercício. Promoção sem rollback testado não é promoção, é estreia, e produção não é lugar de estreia.
Variáveis por ambiente
O mecanismo de parametrização canônico é o arquivo de variáveis por ambiente, o .tfvars, carregado explicitamente na execução. O contrato saudável mantém o conjunto de variáveis igual entre ambientes, com valores divergentes, e trata a diferença de estrutura como sinal de que a variável pertence ao módulo, não ao ambiente. Variáveis obrigatórias sem padrão exigem valor em cada tfvars, o que é bom: ninguém esquece de declarar o tamanho do banco de produção.
Valores sensíveis não vivem em tfvars comitados: credenciais entram por gerenciador de segredos ou por injeção da pipeline, e o tfvars guarda referência, não valor. A validação de variáveis, tipos e restrições, executa no plan, e é a primeira barreira contra o parâmetro absurdo que só explodiria no apply de produção.
# envs/producao/producao.tfvars: parâmetros do ambiente
ambiente = "producao"
regiao = "sa-east-1"
tamanho = "large"
replicas = 3
versao_cluster = "v4.2.1" # tag do módulo promovida
O Inventário de valores por ambiente também é documento vivo: quando dev e produção divergem em uma variável, a divergência deve ter dono e motivo, senão é bug de parametrização esperando incidente. Times com governança madura revisam o diff entre arquivos de variáveis como parte da revisão de promoção, pois é ali que a diferença entre "mesmo desenho" e "desenho parecido" aparece primeiro.
Erros comuns e boas práticas
- Usar workspaces como separação de segurança entre dev e produção; mesmo backend e mesma credencial invalidam a fronteira que o ambiente deveria ser.
- Deixar lógica de ambiente espalhada em condicionais pelo código; parametrização por arquivo é legível, auditável e aplicável por pipeline.
- Permitir que pasta de ambiente cresça com desenho próprio; pasta é parametrização, e desenho pertence a módulo versionado.
- Promover copiando arquivos entre ambientes; promoção é versão, e a mesma tag precisa ser consumida por cada raiz.
- Comitar segredos em tfvars; valor sensível vive em cofre, e o arquivo carrega referência.
- Manter janelas longas de versão entre staging e produção; a janela é onde o teste deixa de representar o que será aplicado.
- Não isolar pipelines por ambiente; pipeline única que aplica tudo é o acoplamento mais caro do modelo.
- Criar ambiente novo por cópia de pasta sem revisar parâmetros; ambiente herdado por cópia carrega divergência silenciosa desde o primeiro dia.
- Esquecer o rollback no processo de promoção; versão anterior precisa continuar aplicável, e o teste de reversão faz parte do exercício de promoção.