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


Cette page vous aide à résoudre les problèmes liés aux événements de mémoire insuffisante (OOM) dans Google Kubernetes Engine (GKE). Apprenez à identifier les causes courantes des événements OOM, à faire la distinction entre les occurrences au niveau du conteneur et au niveau du nœud, et à appliquer des solutions.

Cette page s'adresse aux développeurs d'applications qui souhaitent vérifier que leurs applications sont correctement déployées, ainsi qu'aux administrateurs et opérateurs de plate-forme qui souhaitent comprendre la cause première des événements OOM et vérifier la configuration de la plate-forme. 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 l'utilisateur dans GKE Enterprise.

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 considérablement et atteint la limite de mémoire configurée pour le conteneur.

Les scénarios suivants peuvent entraîner un événement OOM :

  • Limite de mémoire insuffisante : le paramètre resources.limits.memory dans le fichier manifeste du pod est trop bas pour les besoins de mémoire typiques ou maximaux 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 est illimitée.
  • Charge élevée ou irrégulière : des pics de charge soudains et extrêmes peuvent submerger les ressources d'un système, y compris la mémoire, même si les limites sont généralement suffisantes.
  • Fuite de mémoire : l'application peut présenter un défaut de code qui l'empêche de libérer correctement 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 programme OOM Killer de Linux gère chaque événement OOM. Le tueur OOM est un processus de noyau qui s'active lorsque le système manque de mémoire critique. Son objectif est d'empêcher un plantage complet du système en arrêtant stratégiquement les 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, le processus 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 parvient pas à récupérer suffisamment de mémoire à l'aide de ce processus, le cgroup OOM Killer 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 terminé.

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

Impact de la version cgroup sur le comportement du tueur OOM

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

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

    Ce comportement pose un problème majeur pour Kubernetes. Étant donné que Kubernetes surveille principalement l'état du processus de conteneur principal, il n'a pas connaissance de ces arrêts OOM "partiels". Le processus de 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 des applications qui ne sont pas immédiatement visibles par Kubernetes ni 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é des charges de travail dans un environnement cgroup v2, le killer 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 (ce qui rend l'échec visible pour Kubernetes), soit, lorsque la charge de travail n'a pas de tâches utilisant trop de mémoire, la charge de travail est laissée intacte et continue de s'exécuter sans arrêt inattendu de processus internes.

    Pour les scénarios dans lesquels vous souhaitez que cgroup v1 se comporte comme un arrêt d'un seul processus, kubelet fournit l'indicateur singleProcessOOMKill pour cgroup v2. Cet indicateur vous offre un contrôle plus précis, en vous permettant d'arrêter des processus individuels lors d'un événement OOM, plutôt que l'ensemble du cgroup.

Arrêt OOM au niveau du système

Un arrêt OOM 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 sur le nœud et en arrête un pour récupérer de la mémoire pour l'ensemble du système. Le processus sélectionné est généralement de courte durée et utilise une grande quantité de mémoire.

Pour éviter les situations graves d'OOM, Kubernetes utilise l'éviction par pression sur les nœuds pour gérer les ressources des nœuds. Ce processus consiste à évincer les 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 le processus d'un conteneur est arrêté par le tueur OOM, l'effet est généralement identique à celui d'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 OOM

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

Vérifier l'état du pod pour 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 est semblable à 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 OOMKilled s'affiche dans le champ Reason, cela signifie que vous avez confirmé l'événement. Un Exit Code de type 137 indique également un arrêt pour mémoire insuffisante. Si le champ Reason a une valeur différente ou si le pod est toujours en cours d'exécution malgré les erreurs d'application, passez à la section suivante pour examiner le problème plus en détail.

Rechercher les é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 de conteneur principal continue de s'exécuter (un scénario courant dans les environnements cgroup v1). Vous devez rechercher dans les journaux du nœud des preuves de ces événements.

Pour trouver les erreurs 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 "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 identifier les pods ou les nœuds qui 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, recherchez 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 OOM pour toutes les charges de travail GKE et que vous souhaitez identifier le pod spécifique qui a rencontré les événements OOM, procédez comme suit :

    1. Pour chaque événement, notez l'ID du 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 trouvé le 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.

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 :

  • Augmenter les limites de mémoire : il s'agit de 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 typique. Ce paramètre permet à Kubernetes de planifier le pod sur un nœud disposant de suffisamment de mémoire.
  • Faites évoluer la charge de travail horizontalement : pour répartir la charge de trafic et réduire la pression sur la mémoire d'un pod unique, augmentez le nombre de réplicas. Pour que Kubernetes adapte de manière proactive la charge de travail, envisagez d'activer l'autoscaling horizontal des pods.
  • Faites évoluer les nœuds verticalement : si de nombreux pods sur un nœud sont proches de leurs limites, le nœud lui-même 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 mette à l'échelle les nœuds de manière proactive, 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.
  • Supprimez 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