Providers, resources e state no Terraform

1. Introdução aos Providers no Terraform

No ecossistema DevOps, o Terraform atua como uma ferramenta de Infrastructure as Code (IaC) que permite provisionar e gerenciar recursos em múltiplas plataformas de forma declarativa. Os providers são plugins que atuam como pontes entre o Terraform e APIs de serviços como AWS, Azure, GCP, Docker e Kubernetes. Cada provider expõe recursos específicos da plataforma, permitindo que você descreva infraestrutura em arquivos de configuração.

Para ambientes com Docker e Kubernetes, os providers correspondentes permitem gerenciar containers, imagens, deployments e serviços diretamente via código Terraform. Exemplo básico de configuração:

provider "docker" {
  host = "unix:///var/run/docker.sock"
}

provider "kubernetes" {
  config_path = "~/.kube/config"
}

2. Configuração e Versionamento de Providers

O bloco required_providers dentro de terraform define quais providers serão usados e suas versões. Isso garante reprodutibilidade e evita quebras por atualizações inesperadas.

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = ">= 2.20"
    }
  }
}

Após definir os providers, execute terraform init para baixar os plugins e fixar as versões no arquivo .terraform.lock.hcl. Em projetos com múltiplos providers, organize cada configuração em arquivos separados (ex.: providers.tf, docker.tf, kubernetes.tf) para facilitar a manutenção.

3. Resources: Blocos Fundamentais da Infraestrutura

Resources são os blocos de construção no Terraform. Cada resource declara um componente de infraestrutura que será criado, atualizado ou destruído. A sintaxe segue o padrão:

resource "tipo_do_recurso" "nome_local" {
  argumento1 = valor1
  argumento2 = valor2
}

Exemplo prático com Docker:

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}

resource "docker_container" "nginx_server" {
  image = docker_image.nginx.name
  name  = "nginx_container"
  ports {
    internal = 80
    external = 8080
  }
}

Para Kubernetes:

resource "kubernetes_deployment" "app" {
  metadata {
    name = "app-deployment"
  }
  spec {
    replicas = 3
    selector {
      match_labels = {
        app = "myapp"
      }
    }
    template {
      metadata {
        labels = {
          app = "myapp"
        }
      }
      spec {
        container {
          image = "nginx:latest"
          name  = "nginx"
        }
      }
    }
  }
}

Dependências implícitas são criadas automaticamente quando um resource referencia outro (ex.: docker_container referencia docker_image). Dependências explícitas podem ser forçadas com depends_on:

resource "kubernetes_service" "app_service" {
  depends_on = [kubernetes_deployment.app]
  # ...
}

4. Meta-Arguments e Ciclo de Vida dos Resources

Meta-arguments como count e for_each permitem criar múltiplos resources dinamicamente:

resource "docker_container" "web" {
  count = 3
  name  = "web-${count.index}"
  image = "nginx:latest"
}

Com for_each para mapas:

locals {
  containers = {
    frontend = { port = 3000 }
    backend  = { port = 5000 }
  }
}

resource "docker_container" "services" {
  for_each = local.containers
  name     = each.key
  image    = "nginx:latest"
  ports {
    internal = each.value.port
    external = each.value.port
  }
}

O bloco lifecycle controla o comportamento durante atualizações:

resource "kubernetes_deployment" "critical" {
  # ...
  lifecycle {
    create_before_destroy = true
    prevent_destroy       = true
    ignore_changes        = [metadata[0].annotations]
  }
}
  • create_before_destroy: cria novo recurso antes de destruir o antigo (essencial para zero-downtime em Kubernetes)
  • prevent_destroy: protege recursos críticos contra exclusão acidental
  • ignore_changes: ignora alterações externas em atributos específicos (útil para campos gerenciados por controladores Kubernetes)

5. O State do Terraform: Armazenamento e Estrutura

O state file (terraform.tfstate) é o coração do Terraform. Ele mapeia recursos declarados no código para objetos reais na infraestrutura, armazenando:

  • IDs dos recursos no provedor
  • Metadados de dependência
  • Outputs e variáveis
  • Atributos atuais de cada recurso

Sem o state, o Terraform não sabe o que já foi criado, causando duplicação ou destruição acidental. A estrutura interna do state é JSON e contém:

{
  "version": 4,
  "terraform_version": "1.5.0",
  "resources": [
    {
      "mode": "managed",
      "type": "docker_container",
      "name": "nginx_server",
      "provider": "provider[\"registry.terraform.io/kreuzwerker/docker\"]",
      "instances": [
        {
          "attributes": {
            "id": "abc123",
            "name": "nginx_container"
          }
        }
      ]
    }
  ]
}

Em produção, perder o state local pode ser catastrófico, pois o Terraform perderia o vínculo com os recursos reais.

6. Gerenciamento de State Remoto para Times

Para equipes, o state deve ser armazenado remotamente com suporte a bloqueio. Backends comuns:

S3 (AWS) com DynamoDB para lock:

terraform {
  backend "s3" {
    bucket         = "meu-terraform-state"
    key            = "kubernetes/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Azure Storage:

terraform {
  backend "azurerm" {
    storage_account_name = "meustorage"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }
}

Para Kubernetes, o state pode ser armazenado em buckets S3 ou Azure Storage, com o cluster acessando via credenciais configuradas no provider. O bloqueio evita que dois membros da equipe executem apply simultaneamente.

7. Boas Práticas com State e Sensitive Data

O state pode conter dados sensíveis como senhas, tokens e chaves SSH. Para proteger:

  • Marque outputs como sensitive = true:
output "db_password" {
  value     = random_password.db.result
  sensitive = true
}
  • Use terraform state show com cuidado e evite versionar o state em repositórios Git
  • Configure criptografia no backend (ex.: encrypt = true no S3)
  • Para migrar state entre backends sem recriar recursos:
terraform init -migrate-state

Ou manualmente:

terraform state pull > backup.tfstate
# Configurar novo backend
terraform init
terraform state push backup.tfstate

8. Troubleshooting: Resolvendo Problemas comuns

Provider incompatível ou versão incorreta:

Erro: Error: Failed to query available provider packages

Solução: Verifique as versões em required_providers e execute terraform init -upgrade

State desatualizado (drift detection):

O Terraform detecta quando recursos foram alterados fora do Terraform. Use terraform plan para identificar o drift e terraform apply para reconciliar.

Manipulação manual do state:

Comandos úteis:

# Listar recursos no state
terraform state list

# Remover recurso do state (sem destruir)
terraform state rm docker_container.nginx_server

# Importar recurso existente para o state
terraform import docker_container.nginx_server $(docker ps -aqf "name=nginx_container")

# Mover recurso dentro do state
terraform state mv docker_container.old_name docker_container.new_name

Para casos de state corrompido, restaure de backups regulares ou use terraform state pull para inspecionar o conteúdo.


Referências