Desafio: Provisionando uma VPC com Terraform
Esse desafio foi criar uma VPC completa usando Terraform, sem tocar no console da AWS. Trabalhei com subnets públicas e privadas, Internet Gateway, NAT Gateway e tabelas de rota, tudo via código.
Pré-requisitos
Criar uma rede básica na AWS com os seguintes recursos:
- 1 VPC
- 2 Subnets privadas
- 2 Subnets públicas
- 2 NAT Gateways
- 1 Internet Gateway
Por que Terraform?
Já usei CloudFormation antes, mas o Terraform tem uma vantagem clara: funciona em qualquer provider. Com ele dá pra subir o mesmo ambiente várias vezes sem variação, e o state garante que você sempre sabe o que está rodando. Terraform Workspaces então facilitam muito quando precisa isolar ambientes do mesmo projeto.
Como foi pensada a resolução do desafio
Para deixar o código mais dinâmico e reutilizável, utilizei Built-in Functions e Meta-Arguments.
O que são Built-in Functions?
O Terraform tem funções nativas que evitam repetição de lógica. As que usei aqui:
cidrsubnet()→ calcula automaticamente o CIDR das subnetslower()→ transforma strings em minúsculasformat()→ formata strings dinamicamente
O que são Meta-Arguments?
Meta-arguments controlam como os recursos se comportam. Os principais que usei:
for_each→ cria múltiplos recursos a partir de listas ou mapasdepends_on→ garante a ordem correta de criação de recursos
Resolução e criação da VPC
A VPC é sua rede privada dentro da AWS. É nela que fica o CIDR, as subnets, as tabelas de rota. Basicamente a base de tudo.
vpc.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Criando VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr_block
tags = {
Name = "vpc-project-elven"
terraformed = "true"
}
}
// Subnets Públicas
resource "aws_subnet" "public" {
for_each = { for idx, name in local.subnet_public : idx => name } # Cria o recurso com base em uma lista
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr_block, 8, each.key) # Calcula automaticamente o CIDR
map_public_ip_on_launch = true
tags = {
Name = lower(format("subnet-%s", each.value)) # Formata o nome da subnet
}
}
// Subnets Privadas
resource "aws_subnet" "priv" {
for_each = { for idx, name in local.subnet_priv : idx => name }
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr_block, 8, each.key + length(local.subnet_public))
map_public_ip_on_launch = false
tags = {
Name = lower(format("subnet-%s", each.value))
}
}
// Internet Gateway
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "igw"
terraformed = "true"
}
}
// Tabela de Rotas Públicas
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
}
resource "aws_route" "internet_access" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public # Associa cada subnet à tabela de rota
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}
// EIP para NAT Gateways
resource "aws_eip" "nat" {
for_each = aws_subnet.public # Cria um EIP por subnet pública
domain = "vpc"
tags = {
Name = "nat-eip-${each.key}"
terraformed = "true"
}
}
// NAT Gateway
resource "aws_nat_gateway" "ngw" {
for_each = aws_subnet.public
subnet_id = each.value.id
allocation_id = aws_eip.nat[each.key].id
tags = {
Name = "nat-gw-${each.key}"
terraformed = "true"
}
depends_on = [aws_internet_gateway.igw] # Garante que o IGW seja criado antes do NAT Gateway
}
// Tabela de Rotas Privadas
resource "aws_route_table" "priv" {
for_each = aws_subnet.public
vpc_id = aws_vpc.main.id
tags = {
Name = "rtb-priv-${each.key}"
terraformed = "true"
}
}
// Rotas Privadas
resource "aws_route" "priv" {
for_each = aws_route_table.priv
route_table_id = each.value.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.ngw[each.key].id
}
// Associações de Subnets Privadas às Tabelas de Rotas
resource "aws_route_table_association" "priv" {
for_each = aws_subnet.priv
subnet_id = each.value.id
route_table_id = aws_route_table.priv[each.key % length(aws_nat_gateway.ngw)].id
}
Criação do módulo Locals
Locals são variáveis internas do Terraform. Em vez de repetir as mesmas listas em vários recursos, centralizo tudo num lugar só.
locals.tf
1
2
3
4
5
6
7
8
9
10
11
12
locals {
subnet_public = [
"public-1a",
"public-1c"
]
subnet_priv = [
"priv-1a",
"priv-1c"
]
}
Criação das Variables
Variables evitam que você fixe valores no código. Com elas dá pra subir ambientes diferentes sem mudar nada no Terraform, só passando os valores via .tfvars ou linha de comando.
variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
variable "region" {
description = "Região da AWS"
default = "us-east-1"
}
variable "vpc_cidr_block" {
type = string
description = "Bloco CIDR /16 para VLSM"
default = "10.100.0.0/16"
}
variable "aws_subnet" {
type = number
description = "Tamanho do bloco /24"
default = 24
}
Criação do Main
O main.tf é o ponto de entrada: aqui fica a definição do provider e a versão do Terraform. Os outros arquivos complementam, mas é o main que amarra tudo.
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Definindo provedor
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
Finalização do Desafio
Esse desafio faz parte do Programa SRE Advanced da Elvenworks.
- Repositório oficial da Elven: Minha Primeira VPC com Terraform
- Meu repositório com o código deste desafio: Terraform VPC no GitLab