DevOps

Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto Já leu

13 min de leitura

Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto
Artigo 28 — State do Terraform: Entendendo o Arquivo Mais Crítico do Projeto O Que é o State e Por Que Ele Existe Quando o Terraform aplica uma configuração e

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


claude response

Comentários

Mais em DevOps

Instalação Manual do MySQL no Debian, Arch, Fedora e openSUSE
Instalação Manual do MySQL no Debian, Arch, Fedora e openSUSE

Este guia cobre a instalação do MySQL 9.7.0 puro (sem MariaDB, sem pacotes de...

Grafana: Dashboards e Alertas que Fazem Sentido
Grafana: Dashboards e Alertas que Fazem Sentido

Um dashboard mal projetado é quase tão ruim quanto não ter dashboard. Quando...

Introdução ao Terraform: Infraestrutura que Você Pode Versionar
Introdução ao Terraform: Infraestrutura que Você Pode Versionar

Imagine a seguinte situação, comum em empresas que não adotaram Infraestrutur...