Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto
Módulo 5 · Infraestrutura como Código Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
O Que é o State e Por Que Ele Existe
Quando o Terraform aplica uma configuração e cria uma instância EC2, ele precisa de alguma forma lembrar que aquela instância existe — que ela corresponde ao recurso aws_instance.servidor_web declarado no código. Na próxima vez que terraform plan for executado, o Terraform precisa comparar o que está declarado no código com o que realmente existe na nuvem. Sem um registro persistente dessa correspondência, cada plan seria um processo cego, incapaz de determinar o que já foi criado e o que ainda precisa ser criado.
Esse registro é o state — um arquivo JSON chamado terraform.tfstate que mapeia cada recurso declarado no código a um recurso real na infraestrutura, armazenando os atributos desse recurso conforme foram lidos após a criação.
O state é o componente mais crítico de qualquer projeto Terraform. Perdê-lo significa perder a capacidade do Terraform de gerenciar a infraestrutura que ele mesmo criou — os recursos continuam existindo na nuvem, mas o Terraform não sabe disso. Corrompê-lo pode resultar em recursos sendo destruídos e recriados desnecessariamente, causando downtime.
Entender profundamente como o state funciona é o que separa alguém que usa Terraform de alguém que o domina.
Anatomia do Arquivo de State
O terraform.tfstate é um arquivo JSON com estrutura bem definida. Inspecionar o estado de um projeto simples revela sua estrutura:
{
"version": 4,
"terraform_version": "1.7.0",
"serial": 12,
"lineage": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"outputs": {
"ip_publico": {
"value": "54.123.45.67",
"type": "string"
}
},
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "servidor_web",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t3.micro",
"id": "i-0abc123def456789",
"public_ip": "54.123.45.67",
"private_ip": "10.0.1.45",
"tags": {
"Name": "servidor-web",
"Environment": "producao"
}
}
}
]
}
]
}
Alguns campos merecem atenção especial:
serial — um contador incrementado a cada modificação do state. O Terraform usa esse campo para detectar conflitos quando múltiplos usuários tentam modificar o state simultaneamente.
lineage — um UUID único gerado quando o state é criado pela primeira vez. Impede que estados de projetos diferentes sejam confundidos acidentalmente.
attributes — todos os atributos do recurso conforme lidos da API da AWS após a criação. É com esses valores que o Terraform compara o estado desejado durante o plan.
O arquivo de state nunca deve ser editado manualmente exceto em situações de emergência muito específicas, usando sempre os comandos apropriados do Terraform.
O Problema do State Local
Por padrão, o Terraform armazena o state em um arquivo local chamado terraform.tfstate no diretório de trabalho. Esse comportamento é adequado para aprendizado e experimentos individuais, mas é completamente inadequado para uso em equipe por três razões fundamentais.
Ausência de locking — se dois engenheiros executam terraform apply simultaneamente, ambos leem o mesmo state, fazem mudanças e tentam escrever de volta. O resultado é corrupção do state ou sobrescrita de mudanças. Em uma infraestrutura de produção, esse cenário pode causar recursos duplicados, recursos destruídos acidentalmente ou inconsistências que levam horas para diagnosticar.
Sem compartilhamento — o state local existe apenas na máquina de quem executou o apply. Outros membros da equipe não têm acesso a ele. Se a máquina falha ou o arquivo é deletado, a capacidade de gerenciar a infraestrutura é perdida.
Risco de versionamento acidental — o terraform.tfstate frequentemente contém senhas, tokens e outros valores sensíveis extraídos dos recursos durante o apply. Se esse arquivo for commitado acidentalmente no Git — e isso acontece com mais frequência do que se imagina — esses segredos ficam expostos no histórico do repositório para sempre.
A solução para todos esses problemas é o backend remoto.
Configurando um Backend Remoto com S3 e DynamoDB
O backend mais comum para equipes que usam AWS combina um bucket S3 para armazenamento do state com uma tabela DynamoDB para locking distribuído. O S3 resolve o problema de compartilhamento e o DynamoDB resolve o problema de concorrência.
Primeiro, cria-se a infraestrutura necessária para o backend — ironicamente, isso é feito uma única vez de forma manual ou com um projeto Terraform bootstrapper separado:
# bootstrap/main.tf
# Este projeto é executado apenas uma vez para criar a infraestrutura
# necessária para armazenar o state dos demais projetos
provider "aws" {
region = "us-east-1"
}
# Bucket S3 para armazenamento do state
resource "aws_s3_bucket" "terraform_state" {
bucket = "minha-empresa-terraform-state"
# Impede destruição acidental
lifecycle {
prevent_destroy = true
}
tags = {
Name = "Terraform State"
ManagedBy = "terraform"
}
}
# Habilita versionamento — permite recuperar versões anteriores do state
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Criptografia server-side — o state pode conter senhas e tokens
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Bloqueia todo acesso público ao bucket
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Tabela DynamoDB para locking de state
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-state-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
lifecycle {
prevent_destroy = true
}
tags = {
Name = "Terraform State Locks"
ManagedBy = "terraform"
}
}
Com a infraestrutura de backend criada, configura-se o backend nos projetos:
# versions.tf de qualquer projeto que use este backend
terraform {
required_version = ">= 1.7.0"
backend "s3" {
bucket = "minha-empresa-terraform-state"
key = "projetos/minha-api/staging/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-locks"
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.31"
}
}
}
A estrutura de chaves no S3 deve refletir a hierarquia da organização:
terraform-state/
├── projetos/
│ ├── minha-api/
│ │ ├── staging/terraform.tfstate
│ │ └── production/terraform.tfstate
│ └── sistema-pagamentos/
│ ├── staging/terraform.tfstate
│ └── production/terraform.tfstate
└── shared/
├── ecr/terraform.tfstate
└── route53/terraform.tfstate
Como Funciona o State Locking
Quando terraform apply é executado com o backend S3 + DynamoDB configurado, o processo é:
1. Terraform tenta adquirir o lock
→ Cria um item na tabela DynamoDB com o LockID do state
2. Se o lock está disponível:
→ Lê o state do S3
→ Executa o plan
→ Aplica as mudanças
→ Escreve o novo state no S3
→ Libera o lock (remove o item do DynamoDB)
3. Se o lock está ocupado:
→ Exibe mensagem com informações de quem detém o lock
→ Aguarda ou aborta (dependendo da configuração)
Quando um apply é interrompido abruptamente — queda de energia, Ctrl+C, falha de rede — o lock pode ficar preso. Para forçar a liberação após confirmar que nenhum outro processo está rodando:
# Exibe informações sobre o lock atual
terraform force-unlock LOCK_ID
# O LOCK_ID está disponível na mensagem de erro quando o lock está ocupado
# Exemplo de mensagem:
# Error: Error acquiring the state lock
# Lock Info:
# ID: 8a6e0731-6b9a-4c3a-8d9c-1f2e3a4b5c6d
# Path: projetos/minha-api/staging/terraform.tfstate
# Operation: OperationTypeApply
# Who: joao@notebook-joao
# Created: 2025-03-10 14:30:00
Comandos de Inspeção e Manipulação do State
O Terraform fornece um conjunto de comandos para inspecionar e manipular o state sem editá-lo diretamente.
Listando recursos no state:
# Lista todos os recursos gerenciados pelo state atual
terraform state list
# Saída típica:
# aws_instance.servidor_web
# aws_security_group.aplicacao
# aws_db_instance.principal
# module.vpc.aws_vpc.this
# module.vpc.aws_subnet.publica[0]
# module.vpc.aws_subnet.publica[1]
# module.vpc.aws_nat_gateway.this[0]
Inspecionando um recurso específico:
# Exibe todos os atributos de um recurso no state
terraform state show aws_instance.servidor_web
# Exibe recurso dentro de um módulo
terraform state show 'module.vpc.aws_vpc.this'
Movendo recursos no state:
O comando terraform state mv é usado quando o código é refatorado — renomear um recurso ou movê-lo para dentro de um módulo — sem querer destruir e recriar o recurso real:
# Renomeia um recurso no state
terraform state mv \
aws_instance.servidor_web \
aws_instance.servidor_web_principal
# Move um recurso para dentro de um módulo
terraform state mv \
aws_security_group.aplicacao \
module.aplicacao.aws_security_group.this
# Move recurso entre states (de um projeto para outro)
terraform state mv \
-state-out=../outro-projeto/terraform.tfstate \
aws_s3_bucket.assets \
aws_s3_bucket.assets
Removendo recursos do state sem destruí-los:
Quando um recurso precisa deixar de ser gerenciado pelo Terraform — por exemplo, para ser importado em outro projeto — usa-se terraform state rm:
# Remove o recurso do state mas não o destrói na infraestrutura real
terraform state rm aws_instance.servidor_legado
# Remove todos os recursos de um módulo
terraform state rm 'module.vpc'
Importando recursos existentes:
Quando existe infraestrutura criada fora do Terraform que precisa passar a ser gerenciada por ele, usa-se terraform import:
# Importa uma instância EC2 existente
terraform import aws_instance.servidor_legado i-0abc123def456789
# Importa um bucket S3
terraform import aws_s3_bucket.legado nome-do-bucket-existente
# Importa um registro de DNS do Route53
terraform import aws_route53_record.api ZONE_ID_RECORD_ID_TYPE
A partir da versão 1.5, o Terraform suporta um bloco import declarativo nos arquivos .tf, que é preferível ao comando imperativo em projetos novos:
# Bloco de import declarativo — Terraform 1.5+
import {
to = aws_instance.servidor_legado
id = "i-0abc123def456789"
}
import {
to = aws_s3_bucket.legado
id = "nome-do-bucket-existente"
}
Workspaces: Múltiplos States em Uma Configuração
O Terraform suporta workspaces — múltiplos states associados à mesma configuração. Cada workspace tem seu próprio arquivo de state, permitindo usar a mesma configuração para diferentes ambientes:
# Lista os workspaces existentes
terraform workspace list
# Cria e muda para um novo workspace
terraform workspace new staging
terraform workspace new production
# Muda entre workspaces
terraform workspace select staging
# Exibe o workspace atual
terraform workspace show
Dentro da configuração, o workspace atual pode ser referenciado via terraform.workspace:
locals {
ambiente = terraform.workspace
config = {
development = {
instance_type = "t3.micro"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.small"
min_size = 1
max_size = 3
}
production = {
instance_type = "t3.large"
min_size = 2
max_size = 10
}
}
}
resource "aws_instance" "app" {
instance_type = local.config[local.ambiente].instance_type
# ...
}
Embora workspaces sejam úteis para casos simples, a maioria das organizações prefere diretórios separados por ambiente — como mostrado na estrutura de módulos do artigo anterior. Diretórios separados têm configurações explícitas por ambiente, facilitam diferenças maiores entre ambientes e tornam o pipeline de CI/CD mais previsível.
Estratégia de Backup e Recuperação de State
Mesmo com o versionamento habilitado no S3, uma estratégia explícita de backup protege contra cenários de recuperação de desastre:
# Faz backup manual do state atual
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate
# Restaura um state a partir de um backup
terraform state push backup-20250310-143000.tfstate
# Lista versões anteriores do state no S3
aws s3api list-object-versions \
--bucket minha-empresa-terraform-state \
--prefix projetos/minha-api/staging/terraform.tfstate \
--query 'Versions[*].{VersionId:VersionId,LastModified:LastModified}' \
--output table
# Restaura uma versão específica do S3
aws s3api get-object \
--bucket minha-empresa-terraform-state \
--key projetos/minha-api/staging/terraform.tfstate \
--version-id "abc123def456" \
state-restaurado.tfstate
# Verifica a integridade antes de restaurar
terraform state pull > state-atual.tfstate
diff state-atual.tfstate state-restaurado.tfstate
# Aplica o state restaurado
terraform state push state-restaurado.tfstate
State em Pipelines de CI/CD
Em pipelines de CI/CD, o Terraform precisa de acesso ao backend remoto sem interação humana. A configuração do backend pode receber variáveis via flags na linha de comando — útil quando partes da configuração variam entre pipelines:
# .github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
paths: ['infrastructure/**']
jobs:
terraform:
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: infrastructure/environments/production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7.0"
- name: Configura credenciais AWS via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE_ARN }}
aws-region: us-east-1
- name: Terraform Init
run: |
terraform init \
-backend-config="bucket=minha-empresa-terraform-state" \
-backend-config="key=projetos/minha-api/production/terraform.tfstate" \
-backend-config="region=us-east-1" \
-backend-config="dynamodb_table=terraform-state-locks"
- name: Terraform Validate
run: terraform validate
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Plan
id: plan
run: |
terraform plan \
-var="db_password=${{ secrets.DB_PASSWORD }}" \
-out=tfplan \
-no-color 2>&1 | tee plan-output.txt
- name: Publica plano como comentário no PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const planOutput = fs.readFileSync('infrastructure/environments/production/plan-output.txt', 'utf8');
const truncated = planOutput.length > 60000
? planOutput.substring(0, 60000) + '\n... (truncado)'
: planOutput;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Terraform Plan — Produção\n\`\`\`\n${truncated}\n\`\`\``
});
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
O Que Vem a Seguir
O próximo artigo introduz o Ansible — a ferramenta que complementa o Terraform gerenciando o que acontece dentro dos servidores após eles serem provisionados. Enquanto o Terraform cria e gerencia a infraestrutura, o Ansible configura os servidores: instala pacotes, copia arquivos, inicia serviços e garante que o estado interno de cada máquina corresponde ao declarado.
Referências para Aprofundamento
Documentação oficial - Terraform State — developer.hashicorp.com — Documentação completa sobre o state do Terraform, incluindo backends disponíveis, locking e manipulação de state. - Terraform Backend S3 — developer.hashicorp.com — Referência completa do backend S3, cobrindo todas as opções de configuração incluindo assumeRole e workspaces. - Terraform Import — developer.hashicorp.com — Documentação do bloco de import declarativo introduzido no Terraform 1.5, com exemplos práticos.
Boas práticas e segurança - Terraform Best Practices — State — Seção do guia de boas práticas dedicada ao gerenciamento de state em equipe, cobrindo estrutura de backends e estratégias de separação por ambiente. - Terragrunt — gruntwork.io — Ferramenta que adiciona uma camada de abstração sobre o Terraform, simplificando o gerenciamento de múltiplos states e backends em organizações com muitos projetos.
Ferramentas de análise - tfstate.dev — Ferramenta online para visualizar e analisar arquivos de state do Terraform de forma interativa. Útil para diagnóstico e entendimento de states complexos.
Artigo 28 de 52 · Módulo 5 — Infraestrutura como Código Prof. Ricardo Matos · Série Dominando DevOps & Cloud em 1 Ano
you asked
Continue