🚀 Developer Cookbook - FASE 3: Infraestructura como Código (IaC)
Recetas prácticas para construir, desplegar y operar infraestructura moderna
📚 Tabla de Contenidos
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