Límites de recursos y cgroups en Docker | Nicolás Garzón
Texto
Copiar container processes
→ cgroup
→ CPU, memory, PIDs, I/O
→ límites y métricasUn límite protege el host y otros workloads, pero no garantiza rendimiento. Un valor demasiado bajo puede crear OOM, throttling y latencia; uno demasiado alto permite interferencia.
Un container puede competir por la capacidad disponible del host.
memory leak consume RAM;
fork bomb agota PIDs;
build monopoliza CPU;
database satura disco;
un servicio degrada todos los demás.
Containers no reciben automáticamente una “porción segura” solo por estar aislados.
Bash
Copiar docker run \
--memory 512m \
--cpus 1.5 \
--pids-limit 200 \
my-apiBash
Copiar docker stats --no-stream
docker inspect container --format '{{json .HostConfig}}' Bash
Copiar docker run --memory 512m my-apiSi el cgroup supera el límite y no puede recuperar memoria, el kernel puede matar procesos.
Bash
Copiar docker inspect api --format '{{.State.OOMKilled}}'
docker inspect api --format '{{.State.ExitCode}}' Un exit 137 puede ser SIGKILL por OOM, stop timeout o acción manual. Correlaciona OOMKilled, events y logs.
heap del runtime;
buffers;
native addons;
stacks de threads;
page cache;
shared memory;
tmpfs;
memory-mapped files;
procesos hijos.
En Node.js, configurar heap igual al límite total deja cero margen para memoria nativa. Reserva espacio.
Opciones de swap dependen de host, cgroup version y configuración.
evita OOM inmediato;
puede convertir presión en latencia extrema;
datos sensibles podrían llegar a swap;
comportamiento cambia entre entornos.
No asumas que swap está disponible o que un flag aceptado funciona igual en Docker Desktop, rootless y Linux nativo.
Algunas configuraciones permiten preferencias o soft limits bajo contención. No son garantía de memoria reservada.
En planificación, diferencia:
límite máximo;
consumo normal;
request/reservation del scheduler cuando exista;
capacidad física.
Docker Engine single-host no es un scheduler completo de reservas.
El kernel decide qué proceso terminar según cgroup y puntuación. Un container puede quedar running si mata un hijo, o salir si mata PID 1.
limitar concurrencia;
aplicar backpressure;
evitar cargar datasets completos;
observar heap y RSS;
usar streaming;
reiniciar solo como mitigación temporal.
Aumentar memory sin corregir leak aplaza el incidente.
Bash
Copiar docker run --cpus 1.5 my-apiRepresenta una cuota equivalente de tiempo de CPU. No fija un core físico ni reserva capacidad constante.
Bajo contención, el proceso puede ser throttled. Síntomas:
p99 alto;
timeouts;
event loop lag;
jobs que tardan más;
CPU mostrada cerca del límite.
Permiten prioridad relativa cuando varios cgroups compiten. Si sobra CPU, un proceso puede usar más; bajo presión, recibe una proporción.
No confundas peso relativo con hard limit.
Puede fijar CPUs específicas:
Bash
Copiar docker run --cpuset-cpus 0,1 my-app
aislamiento de benchmarks;
NUMA/latency;
evitar interferencia.
reduce flexibilidad;
puede saturar cores elegidos;
necesita conocer topología del host.
Bash
Copiar docker run --pids-limit 200 my-apiLimita procesos y threads contabilizados según kernel.
fork bombs;
loops que crean hijos;
workers sin límite;
agotamiento global de PID table.
Un límite demasiado bajo rompe runtimes con muchos threads, browsers headless o process pools. Mide pico real.
Docker puede configurar pesos o límites para devices según plataforma.
Un container de database puede saturar disco aunque CPU/memory estén limitados.
read/write throughput;
IOPS;
latency;
queue depth;
filesystem errors;
capacidad.
La efectividad de límites depende de storage driver, device, filesystem y cgroup version.
/dev/shm puede tener un tamaño predeterminado pequeño.
Chromium/Puppeteer;
PostgreSQL en ciertos usos;
multiprocessing;
librerías científicas.
Bash
Copiar docker run --shm-size 256m my-browser-testsAumenta con evidencia. Shared memory también contribuye al consumo.
Un tmpfs sin límite puede consumir memoria:
Bash
Copiar docker run --tmpfs /tmp:size= 64m my-apiDiseña tamaño según archivos temporales y límite total.
La interfaz y semántica del kernel varían. Docker abstrae opciones comunes, pero métricas, controladores y rootless support pueden cambiar.
Bash
Copiar docker info
stat -fc %T /sys/fs/cgroupNo construyas scripts dependientes de paths internos sin soportar la versión objetivo.
CPU, memoria y disco pueden estar limitados primero por la VM de Desktop y luego por el container.
Texto
Copiar laptop 32 GB
→ Desktop VM 8 GB
→ container limit 4 GBLa app no puede usar los 32 GB. Diagnostica ambas fronteras.
No basta con asignar límites individualmente.
Texto
Copiar host 16 GB
10 containers × memory limit 4 GB
→ 40 GB de límites potencialesEs overcommit. Puede ser válido si picos no coinciden, pero requiere:
métricas;
admission/scheduling;
margen;
alertas;
load shedding;
plan de fallo.
kernel;
daemon;
logging;
page cache;
builds;
agentes;
recovery;
picos durante deploy.
Aunque Docker Engine usa límites, piensa en dos valores:
consumo esperado/normal;
máximo permitido.
throughput;
p95/p99;
error rate;
queue time;
startup;
OOM count;
throttling.
Un límite correcto mantiene servicio y protege host bajo carga prevista.
RSS normal 180 MB;
pico 320 MB;
heap configurado 256 MB;
buffers/native 80 MB;
límite 512 MB.
Prueba con carga real. Observa:
RSS;
heap used;
GC pauses;
event loop lag;
OOM;
latency.
No uses solo un request sintético pequeño.
Los límites deben acompañarse de control de carga:
máximo de requests concurrentes;
queue limitada;
timeouts;
rate limits;
streaming;
circuit breakers;
rechazo temprano.
Sin backpressure, el container puede alcanzar memory limit repetidamente y reiniciar.
YAML
Copiar services :
api :
image : my- api
mem_limit : 512m
cpus : 1.5
pids_limit : 200 Campos y semántica pueden variar entre Compose local y deploy de orquestadores. Verifica que el backend aplique realmente los límites:
Bash
Copiar docker inspect project-api-1No asumas que una sección ignorada protege el workload.
Bash
Copiar docker inspect api --format '{{json .State}}'
docker events --since 30m
docker stats --no-stream apiCorrelaciona container metrics con latency y cgroup stats. Un 100 % puede representar su cuota, no todo el host.
Errores como resource temporarily unavailable pueden aparecer. Revisa procesos/threads y límite.
Suma consumo de todos los workloads, builds, disk y logging.
En laboratorio, genera carga gradual y observa cuándo:
sube p99;
aparece throttling;
crece queue;
ocurre OOM;
health falla.
Mata una dependencia o reduce resources para validar:
alertas;
restart behavior;
backpressure;
recuperación;
evidencia preservada.
No hagas stress destructivo en producción sin un plan.
Puede ser SIGKILL externo.
Native memory, buffers o children.
Cgroup/rootless/host configuration.
El host entra en presión antes de que cada container alcance su máximo.
CPU/memory limits no protegen storage.
Un workload domina el host.
Producen OOM o desperdicio.
Bajo contención no garantiza latency.
Un bug afecta todo el host.
La configuración puede no aplicarse al backend.
Cgroups limitan recursos; no garantizan rendimiento.
OOM requiere correlacionar estado, señales y memoria real.
CPU quota puede generar throttling.
PIDs limit reduce blast radius.
Tmpfs y shared memory cuentan.
Capacidad global importa más que cada límite aislado.
Mide el workload real y diseña backpressure.
Comprueba lo aprendido
Diseña límites para una API usando mediciones de RSS y latency.
¿Por qué heap size no debe igualar memory limit?
Explica la diferencia entre CPU quota y reserva.
¿Cómo distinguirías OOM de un SIGKILL manual?
¿Qué significa overcommit y cómo lo operarías?
Logs y observabilidad , donde los containers reemplazables necesitan telemetría durable y correlacionada.