Skip to content

Runbook: NodeMemoryHigh

Alerte : NodeMemoryHigh (PrometheusRule, severity warning à 85%, critical à 95%)

Symptôme :

Node <node> a moins de 15% de RAM libre. Risque d'OOM kill imminent.

Severity : 🟡 warning à 85%, 🔴 critical à 95%


Diagnostic

1. État de la RAM sur le node

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl top node
# Ou sur le node directement :
ssh root@<node> 'free -h && cat /proc/meminfo | head -5'

2. Top pods consommateurs mémoire

kubectl top pod -A --sort-by=memory | head -20
kubectl get pod -A -o wide --field-selector spec.nodeName=<node>

3. Détecter les OOM récents (kernel)

ssh root@<node> 'dmesg | grep -i "killed process" | tail -10'

4. Dashboards "Memory usage per pod"

sum by (pod) (container_memory_working_set_bytes{namespace="akko", node="<node>"})

Causes fréquentes + fix

Cause Symptôme Fix
OpenMetadata + OpenSearch Consomment ~3G ensemble OK en prod, sous-dim si node < 8G total
Ollama + gros modèle 4-7 GB par modèle chargé Config Ollama OLLAMA_KEEP_ALIVE=-1 consomme la RAM en continu
Memory leak Airflow scheduler Croissance lente sur plusieurs jours Rolling restart : kubectl rollout restart deploy/airflow-scheduler
Trino cache query results Forte charge SQL concurrente Baisser query.max-memory et query.max-memory-per-node
Spark driver/executor oversized Grosses tables Iceberg Tune spark.driver.memory + spark.executor.memory
Node packaging trop serré Beaucoup de pods sur un node Ajouter des pod anti-affinity pour spread

Fix d'urgence (< 10 min)

1. Evict les pods low-priority pour faire de la place

# Lister les pods avec QoS BestEffort (pas de requests)
kubectl get pod -A --field-selector=spec.nodeName=<node> -o json | \
  jq -r '.items[] | select(.status.qosClass=="BestEffort") | "\(.metadata.namespace) \(.metadata.name)"'

# Kill manuellement (ils seront rescheduled)
kubectl delete pod -n <ns> <pod>

2. Drain partiel du node

kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data \
  --pod-selector='!app.kubernetes.io/component=critical'

3. Restart les deployments gourmands

# Rolling restart libère la mémoire leak
kubectl rollout restart deploy/openmetadata -n akko
kubectl rollout restart deploy/airflow-scheduler -n akko

Fix pérenne (R02)

Bump resource limits ou ajouter un node

# Exemple: réduire l'empreinte OpenSearch
openmetadata:
  opensearch:
    config:
      opensearch.yml: |
        ...
    resources:
      limits:
        memory: 1Gi   # was 2Gi si besoin

Configurer Ollama keep-alive

# helm/akko/charts/akko-ollama/values.yaml
env:
  - name: OLLAMA_KEEP_ALIVE
    value: "5m"    # au lieu de "-1" (infini)
  - name: OLLAMA_MAX_LOADED_MODELS
    value: "1"

Pod anti-affinity

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values: ["trino", "spark", "jupyterhub"]
          topologyKey: kubernetes.io/hostname

Prévention

  • HPA avec targetMemoryUtilizationPercentage: 75 pour les services stateless
  • ResourceQuota par namespace pour éviter qu'un service consomme tout
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: akko-quota
      namespace: akko
    spec:
      hard:
        limits.memory: 28Gi   # 90% du node
    
  • Node-problem-detector pour détecter les OOM noyau et pressure
  • Capacity planning review + graph de croissance mémoire par service

Lessons learned

  • Ollama en mode keep_alive=-1 consomme la RAM à vie, même modèle non-utilisé. En dev c'est OK, en prod préférer 5m avec HPA.

Liens utiles