DevOps

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

11 min de leitura

Introdução ao Terraform: Infraestrutura que Você Pode Versionar
Imagine a seguinte situação, comum em empresas que não adotaram Infraestrutura como Código. Um servidor crítico de produção começa a apresentar problemas. A equipe decide provisionar um novo servidor idêntico para substi

Imagine a seguinte situação, comum em empresas que não adotaram Infraestrutura como Código. Um servidor crítico de produção começa a apresentar problemas. A equipe decide provisionar um novo servidor idêntico para substituí-lo. Ninguém sabe exatamente como o servidor original foi configurado — foi feito manualmente por um engenheiro que saiu da empresa há dois anos, com ajustes adicionais feitos por outras três pessoas ao longo do tempo, nenhum deles documentado. O processo de recriar o ambiente leva dias, envolve tentativa e erro, e o resultado final provavelmente tem diferenças sutis em relação ao original que só serão descobertas em produção.

Esse problema tem um nome: snowflake servers — servidores floco de neve, únicos e impossíveis de reproduzir. São o resultado inevitável de gerenciar infraestrutura manualmente ao longo do tempo.

A Infraestrutura como Código — IaC — resolve isso aplicando à infraestrutura os mesmos princípios que já se aplicam ao código da aplicação: tudo é descrito em arquivos de texto, versionado no Git, revisado em Pull Requests e aplicado de forma automatizada. O estado desejado da infraestrutura é declarado explicitamente, e a ferramenta garante que o estado real corresponde ao declarado.


O Que é o Terraform

O Terraform é a ferramenta de IaC mais amplamente adotada no mercado. Criado pela HashiCorp em 2014 e disponível como open source sob a licença MPL, ele permite descrever infraestrutura em uma linguagem declarativa chamada HCL — HashiCorp Configuration Language — e aplicar essa descrição em dezenas de provedores de nuvem e serviços.

A proposta central do Terraform é ser agnóstico de provedor: o mesmo fluxo de trabalho que provisiona recursos na AWS funciona para provisionar recursos no Azure, no GCP, no Cloudflare, no Datadog, no GitHub e em centenas de outros provedores. Cada provedor é um plugin que traduz os recursos declarados em HCL para as APIs específicas daquele serviço.

O Terraform opera em um modelo declarativo: o engenheiro descreve o que quer — "quero um servidor com 2 vCPUs, 4GB de RAM, rodando Ubuntu 22.04, na região us-east-1" — e o Terraform determina o que precisa ser feito para chegar a esse estado. Se o recurso não existe, ele cria. Se existe mas com configuração diferente, ele atualiza. Se foi removido da configuração, ele destrói.


Instalando o Terraform

# No Ubuntu/Debian — via repositório oficial HashiCorp
wget -O- https://apt.releases.hashicorp.com/gpg | \
  sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

# Verifica a instalação
terraform version

Para gerenciar múltiplas versões do Terraform em diferentes projetos, recomenda-se o tfenv — um gerenciador de versões análogo ao nvm para Node.js:

# Instala o tfenv
git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# Instala uma versão específica do Terraform
tfenv install 1.7.0
tfenv use 1.7.0

# Lista versões disponíveis
tfenv list

Os Quatro Comandos Fundamentais

O fluxo de trabalho do Terraform gira em torno de quatro comandos que serão usados repetidamente ao longo de toda a carreira:

terraform init — inicializa o diretório de trabalho, baixando os plugins de provedor necessários e configurando o backend de estado. Deve ser executado sempre que um novo projeto é clonado ou quando provedores são adicionados ou atualizados.

terraform plan — compara o estado atual da infraestrutura com o estado declarado nos arquivos de configuração e exibe um plano de execução: o que será criado, modificado ou destruído. É essencialmente um dry run — nenhuma mudança real acontece.

terraform apply — aplica o plano, executando as mudanças necessárias para que a infraestrutura real corresponda ao estado declarado. Solicita confirmação interativa por padrão.

terraform destroy — destrói todos os recursos gerenciados pela configuração atual. Útil para ambientes temporários, mas deve ser usado com extrema cautela em produção.


A Linguagem HCL

A HCL foi projetada para ser legível por humanos sem ser um YAML ou JSON. É declarativa, tipada e suporta expressões, funções e referências entre recursos.

A estrutura básica de um arquivo Terraform:

