02 - Remote State (Backend)
video: https://youtu.be/... no frontmatter da nota.Remote State (Backend)
O state é o registro que mapeia os recursos declarados no código para os identificadores reais na nuvem, e é o ativo mais crítico de uma operação de Terraform: sem ele, a ferramenta não sabe o que criou, o que pode alterar e o que pertence a quem. Remote state é a prática de viver esse registro em um backend remoto compartilhado, com lock de execução, criptografia e backup, em vez de em disco local; backend é o nome do mecanismo que fornece essas garantias. Em produção com mais de uma pessoa, o state remoto não é melhoria, é pré-requisito: dois planos calculados sobre estados diferentes produzem operações contraditórias e, no pior caso, perda de recurso por descoordenação. Além da corrida de execução, o state carrega um risco específico: por padrão ele guarda valores em texto claro, incluindo atributos sensíveis de recursos e segredos que passam por ele, o que exige criptografia em repouso, controle de acesso restrito e disciplina de segredos. Este arquivo cobre por que o state não vive no disco local, o backend S3 com lock em DynamoDB, o isolamento por ambiente e por componente, e o tratamento da sensibilidade de dados no state.
Conceito: o que o state registra e por que ele importa
O state é um arquivo que guarda, por recurso declarado, o identificador real na nuvem e os atributos capturados na última sincronização. É por causa dele que aws_instance.app no código significa "aquela instância com aquele id", e é por causa dele que o plan consegue calcular diff sem interrogar a conta inteira a cada execução. Destruir ou perder o state não destrói os recursos, mas deixa a operação cega: reimportar dezenas ou centenas de recursos à mão é o cenário de recuperação que ninguém quer executar sob pressão.
O state também resolve a coordenação dentro do próprio código: outputs de um conjunto de recursos ficam registrados e podem ser lidos por outras raízes via remote state data source, formando a malha de dependências entre componentes sem acoplamento de execução. Essa leitura entre raízes é o mecanismo padrão para compartilhar, por exemplo, ids de sub-redes criadas pela raiz de rede com a raiz que cria o cluster.
Como o state é a verdade operacional da ferramenta, seu ciclo de vida é parecido com o de um banco de dados de produção: precisa de backup, de histórico, de acesso controlado e de plano de recuperação. A analogia é útil para escolher a postura certa: ninguém mantém o banco de produção no notebook de um engenheiro, e o state merece o mesmo cuidado.
O problema do state em disco local
O primeiro problema é a corrida de execução. Duas pessoas com cópias locais do state calculam planos sobre mundos diferentes e aplicam sobre a mesma realidade; o resultado é estado corrompido, recurso duplicado ou recurso órfão. Mesmo com uma única pessoa, o state local vive no notebook: disco formatado, máquina trocada, caminho de projeto movido, e o mapeamento entre código e realidade desaparece.
Há também o problema da colaboração implícita no crescimento do time. O state local transformou cada engenheiro em detentor de uma versão diferente da verdade, e a resolução de divergências entre cópias é um procedimento manual, caro e sem trilha, o exato contrário da promessa de fonte de única verdade que trouxe o time para o IaC.
O segundo problema é a auditoria. State local não tem histórico compartilhado, não tem controle de quem aplicou o quê e não pode ser inspecionado pela operação de segurança. O terceiro problema é o CI: pipeline não pode calcular plano confiável sobre um arquivo que mora na máquina de alguém, e a automação de infraestrutura, o motivo de existir do IaC, fica impossível de forma honesta.
A solução é estrutural e barata: backend remoto, com lock nativo e criptografia, configurado no primeiro dia do projeto. O custo de migração de state local para remoto depois que o repositório cresce, coordenação de todos, congelação de execuções, verificação de integridade, é alto o suficiente para justificar nunca começar errado.
Backend S3 com lock em DynamoDB
O backend canônico na AWS combina S3 e DynamoDB: o S3 guarda o arquivo de state com versionamento e criptografia; a tabela DynamoDB fornece o lock exclusivo, um item por caminho de state, criado no início da execução e liberado no fim. O versionamento do bucket é a rede de recuperação, pois o histórico de objetos permite restaurar um state anterior íntegro após corrupção ou aplicação errada.
A configuração do bucket merece o mesmo rigor de qualquer dado crítico: versionamento obrigatório, criptografia em repouso com KMS, acesso restrito por política, bloqueio de acesso público e proteção contra deleção acidental em produção. A tabela de locks é simples, mas precisa de time to live e de política de acesso mínima; ambas as peças são, tipicamente, provisionadas pelo próprio IaC, o problema de bootstrap clássico do módulo.
O bootstrap do backend é o exercício que introduz o conceito na prática: o código que provisiona o bucket e a tabela de locks não pode depender de um backend que ainda não existe. A solução canônica é um bootstrap separado, um mínimo de infraestrutura criado por ferramenta local ou CLI, documentado e versionado, que toda a operação usa depois como backend; a peça é pequena, mas é a fundação, e merece dono e revisão.
A leitura entre raízes usa o mesmo backend como mecanismo de compartilhamento. A raiz consumidora declara um data source de remote state apontando para o caminho do produtor, e o backend expõe os outputs registrados; a dependência fica explícita no código, com a contrapartida de ordem de execução que a malha cria.
# Backend declarado por raiz: state remoto, lock e criptografia
terraform {
backend "s3" {
bucket = "infra-state-producao"
key = "rede/terraform.tfstate" # um caminho por componente
region = "sa-east-1"
dynamodb_table = "terraform-locks" # lock exclusivo por caminho
encrypt = true # criptografia no bucket
kms_key_id = "alias/terraform-state"
}
}
# Operação típica do state em produção
terraform init -reconfigure # aponta a raiz para o backend remoto
terraform state list # inventário de recursos rastreados
terraform state pull > state.bkp.tfstate # cópia de segurança local pontual
terraform force-unlock <LOCK_ID> # só após confirmar ausência de execução
State locking e corrida de execução
O lock existe para que uma execução seja exclusiva por state: enquanto a pipeline aplica a raiz de rede, qualquer outro plan ou apply sobre a mesma raiz falha com mensagem de lock, e a ferramenta recusa prosseguir. A garantia cobre o caso mais caro, dois applies concorrentes, e também planos longos que, sem lock, aplicariam um delta calculado sobre um mundo que já mudou.
# Compartilhamento entre raízes: consumindo outputs do backend
data "terraform_remote_state" "rede" {
backend = "s3"
config = {
bucket = "infra-state-producao"
key = "rede/terraform.tfstate" # caminho da raiz produtora
region = "sa-east-1"
}
}
resource "aws_db_subnet_group" "banco" {
name = "banco-producao"
subnet_ids = data.terraform_remote_state.rede.outputs.subnets_privadas
}
O lock é também a base do fluxo de pipeline saudável: o gate de aprovação em produção, o plano revisado no pull request e a execução subsequente precisam acontecer sobre o mesmo state, e o lock é o que fecha a janela entre aprovação e execução. Ferramentas de CI que orquestram plan e apply em jobs separados dependem do lock para não aplicar um plano velho.
O incidente operacional típico é o lock órfão: execução morta no meio, lock ficou registrado, e o time precisa decidir entre esperar e forçar a liberação. A regra é procedimento, não impulso: confirmar em logs e na conta que nenhuma execução está de fato em curso, documentar o motivo e só então force-unlock. Forçar liberação com execução viva é o caminho curto para dois applies concorrentes, exatamente o que o lock existe para impedir.
Isolamento de state por ambiente e por componente
State grande é acoplamento grande: se produção inteira vive em um único arquivo, qualquer mudança exige lock global, o plano é enorme e o blast radius de um erro de apply é a plataforma inteira. A prática de produção isola por dois eixos: por ambiente, produção não compartilha state com staging, e por componente, rede, banco, cluster e aplicação são raízes separadas com caminhos de state distintos.
O eixo de ambiente é uma barreira de segurança tanto quanto de operação: credenciais, permissões e até backends separados, com o bucket de produção acessível apenas pela pipeline de produção. Essa separação é o que impede o erro de contexto, o apply de dev com credencial de produção, e é pré-requisito de qualquer política de acesso mínima. O eixo de componente, por sua vez, reduz o lock de execução a uma superfície pequena e o plano a um diff legível.
A fronteira entre componentes cria a necessidade de compartilhar dados entre raízes, e o mecanismo padrão é o remote state data source: a raiz de banco lê os outputs registrados pela raiz de rede. O desenho da malha de dependências é decisão arquitetural, camadas estritas, rede primeiro, banco depois, aplicação por último, costumam ser mais previsíveis em produção que grafos arbitrários, porque a ordem de recuperação em desastre existe e é executável.
A granularidade fina tem um limite prático: cada raiz é um plano, um lock e uma pipeline, e multiplicar raízes sem necessidade fragmenta a operação e multiplica custo de coordenação. O critério funcional é o ciclo de vida: o que muda junto e se recupera junto pode viver junto; o que tem dono, ritmo e risco diferentes merece raiz própria. A convenção de caminhos, por exemplo, ambiente/componente/terraform.tfstate, é o que mantém o conjunto navegável.
| Estratégia de isolamento | Lock afeta | Blast radius de erro | Custo de coordenação |
|---|---|---|---|
| State único global | Toda a plataforma | Toda a plataforma | Baixo, uma raiz só |
| Por ambiente | Ambiente inteiro por mudança | Um ambiente | Médio |
| Por componente | Um componente por mudança | Um componente | Médio-alto, malha de outputs |
| Ambiente e componente | Raiz mínima | Raiz mínima | Alto, exige convenção de caminhos |
A leitura da tabela: estado único é aceitável em projeto de aprendizado; por ambiente é o piso de produção; por componente é o padrão recomendado em plataformas reais, e a combinação ambiente e componente é o que operações grandes acabam adotando à medida que o custo de coordenação fica menor que o custo dos incidentes de acoplamento.
Recuperação, importação e migração de state
O plano de recuperação do state tem três cenários canônicos. Corrupção ou perda do arquivo atual: restaurar a versão anterior do objeto no bucket versionado, conferir o inventário com um plan e seguir. Recurso criado fora do fluxo, manual ou por outra ferramenta: importar o recurso para o state, com o id real, e ajustar o código até o plano ficar sem diff. Mudança estrutural no repositório, como mover recurso de uma raiz para outra: mover o recurso no state com state mv ou usar backend de destino, mantendo o mapeamento coerente com o código.
A importação merece disciplina própria: importar é dizer ao state que aquele recurso real pertence àquele endereço do código, e o passo seguinte obrigatório é o plan que verifica se a descrição bate com a realidade. Importação sem plan de conferência produz o pior estado possível, a ferramenta acha que sabe, e a divergência real só aparece no apply de emergência.
A migração de backend, do local para o S3 ou entre backends, é operação suportada nativamente pela reconfiguração, mas é execução de baixa velocidade: congela-se a raiz, sincronizam-se cópias, executa-se a reconfiguração e valida-se o inventário. O backup anterior à migração e o teste de um plan vazio, sem diff, são os dois checkpoints que transformam a migração em rotina, e não em risco.
Sensibilidade de dados no state e criptografia
O estado guarda, em texto claro por padrão, atributos dos recursos, e alguns atributos são sensíveis por natureza: secrets gerados por recursos, passwords de banco, chaves de acesso, e o conteúdo de variáveis marcadas como sensitive. O marcador de sensibilidade protege a exibição em log e em CLI, mas não o arquivo: quem lê o state lê o valor. Essa é a regra que organiza o tratamento do state como dado sensível.
As garantias mínimas em produção são: criptografia em repouso no backend, com chave KMS própria; política de acesso restrita ao menor grupo possível; versionamento com retenção para recuperação; e atenção ao endpoint, state entre regiões ou entre contas atravessando rede pública é risco de configuração, não inevitabilidade. Backends gerenciados, como o serviço do próprio Terraform Cloud ou do OpenTofu, centralizam parte dessas garantias, com criptografia e permissão por workspace.
A prática de segredos, porém, é a defesa real: segredos de aplicação não devem nascer por IaC de forma que acabem no state. A arquitetura comum cria o cofre por IaC, Secrets Manager, SSM Parameter Store, Vault, e injeta o valor da secret na aplicação em runtime; o state guarda o nome do cofre, não o valor. Onde o valor precisa existir, como password gerado por random provider, a rotação e o ciclo de vida precisam estar desenhados, e o acesso ao backend precisa refletir a sensibilidade que o arquivo carrega.
Erros comuns e boas práticas
- Começar projeto com state local e migrar depois; o custo de migração cresce com o repositório, e o backend remoto custa minutos de configuração no primeiro dia.
- Provisionar o backend de state com o mesmo IaC que depende dele; o bootstrap precisa ser separado, pequeno e versionado.
- Operar sem lock em paralelo com lock; corrida de execução é a forma mais rápida de corromper inventário e duplicar recurso.
- Usar force-unlock sem investigação; libere o lock só após confirmar que nenhuma execução está viva, e registre o incidente.
- Compartilhar um único state para produção inteira; isole por ambiente e por componente, e trate cada raiz como unidade de lock e de plano.
- Deixar o bucket de state sem versionamento e sem criptografia; o histórico de objetos é o plano de recuperação do pior dia da operação.
- Guardar segredos de aplicação no state; crie o cofre por IaC e injete valores em runtime, e trate o state como sensível mesmo assim.
- Permitir acesso amplo ao backend; política mínima por caminho de state é o que separa ambientes de verdade.
- Não ter backup testado do state; cópia restaurável precisa de exercício periódico, e state que nunca foi restaurado é backup sem garantia.
- Comitar state em repositório Git; além de sensível, o arquivo gera corrida de merge e história inútil, e o backend remoto já resolve o que o Git prometia.
- Executar importação sem plan de conferência; o state que se declara correto sem verificação é a divergência mais cara de descobrir depois.
- Ignorar a ordem da malha de dependências entre raízes; recuperação de desastre sem ordem de reconstrução executável transforma o desenho em quebra-cabeça no pior momento.