🚀 Developer Cookbook - FASE 3: Infraestructura como Código (IaC)

Recetas prácticas para construir, desplegar y operar infraestructura moderna


📚 Tabla de Contenidos

  1. Receta 3.5: Terraform Avanzado

Infraestructura como Código

Receta 3.5: Terraform Avanzado

Módulos reutilizables:

# modules/ecs-service/main.tf
variable "service_name" {
  type = string
}

variable "cluster_id" {
  type = string
}

variable "image" {
  type = string
}

variable "cpu" {
  type    = number
  default = 256
}

variable "memory" {
  type    = number
  default = 512
}

variable "desired_count" {
  type    = number
  default = 2
}

variable "environment_variables" {
  type    = map(string)
  default = {}
}

# Task Definition
resource "aws_ecs_task_definition" "this" {
  family                   = var.service_name
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = var.cpu
  memory                   = var.memory

  container_definitions = jsonencode([
    {
      name  = var.service_name
      image = var.image

      environment = [
        for key, value in var.environment_variables : {
          name  = key
          value = value
        }
      ]

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = "/ecs/${var.service_name}"
          "awslogs-region"        = data.aws_region.current.name
          "awslogs-stream-prefix" = "ecs"
        }
      }
    }
  ])
}

# Service
resource "aws_ecs_service" "this" {
  name            = var.service_name
  cluster         = var.cluster_id
  task_definition = aws_ecs_task_definition.this.arn
  desired_count   = var.desired_count
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = var.subnet_ids
    security_groups  = [aws_security_group.service.id]
    assign_public_ip = false
  }
}

output "service_name" {
  value = aws_ecs_service.this.name
}

Usar el módulo:

# main.tf
module "api_service" {
  source = "./modules/ecs-service"

  service_name  = "api"
  cluster_id    = aws_ecs_cluster.main.id
  image         = "myapp:latest"
  cpu           = 512
  memory        = 1024
  desired_count = 3

  environment_variables = {
    NODE_ENV     = "production"
    DATABASE_URL = var.database_url
  }
}

module "worker_service" {
  source = "./modules/ecs-service"

  service_name  = "worker"
  cluster_id    = aws_ecs_cluster.main.id
  image         = "myapp-worker:latest"
  cpu           = 256
  memory        = 512
  desired_count = 2

  environment_variables = {
    REDIS_URL = var.redis_url
  }
}

Workspaces (multi-environment):

# main.tf
locals {
  environment = terraform.workspace

  # Configuración por environment
  config = {
    dev = {
      instance_type = "t3.small"
      min_size      = 1
      max_size      = 2
    }
    staging = {
      instance_type = "t3.medium"
      min_size      = 2
      max_size      = 4
    }
    production = {
      instance_type = "t3.large"
      min_size      = 3
      max_size      = 10
    }
  }

  env_config = local.config[local.environment]
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.env_config.instance_type

  tags = {
    Name        = "app-${local.environment}"
    Environment = local.environment
  }
}
# Crear workspace
terraform workspace new production

# Listar workspaces
terraform workspace list

# Cambiar workspace
terraform workspace select production

# Deploy en workspace actual
terraform apply

Data Sources:

# Buscar AMI más reciente
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

# Buscar VPC existente
data "aws_vpc" "main" {
  tags = {
    Name = "main-vpc"
  }
}

# Availability Zones
data "aws_availability_zones" "available" {
  state = "available"
}

# Caller identity (account ID)
data "aws_caller_identity" "current" {}

# Region actual
data "aws_region" "current" {}

Conditionals y Loops:

# Count para crear múltiples recursos
resource "aws_subnet" "public" {
  count = 3

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "public-${count.index + 1}"
  }
}

# for_each con map
variable "users" {
  type = map(object({
    role = string
  }))

  default = {
    alice = { role = "admin" }
    bob   = { role = "developer" }
  }
}

resource "aws_iam_user" "users" {
  for_each = var.users

  name = each.key

  tags = {
    Role = each.value.role
  }
}

# Conditional resource
resource "aws_instance" "bastion" {
  count = var.create_bastion ? 1 : 0

  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

# Dynamic blocks
resource "aws_security_group" "web" {
  name = "web-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules

    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

Outputs y Dependencies:

# Outputs
output "alb_dns" {
  value       = aws_lb.main.dns_name
  description = "DNS name of the load balancer"
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

# Explicit dependency
resource "aws_instance" "app" {
  # ...

  depends_on = [
    aws_db_instance.main,
    aws_elasticache_cluster.redis
  ]
}

# Prevent destroy
resource "aws_db_instance" "main" {
  # ...

  lifecycle {
    prevent_destroy = true
  }
}

# Create before destroy
resource "aws_launch_template" "app" {
  # ...

  lifecycle {
    create_before_destroy = true
  }
}

Remote State:

# Backend configuration
terraform {
  backend "s3" {
    bucket         = "myapp-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-lock"
  }
}

# Usar output de otro state
data "terraform_remote_state" "vpc" {
  backend = "s3"

  config = {
    bucket = "myapp-terraform-state"
    key    = "vpc/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.vpc.outputs.private_subnet_ids[0]
}

Versión: 1.0 Fecha: 2024 Autor: Roadmap del Desarrollador del Futuro Licencia: Uso educativo