StackPractices
intermediate Por Mathias Paulenko

Patrón de Consolidación de Recursos de Cómputo

Combina cargas de trabajo en menos recursos de cómputo para reducir costos, mejorar la utilización y simplificar las operaciones.

Visión General

El Patrón de Consolidación de Recursos de Cómputo empaqueta varias cargas en menos recursos de cómputo — mejor utilización, menor costo, menos cosas que operar. En lugar de darle a cada carga su propia instancia, agrupas las que de verdad encajan en perfiles de recursos, disponibilidad y límites de seguridad.

He visto a equipos pagar flotas de instancias ociosas al 8% de CPU porque cada servicio tenía su propia máquina “por si acaso”. La consolidación es la solución, pero solo si perfilas antes — el modo de fallo no es sutil: dos cargas que alcanzan su pico a la misma hora se pelearán entre sí y tumbarán ambas.

Este patrón aparece sobre todo en optimización de costos cloud, procesamiento por lotes y proyectos de consolidación de sistemas legados donde la capacidad ociosa es cara y la sobrecarga de gestión es alta.

Cuándo Usar

Recurre a este patrón cuando:

  • Tienes muchas cargas pequeñas con baja utilización individual
  • Tu factura cloud no para de subir porque las instancias están ociosas o sobreaprovisionadas
  • La flota de servidores, contenedores o nodos que gestionas ha crecido más de lo razonable
  • Las cargas se complementan — un trabajo limitado por CPU y otro por memoria hacen buena pareja
  • Las reglas de seguridad y cumplimiento no prohíben compartir la infraestructura

Evítalo cuando las cargas exijan aislamiento físico (cumplimiento), necesiten hardware dedicado para rendimiento extremo, o tengan patrones de consumo impredecibles que garantizarían contención.

Solución

Flujo de consolidación — perfila cada carga, agrupa perfiles compatibles en recursos compartidos, despliega con límites, monitoriza contención y mantén una vía de retorno a capacidad dedicada
# Analizador simplificado de perfiles de recursos para decisiones de consolidación
workloads = [
    {'name': 'report-generator', 'cpu_avg': 0.2, 'mem_avg': 0.8, 'peak_hours': [2, 3]},
    {'name': 'notification-sender', 'cpu_avg': 0.6, 'mem_avg': 0.2, 'peak_hours': [9, 10, 11]},
    {'name': 'data-cleaner', 'cpu_avg': 0.1, 'mem_avg': 0.1, 'peak_hours': [0, 1]},
]

def can_consolidate(a, b):
    overlapping_peaks = set(a['peak_hours']) & set(b['peak_hours'])
    combined_cpu = a['cpu_avg'] + b['cpu_avg']
    combined_mem = a['mem_avg'] + b['mem_avg']
    return not overlapping_peaks and combined_cpu < 0.9 and combined_mem < 0.9

# Ejemplo: report-generator y notification-sender tienen picos complementarios
print(can_consolidate(workloads[0], workloads[1]))  # True
# Pod de Kubernetes con dos contenedores compartiendo un nodo
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: worker-a
      resources:
        requests:
          cpu: "250m"
          memory: "128Mi"
    - name: worker-b
      resources:
        requests:
          cpu: "250m"
          memory: "128Mi"

Explicación

Consolidas estudiando qué exige realmente cada carga y cuándo lo exige. Las cargas compatibles se colocan en el mismo recurso de cómputo siempre que su uso de pico combinado permanezca por debajo de la capacidad. Bien hecho, la utilización sube mientras la contención y las violaciones de aislamiento se quedan en cero — esa tensión es todo el juego.

El patrón suele implicar:

  • Perfilado: medir CPU, memoria, disco y red a lo largo del tiempo — una previsión de capacidad te da los datos para decidir
  • Empaquetado (bin packing): agrupar cargas para que sus necesidades combinadas encajen en la capacidad disponible
  • Programación: colocar cargas con horarios desplazados en el mismo recurso
  • Monitorización: vigilar la contención después de la consolidación
  • Respaldo: mantener capacidad de burst lista para cargas inesperadas

Variantes

VarianteEnfoqueIdeal Para
Consolidación de contenedoresDos o más contenedores en un host o podMicroservicios con baja utilización
Consolidación de VMsDos o más cargas en una máquina virtualAplicaciones legadas
Agrupación serverlessCombinar funciones en un solo proceso o runtimeCargas orientadas a eventos
Programación por lotesEjecutar trabajos en horarios distintos en recursos compartidosCron jobs y pipelines ETL

