Pràctica 4: Exemples Pràctics amb Terraform
Vegem alguns exemples pràctics complets que mostren com utilitzar Terraform en escenaris reals.
Exemple 1: Aplicació Web Completa a AWS
Aquest exemple crea una aplicació web completa amb tots els components necessaris:
# versions.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "mycompany-terraform-state"
key = "webapp/production/terraform.tfstate"
region = "eu-west-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "WebApp"
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
# variables.tf
variable "aws_region" {
description = "Regió d'AWS"
type = string
default = "eu-west-1"
}
variable "environment" {
description = "Entorn de desplegament"
type = string
default = "production"
}
variable "project_name" {
description = "Nom del projecte"
type = string
default = "webapp"
}
variable "vpc_cidr" {
description = "CIDR block per la VPC"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "Zones de disponibilitat a utilitzar"
type = list(string)
default = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}
variable "db_password" {
description = "Password per la base de dades"
type = string
sensitive = true
}
# main.tf
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = {
Project = var.project_name
Environment = var.environment
}
}
# === NETWORKING ===
# VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-vpc"
})
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-igw"
})
}
# Subnets públics (per ALB)
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-public-${var.availability_zones[count.index]}"
Type = "Public"
})
}
# Subnets privats (per aplicació)
resource "aws_subnet" "private_app" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-private-app-${var.availability_zones[count.index]}"
Type = "Private-App"
})
}
# Subnets privats (per base de dades)
resource "aws_subnet" "private_db" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 20)
availability_zone = var.availability_zones[count.index]
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-private-db-${var.availability_zones[count.index]}"
Type = "Private-DB"
})
}
# NAT Gateways (un per AZ per alta disponibilitat)
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-nat-eip-${var.availability_zones[count.index]}"
})
depends_on = [aws_internet_gateway.main]
}
resource "aws_nat_gateway" "main" {
count = length(var.availability_zones)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-nat-${var.availability_zones[count.index]}"
})
}
# Route tables
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-public-rt"
})
}
resource "aws_route_table" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-private-rt-${var.availability_zones[count.index]}"
})
}
# Associacions
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private_app" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private_app[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
resource "aws_route_table_association" "private_db" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private_db[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
# === SECURITY GROUPS ===
# ALB Security Group
resource "aws_security_group" "alb" {
name = "${local.name_prefix}-alb-sg"
description = "Security group for Application Load Balancer"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from Internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from Internet"
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 = merge(local.common_tags, {
Name = "${local.name_prefix}-alb-sg"
})
}
# App Server Security Group
resource "aws_security_group" "app" {
name = "${local.name_prefix}-app-sg"
description = "Security group for application servers"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from ALB"
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-app-sg"
})
}
# Database Security Group
resource "aws_security_group" "db" {
name = "${local.name_prefix}-db-sg"
description = "Security group for database"
vpc_id = aws_vpc.main.id
ingress {
description = "PostgreSQL from app servers"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-db-sg"
})
}
# === LOAD BALANCER ===
resource "aws_lb" "main" {
name = "${local.name_prefix}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
enable_deletion_protection = var.environment == "production"
enable_http2 = true
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-alb"
})
}
resource "aws_lb_target_group" "app" {
name = "${local.name_prefix}-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
deregistration_delay = 30
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-tg"
})
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# === AUTO SCALING ===
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_launch_template" "app" {
name_prefix = "${local.name_prefix}-"
image_id = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.app.id]
user_data = base64encode(<<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from ${local.name_prefix}</h1>" > /var/www/html/index.html
EOF
)
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 1
}
tag_specifications {
resource_type = "instance"
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-app-server"
})
}
}
resource "aws_autoscaling_group" "app" {
name = "${local.name_prefix}-asg"
min_size = 2
max_size = 6
desired_capacity = 3
health_check_type = "ELB"
health_check_grace_period = 300
vpc_zone_identifier = aws_subnet.private_app[*].id
target_group_arns = [aws_lb_target_group.app.arn]
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
tag {
key = "Name"
value = "${local.name_prefix}-app-server"
propagate_at_launch = true
}
dynamic "tag" {
for_each = local.common_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
# === DATABASE ===
resource "aws_db_subnet_group" "main" {
name = "${local.name_prefix}-db-subnet-group"
subnet_ids = aws_subnet.private_db[*].id
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-db-subnet-group"
})
}
resource "aws_db_instance" "main" {
identifier = "${local.name_prefix}-db"
engine = "postgres"
engine_version = "15.3"
instance_class = "db.t3.micro"
allocated_storage = 20
max_allocated_storage = 100
storage_type = "gp3"
storage_encrypted = true
db_name = "webapp"
username = "admin"
password = var.db_password
multi_az = var.environment == "production"
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
publicly_accessible = false
backup_retention_period = var.environment == "production" ? 7 : 1
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
deletion_protection = var.environment == "production"
skip_final_snapshot = var.environment != "production"
final_snapshot_identifier = var.environment == "production" ? "${local.name_prefix}-final-snapshot" : null
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-db"
})
}
# === OUTPUTS ===
output "alb_dns_name" {
description = "DNS name del Application Load Balancer"
value = aws_lb.main.dns_name
}
output "database_endpoint" {
description = "Endpoint de la base de dades"
value = aws_db_instance.main.endpoint
sensitive = true
}
output "vpc_id" {
description = "ID de la VPC"
value = aws_vpc.main.id
}
Aquest exemple crea una infraestructura completa de producció amb: - VPC amb subnets públics i privats en múltiples AZs - NAT Gateways per alta disponibilitat - Application Load Balancer - Auto Scaling Group amb Launch Template - Base de dades RDS PostgreSQL amb Multi-AZ - Security Groups correctament configurats - Tags consistents en tots els recursos
Exemple 2: Kubernetes Cluster amb EKS
Aquest exemple crea un cluster de Kubernetes gestionat a AWS (EKS):
# Utilitzar el mòdul EKS oficial
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"
cluster_name = "${local.name_prefix}-eks"
cluster_version = "1.28"
vpc_id = aws_vpc.main.id
subnet_ids = aws_subnet.private_app[*].id
# Control plane
cluster_endpoint_public_access = true
# OIDC Provider per service accounts
enable_irsa = true
# Managed node groups
eks_managed_node_groups = {
general = {
name = "general-purpose"
instance_types = ["t3.medium"]
capacity_type = "ON_DEMAND"
min_size = 2
max_size = 10
desired_size = 3
labels = {
role = "general"
}
tags = merge(local.common_tags, {
NodeGroup = "general-purpose"
})
}
spot = {
name = "spot-instances"
instance_types = ["t3.medium", "t3a.medium"]
capacity_type = "SPOT"
min_size = 0
max_size = 5
desired_size = 2
labels = {
role = "spot"
}
taints = [{
key = "spot"
value = "true"
effect = "NoSchedule"
}]
}
}
# Cluster security group rules
cluster_security_group_additional_rules = {
ingress_nodes_ephemeral_ports_tcp = {
description = "Nodes on ephemeral ports"
protocol = "tcp"
from_port = 1025
to_port = 65535
type = "ingress"
source_node_security_group = true
}
}
# Node security group rules
node_security_group_additional_rules = {
ingress_self_all = {
description = "Node to node all ports/protocols"
protocol = "-1"
from_port = 0
to_port = 0
type = "ingress"
self = true
}
}
tags = local.common_tags
}
# Add-ons
resource "aws_eks_addon" "vpc_cni" {
cluster_name = module.eks.cluster_name
addon_name = "vpc-cni"
addon_version = "v1.15.0-eksbuild.2"
}
resource "aws_eks_addon" "coredns" {
cluster_name = module.eks.cluster_name
addon_name = "coredns"
addon_version = "v1.10.1-eksbuild.4"
depends_on = [module.eks.eks_managed_node_groups]
}
resource "aws_eks_addon" "kube_proxy" {
cluster_name = module.eks.cluster_name
addon_name = "kube-proxy"
addon_version = "v1.28.1-eksbuild.1"
}
# Output kubeconfig
output "cluster_endpoint" {
description = "Endpoint del cluster EKS"
value = module.eks.cluster_endpoint
}
output "cluster_name" {
description = "Nom del cluster EKS"
value = module.eks.cluster_name
}
output "configure_kubectl" {
description = "Comanda per configurar kubectl"
value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_name}"
}
Aquest exemple mostra com utilitzar mòduls de la comunitat per crear recursos complexos amb poques línies de codi.