Serverless

"Serverless no significa 'sin servidores'. Significa 'no son MIS servidores y solo pago cuando se usan'."

Qué vas a aprender en este capítulo

Serverless lleva la abstracción cloud al extremo: vos solo escribís código de funciones; el proveedor maneja todo lo demás (servidores, escalado, monitoreo). Pagás solo por las invocaciones reales. Este capítulo cubre cuándo es la herramienta correcta y cuándo no.


4.1 ¿Qué es serverless?

📐 Fundamento

Características de serverless:

  1. Sin gestión de servidores — no provisionás VMs ni contenedores.
  2. Pago por uso — pagás por invocaciones y tiempo de ejecución, no por tiempo idle.
  3. Escalado automático — de 0 a millones de invocaciones automáticamente.
  4. Stateless — cada invocación es independiente.
  5. Event-driven — se ejecuta en respuesta a eventos.

Tipos de serverless:

Tipo Ejemplos Para qué
FaaS (Function as a Service) Lambda, Cloud Functions, Azure Functions Código que reacciona a eventos
BaaS (Backend as a Service) Firebase, Supabase, Auth0 Auth, DB, storage gestionados
Serverless containers Cloud Run, ECS Fargate Tus contenedores, sin gestionar infraestructura
Serverless DBs DynamoDB, Aurora Serverless, Neon BD que escala automáticamente

Cuándo usar serverless:

  • Tráfico irregular o impredecible.
  • Workloads esporádicos (cron jobs, procesamiento de archivos).
  • APIs simples con baja latencia.
  • Procesamiento event-driven (file uploaded → procesar).
  • MVPs y prototipos rápidos.

Cuándo NO usar:

  • Workloads constantes y predecibles (sale más caro que VMs reservadas).
  • Aplicaciones con requisito de baja latencia consistente (cold starts).
  • Procesos que tardan más del límite de la plataforma (Lambda: 15 min máx).
  • Procesamiento que requiere mucho estado.

4.2 AWS Lambda en práctica

📐 Fundamento

Estructura de una función Lambda en Python:

# handler.py
import json
import boto3
from PIL import Image
from io import BytesIO

s3 = boto3.client('s3')

def lambda_handler(event, context):
    """
    Trigger: S3 ObjectCreated event.
    Crea thumbnails para imágenes subidas.
    """
    # Extraer bucket y key del event
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    # Skip si ya es thumbnail
    if key.startswith('thumbnails/'):
        return {'statusCode': 200, 'body': 'Already a thumbnail'}
    
    # Descargar la imagen
    obj = s3.get_object(Bucket=bucket, Key=key)
    image_data = obj['Body'].read()
    
    # Generar thumbnail
    img = Image.open(BytesIO(image_data))
    img.thumbnail((200, 200))
    buffer = BytesIO()
    img.save(buffer, format='WEBP', quality=85)
    
    # Subir thumbnail
    thumb_key = f"thumbnails/{key}"
    s3.put_object(
        Bucket=bucket, 
        Key=thumb_key, 
        Body=buffer.getvalue(),
        ContentType='image/webp'
    )
    
    return {
        'statusCode': 200,
        'body': json.dumps({'original': key, 'thumbnail': thumb_key})
    }

Desplegar con Terraform:

# Empaquetar el código
data "archive_file" "lambda_zip" {
  type        = "zip"
  source_dir  = "${path.module}/lambda_code"
  output_path = "${path.module}/lambda.zip"
}

# La función Lambda
resource "aws_lambda_function" "thumbnail_generator" {
  function_name    = "thumbnail-generator"
  filename         = data.archive_file.lambda_zip.output_path
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256
  handler          = "handler.lambda_handler"
  runtime          = "python3.12"
  timeout          = 30
  memory_size      = 512
  
  role = aws_iam_role.lambda_role.arn
  
  environment {
    variables = {
      LOG_LEVEL = "INFO"
    }
  }
}

# IAM role
resource "aws_iam_role" "lambda_role" {
  name = "thumbnail-generator-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
    }]
  })
}

resource "aws_iam_role_policy" "lambda_s3" {
  role = aws_iam_role.lambda_role.id
  policy = jsonencode({
    Statement = [{
      Effect = "Allow"
      Action = ["s3:GetObject", "s3:PutObject"]
      Resource = "arn:aws:s3:::la-esquina-images/*"
    }, {
      Effect = "Allow"
      Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]
      Resource = "*"
    }]
  })
}