# Bloco de configuração do Terraform — define versões e backend
terraform {
  required_version = ">= 1.7.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Bloco de provedor — configura as credenciais e região
provider "aws" {
  region = "us-east-1"
}

# Bloco de recurso — declara um recurso de infraestrutura
resource "aws_instance" "servidor_web" {
  ami           = "ami-0c55b159cbfafe1f0"  # Ubuntu 22.04
  instance_type = "t3.micro"

  tags = {
    Name        = "servidor-web"
    Environment = "producao"
    ManagedBy   = "terraform"
  }
}

A sintaxe resource "tipo_do_recurso" "nome_local" é o bloco mais fundamental do Terraform. O tipo do recurso (aws_instance) determina o que está sendo criado. O nome local (servidor_web) é usado para referenciar esse recurso em outras partes da configuração.


O Primeiro Projeto Terraform

Um projeto completo e funcional que cria uma instância EC2 com grupo de segurança e IP elástico na AWS. A estrutura de arquivos recomendada:

meu-projeto/
├── main.tf          # Recursos principais
├── variables.tf     # Declaração de variáveis
├── outputs.tf       # Outputs do projeto
├── versions.tf      # Versões do Terraform e provedores
└── terraform.tfvars # Valores das variáveis (não versionar se tiver secrets)

versions.tf — configuração do Terraform e provedores:

terraform {
  required_version = ">= 1.7.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.31"
    }
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Project     = var.project_name
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

variables.tf — declaração de variáveis com tipos e descrições:

variable "aws_region" {
  description = "Região AWS onde os recursos serão criados"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  description = "Nome do projeto — usado em tags e nomes de recursos"
  type        = string
}

variable "environment" {
  description = "Ambiente de implantação"
  type        = string

  validation {
    condition     = contains(["development", "staging", "production"], var.environment)
    error_message = "O ambiente deve ser development, staging ou production."
  }
}

variable "instance_type" {
  description = "Tipo da instância EC2"
  type        = string
  default     = "t3.micro"
}

variable "allowed_ssh_cidr" {
  description = "CIDR permitido para acesso SSH"
  type        = string
  default     = "0.0.0.0/0"
}

main.tf — recursos da infraestrutura:

# Busca a AMI mais recente do Ubuntu 22.04
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# Grupo de segurança
resource "aws_security_group" "servidor_web" {
  name        = "${var.project_name}-${var.environment}-sg"
  description = "Security group do servidor web"

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.allowed_ssh_cidr]
  }

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Todo tráfego de saída"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-sg"
  }
}

# Par de chaves SSH
resource "aws_key_pair" "deploy" {
  key_name   = "${var.project_name}-${var.environment}-key"
  public_key = file("~/.ssh/id_ed25519.pub")
}

# Instância EC2
resource "aws_instance" "servidor_web" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  key_name               = aws_key_pair.deploy.key_name
  vpc_security_group_ids = [aws_security_group.servidor_web.id]

  root_block_device {
    volume_size           = 20
    volume_type           = "gp3"
    delete_on_termination = true
    encrypted             = true
  }

  user_data = <<-EOF
    #!/bin/bash
    apt-get update -y
    apt-get install -y docker.io docker-compose-plugin
    systemctl enable docker
    systemctl start docker
    usermod -aG docker ubuntu
  EOF

  tags = {
    Name = "${var.project_name}-${var.environment}-web"
  }
}

# IP elástico
resource "aws_eip" "servidor_web" {
  instance = aws_instance.servidor_web.id
  domain   = "vpc"

  tags = {
    Name = "${var.project_name}-${var.environment}-eip"
  }
}

outputs.tf — valores exportados após o apply:

output "ip_publico" {
  description = "IP público da instância"
  value       = aws_eip.servidor_web.public_ip
}

output "dns_publico" {
  description = "DNS público da instância"
  value       = aws_instance.servidor_web.public_dns
}

output "comando_ssh" {
  description = "Comando SSH para acessar a instância"
  value       = "ssh -i ~/.ssh/id_ed25519 ubuntu@${aws_eip.servidor_web.public_ip}"
}

output "id_instancia" {
  description = "ID da instância EC2"
  value       = aws_instance.servidor_web.id
}

terraform.tfvars — valores concretos das variáveis:

project_name     = "minha-api"
environment      = "staging"
instance_type    = "t3.micro"
allowed_ssh_cidr = "203.0.113.0/24"  # IP do escritório

Executando o Projeto

# 1. Inicializa o projeto — baixa os plugins de provedor
terraform init

