Mòduls: Reutilitzant i Organitzant Codi
6. Mòduls: Reutilitzant i Organitzant Codi
A mesura que les teves configuracions de Terraform creixen, necessites una manera d'organitzar i reutilitzar codi. Aquí és on entren els mòduls.
Què És un Mòdul?
Un mòdul és simplement un conjunt de fitxers de configuració de Terraform en un directori. Cada configuració de Terraform que has escrit fins ara és tècnicament un mòdul (anomenat el "root module"). Però la veritable potència ve quan crees mòduls reutilitzables que encapsulen funcionalitat.
Imagina que necessites crear servidors web en diversos projectes. En lloc de copiar i enganxar el mateix codi una i altra vegada, pots crear un mòdul "webserver" que encapsuli tota la lògica per crear un servidor web amb les millors pràctiques, i després reutilitzar aquest mòdul arreu.
Estructura d'un Mòdul
Un mòdul típic té aquesta estructura:
modules/webserver/
├── main.tf # Recursos principals
├── variables.tf # Variables d'entrada
├── outputs.tf # Outputs del mòdul
├── README.md # Documentació
└── versions.tf # Requeriments de versions
Vegem un exemple de mòdul complet per crear un servidor web:
modules/webserver/variables.tf:
variable "name" {
description = "Nom del servidor web"
type = string
}
variable "environment" {
description = "Entorn (dev, staging, production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "L'entorn ha de ser dev, staging o production."
}
}
variable "instance_type" {
description = "Tipus d'instància EC2"
type = string
default = "t3.micro"
}
variable "vpc_id" {
description = "ID de la VPC on crear el servidor"
type = string
}
variable "subnet_id" {
description = "ID del subnet on crear el servidor"
type = string
}
variable "allowed_cidr_blocks" {
description = "Blocs CIDR permesos per accedir al servidor"
type = list(string)
default = []
}
variable "enable_monitoring" {
description = "Si s'ha d'activar monitorització detallada"
type = bool
default = false
}
variable "tags" {
description = "Tags addicionals per al servidor"
type = map(string)
default = {}
}
modules/webserver/main.tf:
# Obtenir l'AMI més recent d'Ubuntu
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# Security Group per al servidor web
resource "aws_security_group" "webserver" {
name = "${var.name}-sg"
description = "Security group for ${var.name}"
vpc_id = var.vpc_id
# HTTP
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = length(var.allowed_cidr_blocks) > 0 ? var.allowed_cidr_blocks : ["0.0.0.0/0"]
}
# HTTPS
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = length(var.allowed_cidr_blocks) > 0 ? var.allowed_cidr_blocks : ["0.0.0.0/0"]
}
# SSH (només des de les IPs especificades)
dynamic "ingress" {
for_each = length(var.allowed_cidr_blocks) > 0 ? [1] : []
content {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.allowed_cidr_blocks
}
}
# Tot el tràfic sortint
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.tags, {
Name = "${var.name}-sg"
Environment = var.environment
})
}
# IAM Role per al servidor (per utilitzar SSM Session Manager)
resource "aws_iam_role" "webserver" {
name = "${var.name}-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
tags = merge(var.tags, {
Name = "${var.name}-role"
Environment = var.environment
})
}
# Attach managed policy per SSM
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.webserver.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# Instance Profile
resource "aws_iam_instance_profile" "webserver" {
name = "${var.name}-profile"
role = aws_iam_role.webserver.name
}
# User data script
locals {
user_data = <<-EOF
#!/bin/bash
set -e
# Actualitzar sistema
apt-get update
apt-get upgrade -y
# Instal·lar nginx
apt-get install -y nginx
# Configurar nginx
cat > /var/www/html/index.html <<'HTML'
<!DOCTYPE html>
<html>
<head>
<title>Welcome to ${var.name}</title>
</head>
<body>
<h1>${var.name}</h1>
<p>Environment: ${var.environment}</p>
<p>Managed by Terraform</p>
</body>
</html>
HTML
# Assegurar que nginx està executant-se
systemctl enable nginx
systemctl start nginx
# Instal·lar CloudWatch agent si la monitorització està activada
%{ if var.enable_monitoring }
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
dpkg -i amazon-cloudwatch-agent.deb
%{ endif }
EOF
}
# Instància EC2
resource "aws_instance" "webserver" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.webserver.id]
iam_instance_profile = aws_iam_instance_profile.webserver.name
user_data = local.user_data
monitoring = var.enable_monitoring
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 1
}
root_block_device {
volume_type = "gp3"
volume_size = 20
encrypted = true
delete_on_termination = true
}
tags = merge(var.tags, {
Name = var.name
Environment = var.environment
ManagedBy = "Terraform"
})
lifecycle {
create_before_destroy = true
}
}
# Elastic IP (opcional, però recomanat per tenir IP estàtica)
resource "aws_eip" "webserver" {
instance = aws_instance.webserver.id
domain = "vpc"
tags = merge(var.tags, {
Name = "${var.name}-eip"
Environment = var.environment
})
}
modules/webserver/outputs.tf:
output "instance_id" {
description = "ID de la instància EC2"
value = aws_instance.webserver.id
}
output "public_ip" {
description = "IP pública del servidor"
value = aws_eip.webserver.public_ip
}
output "private_ip" {
description = "IP privada del servidor"
value = aws_instance.webserver.private_ip
}
output "security_group_id" {
description = "ID del security group"
value = aws_security_group.webserver.id
}
output "instance_arn" {
description = "ARN de la instància"
value = aws_instance.webserver.arn
}
modules/webserver/versions.tf:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
Utilitzant el Mòdul
Ara que hem creat el mòdul, podem utilitzar-lo fàcilment en diferents projectes:
# main.tf
module "web_server_production" {
source = "./modules/webserver"
name = "production-web-01"
environment = "production"
instance_type = "t3.medium"
vpc_id = aws_vpc.main.id
subnet_id = aws_subnet.public[0].id
allowed_cidr_blocks = ["203.0.113.0/24"] # Només des de l'oficina
enable_monitoring = true
tags = {
Project = "MyApp"
Owner = "DevOps Team"
}
}
module "web_server_staging" {
source = "./modules/webserver"
name = "staging-web-01"
environment = "staging"
instance_type = "t3.small"
vpc_id = aws_vpc.main.id
subnet_id = aws_subnet.public[1].id
allowed_cidr_blocks = ["0.0.0.0/0"] # Accessible públicament per testing
enable_monitoring = false
tags = {
Project = "MyApp"
Owner = "DevOps Team"
}
}
# Utilitzar els outputs dels mòduls
output "production_web_ip" {
value = module.web_server_production.public_ip
}
output "staging_web_ip" {
value = module.web_server_staging.public_ip
}
Mòduls del Terraform Registry
A més de crear els teus propis mòduls, pots utilitzar mòduls de la comunitat des del Terraform Registry (https://registry.terraform.io/):
# Utilitzar el mòdul VPC oficial d'AWS
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
tags = {
Environment = "production"
}
}
Els mòduls del Registry estan generalment molt ben testejats i segueixen les millors pràctiques, estalviant-te molt de temps i esforç.
Best Practices per Mòduls
Quan creïs mòduls, segueix aquestes millors pràctiques:
1. Fer els mòduls petits i enfocats: Cada mòdul hauria de fer una cosa i fer-la bé. Un mòdul "webserver" és millor que un mòdul "application" que fa massa coses.
2. Documentar bé: Escriu un README.md complet explicant què fa el mòdul, quines variables requereix, i quins outputs proporciona.
3. Utilitzar validacions: Valida les entrades per prevenir errors:
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "L'entorn ha de ser dev, staging o production."
}
}
4. Proporcionar defaults sensats: Les variables haurien de tenir valors per defecte raonables quan sigui possible:
5. Fer outputs útils: Exposa tota la informació que els usuaris del mòdul puguin necessitar:
output "instance_id" {
value = aws_instance.this.id
}
output "public_ip" {
value = aws_instance.this.public_ip
}
6. Versionar els mòduls: Utilitza Git tags per versionar els teus mòduls, permetent als usuaris fixar una versió específica.