Aller au contenu

Runbook : NodeMemoryHigh

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

Symptôme :

Le 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 (noyau)

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 + correctif

Cause Symptôme Correctif
OpenMetadata + OpenSearch ~3 G ensemble OK en prod, sous-dim si node < 8 G total
Ollama + gros modèle 4-7 GB par modèle chargé OLLAMA_KEEP_ALIVE=-1 consomme la RAM en continu
Fuite mémoire Airflow scheduler Croissance lente sur plusieurs jours Rolling restart : kubectl rollout restart deploy/airflow-scheduler
Cache query results Trino 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
Pods trop serrés Beaucoup de pods sur un node Ajouter des pod anti-affinity pour répartir

Correctif d'urgence (< 10 min)

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

# Lister les pods 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)"'

# Supprimer 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. Redémarrer les deployments gourmands

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

Correctif pérenne (R02)

Bump des resource limits ou ajout d'un node

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

Configurer le keep-alive Ollama

# 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 la pressure
  • Revue capacity planning + graph de croissance mémoire par service

Lessons learned

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

Liens utiles