Lo que funciona

  • Perfila el uso promedio y de pico primero: consolidar a ciegas es como acabar con dos cargas peleando por la misma ventana de las 3 a.m. Una semana de métricas vale más que un día de suposiciones.
  • Mantén los límites de seguridad explícitos: una carga sensible y una herramienta pública no pertenecen al mismo host, nunca. Documenta qué límites son requisitos duros y cuáles preferencias.
  • Deja margen para picos: dimensiona los recursos compartidos para el pico combinado más ~20% — la carga que olvidaste alcanzará su pico durante el failover que no planeaste.
  • Prueba los pares antes de consolidar: el analizador de cargas del repo companion evalúa qué pares de cargas son seguros de co-ubicar según picos y promedios.
  • Aplica cuotas en todos los niveles: requests/limits por contenedor, cuotas por namespace, topes por nodo. Si lo omites, una carga ruidosa ahogará en silencio a todo lo demás en la máquina.
  • Vigila latencia y tasas de error tras consolidar: que suba la utilización es el objetivo; que suba la latencia p99 es la alarma.
  • Sigue la señal de costo: la consolidación es una palanca de optimización de costos — mide el costo por unidad de trabajo antes y después para poder demostrar que funcionó, y alimenta las cifras a tu práctica de FinOps.
  • Documenta el plan de vuelta atrás: saber exactamente cómo des-consolidar antes de necesitarlo. Un rollback que solo vive en la cabeza de alguien no es un plan.

Errores Comunes

  • Consolidar cargas con picos superpuestos: el clásico. Un par de trabajos escalando a las 9 a.m. se harán daño entre sí por muy modestos que parezcan sus promedios diarios.
  • Ignorar el efecto de vecino ruidoso: la caché de CPU compartida, la I/O de disco y el ancho de banda de red causan contención que nunca aparece en los cálculos de requests/limits.
  • Mezclar zonas de cumplimiento: una carga PCI compartiendo nodo con una herramienta de desarrollo es un hallazgo de auditoría esperando a ocurrir.
  • Recortar demasiado: eliminar tanta capacidad que un único fallo de nodo o pico de tráfico no tenga adónde ir.
  • No actualizar las alertas: tus umbrales por instancia quedan mal tras consolidar — un nodo al 85% que antes estaba bien ahora sostiene cuatro cargas.
  • Tratarlo como un camino de ida: los equipos consolidan, aparece la contención y nadie sabe cómo separar las cargas rápidamente.

Troubleshooting

  • CPU throttling tras consolidar: revisa los contadores cfs_throttled por contenedor en Prometheus — se están alcanzando los límites. Sube el límite de la víctima o muévela a un nodo menos cargado.
  • Pods con OOM-killed o evicted: la presión de memoria combinada supera al nodo. Revisa los requests — probablemente se fijaron desde promedios, no desde picos.
  • Picos de latencia a horas concretas: dos cargas están alcanzando su pico a la vez. Extrae la utilización por hora de cada una y separa la pareja.
  • Interrupciones de instancias spot rompiendo trabajos por lotes: spot es ideal para trabajo por lotes consolidado, pero solo con checkpointing. Cada trabajo tiene que guardar su progreso y reanudar donde lo dejó — el ejemplo de programación spot de más abajo muestra la forma de hacerlo.
  • Una carga dominando disco o red: las cuotas de CPU/memoria no cubren I/O. Añade límites dedicados de volumen o ancho de banda, o mueve la carga intensiva en I/O a hardware dedicado.
  • Nadie sabe qué carga causó un incidente: etiqueta cada métrica, línea de log y traza con un identificador de carga antes de consolidar — reconstruir la atribución en medio de un incidente es doloroso.

Soluciones Avanzadas

Empaquetado dinámico con el scheduler de Kubernetes

Usa schedulers personalizados de Kubernetes o plugins para implementar empaquetado inteligente:

# Config de scheduler de Kubernetes para optimización de bin packing
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: bin-packing-scheduler
    pluginConfig:
      - name: NodeResourcesFit
        args:
          scoringStrategy:
            type: LeastAllocated
            resources:
              - name: cpu
                weight: 1
              - name: memory
                weight: 1
# Plugin de scoring personalizado para perfiles de recursos complementarios
def score_node(pod, node):
    node_cpu_used = sum(cpu for c in node.pods)
    node_mem_used = sum(mem for c in node.pods)
    node_cpu_total = node.allocatable['cpu']
    node_mem_total = node.allocatable['memory']

    pod_cpu = pod.spec.containers[0].resources.requests.cpu
    pod_mem = pod.spec.containers[0].resources.requests.memory

    # Preferir nodos donde el pod llena huecos en el perfil de recursos
    cpu_fill = (node_cpu_used + pod_cpu) / node_cpu_total
    mem_fill = (node_mem_used + pod_mem) / node_mem_total

    # Mayor score para mejor balance de recursos
    return 100 - abs(cpu_fill - mem_fill) * 50

Consolidación basada en tiempo con instancias spot

