Soluciona problemas relacionados con eventos de OOM


En esta página, te ayudaremos a solucionar problemas y resolver eventos de memoria insuficiente (OOM) en Google Kubernetes Engine (GKE). Aprenderás a identificar las causas comunes de los eventos de OOM, a distinguir entre los casos a nivel de contenedor y de nodo, y a aplicar soluciones.

Esta página está dirigida a desarrolladores de aplicaciones que quieren verificar que sus apps se hayan implementado con éxito. También, a administradores y operadores de plataformas que buscan comprender la causa raíz de los eventos de OOM y verificar la configuración de las plataformas. Para obtener más información sobre los roles y las tareas de ejemplo comunes a los que hacemos referencia en el contenido de Trusted Cloud by S3NS , consulta Roles de usuario y tareas comunes de GKE Enterprise.

Causas comunes de los eventos de OOM

En general, los eventos de OOM se producen durante los aumentos repentinos de carga o tráfico, en los que el uso de memoria de la app aumenta y alcanza el límite configurado para el contenedor.

Los siguientes casos pueden generar un evento de OOM:

  • Límite de memoria insuficiente: El parámetro de configuración resources.limits.memory en el manifiesto del Pod es demasiado bajo para las demandas típicas o máximas de memoria de la app.
  • Solicitudes o límites de memoria no definidos: Si no se define resources.limits.memory ni resources.requests.memory, el uso de memoria del contenedor no se ve limitado.
  • Carga alta o repentina: Los aumentos repentinos y extremos de carga pueden sobrecargar los recursos de un sistema, incluida la memoria, a pesar de que los límites suelen ser adecuados.
  • Fuga de memoria: Es posible que la app tenga un defecto de código que impida que libere la memoria de forma adecuada.

Los eventos de OOM pueden iniciar una falla en cascada, ya que quedan menos contenedores para controlar el tráfico, lo que aumenta la carga en los contenedores restantes. Es posible que también se cierren estos contenedores.

Cómo controla Kubernetes los eventos de OOM

El OOM Killer de Linux controla todos los eventos de OOM. El OOM Killer es un proceso del kernel que se activa cuando el sistema tiene muy poca memoria. Su objetivo es evitar una falla total del sistema. Para lograrlo, cierra de manera estratégica procesos y libera recursos. El kernel usa un sistema de puntuación para elegir qué proceso debe detener, con el objetivo de preservar la estabilidad del sistema y minimizar la pérdida de datos.

En un entorno de Kubernetes, el OOM Killer funciona en dos ámbitos diferentes: el grupo de control (cgroup), que afecta a un contenedor, y el sistema, que afecta a todo el nodo.

Cierre por OOM a nivel de contenedor

Un cierre por OOM a nivel de contenedor ocurre cuando un contenedor intenta exceder su límite de memoria predefinido. Kubernetes asigna cada contenedor a un cgroup específico con un límite de memoria fijo. Cuando el uso de memoria de un contenedor alcanza este límite, el kernel primero intenta recuperar memoria dentro de ese cgroup. Si el kernel no puede recuperar suficiente memoria con este proceso, se invoca el OOM Killer del cgroup. Este finaliza los procesos dentro de ese cgroup específico para aplicar el límite de recursos.

Cuando el proceso principal de un contenedor finaliza de esta manera, Kubernetes observa el evento y marca el estado del contenedor como OOMKilled. Luego, la restartPolicy configurada del Pod determina alguno de estos resultados:

  • Always u OnFailure: Se reinicia el contenedor.
  • Never: El contenedor no se reinicia y permanece en estado finalizado.

Aislando la falla en el contenedor infractor, el OOM Killer evita que un solo Pod defectuoso haga fallar todo el nodo.

Cómo la versión de cgroup afecta el comportamiento de OOM Killer

