Résoudre les problèmes liés aux événements OOM

Si vos applications Google Kubernetes Engine (GKE) rencontrent des arrêts de conteneur inattendus, une instabilité ou des erreurs CrashLoopBackOff, cela peut être dû à des événements de mémoire insuffisante (OOM). Les événements de mémoire insuffisante se produisent lorsqu'un conteneur ou un nœud manque de mémoire, ce qui entraîne l'arrêt des processus par le robot tueur OOM Linux pour récupérer des ressources.

Utilisez cette page pour déterminer si un événement OOM s'est produit au niveau du conteneur ou du nœud, et appliquez des stratégies d'atténuation efficaces pour éviter qu'il ne se reproduise.

Ces informations sont importantes pour les développeurs d'applications qui doivent s'assurer que leurs applications sont configurées avec des demandes et des limites de mémoire appropriées, et qu'elles ne présentent pas de fuites de mémoire. Il est également important pour les administrateurs et les opérateurs de plate-forme qui surveillent l'utilisation des ressources des nœuds et s'assurent que le cluster dispose d'une capacité de mémoire suffisante pour prendre en charge les charges de travail déployées. Pour en savoir plus sur les rôles courants et les exemples de tâches que nous citons dans le contenu Google Cloud , consultez Rôles utilisateur et tâches courantes de GKE.

Causes courantes des événements OOM

Les événements OOM se produisent généralement lors de pics de charge ou de trafic, lorsque l'utilisation de la mémoire de l'application augmente et atteint la limite de mémoire configurée pour le conteneur.

Les scénarios suivants peuvent provoquer un événement OOM :

  • Limite de mémoire insuffisante : le paramètre resources.limits.memory dans le fichier manifeste du pod est trop faible pour les demandes de mémoire typiques ou maximales de l'application.
  • Demandes ou limites de mémoire non définies : si resources.limits.memory et resources.requests.memory ne sont pas définis, l'utilisation de la mémoire du conteneur n'est pas limitée.
  • Charge élevée ou pics de charge : des pics de charge soudains et extrêmes peuvent saturer les ressources d'un système, y compris la mémoire, même si les limites sont généralement adéquates.
  • Fuite de mémoire : l'application peut présenter un défaut de code qui l'empêche de libérer correctement de la mémoire.

Les événements OOM peuvent déclencher une défaillance en cascade, car il reste moins de conteneurs pour gérer le trafic, ce qui augmente la charge sur les conteneurs restants. Ces conteneurs peuvent alors également être arrêtés.

Comment Kubernetes gère les événements OOM

Le tueur OOM Linux gère chaque événement OOM. Le tueur OOM est un processus du kernel qui s'active lorsque la mémoire du système est très faible. Son but est d'empêcher un plantage total du système en mettant fin de manière stratégique à des processus pour libérer des ressources. Le noyau utilise un système de notation pour sélectionner le processus à arrêter, dans le but de préserver la stabilité du système et de minimiser la perte de données.

Dans un environnement Kubernetes, l'OOM Killer fonctionne à deux niveaux différents : le groupe de contrôle (cgroup), qui affecte un conteneur, et le système, qui affecte l'ensemble du nœud.

Arrêt OOM au niveau du conteneur

Un arrêt OOM au niveau du conteneur se produit lorsqu'un conteneur tente de dépasser sa limite de mémoire prédéfinie. Kubernetes attribue chaque conteneur à un cgroup spécifique avec une limite de mémoire stricte. Lorsque l'utilisation de la mémoire d'un conteneur atteint cette limite, le noyau tente d'abord de récupérer de la mémoire dans ce cgroup. Si le noyau ne peut pas récupérer suffisamment de mémoire à l'aide de ce processus, le tueur OOM du cgroup est appelé. Il met fin aux processus au sein de ce cgroup spécifique pour appliquer la limite de ressources.