Usa instancias spot con cargas desplazadas en el tiempo para un ahorro máximo:

import boto3

def schedule_spot_consolidation(workloads, region='us-east-1'):
    ec2 = boto3.client('ec2', region_name=region)

    # Agrupar cargas por ventanas de tiempo
    time_windows = {}
    for w in workloads:
        window = (w['start_hour'], w['end_hour'])
        time_windows.setdefault(window, []).append(w)

    # Lanzar una instancia spot por ventana
    for window, ws in time_windows.items():
        total_cpu = sum(w['cpu'] for w in ws)
        total_mem = sum(w['mem'] for w in ws)

        response = ec2.request_spot_instances(
            InstanceCount=1,
            Type='one-time',
            InstanceInterruptionBehavior='terminate',
            LaunchSpecification={
                'ImageId': 'ami-12345678',
                'InstanceType': 'm5.large',
                'UserData': f'#cloud-config\nruncmd:\n  - docker run -d {total_cpu}m {total_mem}Mi'
            }
        )
        print(f"Lanzada spot para ventana {window}: "
              f"{response['SpotInstanceRequests'][0]['SpotInstanceRequestId']}")

Aislamiento de recursos de contenedor con cgroups

Previene el efecto de vecino ruidoso usando cgroups de Linux:

# Crear cgroup para aislamiento de CPU
sudo cgcreate -g cpu,memory:/workload-a

# Configurar cuota de CPU (50% de un core)
sudo cgset -r cpu.cfs_quota_us=50000 /workload-a
sudo cgset -r cpu.cfs_period_us=100000 /workload-a

# Configurar límite de memoria (512MB)
sudo cgset -r memory.limit_in_bytes=536870912 /workload-a

# Ejecutar carga en cgroup
sudo cgexec -g cpu,memory:workload-a python workload-a.py

Preguntas frecuentes

¿Es la consolidación lo mismo que el autoscaling?

No. La consolidación reduce cuántos recursos ejecutas en total; el autoscaling cambia cuántos ejecutas en respuesta a la demanda. Combinan bien — consolida la base, autoescala los picos. Un enfoque de nivelación de carga basada en colas también puede suavizar los picos que hacen arriesgada la consolidación.

¿Debería consolidar cargas de producción y desarrollo?

En general no. Mantén producción aislada de los entornos no productivos — un trabajo ruidoso de desarrollo no debería poder tumbar prod. Desarrollo y staging, en cambio, son candidatos ideales para consolidar.

¿Cómo sé cuándo la consolidación ha ido demasiado lejos?

Latencia en aumento, tasas de error más altas, presión de memoria o CPU throttling — todas señales de que las cargas compiten por los mismos recursos. Si tu p99 consolidado es peor que tu p95 sin consolidar, has ido demasiado lejos.

¿Cómo mido si la consolidación funcionó de verdad?

Rastrea el conteo total de recursos, la utilización media, el costo por unidad de trabajo y la tasa de incidentes antes y después. Calcula el ratio de consolidación (recursos antes / después) — 2:1 o mejor manteniendo los SLOs es un buen objetivo. Reporta el ahorro con una plantilla de asignación de costos para que el resultado sea visible a finanzas.

¿Debería consolidar aplicaciones con estado?

Con cuidado. Las bases de datos y servicios con estado necesitan almacenamiento dedicado y aislamiento de red incluso cuando el cómputo se comparte; los servicios gestionados de bases de datos ya manejan la multi-tenancy internamente y suelen ser el camino más seguro.

¿Cómo manejo las interrupciones de instancias spot?

Escribe handlers de apagado graceful que hagan checkpoint del estado antes de que la instancia muera y muevan el trabajo a capacidad on-demand. En Kubernetes, los pod disruption budgets mantienen capacidad mínima durante las terminaciones; guarda los checkpoints en almacenamiento duradero como S3 o EFS.

¿Qué herramientas ayudan a automatizar la consolidación?

El cluster autoscaler de Kubernetes con políticas de scoring, AWS Compute Optimizer para recomendaciones de right-sizing y Azure Advisor para sugerencias de consolidación. Para observabilidad, Prometheus más Grafana a granularidad de 1 minuto te mostrará qué cargas realmente se complementan.

¿Es este patrón adecuado para proyectos pequeños?

Normalmente no merece la pena. Con un puñado de componentes, la complejidad operativa de perfilar, empaquetar y planificar el respaldo supera al ahorro. Empieza simple e introduce la consolidación cuando la factura o el número de nodos realmente duela.

¿Puedo aplicar este patrón parcialmente?

Sí, y honestamente así lo haría yo. Empieza con tus cargas menos críticas, obsérvalas un mes y luego expande. La consolidación big-bang es como ocurren las caídas.