← SCRAM AI Lab
Aprende por qué los hard limits de Docker provocan caídas innecesarias en GCP y cómo configurar soft limits para maximizar la densidad y estabilidad de memoria.
May 21, 2026
416 lecturas

En el servidor de producción de SCRAM corren 94 contenedores Docker en una sola máquina GCP e2-standard-4 (4 vCPU, 16GB RAM, Debian 12). La regla operativa que aprendimos a las malas y que ahora es ley en el equipo: nunca uses hard limits (mem_limit, cpus, limits). Solo soft limits — reservations. Los hard limits no son una red de seguridad, son una pistola apuntada a tu uptime.
Un container con mem_limit: 512m recibe SIGKILL del kernel en el momento en que toca 513MB. No hay gracia. No hay swap. No hay log útil — solo OOMKilled: true en docker inspect. Y los bursts ocurren todo el tiempo en cargas reales:
El container muere, Docker lo reinicia, y por 8-15 segundos tu servicio está caído. Multiplica por 94 containers y tienes un patrón de flapping permanente.
Reservations son piso, no techo. Le dicen al scheduler de Docker: "garantiza al menos esta memoria/CPU; si hay disponible, deja que use más". El kernel solo mata por OOM cuando la máquina entera se queda sin memoria — no por un burst individual.
# docker-compose.yml — patrón correcto
services:
scram-api:
image: scram-api:latest
deploy:
resources:
reservations:
memory: 512M
cpus: '0.5'
# NO limits aquí. NUNCA.
restart: unless-stopped
scram-web:
image: scram-web:latest
deploy:
resources:
reservations:
memory: 256M
cpus: '0.25'
postgres:
image: postgres:16
deploy:
resources:
reservations:
memory: 2G
cpus: '1.0'
Conviene usar soft limits cuando consolidas múltiples microservicios heterogéneos en un mismo nodo para maximizar la rentabilidad de infraestructura. Esta configuración garantiza un piso computacional para cada servicio sin generar un SIGKILL fulminante durante picos transitorios, permitiendo que las cargas absorban memoria libre del sistema operativo antes de reiniciar procesos de forma destructiva.
Si operas en entornos con tráfico impredecible, los soft limits actúan como un amortiguador elástico. El kernel de Linux gestiona la memoria como un recurso compartido en lugar de cajas herméticas, facilitando que procesos auxiliares concluyan sus tareas sin interrumpir el flujo principal de transacciones de tu negocio.
El argumento clásico: "pero entonces un container loco se come toda la RAM y mata a los demás". Es válido, pero el remedio (hard limits) es peor que la enfermedad. La forma correcta de manejar containers locos:
La siguiente matriz sintetiza los criterios de estabilidad, uso y riesgo evaluados tras someter nuestro clúster a pruebas de estrés recurrentes:
| Criterio | Hard Limits (mem_limit) | Soft Limits (reservations) | cgroups v2 (memory.high) |
|---|---|---|---|
| Respuesta ante ráfagas | SIGKILL inmediato por el kernel | Absorción elástica con RAM libre | Throttling y presión sobre swap |
| Riesgo de reinicio en cadena | Alto (patrón de flapping) | Bajo (reinicio solo por OOM global) | Muy bajo (degradación gradual) |
| Oversubscription ratio | 1.0x (estricto y costoso) | 1.3x a 1.6x (altamente eficiente) | 1.2x a 1.4x |
| Complejidad de observabilidad | Baja (inspección de OOMKilled) | Media (monitoreo proactivo continuo) | Alta (rastreo de eventos memory.events) |
| Caso de uso sugerido | Entornos multi-tenant no confiables | Monolitos modulares y microservicios propios | Nodos avanzados con orquestación Linux nativa |
En SCRAM la suma de reservations de los 94 containers es ~22GB sobre 16GB físicos. Oversubscription ratio 1.4x. Esto funciona porque no todos los containers pegan su pico a la vez — Redis pica de noche, los workers de importación pican de mañana, las APIs públicas en horario laboral. Si tu carga es sincrónica (todos pican al mismo evento), no oversuscribas más de 1.1x.
En América Latina, la eficiencia de infraestructura en la nube tiene un impacto directo sobre los márgenes operativos, en especial cuando los ingresos se capturan en monedas locales y los costos de cómputo se facturan en dólares estadounidenses. De acuerdo con la Calculadora de Precios de Google Cloud, una instancia e2-standard-4 en la región de us-central1 (Iowa) tiene un costo de lista base que ronda los 97 dólares mensuales, mientras que provisionar una e2-standard-8 para compensar hard limits mal dimensionados eleva la factura aproximadamente a 194 dólares mensuales por cada host activo.
El uso agresivo de soft limits permite consolidar cargas de trabajo que tradicionalmente demandarían dos o tres instancias intermedias dentro de un solo nodo equilibrado. Esto representa una reducción de hasta el 50% en el gasto recurrente de máquinas virtuales, eliminando el desperdicio generado por memoria asignada que permanece inactiva durante el 95% del ciclo operativo.
Para implementar soft limits sin el riesgo de que el kernel liquide servicios críticos de base de datos antes que contenedores prescindibles, es indispensable configurar el comportamiento del Out-Of-Memory killer de Linux a nivel de sistema operativo.
Según la documentación del kernel de Linux, cada proceso cuenta con una puntuación llamada oom_score. Al momento de presentarse un agotamiento global de RAM, el kernel elimina el proceso con el valor más alto. Puedes proteger tus contenedores esenciales ajustando este parámetro directamente en el host o en la definición del servicio:
# Elevar prioridad de PostgreSQL frente al OOM killer del host
echo -900 > /proc/$(pgrep -f postgres | head -n 1)/oom_score_adj
# Reducir prioridad de workers de segundo plano para ser sacrificados primero
echo 800 > /proc/$(pgrep -f csv_worker | head -n 1)/oom_score_adj
Un error frecuente consiste en desactivar la swap por completo bajo la creencia de que degrada el rendimiento de disco. Un archivo de intercambio pequeño (2GB a 4GB en SSD rápido) combinado con un valor vm.swappiness = 10 permite que el sistema desaloje páginas anónimas frías sin penalizar el I/O en caliente, reduciendo sustancialmente los fallos abruptos.
Bajo carga extrema (todos pidiendo a la vez), Linux empieza a swappear y la máquina se vuelve lenta para todos. Esto pasa 2-3 veces al año en SCRAM, dura ~90 segundos, y se resuelve solo cuando el burst pasa. Vs hard limits: pasaría 30-40 veces al mes, con downtime real de containers individuales. El trade-off de soft limits gana por dos órdenes de magnitud.
# ver memoria real consumida vs reservada
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
# detectar containers cerca del pico de la máquina
docker stats --no-stream | awk '{print $1, $4}' | sort -k2 -h | tail -20
Alertas en Grafana sobre container_memory_working_set_bytes de cAdvisor, threshold 85% del host. Si dispara, miramos quién creció y por qué — no movemos mem_limits.
El principal riesgo de los soft limits es el memory leak silencioso. Un proceso que retiene memoria de manera progresiva y sin control eventualmente agotará la memoria de todo el host físico, obligando al kernel a intervenir. Si no se monitorea la pendiente de consumo, un solo microservicio mal programado degradará el desempeño de los demás contenedores alojados en el mismo nodo.
Para mitigar esto, implementamos revisiones automáticas que cotejan la tasa de crecimiento de container_memory_working_set_bytes durante ventanas de cuatro horas continuas. Si la tasa de consumo de un contenedor muestra una pendiente estrictamente ascendente que no se aplana tras la ejecución del recolector de basura, el orquestador programa un reinicio ordenado durante la ventana de mantenimiento nocturno antes de comprometer la estabilidad del sistema.
La pregunta filosófica del 2026: ¿migrar a Kubernetes te resuelve esto? No. Te da mejores primitivas (QoS classes, PodDisruptionBudgets) pero la regla sigue siendo la misma: requests sí, limits no para memoria, hasta que pruebes que la necesitas.
Artículos relacionados
¿Quién autoriza a un agente? Permisos automáticos, aprobación humana y dos turnos
Claude, OpenAI y Microsoft ya traen controles para decidir qué ejecuta un agente. Los comparamos con el nuestro: la llave identifica y el código autoriza.
Traefik v2.10 con auto-renewal certs para 94 containers
Wildcard *.scram2k.com cubre la mayoría, certs individuales para el resto. acme.json shared, DNS-01 para wildcards, HTTP-01 para subdomains. Anti-patrón: cert por container.
OpenTelemetry tracing para pipelines LLM
Domina el tracing de pipelines LLM con OpenTelemetry y GenAI semantic conventions. Optimiza latencias en producción y evita sobrecostos con tail sampling.