Lorsque le processus principal d'un conteneur est arrêté de cette manière, Kubernetes observe l'événement et marque l'état du conteneur comme OOMKilled. Le restartPolicy configuré du pod détermine ensuite le résultat :

  • Always ou OnFailure : le conteneur est redémarré.
  • Never : le conteneur n'est pas redémarré et reste à l'état "arrêté".

En isolant la défaillance au conteneur incriminé, l'OOM Killer empêche un seul pod défaillant de faire planter l'ensemble du nœud.

Impact de la version de cgroup sur le comportement d'OOM Killer

Le comportement d'arrêt OOM peut varier considérablement d'une version de cgroup à l'autre. Si vous ne savez pas quelle version de cgroup vous utilisez, vérifiez le mode cgroup des nœuds de cluster.

  • Dans cgroup v1, un événement de mémoire insuffisante dans le cgroup de mémoire d'un conteneur peut entraîner un comportement imprévisible. Le tueur OOM peut arrêter n'importe quel processus au sein de ce cgroup, y compris les processus enfants qui ne sont pas le processus principal du conteneur (PID 1).

    Ce comportement présente un défi de taille pour Kubernetes. Étant donné que Kubernetes surveille principalement l'état du processus du conteneur principal, il n'est pas informé de ces arrêts "partiels" pour cause de mémoire insuffisante. Le processus du conteneur principal peut continuer à s'exécuter, même si des processus enfants critiques ont été arrêtés. Ce comportement peut entraîner des défaillances subtiles de l'application qui ne sont pas immédiatement visibles par Kubernetes ou par les opérateurs, mais qui sont toujours visibles dans le journal système du nœud (journalctl).

  • cgroup v2 offre un comportement plus prévisible du tueur OOM.

    Pour garantir l'intégrité de la charge de travail dans un environnement cgroup v2, le tueur OOM empêche les arrêts partiels et garantit l'un des deux résultats suivants : soit toutes les tâches appartenant à ce cgroup et à ses descendants sont arrêtées (rendant l'échec visible par Kubernetes), soit lorsque la charge de travail ne comporte aucune tâche utilisant trop de mémoire, la charge de travail reste intacte et continue de s'exécuter sans arrêt inattendu du processus interne.

    Pour les scénarios dans lesquels vous souhaitez que le comportement cgroup v1 mette fin à un seul processus, kubelet fournit l'option singleProcessOOMKill pour cgroup v2. Cette option vous offre un contrôle plus précis, ce qui permet d'arrêter des processus individuels lors d'un événement OOM, plutôt que l'ensemble du cgroup.

Arrêt pour cause de mémoire insuffisante (OOM) au niveau du système

Un arrêt pour cause de mémoire insuffisante au niveau du système est un événement plus grave qui se produit lorsque l'ensemble du nœud, et pas seulement un seul conteneur, manque de mémoire disponible. Cet événement peut se produire si l'utilisation combinée de la mémoire de tous les processus (y compris tous les pods et les daemons système) dépasse la capacité du nœud.

Lorsque ce nœud manque de mémoire, le robot tueur OOM global évalue tous les processus du nœud et met fin à un processus pour récupérer de la mémoire pour l'ensemble du système. Le processus sélectionné est généralement un processus de courte durée qui utilise une grande quantité de mémoire.

Pour éviter les situations de mémoire insuffisante graves, Kubernetes utilise l'éviction par pression sur les nœuds pour gérer les ressources des nœuds. Ce processus implique l'éviction des pods moins critiques d'un nœud lorsque les ressources, telles que la mémoire ou l'espace disque, deviennent extrêmement faibles. Un arrêt OOM au niveau du système indique que ce processus d'éviction n'a pas pu libérer de la mémoire assez rapidement pour éviter le problème.

Si l'OOM Killer met fin au processus d'un conteneur, l'effet est généralement identique à un arrêt déclenché par un cgroup : le conteneur est marqué OOMKilled et redémarré en fonction de sa règle. Toutefois, si un processus système critique est arrêté (ce qui est rare), le nœud lui-même peut devenir instable.

Examiner les événements de mémoire insuffisante

Les sections suivantes vous aident à détecter et à confirmer un événement OOM, en commençant par les outils Kubernetes les plus simples, puis en passant à une analyse plus détaillée des journaux.

Vérifier l'état du pod pour détecter les événements OOM visibles

La première étape pour confirmer un événement OOM consiste à vérifier si Kubernetes l'a observé. Kubernetes observe l'événement lorsque le processus principal du conteneur est arrêté, ce qui est un comportement standard dans les environnements cgroup v2.

  • Inspectez l'état du pod :

    kubectl describe pod POD_NAME
    

    Remplacez POD_NAME par le nom du pod que vous souhaitez examiner.

    Si un événement OOM visible s'est produit, le résultat ressemble à ce qui suit :

    ...
      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 vous voyez OOMKilled dans le champ Reason, cela signifie que vous avez confirmé l'événement. Un Exit Code de 137 indique également un arrêt OOM. Si le champ Reason a une valeur différente ou si le pod est toujours en cours d'exécution malgré les erreurs de l'application, passez à la section suivante pour approfondir l'analyse.

Rechercher des événements OOM invisibles dans les journaux

Un arrêt OOM est "invisible" pour Kubernetes si un processus enfant est arrêté, mais que le processus du conteneur principal continue de s'exécuter (scénario courant dans les environnements cgroup v1). Vous devez rechercher les journaux du nœud pour trouver des preuves de ces événements.

Pour trouver les arrêts OOM invisibles, utilisez l'explorateur de journaux :

  1. Dans la console Google Cloud , accédez à l'explorateur de journaux.

    Accéder à l'explorateur de journaux

  2. Dans le volet de requête, saisissez l'une des requêtes suivantes :

    • Si vous disposez déjà d'un pod qui, selon vous, a rencontré un événement OOM, interrogez ce pod spécifique :

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

      Remplacez les éléments suivants :

      • POD_NAME : nom du pod que vous souhaitez interroger.
      • CLUSTER_NAME : nom du cluster auquel appartient le pod.
    • Pour découvrir quels pods ou nœuds ont rencontré un événement OOM, interrogez toutes les charges de travail GKE :

      resource.type="k8s_node"
      jsonPayload.MESSAGE:("ContainerDied" OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      
  3. Cliquez sur Exécuter la requête.

  4. Dans le résultat, localisez les événements OOM en recherchant les entrées de journal contenant la chaîne TaskOOM.

  5. Facultatif : Si vous avez recherché des événements de mémoire insuffisante pour toutes les charges de travail GKE et que vous souhaitez identifier le pod spécifique qui a rencontré ces événements, procédez comme suit :

    1. Pour chaque événement, notez l'ID de conteneur qui lui est associé.
    2. Identifiez les arrêts de conteneur en recherchant les entrées de journal contenant la chaîne ContainerDied et qui se sont produites peu de temps après les événements OOM. Faites correspondre l'ID de conteneur de l'événement OOM à la ligne ContainerDied correspondante.

    3. Une fois que vous avez mis en correspondance container IDs, la ligne ContainerDied inclut généralement le nom du pod associé au conteneur défaillant. Ce pod a été affecté par l'événement OOM.

Corréler les événements de mémoire insuffisante avec les redémarrages de conteneurs

Lorsqu'un arrêt OOM met fin au processus principal d'un conteneur, Kubernetes redémarre le conteneur en fonction de la valeur restartPolicy du pod, comme décrit dans Arrêt OOM au niveau du conteneur et Arrêt OOM au niveau du système. Pour afficher les événements OOMKilling et les démarrages de conteneurs qui les suivent dans une seule liste chronologique, utilisez l'explorateur de journaux :

  1. Dans la console Google Cloud , accédez à l'explorateur de journaux.

    Accéder à l'explorateur de journaux

  2. Dans le volet de requête, saisissez la requête suivante :

    log_id("events")
    resource.labels.cluster_name="CLUSTER_NAME"
    (
      (
        resource.type="k8s_pod"
        resource.labels.namespace_name="NAMESPACE_NAME"
        jsonPayload.message:"Started container"
      ) OR (
        resource.type="k8s_node"
        jsonPayload.reason="OOMKilling"
      )
    )
    

    Remplacez les éléments suivants :

    • CLUSTER_NAME : nom du cluster que vous souhaitez interroger.
    • NAMESPACE_NAME : espace de noms des pods que vous souhaitez examiner.

    Les événements OOMKilling sont signalés pour le nœud (type de ressource k8s_node). Ils n'ont donc pas d'étiquette d'espace de noms. Le filtre d'espace de noms ne s'applique qu'aux événements Started container, qui sont signalés pour le pod (type de ressource k8s_pod).

  3. Cliquez sur Exécuter la requête.

  4. Dans le résultat, recherchez les événements Started container qui se produisent peu de temps après un événement OOMKilling sur le même nœud. Le message d'événement OOMKilling identifie le processus qui a été arrêté, et l'événement Started container identifie le pod et le conteneur qui ont démarré.

    Tous les événements Started container ne sont pas causés par un arrêt OOM. Pour confirmer qu'un conteneur a été redémarré en raison d'un événement OOM, vérifiez l'état du pod comme décrit dans Vérifier l'état du pod pour les événements OOM visibles.

Utiliser journalctl pour obtenir des informations en temps réel

Si vous devez effectuer une analyse en temps réel de votre système, utilisez les commandes journalctl.

  1. Connectez-vous au nœud à l'aide de SSH :

    gcloud compute ssh NODE_NAME --location ZONE
    

    Remplacez les éléments suivants :

    • NODE_NAME : nom du nœud que vous souhaitez examiner.
    • ZONE : zone Compute Engine à laquelle appartient votre nœud.
  2. Dans le shell, explorez les messages du noyau à partir du journal système du nœud :

    journalctl -k
    
  3. Analysez le résultat pour distinguer le type d'événement :

    • Arrêt au niveau du conteneur : l'entrée de journal contient des termes tels que memory cgroup, mem_cgroup ou memcg, qui indiquent qu'une limite de cgroup a été appliquée.
    • Arrêt au niveau du système : l'entrée de journal est un message général tel que Out of memory: Killed process..., sans mentionner de cgroup.

Résoudre les événements OOM

Pour résoudre un événement OOM, essayez les solutions suivantes :

  • Augmentez les limites de mémoire : c'est la solution la plus directe. Modifiez le fichier manifeste du pod pour fournir une valeur resources.limits.memory plus élevée qui s'adapte à l'utilisation maximale de l'application. Pour en savoir plus sur la définition de limites, consultez Gestion des ressources pour les pods et les conteneurs dans la documentation Kubernetes.
  • Ajouter ou ajuster les demandes de mémoire : dans le fichier manifeste du pod, vérifiez que le champ resources.requests.memory est défini sur une valeur réaliste pour une utilisation standard. Ce paramètre aide Kubernetes à planifier le pod sur un nœud disposant de suffisamment de mémoire.
  • Utilisez le scaling horizontal de la charge de travail : pour répartir la charge de trafic et réduire la pression de la mémoire sur un seul pod, augmentez le nombre d'instances répliquées. Pour que Kubernetes effectue un scaling proactif de la charge de travail, envisagez d'activer l'autoscaling horizontal des pods.
  • Effectuez un scaling vertical des nœuds : si de nombreux pods d'un nœud sont proches de leurs limites, le nœud lui-même est peut-être trop petit. Pour augmenter la taille des nœuds, migrez vos charges de travail vers un pool de nœuds disposant de plus de mémoire. Pour que Kubernetes effectue un scaling proactif des nœuds, envisagez d'activer l'autoscaling vertical des pods.
  • Optimisez votre application : examinez votre application pour identifier et résoudre les fuites de mémoire, et optimisez le code qui consomme de grandes quantités de mémoire lors des pics de trafic.
  • Supprimer les charges de travail problématiques : en dernier recours pour les charges de travail non critiques, supprimez le pod pour soulager immédiatement la pression sur le cluster.

Étapes suivantes