Pràctica 5: Case Study Infraestructura Cloud Completa
En aquesta secció, veurem un case study complet on una empresa fictícia, "TechStart", migra la seva aplicació des d'un entorn on-premises a AWS utilitzant Terraform.
Context del Projecte
TechStart és una empresa de tecnologia que ha crescut ràpidament i necessita escalar la seva infraestructura. Actualment tenen: - Una aplicació web Python (Django) amb aproximadament 10.000 usuaris actius diaris - Una base de dades PostgreSQL - Un servidor Redis per caching - Diversos serveis de background jobs (Celery workers) - Tot allotjat en servidors físics al seu propi data center
El seu objectiu és migrar a AWS per obtenir: - Escalabilitat automàtica - Alta disponibilitat - Millor rendiment global - Reducció de costos operatius - Agilitat en el desplegament
Arquitectura Objectiu
L'arquitectura que volen implementar inclou: - Multi-AZ per alta disponibilitat - Auto Scaling per gestionar pics de tràfic - CloudFront CDN per contingut estàtic - S3 per emmagatzematge d'assets - RDS Multi-AZ per la base de dades - ElastiCache per Redis - ALB per distribució de càrrega - Route53 per DNS
Fase 1: Configuració Base i Xarxa
El primer pas és establir la infraestructura de xarxa base. TechStart divideix el seu codi en mòduls reutilitzables:
Directory Structure:
techstart-infrastructure/
├── environments/
│ ├── development/
│ ├── staging/
│ └── production/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── modules/
│ ├── networking/
│ ├── compute/
│ ├── database/
│ ├── cache/
│ └── cdn/
└── shared/
├── iam/
└── monitoring/
modules/networking/main.tf:
# Crear VPC amb subnets privats i públics en 3 AZs
variable "project_name" { type = string }
variable "environment" { type = string }
variable "vpc_cidr" { type = string }
variable "availability_zones" { type = list(string) }
locals {
name_prefix = "${var.project_name}-${var.environment}"
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${local.name_prefix}-vpc"
Environment = var.environment
Project = var.project_name
}
}
# 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, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${local.name_prefix}-public-${count.index + 1}"
Tier = "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, 4, count.index + 3)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${local.name_prefix}-private-app-${count.index + 1}"
Tier = "Private-App"
}
}
# Subnets privats per base de dades
resource "aws_subnet" "private_data" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + 6)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${local.name_prefix}-private-data-${count.index + 1}"
Tier = "Private-Data"
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${local.name_prefix}-igw"
}
}
# NAT Gateways (un per AZ)
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = {
Name = "${local.name_prefix}-nat-eip-${count.index + 1}"
}
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 = {
Name = "${local.name_prefix}-nat-${count.index + 1}"
}
}
# 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 = {
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 = {
Name = "${local.name_prefix}-private-rt-${count.index + 1}"
}
}
# 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_data" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private_data[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
# VPC Endpoints per S3 i DynamoDB (estalviar costos de NAT)
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${data.aws_region.current.name}.s3"
route_table_ids = concat(
[aws_route_table.public.id],
aws_route_table.private[*].id
)
tags = {
Name = "${local.name_prefix}-s3-endpoint"
}
}
data "aws_region" "current" {}
# Outputs
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
output "private_app_subnet_ids" {
value = aws_subnet.private_app[*].id
}
output "private_data_subnet_ids" {
value = aws_subnet.private_data[*].id
}
Fase 2: Base de Dades i Cache
modules/database/main.tf:
variable "project_name" { type = string }
variable "environment" { type = string }
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string) }
variable "allowed_security_groups" { type = list(string) }
variable "db_name" { type = string }
variable "db_username" { type = string }
variable "db_password" { type = string; sensitive = true }
variable "instance_class" { type = string; default = "db.t3.medium" }
variable "allocated_storage" { type = number; default = 100 }
variable "multi_az" { type = bool; default = true }
variable "backup_retention_period" { type = number; default = 7 }
locals {
name_prefix = "${var.project_name}-${var.environment}"
}
# Security Group per RDS
resource "aws_security_group" "rds" {
name = "${local.name_prefix}-rds-sg"
description = "Security group for RDS PostgreSQL"
vpc_id = var.vpc_id
ingress {
description = "PostgreSQL from application"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = var.allowed_security_groups
}
tags = {
Name = "${local.name_prefix}-rds-sg"
}
}
# Subnet Group per RDS
resource "aws_db_subnet_group" "main" {
name = "${local.name_prefix}-db-subnet-group"
subnet_ids = var.subnet_ids
tags = {
Name = "${local.name_prefix}-db-subnet-group"
}
}
# Parameter Group personalitzat
resource "aws_db_parameter_group" "postgres" {
name = "${local.name_prefix}-postgres15"
family = "postgres15"
parameter {
name = "log_connections"
value = "1"
}
parameter {
name = "log_disconnections"
value = "1"
}
parameter {
name = "log_duration"
value = "1"
}
parameter {
name = "shared_preload_libraries"
value = "pg_stat_statements"
}
tags = {
Name = "${local.name_prefix}-postgres-params"
}
}
# RDS Instance
resource "aws_db_instance" "main" {
identifier = "${local.name_prefix}-postgres"
engine = "postgres"
engine_version = "15.3"
instance_class = var.instance_class
allocated_storage = var.allocated_storage
max_allocated_storage = var.allocated_storage * 2
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
multi_az = var.multi_az
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
parameter_group_name = aws_db_parameter_group.postgres.name
publicly_accessible = false
backup_retention_period = var.backup_retention_period
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
deletion_protection = var.environment == "production"
skip_final_snapshot = var.environment != "production"
final_snapshot_identifier = var.environment == "production" ? "${local.name_prefix}-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" : null
performance_insights_enabled = true
performance_insights_retention_period = 7
tags = {
Name = "${local.name_prefix}-postgres"
Environment = var.environment
}
}
# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "database_cpu" {
alarm_name = "${local.name_prefix}-db-cpu-utilization"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/RDS"
period = "300"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitors RDS CPU utilization"
dimensions = {
DBInstanceIdentifier = aws_db_instance.main.id
}
}
resource "aws_cloudwatch_metric_alarm" "database_connections" {
alarm_name = "${local.name_prefix}-db-connections"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "DatabaseConnections"
namespace = "AWS/RDS"
period = "300"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitors RDS database connections"
dimensions = {
DBInstanceIdentifier = aws_db_instance.main.id
}
}
output "endpoint" {
value = aws_db_instance.main.endpoint
sensitive = true
}
output "address" {
value = aws_db_instance.main.address
}
output "security_group_id" {
value = aws_security_group.rds.id
}
modules/cache/main.tf:
variable "project_name" { type = string }
variable "environment" { type = string }
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string) }
variable "allowed_security_groups" { type = list(string) }
variable "node_type" { type = string; default = "cache.t3.micro" }
variable "num_cache_nodes" { type = number; default = 2 }
locals {
name_prefix = "${var.project_name}-${var.environment}"
}
# Security Group per ElastiCache
resource "aws_security_group" "redis" {
name = "${local.name_prefix}-redis-sg"
description = "Security group for ElastiCache Redis"
vpc_id = var.vpc_id
ingress {
description = "Redis from application"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = var.allowed_security_groups
}
tags = {
Name = "${local.name_prefix}-redis-sg"
}
}
# Subnet Group per ElastiCache
resource "aws_elasticache_subnet_group" "main" {
name = "${local.name_prefix}-redis-subnet-group"
subnet_ids = var.subnet_ids
tags = {
Name = "${local.name_prefix}-redis-subnet-group"
}
}
# Parameter Group
resource "aws_elasticache_parameter_group" "redis" {
name = "${local.name_prefix}-redis7"
family = "redis7"
parameter {
name = "maxmemory-policy"
value = "allkeys-lru"
}
tags = {
Name = "${local.name_prefix}-redis-params"
}
}
# Replication Group (Redis Cluster)
resource "aws_elasticache_replication_group" "redis" {
replication_group_id = "${local.name_prefix}-redis"
replication_group_description = "Redis cluster for ${local.name_prefix}"
engine = "redis"
engine_version = "7.0"
node_type = var.node_type
num_cache_clusters = var.num_cache_nodes
parameter_group_name = aws_elasticache_parameter_group.redis.name
port = 6379
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.redis.id]
automatic_failover_enabled = var.num_cache_nodes > 1
multi_az_enabled = var.num_cache_nodes > 1
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token_enabled = true
auth_token = random_password.redis_auth.result
snapshot_retention_limit = 5
snapshot_window = "03:00-05:00"
maintenance_window = "sun:05:00-sun:07:00"
notification_topic_arn = aws_sns_topic.cache_notifications.arn
log_delivery_configuration {
destination = aws_cloudwatch_log_group.redis_slow_log.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
tags = {
Name = "${local.name_prefix}-redis"
Environment = var.environment
}
}
# Auth token per Redis
resource "random_password" "redis_auth" {
length = 32
special = false
}
# Guardar auth token a Secrets Manager
resource "aws_secretsmanager_secret" "redis_auth" {
name = "${local.name_prefix}/redis/auth-token"
tags = {
Name = "${local.name_prefix}-redis-auth"
}
}
resource "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = aws_secretsmanager_secret.redis_auth.id
secret_string = random_password.redis_auth.result
}
# SNS Topic per notificacions
resource "aws_sns_topic" "cache_notifications" {
name = "${local.name_prefix}-cache-notifications"
}
# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "redis_slow_log" {
name = "/aws/elasticache/${local.name_prefix}-redis/slow-log"
retention_in_days = 7
}
output "primary_endpoint" {
value = aws_elasticache_replication_group.redis.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.redis.reader_endpoint_address
}
output "auth_token_secret_arn" {
value = aws_secretsmanager_secret.redis_auth.arn
}
output "security_group_id" {
value = aws_security_group.redis.id
}
Fase 3: Compute i Auto Scaling
modules/compute/main.tf:
variable "project_name" { type = string }
variable "environment" { type = string }
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string) }
variable "target_group_arns" { type = list(string) }
variable "instance_type" { type = string; default = "t3.medium" }
variable "min_size" { type = number; default = 2 }
variable "max_size" { type = number; default = 10 }
variable "desired_capacity" { type = number; default = 3 }
variable "db_endpoint" { type = string }
variable "redis_endpoint" { type = string }
variable "app_secrets_arn" { type = string }
locals {
name_prefix = "${var.project_name}-${var.environment}"
}
# Security Group per instàncies de l'aplicació
resource "aws_security_group" "app" {
name = "${local.name_prefix}-app-sg"
description = "Security group for application instances"
vpc_id = var.vpc_id
# Permetre tràfic des del ALB
ingress {
description = "HTTP from ALB"
from_port = 8000
to_port = 8000
protocol = "tcp"
security_groups = [var.alb_security_group_id]
}
# Permetre tot el tràfic sortint
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${local.name_prefix}-app-sg"
}
}
# IAM Role per les instàncies
resource "aws_iam_role" "app" {
name = "${local.name_prefix}-app-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}]
})
tags = {
Name = "${local.name_prefix}-app-role"
}
}
# Política per accedir a Secrets Manager
resource "aws_iam_role_policy" "secrets_access" {
name = "${local.name_prefix}-secrets-access"
role = aws_iam_role.app.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
Resource = var.app_secrets_arn
}]
})
}
# Attached managed policies
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.app.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role_policy_attachment" "cloudwatch" {
role = aws_iam_role.app.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}
# Instance Profile
resource "aws_iam_instance_profile" "app" {
name = "${local.name_prefix}-app-profile"
role = aws_iam_role.app.name
}
# Obtenir l'AMI més recent d'Amazon Linux 2023
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
# User data script
locals {
user_data = templatefile("${path.module}/user_data.sh", {
environment = var.environment
db_endpoint = var.db_endpoint
redis_endpoint = var.redis_endpoint
secrets_arn = var.app_secrets_arn
region = data.aws_region.current.name
})
}
data "aws_region" "current" {}
# Launch Template
resource "aws_launch_template" "app" {
name_prefix = "${local.name_prefix}-"
image_id = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile {
arn = aws_iam_instance_profile.app.arn
}
user_data = base64encode(local.user_data)
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 1
instance_metadata_tags = "enabled"
}
monitoring {
enabled = true
}
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = 30
volume_type = "gp3"
encrypted = true
delete_on_termination = true
}
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "${local.name_prefix}-app"
Environment = var.environment
Project = var.project_name
}
}
tag_specifications {
resource_type = "volume"
tags = {
Name = "${local.name_prefix}-app-volume"
Environment = var.environment
}
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "app" {
name = "${local.name_prefix}-asg"
min_size = var.min_size
max_size = var.max_size
desired_capacity = var.desired_capacity
health_check_type = "ELB"
health_check_grace_period = 300
vpc_zone_identifier = var.subnet_ids
target_group_arns = var.target_group_arns
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
enabled_metrics = [
"GroupDesiredCapacity",
"GroupInServiceInstances",
"GroupMaxSize",
"GroupMinSize",
"GroupPendingInstances",
"GroupStandbyInstances",
"GroupTerminatingInstances",
"GroupTotalInstances"
]
tag {
key = "Name"
value = "${local.name_prefix}-app"
propagate_at_launch = true
}
tag {
key = "Environment"
value = var.environment
propagate_at_launch = true
}
lifecycle {
create_before_destroy = true
}
}
# Target Tracking Scaling Policy - CPU
resource "aws_autoscaling_policy" "cpu" {
name = "${local.name_prefix}-cpu-scaling"
autoscaling_group_name = aws_autoscaling_group.app.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 70.0
}
}
# Target Tracking Scaling Policy - Request Count
resource "aws_autoscaling_policy" "requests" {
name = "${local.name_prefix}-request-scaling"
autoscaling_group_name = aws_autoscaling_group.app.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ALBRequestCountPerTarget"
resource_label = "${var.alb_arn_suffix}/${var.target_group_arn_suffix}"
}
target_value = 1000.0
}
}
output "security_group_id" {
value = aws_security_group.app.id
}
output "autoscaling_group_name" {
value = aws_autoscaling_group.app.name
}
modules/compute/user_data.sh:
#!/bin/bash
set -e
# Variables
ENVIRONMENT="${environment}"
DB_ENDPOINT="${db_endpoint}"
REDIS_ENDPOINT="${redis_endpoint}"
SECRETS_ARN="${secrets_arn}"
REGION="${region}"
# Actualitzar sistema
dnf update -y
# Instal·lar dependencies
dnf install -y python3 python3-pip git postgresql15
# Instal·lar CloudWatch Agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm
# Configurar CloudWatch Agent
cat > /opt/aws/amazon-cloudwatch-agent/etc/config.json << 'EOF'
{
"metrics": {
"namespace": "TechStart/${environment}",
"metrics_collected": {
"cpu": {
"measurement": [{"name": "cpu_usage_idle"}],
"metrics_collection_interval": 60,
"totalcpu": false
},
"mem": {
"measurement": [{"name": "mem_used_percent"}],
"metrics_collection_interval": 60
}
}
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/app/application.log",
"log_group_name": "/aws/ec2/${environment}/application",
"log_stream_name": "{instance_id}"
}
]
}
}
}
}
EOF
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config \
-m ec2 \
-s \
-c file:/opt/aws/amazon-cloudwatch-agent/etc/config.json
# Obtenir secrets
SECRETS=$(aws secretsmanager get-secret-value \
--secret-id $SECRETS_ARN \
--region $REGION \
--query SecretString \
--output text)
# Crear usuari per l'aplicació
useradd -m -s /bin/bash appuser
# Clonar aplicació (substitueix amb el teu repo real)
cd /home/appuser
git clone https://github.com/techstart/app.git
cd app
# Crear virtual environment
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Crear fitxer de configuració
cat > /home/appuser/app/.env << EOF
ENVIRONMENT=$ENVIRONMENT
DB_HOST=$(echo $DB_ENDPOINT | cut -d: -f1)
DB_PORT=5432
DB_NAME=techstart
REDIS_HOST=$REDIS_ENDPOINT
REDIS_PORT=6379
$(echo $SECRETS | jq -r 'to_entries | .[] | "\(.key)=\(.value)"')
EOF
# Executar migracions (només en la primera instància)
if aws dynamodb get-item \
--table-name techstart-migrations-lock \
--key '{"LockID": {"S": "migration"}}' \
--region $REGION 2>&1 | grep -q "Item"; then
echo "Migrations already running or completed"
else
aws dynamodb put-item \
--table-name techstart-migrations-lock \
--item '{"LockID": {"S": "migration"}, "Timestamp": {"N": "'$(date +%s)'"}}' \
--region $REGION
python manage.py migrate
aws dynamodb delete-item \
--table-name techstart-migrations-lock \
--key '{"LockID": {"S": "migration"}}' \
--region $REGION
fi
# Crear systemd service
cat > /etc/systemd/system/techstart-app.service << 'EOF'
[Unit]
Description=TechStart Application
After=network.target
[Service]
Type=notify
User=appuser
WorkingDirectory=/home/appuser/app
Environment="PATH=/home/appuser/app/venv/bin"
EnvironmentFile=/home/appuser/app/.env
ExecStart=/home/appuser/app/venv/bin/gunicorn \
--workers 4 \
--bind 0.0.0.0:8000 \
--access-logfile /var/log/app/access.log \
--error-logfile /var/log/app/error.log \
techstart.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Crear directori de logs
mkdir -p /var/log/app
chown appuser:appuser /var/log/app
# Iniciar aplicació
systemctl daemon-reload
systemctl enable techstart-app
systemctl start techstart-app
# Health check
sleep 10
curl -f http://localhost:8000/health || exit 1
echo "Application started successfully"
Fase 4: Desplegament en Producció
environments/production/main.tf:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "TechStart"
Environment = "production"
ManagedBy = "Terraform"
Team = "DevOps"
}
}
}
# Variables locals
locals {
project_name = "techstart"
environment = "production"
availability_zones = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}
# Networking
module "networking" {
source = "../../modules/networking"
project_name = local.project_name
environment = local.environment
vpc_cidr = "10.0.0.0/16"
availability_zones = local.availability_zones
}
# Database
module "database" {
source = "../../modules/database"
project_name = local.project_name
environment = local.environment
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_data_subnet_ids
allowed_security_groups = [module.compute.security_group_id]
db_name = "techstart"
db_username = "admin"
db_password = var.db_password
instance_class = "db.t3.large"
allocated_storage = 200
multi_az = true
backup_retention_period = 7
}
# Cache
module "cache" {
source = "../../modules/cache"
project_name = local.project_name
environment = local.environment
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_data_subnet_ids
allowed_security_groups = [module.compute.security_group_id]
node_type = "cache.t3.medium"
num_cache_nodes = 3
}
# ALB
module "load_balancer" {
source = "../../modules/load-balancer"
project_name = local.project_name
environment = local.environment
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.public_subnet_ids
certificate_arn = var.ssl_certificate_arn
}
# Compute
module "compute" {
source = "../../modules/compute"
project_name = local.project_name
environment = local.environment
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_app_subnet_ids
target_group_arns = [module.load_balancer.target_group_arn]
instance_type = "t3.medium"
min_size = 3
max_size = 15
desired_capacity = 6
db_endpoint = module.database.address
redis_endpoint = module.cache.primary_endpoint
app_secrets_arn = aws_secretsmanager_secret.app_secrets.arn
alb_security_group_id = module.load_balancer.security_group_id
alb_arn_suffix = module.load_balancer.alb_arn_suffix
target_group_arn_suffix = module.load_balancer.target_group_arn_suffix
}
# Secrets per l'aplicació
resource "aws_secretsmanager_secret" "app_secrets" {
name = "${local.project_name}/${local.environment}/app-secrets"
tags = {
Name = "${local.project_name}-${local.environment}-app-secrets"
}
}
resource "aws_secretsmanager_secret_version" "app_secrets" {
secret_id = aws_secretsmanager_secret.app_secrets.id
secret_string = jsonencode({
SECRET_KEY = var.django_secret_key
DB_PASSWORD = var.db_password
REDIS_AUTH_TOKEN = module.cache.auth_token
AWS_ACCESS_KEY_ID = var.app_aws_access_key
AWS_SECRET_ACCESS_KEY = var.app_aws_secret_key
})
}
# CloudFront i S3 per assets estàtics
module "cdn" {
source = "../../modules/cdn"
project_name = local.project_name
environment = local.environment
origin_domain = module.load_balancer.dns_name
domain_name = var.domain_name
certificate_arn = var.cloudfront_certificate_arn
}
# Outputs
output "alb_dns_name" {
description = "DNS name del Application Load Balancer"
value = module.load_balancer.dns_name
}
output "cloudfront_domain" {
description = "Domini de CloudFront"
value = module.cdn.cloudfront_domain
}
output "database_endpoint" {
description = "Endpoint de la base de dades"
value = module.database.endpoint
sensitive = true
}
Resultats del Case Study
Després d'implementar aquesta infraestructura amb Terraform, TechStart va aconseguir:
Velocitat de Desplegament:
- Abans: 2-3 setmanes per configurar manualment un nou entorn
- Després: 30 minuts amb terraform apply
Consistència: - Els entorns de development, staging i production són idèntics (excepte mida de recursos) - No més "funciona en el meu entorn però no en producció"
Escalabilitat: - Auto Scaling gestiona pics de tràfic automàticament - Van poder gestionar Black Friday (10x tràfic normal) sense problemes
Costs: - Reducció del 40% en costos d'infraestructura - Auto Scaling escala cap avall durant hores vall
Documentació: - Tot el codi està versionat a Git - Cada canvi té un commit message explicant el "per què" - Nous membres de l'equip poden entendre la infraestructura llegint el codi
Disaster Recovery: - Poden recrear tota la infraestructura en una altra regió en 1 hora - Backups automàtics de bases de dades i state