# Saída esperada:
# Initializing provider plugins...
# - Finding hashicorp/aws versions matching "~> 5.31"...
# - Installing hashicorp/aws v5.31.0...
# Terraform has been successfully initialized!

# 2. Valida a sintaxe dos arquivos
terraform validate

# 3. Formata os arquivos seguindo o padrão oficial
terraform fmt

# 4. Gera e exibe o plano de execução
terraform plan

# Saída esperada:
# Plan: 4 to add, 0 to change, 0 to destroy.
# + aws_eip.servidor_web
# + aws_instance.servidor_web
# + aws_key_pair.deploy
# + aws_security_group.servidor_web

# 5. Aplica as mudanças
terraform apply

# Para aplicar sem confirmação interativa (útil em pipelines)
terraform apply -auto-approve

# 6. Exibe os outputs após o apply
terraform output

# 7. Quando não precisar mais do ambiente
terraform destroy

Entendendo o Grafo de Dependências

O Terraform constrói automaticamente um grafo de dependências entre os recursos com base nas referências entre eles. No exemplo acima, o Terraform sabe que:

  • aws_eip depende de aws_instance (referência aws_instance.servidor_web.id)
  • aws_instance depende de aws_security_group e aws_key_pair
  • aws_security_group e aws_key_pair não têm dependências entre si

Com esse grafo, o Terraform pode criar aws_security_group e aws_key_pair em paralelo, depois criar aws_instance quando ambos estiverem prontos, e finalmente criar aws_eip. Essa paralelização automática acelera significativamente a aplicação de configurações complexas.

Para visualizar o grafo:

# Gera o grafo em formato DOT
terraform graph | dot -Tpng > grafo.png

Blocos de Dados: Consultando Recursos Existentes

O bloco data permite consultar informações de recursos que já existem na infraestrutura sem gerenciá-los. É útil para referenciar recursos criados fora do Terraform ou compartilhados entre projetos:

# Busca uma VPC existente pelo nome
data "aws_vpc" "principal" {
  filter {
    name   = "tag:Name"
    values = ["vpc-principal"]
  }
}

# Usa a VPC encontrada em um novo recurso
resource "aws_subnet" "app" {
  vpc_id     = data.aws_vpc.principal.id
  cidr_block = "10.0.10.0/24"
}

# Busca o ID da conta AWS atual
data "aws_caller_identity" "atual" {}

output "account_id" {
  value = data.aws_caller_identity.atual.account_id
}

O Que Vem a Seguir

O próximo artigo aprofunda o conceito de state — o arquivo que o Terraform mantém para rastrear o estado atual da infraestrutura. Entender o state é fundamental para trabalhar em equipe, configurar backends remotos e evitar os problemas mais comuns que os engenheiros enfrentam ao usar Terraform em produção.


Referências para Aprofundamento

Documentação oficial - Terraform Documentation — developer.hashicorp.com — Documentação completa do Terraform, incluindo referência da linguagem HCL, guias de provedores e tutoriais interativos. - Terraform AWS Provider — registry.terraform.io — Documentação completa de todos os recursos e data sources do provedor AWS, com exemplos para cada recurso.

Aprendizado interativo - HashiCorp Learn — Terraform — Tutoriais oficiais interativos da HashiCorp cobrindo desde os primeiros passos até tópicos avançados como módulos e workspaces. Incluem ambientes de laboratório sem necessidade de conta na nuvem.

Boas práticas - Terraform Best Practices — terraform-best-practices.com — Guia independente e amplamente referenciado sobre boas práticas de estrutura de projetos, nomenclatura e organização de módulos Terraform. - tfenv — GitHub — Repositório oficial do gerenciador de versões do Terraform com instruções de instalação para diferentes sistemas operacionais.

Comentários

Mais em DevOps

Capstone: Operações em Produção e Retrospectiva da Jornada
Capstone: Operações em Produção e Retrospectiva da Jornada

Um sistema de software não termina quando o último deploy é feito. Ele começa...

Trabalhando em Equipe com Git Flow
Trabalhando em Equipe com Git Flow

O Git oferece branches, mas n&atilde;o diz como us&aacute;-las. Em uma equipe...

Permissões, Usuários e Grupos no Linux
Permissões, Usuários e Grupos no Linux

Em um servidor de produ&ccedil;&atilde;o, m&uacute;ltiplos servi&ccedil;os ro...