# Trigger desde S3
resource "aws_s3_bucket_notification" "image_upload" {
  bucket = "la-esquina-images"
  
  lambda_function {
    lambda_function_arn = aws_lambda_function.thumbnail_generator.arn
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "originals/"
  }
}

Triggers comunes:

Trigger Caso de uso
API Gateway HTTP endpoints serverless
S3 events Procesar archivos uploaded
EventBridge / SQS Mensajes y eventos
DynamoDB Streams Reaccionar a cambios en BD
CloudWatch Events Cron jobs (rate(1 hour) o cron expressions)
Cognito Triggers en signup/login

4.3 Cold starts y limitaciones

💡 Intuición

Cold start: la primera vez que se invoca una Lambda (o después de mucho tiempo idle), AWS necesita: aprovisionar un container, cargar tu código, inicializar la runtime. Esto puede tardar 100ms-2000ms — significativo si querés latencia baja.

Las invocaciones siguientes (mientras el container sigue caliente) son rápidas.

📐 Fundamento

Mitigar cold starts:

  1. Provisioned Concurrency — pagar para mantener N instances calientes.
  2. Lenguaje correcto — Python/Node.js ~200ms, Java/.NET ~1000ms+.
  3. Reducir el package size — menos código = más rápido el load.
  4. Inicialización fuera del handler — solo se ejecuta una vez por container.
# Inicializar conexiones FUERA del handler
db_client = boto3.client('dynamodb')   # se reusa entre invocaciones
config = load_config_from_ssm()         # cargado una vez

def lambda_handler(event, context):
    # Solo lo específico del request va aquí
    return db_client.get_item(...)

Límites de Lambda:

Recurso Límite
Tiempo de ejecución 15 minutos
Memoria 128 MB - 10 GB
Tamaño del package 50 MB zipped, 250 MB unzipped, 10 GB con Container Image
/tmp storage 512 MB - 10 GB
Concurrent executions 1000 (default, ampliable)
Payload size 6 MB sync, 256 KB async

Container Images en Lambda:

Para casos donde necesitás más de 250 MB (ej: ML models), Lambda soporta Container Images de hasta 10 GB:

FROM public.ecr.aws/lambda/python:3.12

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY handler.py modelo.pkl ${LAMBDA_TASK_ROOT}/

CMD ["handler.lambda_handler"]

4.4 Arquitecturas event-driven

📐 Fundamento

Patrón: API serverless con DynamoDB:

Client
  │
  ▼
[API Gateway]  ←─── HTTP routing, throttling, auth
  │
  ▼
[Lambda] ────→ [DynamoDB]   ←─── BD serverless

Ejemplo: API completa de pedidos serverless con SAM/Terraform:

# pedidos_api.py
import json
import boto3
import uuid
from datetime import datetime

dynamodb = boto3.resource('dynamodb')
tabla_pedidos = dynamodb.Table('pedidos')

def crear_pedido(event, context):
    body = json.loads(event['body'])
    
    pedido = {
        'pedido_id': str(uuid.uuid4()),
        'mesa_id': body['mesa_id'],
        'items': body['items'],
        'total': sum(i['precio'] * i['cantidad'] for i in body['items']),
        'estado': 'ABIERTO',
        'creado_en': datetime.utcnow().isoformat()
    }
    
    tabla_pedidos.put_item(Item=pedido)
    
    return {
        'statusCode': 201,
        'body': json.dumps(pedido),
        'headers': {'Content-Type': 'application/json'}
    }

def listar_pedidos(event, context):
    response = tabla_pedidos.scan(Limit=20)
    return {
        'statusCode': 200,
        'body': json.dumps({'items': response['Items']})
    }

Patrón: procesamiento async con SQS:

[API] → [SQS Queue] → [Lambda Worker] → [DB]

Ventajas:
- Si hay un pico, los mensajes se acumulan en SQS
- Lambda escala automáticamente para procesar
- Si Lambda falla, mensaje vuelve a la cola (retry automático)

Patrón: fan-out con SNS:

