🔎 Buscar

⚖️ Infraestructura y orquestación

El patrón de despliegue de sistemas reales: load balancers (L4/L7), réplicas y failover de bases de datos, connection pooling, caché distribuida, orquestación de contenedores, service discovery y escalado automático. Teoría, configuración y práctica.

DevOps & Infra📖 Contenido

Infraestructura y orquestación

Cuando tu sistema pasa de «un servidor» a «varios», aparece un conjunto de piezas que sostienen la escala: balanceadores de carga, réplicas de base de datos, caché compartida y orquestación de contenedores. Esta página reúne todo lo que debes saber de ese nivel, con sus referencias. La teoría conceptual está en System Design: conceptos y Distribuidos; aquí ves el cómo operarlo.

📌 El patrón de una arquitectura de producción
                  [CDN (estático)]

[Clientes] ──▶ [Load Balancer] ──▶ [App 1] ──▶ [Caché Redis]
                        │            [App 2]        │
                        │            [App 3]        ▼
                        └─────────────────────▶ [BBDD primaria]
                                                    │ replicación

                                              [BBDD réplicas (lecturas)]

Toda pieza de este diagrama tiene alternativas y trade-offs; abajo ves cada una con detalle.

Load balancer (balanceador de carga)

Ya viste los conceptos en System Design. Aquí, el detalle operativo que separa al que lo configura del que lo entiende.

L4 vs L7

L4 (transporte) L7 (aplicación)
Qué mira IP + puerto (TCP/UDP) La petición completa (URL, headers, cookies)
Velocidad Muy rápida (no inspecciona el contenido) Más lenta (tiene que leer el tráfico)
Capacidades Balanceo simple, NAT, TLS passthrough Routing por URL, sticky sessions, rate limit, compresión
Ejemplos HAProxy (L4), AWS NLB, iptables Nginx, HAProxy (L7), AWS ALB, Traefik

💡 Regla práctica: L4 para rendimiento puro (balanceo de BBDD, gRPC, WebSocket); L7 cuando necesitas decidir por contenido (routing /api vs /admin, auth, caching).

Configuración real con Nginx (L7)

upstream backend {
    least_conn;                      # al que menos conexiones tenga
    server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080 backup;     # solo si los demás caen
}