El comportamiento del cierre por OOM puede diferir de manera significativa entre las versiones de cgroup. Si no sabes qué versión de cgroup usas, verifica el modo de cgroup de los nodos del clúster.

  • En cgroup v1, un evento de OOM dentro del cgroup de memoria de un contenedor puede generar un comportamiento impredecible. El OOM Killer puede finalizar cualquier proceso dentro de ese cgroup, incluidos los procesos secundarios que no son el proceso principal del contenedor (PID 1).

    Este comportamiento representa un desafío importante para Kubernetes. Dado que Kubernetes supervisa principalmente el estado del proceso del contenedor principal, no detecta estos cierres por falta de memoria “parciales”. Es posible que el proceso principal del contenedor siga ejecutándose incluso si se finalizaron procesos secundarios esenciales. Este comportamiento puede provocar fallas sutiles en la app que no son visibles de inmediato para Kubernetes ni los operadores, pero que sí se pueden ver en el registro del sistema del nodo (journalctl).

  • cgroup v2 ofrece un comportamiento más predecible del OOM Killer.

    Para garantizar la integridad de la carga de trabajo en un entorno de cgroup v2, el OOM Killer evita los cierres parciales. De este modo, garantiza uno de dos resultados: o bien se finalizan todas las tareas que pertenecen a ese cgroup y a sus subordinados (lo que hace que la falla sea visible para Kubernetes), o bien, cuando la carga de trabajo no tiene tareas que usen demasiada memoria, esta permanece intacta y continúa ejecutándose sin finalizaciones inesperadas de procesos internos.

    En casos en los que quieras replicar el comportamiento de cgroup v1, en el que se finaliza un solo proceso, kubelet proporciona la marca singleProcessOOMKill para cgroup v2. Esta marca brinda un control más detallado, ya que permite finalizar procesos individuales durante un evento de OOM, en lugar de todo el cgroup.

Cierre por OOM a nivel de sistema

Un cierre por OOM a nivel de sistema es un evento más grave que ocurre cuando todo el nodo, no solo un contenedor, se queda sin memoria disponible. Este evento puede ocurrir si el uso de memoria combinado de todos los procesos (incluidos todos los Pods y daemons del sistema) supera la capacidad del nodo.

Cuando este nodo se queda sin memoria, el OOM Killer global evalúa todos los procesos del nodo y finaliza uno con el objetivo de recuperar memoria para todo el sistema. En general, el proceso seleccionado es de corta duración y usa una gran cantidad de memoria.

Para evitar situaciones graves de OOM, Kubernetes usa la expulsión por presión del nodo para administrar los recursos del nodo. Este proceso implica expulsar los Pods menos esenciales de un nodo cuando los recursos, como la memoria o el espacio en el disco, están demasiado bajos. Un cierre por OOM a nivel de sistema indica que este proceso de expulsión no pudo liberar memoria lo suficientemente rápido como para evitar el problema.

Si el OOM Killer finaliza el proceso de un contenedor, el efecto suele ser idéntico a una finalización activada por cgroup: el contenedor se marca como OOMKilled y se reinicia según su política. Sin embargo, si se cierra un proceso esencial del sistema (lo que es poco frecuente), el nodo podría volverse inestable.

Investiga eventos de OOM

En las siguientes secciones, se explica cómo detectar y confirmar un evento de OOM. Empezaremos con las herramientas de Kubernetes más simples y pasaremos a un análisis de registros más detallado.

Verifica el estado del Pod para detectar eventos de OOM visibles

El primer paso para confirmar un evento de OOM consiste en verificar si Kubernetes observó el evento de OOM. Kubernetes observa el evento cuando se cierra el proceso principal del contenedor, lo que es un comportamiento habitual en los entornos de cgroup v2.

  • Haz lo siguiente para inspeccionar el estado del Pod:

    kubectl describe pod POD_NAME
    

    Reemplaza POD_NAME por el nombre del Pod que quieres investigar.

    Si se produjo un evento de OOM visible, el resultado será similar al siguiente:

    ...
      Last State:     Terminated
        Reason:       OOMKilled
        Exit Code:    137
        Started:      Tue, 13 May 2025 19:05:28 +0000
        Finished:     Tue, 13 May 2025 19:05:30 +0000
    ...
    

Si ves OOMKilled en el campo Reason, significa que confirmaste el evento. Un Exit Code de 137 también indica un cierre por OOM. Si el campo Reason tiene un valor diferente o el Pod sigue ejecutándose a pesar de los errores de la app, continúa con la siguiente sección para seguir investigando.

Busca registros de eventos de OOM invisibles

Kubernetes no detecta un cierre por OOM si se cierra un proceso secundario, pero el proceso principal del contenedor sigue ejecutándose (una situación común en los entornos de cgroup v1). Debes buscar en los registros del nodo para encontrar evidencias de estos eventos.