[Pedido creado] → [SNS Topic]
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   [Lambda email] [Lambda SMS] [Lambda Analytics]
   
Un evento → múltiples acciones independientes en paralelo.

Patrón: orquestación con Step Functions:

# state-machine.json
{
  "StartAt": "ValidarPedido",
  "States": {
    "ValidarPedido": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:...:validar",
      "Next": "VerificarStock"
    },
    "VerificarStock": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:...:stock",
      "Next": "ProcesarPago"
    },
    "ProcesarPago": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:...:pago",
      "Catch": [{
        "ErrorEquals": ["PagoFallido"],
        "Next": "RevertirStock"
      }],
      "Next": "ConfirmarPedido"
    },
    "RevertirStock": { "Type": "Task", "Resource": "...", "End": true },
    "ConfirmarPedido": { "Type": "Succeed" }
  }
}

Step Functions = orquestación visual de Lambdas con manejo de errores y retries declarativo. Útil para sagas (visto en SIS615).


4.5 Frameworks serverless

📐 Fundamento

Configurar Lambda + API Gateway + IAM + S3 manualmente es tedioso. Frameworks lo simplifican.

AWS SAM (Serverless Application Model):

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  PedidosFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./
      Handler: pedidos_api.crear_pedido
      Runtime: python3.12
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /pedidos
            Method: POST
sam build
sam deploy --guided

Serverless Framework (multi-cloud):

# serverless.yml
service: la-esquina-api
provider:
  name: aws
  runtime: python3.12

functions:
  crearPedido:
    handler: pedidos_api.crear_pedido
    events:
      - http: POST /pedidos
  
  listarPedidos:
    handler: pedidos_api.listar_pedidos
    events:
      - http: GET /pedidos
npm install -g serverless
sls deploy

Otras alternativas:

  • Vercel — frontend + serverless functions, super simple para web apps.
  • Cloudflare Workers — edge compute (corre en 300+ datacenters).
  • Netlify Functions — similar a Vercel.
  • Cloud Run (GCP) — contenedores serverless.

🛠️ En la práctica

La Esquina Cloud — arquitectura híbrida (containers + serverless):

┌──────────────────────────────────────────────┐
│             Frontend (Vercel)                │  ← SPA estática
└──────────────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────────┐
│         API Gateway / CloudFront             │
└──────────────────────────────────────────────┘
                      │
        ┌─────────────┼─────────────┐
        ▼                           ▼
┌──────────────┐            ┌──────────────┐
│  EKS         │            │  Lambda      │
│  (apps core) │            │  (workloads  │
│  - Pedidos   │            │   esporádicos)│
│  - Pagos     │            │  - Thumbnails│
│  - Usuarios  │            │  - Reports   │
│              │            │  - Webhooks  │
└──────────────┘            └──────────────┘
        │                           │
        └────────────┬──────────────┘
                     ▼
              [PostgreSQL RDS]
              [DynamoDB]
              [S3]
              [Redis ElastiCache]

Decisión de qué va dónde:

Workload Plataforma Por qué
API principal de pedidos (24/7, alto tráfico) EKS Latencia consistente, sin cold starts
Generación de thumbnails (eventos esporádicos) Lambda Pagás solo por uploads
Reportes diarios a las 2 AM Lambda + EventBridge Solo ejecuta una vez por día
Webhooks de Stripe (impredecible) Lambda Maneja picos sin pre-provisionar
WebSockets de chat (conexiones largas) EKS Lambda no maneja conexiones persistentes bien

4.6 Ejercicios

✏️ Ejercicio 4.1 — Diseñar arquitectura serverless

Diseñá una arquitectura serverless para los siguientes requisitos:

  • Los usuarios suben videos a un servicio.
  • El video debe convertirse a múltiples resoluciones (480p, 720p, 1080p).
  • El usuario recibe un email cuando la conversión termina.
  • Los videos se sirven desde un CDN.

¿Qué servicios usarías? ¿Cómo se conectan?


4.7 Para profundizar


Definiciones nuevas: serverless, FaaS, BaaS, AWS Lambda, cold start, provisioned concurrency, trigger, event source, API Gateway, EventBridge, SQS, SNS, Step Functions, AWS SAM, Serverless Framework, Cloud Run, Cloudflare Workers, fan-out.