server {
    listen 443 ssl;
    server_name api.misitio.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Sticky sessions: cuándo sí y cuándo no

upstream backend {
    ip_hash;                         # misma IP → misma máquina
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

⚠️ Las sticky sessions (misma IP/sesión → misma máquina) son un parche. Si una máquina cae, su usuario se pierde. La solución correcta en producción: sesiones fuera de la máquina (Redis) o JWT stateless — así cualquier instancia atiende a cualquiera (patrón stateless del 12-factor).

Health checks y failover

El balanceador vigila a los servidores y saca a los que no responden:

upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

# Health check activo con una ruta dedicada
location /healthz {
    proxy_pass http://backend;
}
App 2 muere → el LB deja de mandarle peticiones (health check falla)
             → App 1 y App 3 absorben la carga
             → al volver, el LB lo detecta y lo reintroduce

💡 El endpoint /healthz es el contrato entre la app y la infraestructura: debe responder 200 rápido cuando la app está sana, y no-200 (o no responder) cuando no puede servir. Incluye la BBDD y las dependencias críticas en el check.

Réplicas y failover de bases de datos

La teoría está en Distribuidos: replicación. Aquí, el patrón operativo real.

El patrón estándar: líder + réplicas de lectura

Escrituras ──▶ [Primaria] ──replicación──▶ [Réplica 1] ──▶ lecturas
                                            [Réplica 2] ──▶ lecturas
# PostgreSQL: crear una réplica (streaming replication)
pg_basebackup -h primaria -D /var/lib/postgresql/15/replica -R -X stream

# Y en primaria, los archivos de config que lo activan
# postgresql.conf (primaria):  wal_level = replica, max_wal_senders = 10
Configuración Qué permite
Réplicas de lectura La app manda SELECT a réplicas, INSERT/UPDATE a la primaria
Failover Si la primaria cae, una réplica se promueve a primaria
Read-after-write El usuario que escribe debe leer su propia escritura (routing fino)

⚠️ El problema de las réplicas: con replicación asíncrona (la común), una réplica puede ir unos milisegundos por detrás. Si tu app hace INSERT y luego SELECT inmediato del mismo dato, la réplica puede no tenerlo aún (read-after-write). Soluciones: leer la escritura de la primaria, o usar replicación síncrona para ese dato crítico.

Failover: qué pasa cuando cae la primaria

Primaria cae
  → una réplica se promueve (Promote / pg_ctl promote / tools)
  → la app debe apuntar a la nueva primaria
  → el antiguo réplica vuelve como réplica de la nueva

💡 Automatizar el failover: herramientas como Patroni, Repmgr o el RDS Multi-AZ de AWS lo hacen solos (con consenso). A mano, pg_ctl promote promueve una réplica. La decisión de quién se promueve y cuándo (para no tener dos primarias = split-brain) es un problema de consenso — lo viste en Raft.

Connection pooling

Cada conexión a BBDD es cara (proceso, memoria, handshake). Un pooler reutiliza un número fijo de conexiones entre miles de clientes.

[App ×100] ──▶ [PgBouncer (pool de 50)] ──▶ [PostgreSQL]
   (100 apps comparten 50 conexiones)
# pgbouncer.ini
[databases]
mydb = host=primaria port=5432

[pgbouncer]
pool_mode = transaction        # la conexión se libera al acabar la transacción
max_client_conn = 1000
default_pool_size = 50

⚠️ El error clásico: sin pooling, 1000 apps × 10 conexiones = 10.000 conexiones → la BBDD se ahoga (“too many connections”). El pooler es la pieza que hace que el número real de conexiones sea manejable.

Caché distribuida: Redis a escala

La teoría de caché está en System Design. Operativamente, Redis es el estándar.

Redis cluster vs replicación

Replicación Redis:   [Master] ──▶ [Replica]      (alta disponibilidad, lectura)
Redis Cluster:       3 masters × 2 réplicas      (datos repartidos + HA)
Patrón Qué resuelve
Master-replica HA y lecturas (un master, réplicas)
Sentinel Fallover automático del master
Cluster Sharding: los datos se reparten entre masters (hash slots)
Redis on Flash / persistencia No perder datos (AOF/RDB, o elige Redis solo para caché)

💡 Regla de diseño: si los datos se pueden reconstruir (caché, sesión, rate limit) → Redis sin demasiada persistencia, todo rápido. Si son la fuente de verdad → mejor la BBDD; Redis no debe ser la única copia de nada crítico.

Orquestación de contenedores

La teoría está en DevOps: contenido. Aquí, el patrón completo de orquestación.

La jerarquía de abstracción

Dockerfile (imagen) 
   → docker run (un contenedor)
   → Docker Compose (varios servicios en un host)
   → Kubernetes (clúster: muchos hosts, despliegue, escala, red, HA)

Kubernetes: el patrón declarativo

┌───────────────────────────────────────────────┐
│  Control plane                                │
│  ┌────────┐ ┌───────────┐ ┌──────────┐        │
│  │ API    │ │ Scheduler │ │ etcd     │        │
│  │ server │ │           │ │ (Raft)   │        │
│  └────────┘ └───────────┘ └──────────┘        │
└──────────┬────────────────────────────────────┘

┌──────────▼────────────────────────────────────┐
│  Worker nodes                                 │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐       │
│  │ kubelet  │ │ kube-proxy│ │ pods     │       │
│  └──────────┘ └──────────┘ └──────────┘       │
└────────────────────────────────────────────────┘

Los objetos clave que definen la orquestación:

Objeto Qué hace Declara
Deployment “Quiero N réplicas” replicas: 3
Service IP estable + balanceo a los pods selector por labels
Ingress Entrada HTTP externa (rutas, TLS) host + path → service
ConfigMap / Secret Configuración / secretos datos
HPA Escalado automático por CPU/métricas target + mín/máx
# deployment + service + hpa: el patrón completo
apiVersion: apps/v1
kind: Deployment
metadata:
  name: miapp
spec:
  replicas: 3
  selector:
    matchLabels: { app: miapp }
  template:
    metadata:
      labels: { app: miapp }
    spec:
      containers:
        - name: miapp
          image: miapp:1.4.2
          ports: [{ containerPort: 3000 }]
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits:   { cpu: 500m, memory: 512Mi }
---
apiVersion: v1
kind: Service
metadata:
  name: miapp
spec:
  selector: { app: miapp }
  ports: [{ port: 80, targetPort: 3000 }]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: miapp
spec:
  scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: miapp }
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

💡 Declarativo: tú escribes “el estado deseado” (3 réplicas, auto-escalar hasta 20 al 70% CPU). Kubernetes hace el trabajo de llegar ahí y mantenerlo: si un pod muere, crea otro; si la carga sube, el HPA añade réplicas; si baja, las quita.

Service discovery: cómo se encuentran los servicios

Sin discovery, la app no sabría a qué IP llamar:

K8s:  Service → nombre DNS estable (miapp.svc.cluster.local)
      → kube-proxy enruta a los pods detrás

Otras: Consul / etcd / AWS Cloud Map / ECS Service Discovery
      → registro + DNS + health checks
Mi app:  GET http://miapp.svc.cluster.local/api/usuarios
            │ (DNS resuelve al Service, que balancea a los pods)

         [pod miapp-abc12] [pod miapp-def34] [pod miapp-ghi56]

Escalado automático

Nivel Quién escala Cómo
Apps HPA (K8s) / ASG (AWS) Por CPU, memoria, métricas custom o cola
Caché Redis Cluster Añadir shards / réplicas
BBDD Réplicas de lectura Añadir réplicas para lecturas; escalado vertical para escrituras
Colas Worker pool Por profundidad de la cola (KEDA, SQS autoscaling)

⚠️ El cuello de botella final: puedes escalar apps y réplicas de lectura casi infinitamente, pero las escrituras de la BBDD siempre tienen un límite (una primaria). Cuando se satura la escritura, ahí entran sharding, particionado por tenant, o colas asíncronas para descargar la primaria.

Cheatsheet

Concepto En una frase
L4 / L7 Balanceo por IP/puerto / por contenido de la petición
Health check El check que decide si una instancia sirve o no
Sticky session Misma IP → misma máquina (evitar en producción)
Failover Promoción de una réplica cuando la primaria cae
Connection pooling Reutilizar N conexiones entre muchos clientes
Read replica Copia que atiende lecturas
Read-after-write La réplica puede ir por detrás de la escritura
Redis cluster Sharding + HA de la caché
Orquestación Gestionar contenedores a escala (K8s)
Deployment / Service / Ingress Réplicas / IP estable / entrada HTTP
HPA Escalado automático horizontal
Service discovery Encontrar servicios por nombre, no por IP
Escala horizontal Más instancias tras un balanceador
Split-brain Dos primarias a la vez (catástrofe)

Práctica propuesta

  1. Monta Nginx como L7 load balancer delante de 2 instancias de una app tuya. Añade /healthz y verifica que al matar una app, el LB deja de mandarle tráfico.
  2. Crea réplicas de PostgreSQL (líder + 2 réplicas) y enruta lecturas a réplicas, escrituras a la primaria. Provoca el failover.
  3. Añade PgBouncer delante y observa cómo las conexiones reales a la BBDD bajan de 100 a 20.
  4. Monta Redis con replicación y luego Redis Cluster con 3 masters. Compara.
  5. Despliega tu app en Kubernetes con Deployment (3 réplicas) + Service + HPA. Lanza carga con wrk y observa el auto-escalado.

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.