Como usar Terraform para gerenciar infraestrutura como código

1. Fundamentos do Terraform e Infraestrutura como Código

Infrastructure as Code (IaC) é a prática de gerenciar e provisionar infraestrutura por meio de arquivos de configuração, em vez de processos manuais. Essa abordagem permite automação, repetibilidade e versionamento da infraestrutura, eliminando erros humanos e garantindo consistência entre ambientes.

O Terraform, desenvolvido pela HashiCorp, é uma ferramenta declarativa e multi-cloud que utiliza arquivos de configuração escritos em HCL (HashiCorp Configuration Language) para definir recursos de infraestrutura. Diferente de ferramentas imperativas (como scripts shell), o Terraform descreve o estado desejado e calcula automaticamente as ações necessárias para alcançá-lo.

O ciclo de vida básico do Terraform consiste em quatro comandos principais:
- terraform init: inicializa o diretório de trabalho, baixa plugins de providers e configura o backend de estado
- terraform plan: cria um plano de execução mostrando quais recursos serão criados, modificados ou destruídos
- terraform apply: aplica as mudanças descritas no plano
- terraform destroy: remove todos os recursos gerenciados pelo Terraform

2. Configuração Inicial e Estrutura de Projetos

Para instalar o Terraform, baixe o binário apropriado para seu sistema operacional em terraform.io/downloads. Após a instalação, configure um provider. Exemplo para AWS:

provider "aws" {
  region = "us-east-1"
}

Uma estrutura de projeto recomendada segue esta organização:

projeto-terraform/
├── main.tf          # Configuração principal e recursos
├── variables.tf     # Declaração de variáveis
├── outputs.tf       # Saídas do módulo
├── terraform.tfvars # Valores das variáveis (não versionar)
├── modules/         # Módulos reutilizáveis
│   ├── network/
│   └── compute/
└── environments/    # Configurações por ambiente
    ├── dev/
    └── prod/

O arquivo terraform.tfvars armazena valores sensíveis e específicos:

aws_access_key = "AKIAXXXXXXXXXXXX"
aws_secret_key = "wJalrXUt***********"
instance_type  = "t2.micro"

3. Gerenciamento de Estado (State) e Locking

O arquivo terraform.tfstate é o coração do Terraform — ele mapeia os recursos reais para sua configuração. Perder ou corromper esse arquivo pode causar inconsistências graves. Para equipes, o armazenamento remoto é essencial.

Exemplo de backend remoto com S3 e DynamoDB para locking:

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

O DynamoDB garante locking concorrente: se dois membros da equipe tentarem aplicar mudanças simultaneamente, apenas um terá sucesso, evitando corrupção de estado.

4. Criação de Recursos com HCL

A sintaxe básica do HCL utiliza blocos resource para definir componentes. Exemplo prático de provisionamento de uma VPC, sub-rede e instância EC2:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "main-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.public.id

  tags = {
    Name = "web-server"
  }
}

O Terraform cria automaticamente dependências implícitas entre recursos (como aws_instance.web que depende de aws_subnet.public). Quando necessário, use depends_on para dependências explícitas:

resource "aws_instance" "web" {
  # ... configuração ...

  depends_on = [aws_vpc.main]
}

5. Modularização e Reutilização de Código

Módulos permitem encapsular configurações reutilizáveis. Exemplo de módulo de rede (modules/network/):

# modules/network/main.tf
variable "vpc_cidr" {}
variable "subnet_cidr" {}

resource "aws_vpc" "this" {
  cidr_block = var.vpc_cidr
}

resource "aws_subnet" "this" {
  vpc_id     = aws_vpc.this.id
  cidr_block = var.subnet_cidr
}

Uso do módulo no projeto principal:

module "network" {
  source      = "./modules/network"
  vpc_cidr    = "10.0.0.0/16"
  subnet_cidr = "10.0.1.0/24"
}

Para módulos públicos, utilize o Terraform Registry. Versionamento de módulos com tags Git:

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
  # ... configuração ...
}

6. Estratégias de Deploy, Versionamento e CI/CD

Integração com CI/CD usando GitHub Actions:

# .github/workflows/terraform.yml
name: 'Terraform CI/CD'
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: 1.5.0
    - name: Terraform Init
      run: terraform init
    - name: Terraform Plan
      run: terraform plan
    - name: Terraform Apply
      if: github.ref == 'refs/heads/main'
      run: terraform apply -auto-approve

Workspaces permitem gerenciar múltiplos ambientes:

terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace select dev
terraform apply -auto-approve

7. Boas Práticas, Segurança e Troubleshooting

Para gerenciar segredos, evite versionar terraform.tfvars. Utilize variáveis de ambiente ou ferramentas como Vault:

export TF_VAR_db_password="senha_segura"

Comandos de qualidade:

terraform validate   # Verifica sintaxe
terraform fmt        # Formata código
terraform graph      # Gera gráfico de dependências
terraform graph | dot -Tpng > graph.png

Depuração de erros comuns:

  • Conflitos de estado: Use terraform refresh para sincronizar estado real com o arquivo
  • Drift detection: Execute terraform plan regularmente para detectar mudanças manuais
  • Recursos corrompidos: terraform taint resource_type.name força recriação na próxima execução
  • Logs detalhados: TF_LOG=DEBUG terraform apply

Referências