OOMKilled significa que un pod excedió su límite de memoria y el kernel lo terminó (código de salida 137). Esto sucede porque tu límite de memoria es muy bajo o tu app tiene una fuga de memoria. Esta guía te muestra cómo diagnosticar, dimensionar límites correctamente, y entender las clases QoS.

Síntoma

El pod se reinicia repetidamente y kubectl describe pod muestra:

Name:           example-app-5f7c9d4bfb-7x8kq
Namespace:      default
Status:         Running
Restarts:       12
Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
  Signal:       9
  Last Probe:   2m30s ago
  Reason:       None

Alternativamente, el pod no muestra razón OOMKilled pero consistentemente sale con código 137:

Exit Code:    137
Signal:       9

En kubectl get events:

pod-name   OOMKilled   The container was OOMKilled

El uso de memoria podría ser visible en kubectl top pod:

NAME                       CPU(cores)   MEMORY(bytes)
example-app-5f7c9d4bfb    100m         1536Mi    # En o cerca del límite

Por qué ocurre

Ordenado por probabilidad:

  1. Límite de memoria es muy bajo para la carga de trabajo normal de la app — La app necesita más memoria para almacenar datos, cache, o procesar solicitudes, pero el límite no lo permite.
  2. Fuga de memoria en la app — La app asigna memoria con el tiempo sin liberarla, eventualmente alcanzando el límite aunque la necesidad inicial fuera menor.
  3. Heap de JVM o runtime no configurado — Java, Node.js, o Python está usando toda la memoria del sistema disponible en lugar de respetar un tamaño de heap explícito.
  4. Pico repentino en procesamiento de datos — Un trabajo batch o generación de reporte requiere temporalmente más memoria de la que el límite permite.
  5. Sin solicitud de memoria configurada — El pod tiene un límite pero sin solicitud, por lo que Kubernetes lo programa en un nodo que se ve libre pero está realmente bajo presión.
  6. Clase QoS es Burstable, no Guaranteed — El pod puede ser desalojado si el nodo se queda bajo en memoria, aunque técnicamente no haya excedido el límite.

Solución

1. Límite de memoria es muy bajo

Primero, mide cuánta memoria usa realmente tu app:

# Instalar metrics-server si no está presente (generalmente pre-instalado en clústeres en la nube)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# Esperar 1-2 minutos para que las métricas se recopilen
sleep 120

# Verificar el uso en tiempo real
kubectl top pod <pod-name>

# Monitorear con el tiempo (buscar picos)
kubectl top pod <pod-name> --containers

Registra el uso de memoria máximo. Luego aumenta el límite en 20–50% como margen de seguridad:

# Si el pico es 512 Mi, establecer límite a 768 Mi
kubectl set resources deployment <deployment-name> \
  --limits=memory=768Mi \
  --requests=memory=512Mi

O edita el Deployment directamente:

kubectl edit deployment <deployment-name>

Y actualiza la sección de resources:

containers:
- name: app
  resources:
    requests:
      memory: "512Mi"
      cpu: "100m"
    limits:
      memory: "768Mi"        # Aumentado de 512 Mi
      cpu: "500m"

Aplica y verifica:

kubectl rollout restart deployment/<deployment-name>
kubectl top pod <pod-name>    # Verificar que el nuevo pod tiene suficiente memoria

2. Fuga de memoria en la app

Si la memoria de la app crece con el tiempo hasta alcanzar el límite, hay una fuga. Monitorea la memoria durante varias horas o días:

# Recopilar el uso de memoria cada 30 segundos y guardar en un archivo
watch -n 30 "kubectl top pod <pod-name> >> memory-log.txt"

Si la memoria crece consistentemente, la app tiene una fuga. Corregir la fuga en el código de la aplicación:

  • Java: Usar heap dumps para encontrar objetos retenidos:
    jmap -dump:live,format=b,file=heap.bin <pid>
    
  • Node.js: Usar clinic.js o snapshots de heap:
    node --inspect=0.0.0.0:9229 app.js
    # Luego conectar DevTools y tomar snapshots de heap
    
  • Python: Usar memory_profiler u objgraph para rastrear asignaciones.

Para una solución rápida mientras se corrige la fuga, establece un límite de memoria más bajo y aumenta la política de reinicio:

restartPolicy: Always
containers:
- name: app
  resources:
    limits:
      memory: "512Mi"    # Límite más bajo fuerza reinicio más rápido

Esto no es una corrección pero compra tiempo mientras depuras la fuga real.

3. Heap de JVM o runtime no configurado

Si tu app se ejecuta en una JVM, Node.js, o Python, configura el heap o límite de memoria explícitamente:

Java/JVM:
Establece el tamaño de heap en tu Dockerfile o spec de pod:

FROM openjdk:17-slim
ENV JAVA_OPTS="-Xmx512m -Xms256m"
COPY app.jar /app.jar
ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", "/app.jar"]

O pásalo como una variable de entorno:

containers:
- name: app
  image: myapp:jvm
  env:
  - name: JAVA_OPTS
    value: "-Xmx512m -Xms256m"

Node.js:
Establece el max-old-space-size:

