Planteamiento y cuándo aplica
Un nodo Linux de 64 GiB todavía tiene 18 GiB de MemAvailable, pero un contenedor se cierra repetidamente a lo largo de seis horas. El entorno de ejecución registra OOMKilled y el código de salida 137. El memory.max de cgroup v2 de la carga de trabajo es de 4 GiB; antes del fallo, memory.current se aproxima a ese límite y oom_kill aumenta en memory.events. Durante el mismo período, anon en memory.stat crece de 1.2 GiB a 3.5 GiB mientras que file es de aproximadamente 280 MiB. Explica cómo demostrarías el desencadenante, distinguirías un OOM de cgroup de un OOM global, localizarías el origen del crecimiento, contendrías el incidente de forma segura y evitarías su recurrencia.
Los 64 GiB, 18 GiB, 4 GiB, la ventana de seis horas y las cifras de uso son supuestos de entrevista, no pruebas de referencia de capacidad. Asume que Linux utiliza cgroup v2 y que el estado del entorno de ejecución y los archivos de cgroup se refieren a la misma ventana de fallo. Esta pregunta pertenece a general porque evalúa la recuperación de memoria del sistema operativo, la contabilidad de cgroups, la evidencia de OOM y el manejo de procesos; la plataforma de contenedores solo proporciona el entorno.
El material público de entrevistas de Linux y sistemas operativos publicado en 2026 incluye directamente escenarios de OOM y pide a los candidatos razonar a partir de registros del kernel, límites de recursos y comportamiento de procesos. Una respuesta completa debe ir más allá de «añadir memoria» o «vi un 137»: necesita una cadena de evidencias, contención segura, atribución del origen y una prueba reproducible que demuestre que la solución funciona.
Qué evalúa el entrevistador
La primera señal es si el candidato interpreta la evidencia correctamente. Bajo la convención de estado de salida de Bash, 137 puede ser 128 más la señal 9, por lo que indica que el proceso terminó debido a SIGKILL. Un administrador, un controlador de tiempo de espera (timeout) o el OOM killer pueden enviar esa señal. La razón OOMKilled del entorno de ejecución, un aumento en memory.events:oom_kill en la misma ventana y los registros del kernel son lo que reducen la causa a un OOM.
La segunda señal es si el candidato identifica el dominio del recurso. memory.max es un límite estricto (hard limit) de cgroup. Si el uso lo alcanza y la recuperación (reclaim) no puede reducir el cargo, el kernel puede invocar OOM dentro de ese cgroup. El nodo aún puede tener 18 GiB disponibles. Por el contrario, un OOM global surge de la presión de asignación en todo el nodo, con un grupo de víctimas distinto y diferentes acciones de recuperación.
La tercera señal es la disciplina de contabilidad de memoria. Mirar solo el RSS de un proceso pasa por alto descendientes, page cache, tmpfs, memoria compartida, búferes de sockets y datos del kernel imputados al mismo cgroup. Una respuesta sólida concilia memory.current, memory.peak, memory.stat, mediciones por proceso y perfiles de aplicación en lugar de tratar VSZ, RSS y el cargo del cgroup como equivalentes.
Por último, el entrevistador evalúa el criterio ante incidentes. Aumentar temporalmente un límite puede restaurar el servicio, o puede convertir una fuga continua en una falla a nivel de todo el nodo. La respuesta debe explicar cuándo reducir carga (shed load), reiniciar, escalar o elevar el límite; cómo distinguir un pico normal de una fuga; y cómo las pruebas de carga, de resistencia (soak) y de fallos validan la directiva.
Preguntas para clarificar antes de responder
- ¿Los registros se refieren a la misma instancia de contenedor y ventana de fallo? Un estado antiguo de
OOMKilled, un contador actual
de cgroup y una salida diferente no pueden formar una cadena causal. Alinea el ID del contenedor, las horas de inicio y salida, y la ruta.
- ¿El host realmente está usando cgroup v2? v1 y v2 exponen diferentes archivos y semánticas. Confirma si la ruta
es el cgroup del contenedor o un padre que contiene varias cargas de trabajo descendientes.
- ¿Cambió
oom_killo solooom?oomindica que se alcanzó el límite y una asignación estaba a punto de fallar;
oom_kill registra un proceso realmente terminado por un OOM killer. Realiza una verificación cruzada de los recuentos jerárquicos con memory.events.local.
- ¿El nodo también estaba bajo presión global de memoria? Inspecciona los registros del kernel,
MemAvailable, swap, PSI, eventos de desalojo (eviction)
y cargas de trabajo vecinas. Un OOM de cgroup y la presión del nodo pueden ocurrir de forma cercana entre sí.
- ¿El límite de 4 GiB pertenece al contenedor, al Pod o a un cgroup padre? Un límite padre puede restringir a un hijo
antes de que se alcance el propio límite del hijo. Recorre la jerarquía e inspecciona cada límite efectivo.
- ¿Qué carga de trabajo se correlaciona con el crecimiento? La concurrencia de solicitudes, la profundidad de la cola, el tamaño de lote, las claves de caché, las conexiones,
el tamaño de entrada y la versión desplegada ayudan a separar un conjunto de trabajo (working set) que retrocede de la memoria retenida que crece con el tiempo.
- ¿Cuántos procesos comparten el cgroup? Los sidecars, workers y procesos secundarios generados contribuyen al cargo.
Perfilar únicamente el proceso principal puede pasar por alto al responsable del crecimiento.
- ¿Cuáles son los requisitos de recuperación e integridad de datos? Terminar un miembro de una carga de trabajo multiproceso puede dejar
un estado inconsistente. El reinicio, la reducción de carga, el desvío de tráfico y la terminación de grupo dependen de la idempotencia y el RTO.
Estructura de respuesta en 30 segundos
«Trataría el 137 como una pista de SIGKILL, no como una conclusión de OOM. Alineara el ID del contenedor y la hora del fallo, luego verificaría la razón OOMKilled del entorno de ejecución, memory.events.local del cgroup objetivo, memory.current, memory.max y los registros del kernel. El nodo tiene memoria libre, pero el cgroup alcanzó su límite y oom_kill aumentó, por lo que la evidencia apunta a un OOM de cgroup. Reduciría o desviaría la carga y preservaría la evidencia, elevando el límite temporalmente solo tras verificar el margen disponible del nodo. Luego desglosaría la memoria anónima, de archivos, compartida y de kernel con memory.stat, y correlacionaría los perfiles por proceso con métricas de la carga de trabajo para distinguir una fuga, caché sin límite, pico de concurrencia o un límite insuficiente. Tras la corrección, ejecutaría pruebas de pico representativo, pruebas prolongadas de soak y de límite controlado, y configuraría alertas sobre memory.high, presión, picos y eventos de OOM».
Análisis detallado paso a paso
Paso uno: convertir la terminación en una sola línea de tiempo.
Registra el ID del contenedor, PID, horas de inicio y salida, versión desplegada, recuento de reinicios y ruta del cgroup. La salida 137 corresponde comúnmente a SIGKILL en el estado de shell y contenedores, pero no demuestra un OOM: kill -9, un timeout de la plataforma o un agente de nodo pueden producir el mismo resultado. Correlaciona reason: OOMKilled, el delta en los eventos del cgroup y los mensajes del kernel alrededor del mismo segundo.
Prefiere memory.events.local para que el contador jerárquico de un padre no mezcle eventos de descendientes. Si oom_kill cambia de 7 a 8 en esta salida, memory.current se aproxima a 4 GiB y el registro del kernel reporta un OOM de cgroup de memoria, esa es una cadena coherente de OOM de cgroup. Si solo hay 137, sin cambio de contador ni razón de OOM en el runtime, investiga en su lugar señales manuales, timeouts de health-checks, systemd-oomd, desalojo y terminación por parte del runtime.
Paso dos: identificar el dominio de recursos del OOM.
memory.max restringe la memoria contabilizada para el cgroup y sus descendientes. Cuando se alcanza y la recuperación directa no puede satisfacer una asignación, el kernel solo puede elegir una víctima de ese cgroup. Por lo tanto, los 18 GiB de MemAvailable del host no protegen al contenedor. Recorre hacia arriba los cgroups padres para encontrar el límite cuyo memory.max y contadores de eventos se alcanzaron.
Un OOM global presenta evidencias diferentes: la memoria del nodo y swap se acercan al agotamiento, la PSI de memoria aumenta, los registros del kernel incluyen el estado global de la memoria y una víctima, y otras cargas de trabajo se ven afectadas. Bajo presión del nodo, la plataforma puede desalojar un Pod primero. La remediación de un OOM de cgroup se centra en el uso de la carga de trabajo y su límite; un OOM global también requiere corregir el sobrecompromiso (overcommit) del nodo, requests y reservas, daemons del sistema y la ubicación de las cargas de trabajo.
Paso tres: explicar a dónde se fueron los 4 GiB.
Comienza con memory.current y memory.peak, luego desglosa el cargo con memory.stat. Aquí, anon aumenta de 1.2 GiB a 3.5 GiB durante seis horas mientras que file se mantiene alrededor de 280 MiB. Eso prioriza heaps, asignaciones anónimas y los procesos que las poseen, pero aún no demuestra una fuga. La documentación del kernel indica que la contabilidad de cgroup también cubre page cache, tmpfs y memoria compartida, estructuras del kernel y búferes de sockets; un padre incluye a los descendientes.
Coloca el total junto al RSS, PSS, asignaciones anónimas, recuentos de procesos y subprocesos, métricas del heap de runtime y mediciones de negocio de cada proceso en una misma línea de tiempo. Si los objetos vivos y el heap de la aplicación crecen juntos, investiga referencias retenidas o una caché sin límites. Si el heap es estable pero el RSS sigue siendo alto, inspecciona fragmentación del asignador, librerías nativas o mmap. El crecimiento en file, shmem, sock o slab apunta en cambio hacia archivos en tmpfs, caché, acumulación en colas de conexión u objetos del kernel.
VSZ es espacio de direcciones virtuales, no residencia física ni el cargo de cgroup. Una sola captura instantánea (snapshot) tampoco es suficiente. Una carga de trabajo por lotes saludable puede tener picos y caer tras la recuperación; una fuga generalmente eleva la línea base a lo largo de ciclos repetidos bajo una carga comparable. Compara la pendiente y el estado estacionario posterior a la liberación con el mismo throughput.
Paso cuatro: hacer que cada hipótesis sea refutable.
Mantén una lista corta de candidatos y establece una predicción para cada uno. Con un pico de concurrencia, la memoria debería seguir a las solicitudes en curso y retroceder cuando terminen. Con una caché sin límites, el recuento de entradas y anon aumentan juntos y se estabilizan cuando se limita la capacidad. Con acumulación de consumidores (backlog), la profundidad de la cola, los objetos por lote y la memoria se mueven juntos. Con un límite insuficiente, el conjunto de trabajo representativo y estable se aproxima repetidamente al límite sin una pendiente ascendente a largo plazo.
Utiliza un perfil de heap, un perfil de asignaciones o un histograma de objetos adecuado para el entorno de ejecución, pero ten en cuenta la sobrecarga de recolección. Comienza con métricas de producción de bajo riesgo o una réplica con desvío de tráfico; antes de tomar un volcado (dump), verifica el espacio en disco, la privacidad y el costo de pausa. Cambia un factor a la vez en comparaciones de versiones o reproducción de tráfico. Aumentar el límite, desactivar la caché y reducir la concurrencia simultáneamente ocultaría la causa.
Paso cinco: separar la contención de la solución definitiva.
Protege a los usuarios y al nodo primero: limita la concurrencia hacia la instancia, pausa los lotes intensivos en memoria, desvía el tráfico o haz un rollback a una versión segura conocida. Si la reproducción es segura y el estado multiproceso seguirá siendo consistente, un reinicio libera memoria rápidamente. Si terminar un worker corrompe el estado compartido, considera terminar la carga de trabajo como una unidad. En cgroup v2, memory.oom.group=1 pide al OOM killer que trate al cgroup como indivisible, pero su semántica de recuperación debe ser probada.
Aumenta memory.max temporalmente solo tras medir el margen del nodo, la protección de cargas vecinas y el pico esperado. Adjunta una fecha de expiración, monitorización y un umbral de reversión. Para un crecimiento continuo, un límite mayor solo retrasa el siguiente OOM. Las soluciones duraderas pueden limitar una caché, liberar referencias, limitar lotes y concurrencia, corregir los ciclos de vida de procesos hijos o redimensionar requests y límites a partir de un conjunto de trabajo medido y un margen para ráfagas (burst allowance).
No «resuelvas» el incidente asignando oom_score_adj=-1000 a un servicio normal. Eso lo exime de la selección de OOM y puede forzar al kernel a terminar procesos más importantes o más numerosos; resérvalo para tareas esenciales del sistema tras una revisión integral de la directiva de fallos. Swap también modifica el comportamiento de recuperación y latencia. Puede absorber una ráfaga corta pero no repara un crecimiento desmedido.
Paso seis: validar la capacidad y crear señales tempranas.
Reproduce tráfico representativo bajo una contabilidad de cgroups y límites equivalentes a producción. Cubre carga constante, picos, entradas grandes, recuperación de backlog y comportamiento multiproceso. Usa una prueba de carga corta para el pico, una prueba de soak prolongada para la pendiente de crecimiento y la línea base posterior a la liberación, y un límite inferior controlado para ejercitar alertas, terminación, reinicio e integridad de datos. El éxito significa un margen definido al throughput y latencia objetivo, un memory.current que retrocede, ningún nuevo oom_kill y ningún empeoramiento de la presión del nodo o vecinos.
Usa memory.high como un límite de control antes del límite estricto: superarlo ralentiza (throttles) el cgroup e impulsa la recuperación directa sin invocar directamente el OOM, dejando tiempo para alertar o automatizar la contención. Monitoriza la relación memory.current/memory.max, memory.peak, los eventos high, max, oom y oom_kill, la PSI de memoria, la tasa de reinicios, el heap de la aplicación y la pendiente de crecimiento. Los límites deben provenir de mediciones de pico y soak, y revisarse cuando cambien las versiones, la concurrencia o las distribuciones de entrada.
Ejemplo de respuesta de alta calidad
«Primero demostraría si un OOM causó esta terminación. La salida 137 indica que el proceso recibió SIGKILL; una terminación manual y los timeouts también pueden provocarlo. Alinearía el ID del contenedor, la hora de salida y la ruta del cgroup, luego examinaría la razón OOMKilled del entorno de ejecución, el delta en memory.events.local y los registros del kernel. Si oom_kill aumentó en esta salida, memory.current tocó los 4 GiB y el registro identifica un cgroup de memoria, eso respalda un OOM de cgroup.
Eso también explica por qué el nodo puede tener 18 GiB disponibles. memory.max es el límite estricto del cgroup, y una recuperación fallida hace que el kernel elija dentro de ese dominio de recursos sin agotar primero el host. Aun así, inspeccionaría los cgroups padres, la PSI del nodo, swap y los registros de OOM global para excluir una presión concurrente en el nodo o un desalojo de la plataforma.
Para la contención, reduciría o desviaría el tráfico intensivo en memoria y preservaría las métricas previas al fallo y un perfil de bajo overhead. Si reiniciar es seguro, restauraría una versión conocida. Aumentaría el límite temporalmente solo tras verificar el margen del nodo y las cargas vecinas, con una condición de expiración y rollback. Más memoria no es la solución definitiva.
Para el diagnóstico, conciliaría el total del cgroup usando memory.current, memory.peak y memory.stat, luego inspeccionaría RSS, PSS, asignaciones anónimas y heaps de runtime por proceso. Aquí anon creció de 1.2 GiB a 3.5 GiB mientras que la memoria de archivos era de unos 280 MiB, por lo que priorizaría el heap, mmap anónimo, procesos hijos y el asignador, pero demostrando al responsable con perfiles. Correlacionaría la memoria con la concurrencia, profundidad de cola, entradas de caché, tamaño de lote y versión. Una línea base ascendente a carga comparable sugiere una fuga; un conjunto de trabajo estable que retrocede sugiere un pico normal o un límite bajo.
Tras la corrección, ejecutaría carga pico y un soak prolongado bajo el mismo límite de cgroup, verificando que la memoria retroceda, los contadores de eventos dejen de aumentar y la latencia y el throughput cumplan los objetivos. Luego configuraría memory.high y alertas antes del límite estricto, monitorizaría picos, presión, eventos de OOM y la pendiente de crecimiento, y dimensionaría la concurrencia, caché y capacidad a partir del conjunto de trabajo medido. Para un servicio multiproceso, también probaría la integridad de datos tras una terminación parcial antes de decidir si el OOM debería terminar al grupo completo».
Errores comunes
- Declarar OOM basándose únicamente en el código 137 →
SIGKILLpuede provenir de un operador o un controlador de timeouts → **Correlaciona la razón del
runtime, eventos de cgroup y registros del kernel.**
- Descartar OOM porque el host tiene memoria libre → Un cgroup puede alcanzar su límite estricto mientras el nodo tiene margen →
Identifica primero el dominio del recurso.
- Mirar únicamente el RSS de un proceso → Descendientes, archivos, memoria compartida y cargos del kernel también cuentan → **Concilia
el total y desglosa memory.stat.**
- Tratar VSZ como uso físico → El tamaño del espacio de direcciones no es residencia ni memoria contabilizada → **Verifica de forma cruzada RSS/PSS,
mapeos y métricas de cgroup.**
- Llamar fuga a cualquier crecimiento de
anon→ Un conjunto de trabajo normal o un pico por lotes también pueden crecer → **Compara las pendientes y la
línea base posterior a la liberación a carga equivalente.**
- Duplicar el límite de inmediato → Esto puede posponer una fuga y consumir el margen de seguridad del nodo → **Exige evidencias
de capacidad, una expiración y umbrales de rollback.**
- Inmunizar la aplicación contra el OOM → El daño puede trasladarse a otras tareas o al nodo → **Modifica
oom_score_adj
únicamente como parte de una directiva integral de fallos.**
- Ejecutar solo una prueba de carga de un minuto → Una prueba corta no detecta fugas lentas ni fragmentación → **Ejecuta tanto pruebas de
pico como pruebas prolongadas de soak.**
- Alertar únicamente en el límite estricto → Responder en
memory.maxsuele ser demasiado tarde → **Usamemory.high, PSI y
la pendiente de crecimiento para actuar anticipadamente.**
- Asumir la recuperación tras la muerte de un worker → El estado compartido multiproceso puede quedar inconsistente → **Prueba la terminación
de grupo, el reinicio y la semántica de recuperación de datos.**
Preguntas de seguimiento y cómo responder
Pregunta de seguimiento 1: El código de salida 137 está presente, pero oom_kill no aumentó. ¿Qué inspeccionas a continuación?
Primero confirma la ruta del cgroup y la hora del evento, y lee memory.events.local para no comparar un padre o una nueva instancia. Si la evidencia de OOM sigue ausente, inspecciona la razón de terminación del runtime, timeouts de despliegue o health-checks, el registro de auditoría de operadores, systemd-oomd, desalojo por parte del nodo y registros del kernel. El código de salida 137 delimita el resultado a SIGKILL; no identifica al emisor.
Pregunta de seguimiento 2: ¿Cómo distingues una fuga de memoria de un límite insuficiente?
Compara ciclos repetidos con throughput, entradas y concurrencia similares. Una fuga generalmente eleva la línea base o el recuento de objetos vivos y no retrocede con baja carga. Un límite insuficiente es más propenso a fallar en un pico repetible y volver a un conjunto de trabajo estable después. Valida con perfiles de heap o asignación, entradas de caché, procesos hijos, tamaño de lote y memory.stat; no dependas de una sola gráfica. Ambas condiciones pueden coexistir.
Pregunta de seguimiento 3: ¿Cuándo es válido aumentar memory.max?
Auméntalo cuando pruebas representativas de pico y soak muestren un comportamiento saludable cuyo conjunto de trabajo requerido supere el límite actual, mientras que la capacidad del nodo, las reservas y la protección de vecinos aún dejen margen. Un incremento durante un incidente necesita una expiración, monitorización y un umbral de reversión. Si el uso crece sin límite, un límite mayor es solo contención; también reduce la carga y repara el origen del crecimiento.
Pregunta de seguimiento 4: ¿Cómo deberían trabajar juntos memory.high y memory.max?
memory.high es un límite de ralentización (throttling) y recuperación directa; cruzarlo no invoca directamente al OOM killer, por lo que puede proporcionar una ventana de observación y automatización. memory.max es el límite de aislamiento final y puede invocar un OOM de cgroup si la recuperación falla. Configura ambos a partir de conjuntos de trabajo medidos, picos, tolerancia de latencia y margen del nodo, y alerta por separado sobre eventos high, max, oom y oom_kill.
Pregunta de seguimiento 5: ¿Por qué un servicio multiproceso podría usar memory.oom.group?
Si terminar un worker deja un estado compartido inconsistente, bloqueos o transacciones sin terminar, tratar al cgroup como una carga de trabajo indivisible puede hacer que el fallo y la recuperación sean deterministas. Antes de habilitarlo, prueba el tiempo de reinicio de todo el grupo, la idempotencia de las tareas y la recuperación de datos. Las tareas con oom_score_adj=-1000 son excepciones, así que verifica también que no quede ninguna carga de trabajo parcial.