Para encontrar cierres por OOM invisibles, usa el Explorador de registros:

  1. En la consola de Trusted Cloud , ve al Explorador de registros.

    Ir al Explorador de registros

  2. En el panel de consultas, ingresa una de las siguientes consultas:

    • Si ya tienes un Pod que crees que experimentó un evento de OOM, consulta ese Pod específico:

      resource.type="k8s_node"
      jsonPayload.MESSAGE:(("POD_NAME" AND "ContainerDied") OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      

      Reemplaza lo siguiente:

      • POD_NAME: es el nombre del Pod que quieres consultar.
      • CLUSTER_NAME: es el nombre del clúster al que pertenece el Pod.
    • Para descubrir qué Pods o nodos experimentaron un evento de OOM, consulta todas las cargas de trabajo de GKE:

      resource.type="k8s_node"
      jsonPayload.MESSAGE:("ContainerDied" OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      
  3. Haz clic en Ejecutar consulta.

  4. En el resultado, busca eventos de OOM. Para ello, busca entradas de registro que contengan la cadena TaskOOM.

  5. Opcional: Si buscaste eventos de OOM para todas las cargas de trabajo de GKE y quieres identificar el Pod específico que experimentó los eventos de OOM, completa los siguientes pasos:

    1. Para cada evento, anota el ID del contenedor asociado.
    2. Identifica las detenciones del contenedor buscando entradas de registro que contengan la cadena ContainerDied y que se hayan producido poco después de los eventos de OOM. Haz coincidir el ID del contenedor del evento de OOM con la línea ContainerDied correspondiente.

    3. Después de hacer coincidir los container IDs, la línea ContainerDied suele incluir el nombre del Pod asociado con el contenedor que falló. Este Pod se vio afectado por el evento de OOM.

Usa journalctl para obtener información en tiempo real

Si necesitas realizar un análisis en tiempo real del sistema, usa los comandos journalctl.

  1. Conéctate al nodo con SSH:

    gcloud compute ssh NODE_NAME --location ZONE
    

    Reemplaza lo siguiente:

    • NODE_NAME: es el nombre del nodo que quieres examinar.
    • ZONE: es la zona de Compute Engine a la que pertenece el nodo.
  2. En la shell, explora los mensajes del kernel que provienen del registro del sistema del nodo:

    journalctl -k
    
  3. Analiza el resultado para distinguir el tipo de evento:

    • Cierre a nivel de contenedor: La entrada de registro contiene términos como memory cgroup, mem_cgroup o memcg, que indican que se aplicó un límite de cgroup.
    • Cierre a nivel de sistema: La entrada de registro es un mensaje general, como Out of memory: Killed process..., en el que no se menciona un cgroup.

Resuelve eventos de OOM

Para resolver un evento de OOM, prueba las siguientes soluciones:

  • Aumenta los límites de memoria: Esta es la solución más directa. Edita el manifiesto del Pod para proporcionar un valor de resources.limits.memory más alto que se adapte al uso máximo de la app. Para obtener más información sobre cómo establecer límites, consulta Administración de recursos para Pods y contenedores en la documentación de Kubernetes.
  • Agrega o ajusta las solicitudes de memoria: En el manifiesto del Pod, verifica que el campo resources.requests.memory esté establecido en un valor realista para el uso habitual. Este parámetro de configuración ayuda a Kubernetes a programar el Pod en un nodo con suficiente memoria.
  • Escala horizontalmente la carga de trabajo: Para distribuir la carga de tráfico y reducir la presión de la memoria en cualquier Pod individual, aumenta la cantidad de réplicas. Para que Kubernetes escale la carga de trabajo de forma proactiva, considera habilitar el escalado automático horizontal de Pods.
  • Escala verticalmente los nodos: Si muchos Pods de un nodo están por llegar al límite, es posible que el nodo en sí sea demasiado pequeño. Para aumentar el tamaño de los nodos, migra las cargas de trabajo a un grupo de nodos con más memoria. Para que Kubernetes escale los nodos de forma proactiva, considera habilitar el escalado automático vertical de Pods.
  • Optimiza la app: Revisa la app para identificar y resolver fugas de memoria y optimiza el código que consume grandes cantidades de memoria durante los aumentos repentinos de tráfico.
  • Borra las cargas de trabajo problemáticas: Como último recurso para las cargas de trabajo que no son esenciales, borra el Pod para aliviar de inmediato la presión sobre el clúster.

¿Qué sigue?