Marcio Cunha

Contenedores reiniciando sin parar? Cómo investigar OOMKilled

Descubre por qué tus contenedores de Kubernetes mueren de repente con el error OOMKilled y aprende a ajustar límites de memoria y cazar fugas.

Marcio Cunha•5 min
También disponible en:PortuguêsEnglish
Resumen
  • El error OOMKilled ocurre cuando un contenedor consume más memoria que el límite estricto establecido por Kubernetes.
  • La falta de límites claros de recursos en los pods puede colapsar nodos enteros por agotamiento de memoria del sistema.
  • El núcleo de Linux actúa rápidamente terminando el proceso más pesado para proteger la integridad del sistema operativo.
  • Las herramientas de observabilidad y colectores de logs evitan sorpresas y ayudan a mapear cuellos de botella antes de un apagón.
  • El dimensionamiento correcto previene el desperdicio de infraestructura y garantiza estabilidad operativa en producción.

El terror silencioso: qué significa realmente el código OOMKilled

Cualquier administrador de aplicaciones modernas en la nube tarde o temprano se topa con un comportamiento frustrante: un contenedor simplemente desaparece, se reinicia y deja rastros vagos en los registros del sistema. La gran mayoría de las veces, el culpable es el temido código OOMKilled, que significa Out Of Memory Killed. En la práctica, esto quiere decir que el núcleo de Linux, el cerebro detrás del sistema operativo que gestiona el hardware, notó que la aplicación estaba consumiendo más memoria RAM de la permitida y la eliminó sumariamente para evitar que todo el servidor se congelara.

Para entender este mecanismo, imagina que Kubernetes funciona como un barrio cerrado donde cada residente —que representaría a un contenedor— tiene una cuota estricta de espacio y provisiones. Si un residente comienza a acumular basura y bloquear los pasillos de los vecinos, la administración del edificio interviene de inmediato y expulsa al infractor. En los servidores, el juez y verdugo es el OOM Killer, un subsistema del núcleo de Linux que supervisa el consumo de RAM segundo a segundo. Cuando la presión de memoria alcanza niveles críticos, elige el proceso que consume más recursos proporcionalmente y lo apaga enviando una señal fatal.

Por qué los límites de recursos salvan o destruyen tu clúster

Uno de los errores más comunes de quienes comienzan a orquestar aplicaciones es dejar los campos de limits y requests en blanco en los archivos de configuración de Kubernetes. El requests le dice al programador cuánta memoria necesita un contenedor para arrancar, mientras que limits define el techo absoluto que nunca podrá superar. Omitir estos valores le da al contenedor vía libre para consumir toda la memoria disponible en el nodo, provocando frecuentemente un efecto dominó que afecta a otras aplicaciones vecinas en el mismo servidor físico.

En escenarios corporativos, la ausencia de estas restricciones transforma un pico repentino de tráfico en un apagón generalizado. Antes de planificar ajustes finos, vale la pena recordar que los problemas de infraestructura suelen venir acompañados de otros dolores operativos, como fallas intermitentes de resolución de nombres causadas por CoreDNS o discos llenos que bloquean el entorno, tal como se analiza al examinar cómo mantener el pipeline de CI/CD funcionando sin falta de espacio en Docker. Definir límites claros aísla el daño, garantizando que si un servicio sufre un ataque de denegación de servicio o una fuga interna, muera solo sin arrastrar al resto de la infraestructura.

Cómo rastrear el momento exacto de la muerte por memoria

Cuando un pod se reinicia, la primera herramienta de investigación es el comando estándar de inspección de Kubernetes. Al ejecutar el listado de pods, probablemente notarás estados como OOMKilled o un código de salida 137. Este número 137 es una firma matemática que resulta de sumar 128 más el código 9, que representa la señal SIGKILL enviada por el sistema operativo para terminar a la fuerza el proceso desbocado. Saber decodificar este número ahorra horas de prueba y error en el banco de trabajo de ingeniería.

Para extraer detalles adicionales del historial reciente de eventos del clúster, utiliza el comando detallado de descripción. Muestra el historial de reinicios y la razón exacta de la última terminación del contenedor. Observa cómo inspeccionar el comportamiento de tu pod directamente en la terminal:

kubectl get pods -n produccion
kubectl describe pod mi-servicio-web-7b99d-xyz9q -n produccion

Si los registros textuales tradicionales desaparecen junto con el proceso, la mejor alternativa es centralizar la información de eventos y métricas de infraestructura antes de que el contenedor sea exterminado. Mantener un oleoducto de observabilidad robusto complementa la estrategia de diagnóstico, al igual que centralizar los registros dispersos en el clúster con Fluent Bit permite auditar el comportamiento de la aplicación milisegundos antes del colapso.

Identificando fugas de memoria en la raíz del código

A menudo, el error OOMKilled no es culpa de un límite bajo, sino de una fuga de memoria dentro de la aplicación. Esto ocurre cuando el código asigna espacio en RAM para procesar datos pero olvida liberar esa referencia al terminar la tarea. Los lenguajes administrados como Java, Node.js y Python poseen recolectores de basura automáticos, pero si las referencias globales acumulan objetos en listas o cachés infinitos, el recolector se vuelve inútil y la memoria crece linealmente hasta chocar con el techo de Kubernetes.

Para investigar fugas en entornos de desarrollo o staging, utiliza herramientas de perfilado de memoria específicas del lenguaje, como heapdump en Node.js o pprof en aplicaciones de Go. Monitorea el comportamiento del contenedor bajo carga simulada y observa si el consumo de RAM disminuye después de los picos de tráfico o mantiene una curva ascendente constante. Si la memoria nunca vuelve al nivel base tras finalizar las solicitudes, has encontrado una fuga de código que debe corregirse en la raíz en lugar de simplemente subir los límites de recursos en el archivo YAML.

Paso a paso para ajustar requests y limits de forma segura

Si el análisis de código descartó fugas y la aplicación simplemente exige más espacio para funcionar con comodidad, el siguiente paso es recalibrar el manifiesto de Kubernetes. Nunca adivines valores aleatorios; básate en métricas reales recopiladas por herramientas como Prometheus y Grafana durante las horas pico de operación. Sigue el procedimiento a continuación para actualizar el dimensionamiento de recursos de un deployment en producción:

  1. Consulta el consumo máximo histórico de memoria del contenedor durante las últimas dos semanas utilizando las métricas del clúster.
  2. Edita el archivo de manifiesto YAML de tu deployment localizando la sección de recursos del contenedor afectado.
  3. Aplica los cambios actualizados en el clúster ejecutando el comando de actualización y valida la estabilidad del despliegue.

Aquí tienes un ejemplo práctico de cómo estructurar los bloques de recursos dentro del archivo de configuración de tu deployment en Kubernetes:

resources:
  requests:
    memory: "512Mi"
    cpu: "250m"
  limits:
    memory: "1Gi"
    cpu: "500m"

Consideraciones finales

Investigar fallas de OOMKilled exige paciencia y una visión sistémica que abarca desde la escritura del código hasta la configuración de los nodos de infraestructura. Comprender que el límite de memoria no es un castigo, sino un contrato de estabilidad, cambia la forma en que diseñamos aplicaciones resilientes para entornos en la nube. Monitorear las tendencias de consumo, aplicar ajustes basados en datos reales y corregir fugas internas garantiza que tus contenedores se mantengan firmes, entregando valor continuo a los usuarios sin sobresaltos inesperados en la madrugada.