<h2>Introdução ao Terraform</h2>
<p>Terraform é uma ferramenta de Infrastructure as Code (IaC) que permite definir, visualizar e gerenciar infraestrutura através de código declarativo. Diferente de ferramentas imperativas, onde você descreve <em>como</em> fazer algo, Terraform permite descrever <em>o que</em> você quer que exista. Isso traz reproducibilidade, versionamento e automação para suas operações de infraestrutura.</p>
<p>O grande diferencial do Terraform é sua abordagem agnóstica em relação aos provedores de nuvem. Você usa a mesma sintaxe e lógica para gerenciar recursos em AWS, Azure, Google Cloud, ou até infraestrutura on-premises. Essa versatilidade o tornou a ferramenta padrão da indústria para IaC, especialmente em ambientes multi-cloud.</p>
<h2>Providers: A Ponte Entre Você e a Infraestrutura</h2>
<h3>O que é um Provider?</h3>
<p>Um Provider é um plugin do Terraform que traduz suas instruções em chamadas de API específicas de um serviço (AWS, Azure, Google Cloud, etc.). O provider é responsável por autenticar-se, comunicar-se com a plataforma alvo e executar as operações necessárias. Sem providers, o Terraform seria apenas um interpretador de sintaxe.</p>
<p>Cada provider mantém documentação sobre quais recursos ele suporta e quais argumentos cada recurso aceita. Você precisa declarar explicitamente qual provider usar antes de criar qualquer recurso. O Terraform automaticamente faz download do binário do provider da versão especificada quando você executa <code>terraform init</code>.</p>
<h3>Configurando Providers</h3>
<p>A configuração de providers é feita no bloco <code>terraform</code> ou diretamente no arquivo de configuração. Veja um exemplo prático com AWS:</p>
<pre><code class="language-hcl">terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
Environment = "development"
ManagedBy = "terraform"
}
}
}</code></pre>
<p>Neste exemplo, estamos dizendo explicitamente que queremos a versão 5.x do provider AWS. O bloco <code>default_tags</code> é particularmente útil porque adiciona tags automaticamente a todos os recursos criados, evitando repetição. A autenticação do AWS é feita através de variáveis de ambiente (<code>AWS_ACCESS_KEY_ID</code> e <code>AWS_SECRET_ACCESS_KEY</code>) ou arquivo de credenciais.</p>
<p>Um exemplo com múltiplos providers (cenário multi-cloud):</p>
<pre><code class="language-hcl">terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "azurerm" {
features {}
subscription_id = var.azure_subscription_id
}</code></pre>
<p>Nesse cenário, você pode criar recursos tanto em AWS quanto em Azure no mesmo projeto, mantendo o código organizado e centralizado.</p>
<h2>Resources: Definindo Sua Infraestrutura</h2>
<h3>Estrutura Fundamental de um Resource</h3>
<p>Um resource no Terraform é a unidade básica de infraestrutura que você deseja criar e gerenciar. Cada resource tem um tipo (como <code>aws_instance</code>, <code>aws_s3_bucket</code>, etc.) e um nome local que você define. O formato é <code>resource "tipo" "nome_local"</code>.</p>
<pre><code class="language-hcl">resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "Production Web Server"
}
}</code></pre>
<p>Neste exemplo, criamos uma instância EC2 da AWS. O tipo é <code>aws_instance</code> e o nome local é <code>web_server</code>. Esse nome local é usado para referenciar este recurso em outras partes do seu código Terraform. Os argumentos (<code>ami</code>, <code>instance_type</code>, etc.) são específicos de cada tipo de resource e definidos pelo provider.</p>
<h3>Referências Entre Resources</h3>
<p>Um dos poderes do Terraform é a capacidade de referenciar outputs de um resource como inputs de outro. Isso cria dependências implícitas que o Terraform entende automaticamente:</p>
<pre><code class="language-hcl">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_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}</code></pre>
<p>Repare que em <code>aws_subnet</code> usamos <code>aws_vpc.main.id</code> — isso referencia o atributo <code>id</code> do VPC que criamos. O Terraform compreende que o subnet depende do VPC e ordena a criação corretamente. Se você deletar o VPC, o subnet será deletado também porque o Terraform rastreia essa dependência.</p>
<h3>Argumentos Opcionais e Computed</h3>
<p>Nem todos os argumentos de um resource são obrigatórios. Alguns são opcionais e outros são "computed" (calculados pelo provider). Argumentos computed são gerados pelo provedor após a criação do recurso:</p>
<pre><code class="language-hcl">resource "aws_s3_bucket" "data_storage" {
bucket = "my-unique-bucket-name-${data.aws_caller_identity.current.account_id}"
tags = {
Name = "Data Storage"
}
}
'arn', 'region' e 'hosted_zone_id' são computed
Você não pode definir, apenas ler após criação
data "aws_caller_identity" "current" {}
output "bucket_arn" {
value = aws_s3_bucket.data_storage.arn
description = "ARN do bucket S3 criado"
}</code></pre>
<p>Neste exemplo, <code>arn</code> é um atributo computed — o AWS gera automaticamente quando o bucket é criado. Você não pode especificar qual ARN deseja; apenas lê o valor após a criação. Usamos data source <code>aws_caller_identity</code> para obter o ID da conta AWS atual, demonstrando como combinar dados e recursos.</p>
<h2>State: O Coração do Terraform</h2>
<h3>Entendendo o State File</h3>
<p>O state file é um arquivo JSON que o Terraform mantém para rastrear qual infraestrutura ele criou e qual é o estado atual. É absolutamente crítico: sem o state, o Terraform não sabe que ele já criou um recurso e tentaria criá-lo novamente. O state mapeia sua configuração (código) com os recursos reais da sua infraestrutura.</p>
<pre><code class="language-json">{
"version": 4,
"terraform_version": "1.5.0",
"serial": 3,
"lineage": "abc123def456",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_vpc",
"name": "main",
"instances": [
{
"schema_version": 1,
"attributes": {
"id": "vpc-0123456789abcdef",
"cidr_block": "10.0.0.0/16",
"enable_dns_hostnames": false
}
}
]
}
]
}</code></pre>
<p>Este é um exemplo simplificado de um state file. Cada recurso criado tem uma entrada aqui com seus atributos atuais. Quando você executa <code>terraform plan</code>, o Terraform compara seu código com este state para determinar o que precisa mudar.</p>
<h3>State Local vs. Remoto</h3>
<p>Por padrão, o Terraform armazena o state localmente em <code>terraform.tfstate</code>. Para ambientes de equipe ou produção, você <strong>nunca</strong> deve usar state local — é inseguro e causa problemas de concorrência. Use state remoto:</p>
<pre><code class="language-hcl">terraform {
backend "s3" {
bucket = "meu-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}</code></pre>
<p>Aqui, o state é armazenado em um bucket S3 com criptografia ativada. O DynamoDB table é usado para locking — apenas uma operação Terraform pode rodar por vez, evitando race conditions. Sem locking, duas pessoas poderiam executar <code>terraform apply</code> simultaneamente e corromper o state.</p>
<h3>Manipulando State com Cautela</h3>
<p>Às vezes, você precisa modificar o state manualmente. O Terraform fornece comandos para isso, mas use com cuidado:</p>
<pre><code class="language-bash"># Ver o state atual
terraform state list
terraform state show aws_instance.web_server
Remover um recurso do state (sem deletar a infraestrutura real!)
terraform state rm aws_instance.web_server
Importar um recurso existente ao state
terraform import aws_instance.web_server i-0123456789abcdef</code></pre>
<p>Um cenário comum: você criou uma instância EC2 manualmente (fora do Terraform) e agora quer que o Terraform a gerencie. Use <code>terraform import</code> com o ID do recurso real. O Terraform consultará a AWS, aprenderá os atributos e adicionará ao state. Depois, você precisa escrever a configuração Terraform correspondente.</p>
<h2>Plan: Visualizando Mudanças Antes de Aplicar</h2>
<h3>O Comando Plan</h3>
<p>Antes de fazer qualquer mudança real, execute <code>terraform plan</code>. Este comando analisa sua configuração, consulta o state atual e calcula exatamente o que mudará na infraestrutura real. É sua oportunidade de revisar antes de comprometer.</p>
<pre><code class="language-bash">terraform plan -out=tfplan</code></pre>
<p>O parâmetro <code>-out</code> salva o plano em arquivo binário <code>tfplan</code>, que pode ser reutilizado exatamente no <code>apply</code>. Isto garante que você aplica exatamente o que revisou.</p>
<h3>Interpretando a Saída do Plan</h3>
<pre><code>Terraform will perform the following actions:
aws_instance.web_server will be created
+ resource "aws_instance" "web_server" {
+ ami = "ami-0c55b159cbfafe1f0"
+ availability_zone = (known after apply)
+ instance_type = "t2.micro"
+ key_name = (known after apply)
+ private_ip = (known after apply)
+ public_ip = (known after apply)
+ security_groups = (known after apply)
+ tags = {
+ "Name" = "Web Server"
}
+ tenancy = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.</code></pre>
<p>O símbolo <code>+</code> indica criação, <code>~</code> indica mudança, <code>-</code> indica destruição e <code>-/+</code> indica substituição (destruir e recriar). Valores marcados com <code>(known after apply)</code> não podem ser conhecidos até o recurso ser criado (como IPs públicos).</p>
<h3>Plan com Variáveis e Targets</h3>
<pre><code class="language-hcl"># variables.tf
variable "environment" {
type = string
default = "development"
}
variable "instance_count" {
type = number
default = 2
}
main.tf
resource "aws_instance" "app_servers" {
count = var.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.environment == "production" ? "t2.large" : "t2.micro"
tags = {
Name = "app-server-${count.index + 1}"
}
}</code></pre>
<pre><code class="language-bash"># Plan apenas para produção com 4 instâncias
terraform plan -var="environment=production" -var="instance_count=4"
Plan apenas para um recurso específico
terraform plan -target="aws_instance.app_servers[0]"</code></pre>
<p>Variáveis permitem parametrizar sua configuração. Use <code>-var</code> para sobrescrever valores padrão. O <code>count</code> cria múltiplas instâncias do mesmo recurso. Usar <code>-target</code> é útil para debugging, mas não é recomendado no fluxo normal porque você não está vendo o impacto completo.</p>
<h2>Apply e Execution</h2>
<h3>Executando o Plan com Apply</h3>
<p>Após revisar o plan e estar confiante, aplique as mudanças:</p>
<pre><code class="language-bash"># Aplicar o plan salvo anteriormente (seguro!)
terraform apply tfplan
Ou criar um novo plan e aplicar em um passo (perigoso, pede confirmação)
terraform apply</code></pre>
<p>Quando você executa <code>terraform apply</code> sem argumentos, o Terraform cria um novo plan, mostra para você e pede confirmação digitando <code>yes</code>. Usar um plan salvo (<code>tfplan</code>) é mais seguro porque garante que você está aplicando exatamente o que revisou.</p>
<h3>Destruindo Infraestrutura</h3>
<p>Para remover toda a infraestrutura:</p>
<pre><code class="language-bash">terraform destroy</code></pre>
<p>O Terraform calcula quais recursos deletar baseado no state, mostra um plan e pede confirmação. Use <code>-auto-approve</code> apenas em ambientes automatizados onde você confia no processo:</p>
<pre><code class="language-bash">terraform destroy -auto-approve</code></pre>
<h2>Ciclo Prático Completo: Exemplo Real</h2>
<p>Vamos ver um exemplo completo e realista de um projeto Terraform:</p>
<pre><code class="language-hcl"># providers.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "company-terraform-state"
key = "web-app/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
}
variables.tf
variable "aws_region" {
type = string
default = "us-east-1"
description = "Região AWS"
}
variable "environment" {
type = string
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Environment deve ser development, staging ou production."
}
}
variable "instance_type" {
type = map(string)
default = {
development = "t2.micro"
staging = "t2.small"
production = "t2.medium"
}
}
main.tf
resource "aws_vpc" "app" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.app.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet"
}
}
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.app.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.environment}-web-sg"
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type[var.environment]
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
environment = var.environment
}))
tags = {
Name = "${var.environment}-web-server"
Environment = var.environment
}
lifecycle {
create_before_destroy = true
}
}
data.tf
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
outputs.tf
output "instance_ip" {
value = aws_instance.web.public_ip
description = "IP público da instância"
}
output "vpc_id" {
value = aws_vpc.app.id
description = "ID do VPC criado"
}</code></pre>
<p>Fluxo de execução:</p>
<pre><code class="language-bash"># Inicializar (fazer download de providers, configurar backend)
terraform init
Validar sintaxe
terraform validate
Ver plan para desenvolvimento
terraform plan -var="environment=development"
Aplicar para desenvolvimento
terraform apply -var="environment=development"
Depois, para produção (com variáveis diferentes)
terraform plan -var="environment=production"
terraform apply -var="environment=production"</code></pre>
<p>Este exemplo demonstra: providers, resources, state remoto, data sources, variáveis com validação, templates, lifecycle rules e outputs. É um projeto real que você pode adaptar para seus casos de uso.</p>
<h2>Conclusão</h2>
<p>Os quatro pilares do Terraform que aprendemos aqui trabalham juntos de forma coesa: <strong>Providers</strong> conectam você aos serviços reais, <strong>Resources</strong> definem o que você quer criar, <strong>State</strong> rastreia o que existe, e <strong>Plan</strong> permite visualizar mudanças antes de aplicá-las. Dominar esses conceitos é fundamental para usar Terraform efetivamente.</p>
<p>O grande aprendizado é entender que Terraform é declarativo, não imperativo — você declara o estado desejado, e o Terraform calcula as mudanças necessárias. Sempre execute <code>plan</code> antes de <code>apply</code> e mantenha seu state remoto, seguro e com locking habilitado. Com essa disciplina, você terá infraestrutura reproduzível, versionável e auditável.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.terraform.io/docs" target="_blank" rel="noopener noreferrer">Terraform Official Documentation</a></li>
<li><a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs" target="_blank" rel="noopener noreferrer">Terraform AWS Provider Documentation</a></li>
<li><a href="https://www.terraform.io/language/state" target="_blank" rel="noopener noreferrer">Terraform State Management</a></li>
<li><a href="https://github.com/antonbabenko/terraform-best-practices" target="_blank" rel="noopener noreferrer">Terraform Best Practices Guide - Anton Babenko</a></li>
<li><a href="https://www.oreilly.com/library/view/terraform-in-action/9781617296895/" target="_blank" rel="noopener noreferrer">Terraform in Action - Scott Winkler (O'Reilly)</a></li>
</ul>
<p><!-- FIM --></p>