FROM node:20-alpine
ENV NODE_OPTIONS="--max-old-space-size=512"
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]

O:

containers:
- name: app
  env:
  - name: NODE_OPTIONS
    value: "--max-old-space-size=512"

Python:
Algunos frameworks pre-asignan memoria. Verifica si hay una configuración de memoria:

containers:
- name: app
  env:
  - name: PYTHONUNBUFFERED
    value: "1"    # Ayuda con logging en contenedores

Redeploy:

kubectl rollout restart deployment/<deployment-name>

4. Pico repentino en procesamiento de datos

Para trabajos batch o generación de reportes, usa una clase de recurso separada con límites más altos:

apiVersion: batch/v1
kind: Job
metadata:
  name: heavy-report
spec:
  template:
    spec:
      containers:
      - name: report
        image: myapp:latest
        resources:
          requests:
            memory: "2Gi"
            cpu: "1000m"
          limits:
            memory: "3Gi"
            cpu: "1500m"
      restartPolicy: Never

Para apps de larga duración que ocasionalmente pican, considera usar un Vertical Pod Autoscaler (VPA) para ajustar automáticamente los límites basado en uso histórico:

# Instalar VPA (si no está ya instalado)
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh

# Luego anotar tu Deployment
kubectl annotate deployment <deployment-name> \
  vpa.k8s.io/updateMode=auto \
  vpa.k8s.io/updatePolicy.updateMode=auto

5. Sin solicitud de memoria configurada

Si estableces un límite pero sin solicitud, Kubernetes trata la solicitud como igual al límite. Esto causa problemas de scheduling. Siempre configura ambos:

resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"
    cpu: "500m"

La regla de oro:
– Request = memoria mínima necesaria (para scheduling)
– Limit = máximo que la app debería usar (para enforcement)
– Ratio: el límite debería ser 1.5–2x la solicitud

Actualiza tu Deployment:

kubectl set resources deployment <deployment-name> \
  --requests=memory=256Mi,cpu=100m \
  --limits=memory=512Mi,cpu=500m

6. Clase QoS es Burstable en lugar de Guaranteed

Verifica la clase QoS:

kubectl get pod <pod-name> -o yaml | grep qosClass

Existen tres clases QoS:
– Guaranteed: requests == limits (prioridad más alta, nunca desalojado por memoria)
– Burstable: requests < limits (prioridad media, puede ser desalojado)
– BestEffort: sin requests o limits (prioridad más baja, desalojado primero)

Para apps de producción, usa Guaranteed:

resources:
  requests:
    memory: "512Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"    # Igual que la solicitud
    cpu: "100m"

Aplica y reinicia:

kubectl apply -f deployment.yaml
kubectl rollout restart deployment/<deployment-name>

Cómo evitarlo

  1. Siempre mide antes de establecer límites:

    kubectl top pod <pod-name>
    

    Establece solicitudes y límites basado en el uso de pico observado real, no en adivinanzas.

  2. Establece tanto requests como limits, nunca solo uno:

    resources:
      requests:
        memory: "256Mi"
      limits:
        memory: "512Mi"
    

  3. Configura explícitamente el heap de JVM/runtime:

    ENV JAVA_OPTS="-Xmx512m -Xms256m"
    ENV NODE_OPTIONS="--max-old-space-size=512"
    

  4. Habilita metrics-server para monitorear el uso de memoria:

    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
    

  5. Usa un Vertical Pod Autoscaler (VPA) para dimensionamiento automático:

    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: example-vpa
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: example-app
      updatePolicy:
        updateMode: "Auto"
    

  6. Perfila tu app para fugas de memoria durante el desarrollo:
    – Java: heap dumps
    – Node.js: clinic.js o DevTools
    – Python: memory_profiler

  7. Establece startup probes para dar a la app tiempo de asignar memoria inicial:

    startupProbe:
      httpGet:
        path: /health
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    

Preguntas frecuentes

¿Cómo sé si un pod fue OOMKilled?

Ejecuta kubectl describe pod <pod> y busca ‘Reason: OOMKilled’ en Last State, o código de salida 137. También verifica kubectl get events.

¿Por qué el límite de memoria de mi pod es diferente de lo que establecí?

Kubernetes configura límites y solicitudes por separado. Si solo estableces un límite, la solicitud por defecto es el límite. Ver ambos con kubectl get pod -o yaml | grep -A 3 resources:.

¿Cómo mido cuánta memoria usa realmente mi app?

Usa kubectl top pod <pod> para ver el uso de memoria en tiempo real. Si metrics-server no está instalado, instálalo primero (generalmente pre-instalado en clústeres en la nube).

¿Cuál es la diferencia entre requests y limits?

Las requests son garantías (para scheduling); los limits son caps (para enforcement). Si un pod excede su límite, es OOMKilled. Siempre configura ambos, con límite 1.5–2x la solicitud.

Cómo se verificó esta guía

Escrita a mano a partir de la documentación oficial, con cada comando comprobado de nuevo y una segunda revisión antes de publicar. Última revisión: .

Fuentes oficiales consultadas para este artículo

Share.

Los comentarios están cerrados.