12 votes

Le conteneur GKE est tué par le message "Memory cgroup out of memory", mais la surveillance, les tests locaux et pprof montrent une utilisation bien inférieure à la limite.

J'ai récemment poussé une nouvelle image de conteneur vers l'un de mes déploiements GKE et j'ai remarqué que la latence de l'API a augmenté et que les demandes ont commencé à renvoyer des 502.

En regardant les journaux, j'ai constaté que le conteneur a commencé à se bloquer à cause de l'OOM :

Memory cgroup out of memory: Killed process 2774370 (main) total-vm:1801348kB, anon-rss:1043688kB, file-rss:12884kB, shmem-rss:0kB, UID:0 pgtables:2236kB oom_score_adj:980

En regardant le graphique d'utilisation de la mémoire, il ne semble pas que les pods utilisent plus de 50MB de mémoire combinée. Mes demandes de ressources initiales étaient les suivantes :

...
spec:
...
  template:
...
    spec:
...
      containers:
      - name: api-server
...
        resources:
          # You must specify requests for CPU to autoscale
          # based on CPU utilization
          requests:
            cpu: "150m"
            memory: "80Mi"
          limits:
            cpu: "1"
            memory: "1024Mi"
      - name: cloud-sql-proxy
        # It is recommended to use the latest version of the Cloud SQL proxy
        # Make sure to update on a regular schedule!
        image: gcr.io/cloudsql-docker/gce-proxy:1.17
        resources:
          # You must specify requests for CPU to autoscale
          # based on CPU utilization
          requests:
            cpu: "100m"
...

J'ai ensuite essayé de faire passer la demande de serveur API à 1 Go, mais cela n'a pas aidé. Finalement, ce qui a aidé, c'est de rétablir l'image du conteneur à la version précédente :

enter image description here

En regardant les changements dans le binaire golang, il n'y a pas de fuites de mémoire évidentes. Lorsque je l'exécute localement, il utilise au maximum 80 Mo de mémoire, même sous la charge des mêmes requêtes qu'en production.

Et le graphique ci-dessus, que j'ai obtenu de la console GKE, montre également que le pod utilise bien moins que la limite de 1 Go de mémoire.

Ma question est donc la suivante : qu'est-ce qui pourrait amener GKE à tuer mon processus pour cause d'OOM alors que la surveillance GKE et l'exécution locale n'utilisent que 80 Mo sur la limite de 1 Go ?

\=== EDIT ===

enter image description here

Ajout d'un autre graphique de la même panne. Cette fois-ci en divisant les deux conteneurs dans le pod. Si je comprends bien, la métrique ici est conteneur non-évictable/mémoire/octets utilisés :

container/memory/used_bytes GA
Memory usage
GAUGE, INT64, By
k8s_container   Memory usage in bytes. Sampled every 60 seconds.
memory_type: Either `evictable` or `non-evictable`. Evictable memory is memory that can be easily reclaimed by the kernel, while non-evictable memory cannot.

Editer le 26 avril 2021

J'ai essayé de mettre à jour le champ des ressources dans le yaml de déploiement à 1GB RAM demandé et 1GB RAM limite comme suggéré par Paul et Ryan :

        resources:
          # You must specify requests for CPU to autoscale
          # based on CPU utilization
          requests:
            cpu: "150m"
            memory: "1024Mi"
          limits:
            cpu: "1"
            memory: "1024Mi"

Malheureusement, le même résultat a été obtenu après la mise à jour avec kubectl apply -f api_server_deployment.yaml :

{
 insertId: "yyq7u3g2sy7f00"  
 jsonPayload: {
  apiVersion: "v1"   
  eventTime: null   
  involvedObject: {
   kind: "Node"    
   name: "gke-api-us-central-1-e2-highcpu-4-nod-dfe5c3a6-c0jy"    
   uid: "gke-api-us-central-1-e2-highcpu-4-nod-dfe5c3a6-c0jy"    
  }
  kind: "Event"   
  message: "Memory cgroup out of memory: Killed process 1707107 (main) total-vm:1801412kB, anon-rss:1043284kB, file-rss:9732kB, shmem-rss:0kB, UID:0 pgtables:2224kB oom_score_adj:741"   
  metadata: {
   creationTimestamp: "2021-04-26T23:13:13Z"    
   managedFields: [
    0: {
     apiVersion: "v1"      
     fieldsType: "FieldsV1"      
     fieldsV1: {
      f:count: {
      }
      f:firstTimestamp: {
      }
      f:involvedObject: {
       f:kind: {
       }
       f:name: {
       }
       f:uid: {
       }
      }
      f:lastTimestamp: {
      }
      f:message: {
      }
      f:reason: {
      }
      f:source: {
       f:component: {
       }
       f:host: {
       }
      }
      f:type: {
      }
     }
     manager: "node-problem-detector"      
     operation: "Update"      
     time: "2021-04-26T23:13:13Z"      
    }
   ]
   name: "gke-api-us-central-1-e2-highcpu-4-nod-dfe5c3a6-c0jy.16798b61e3b76ec7"    
   namespace: "default"    
   resourceVersion: "156359"    
   selfLink: "/api/v1/namespaces/default/events/gke-api-us-central-1-e2-highcpu-4-nod-dfe5c3a6-c0jy.16798b61e3b76ec7"    
   uid: "da2ad319-3f86-4ec7-8467-e7523c9eff1c"    
  }
  reason: "OOMKilling"   
  reportingComponent: ""   
  reportingInstance: ""   
  source: {
   component: "kernel-monitor"    
   host: "gke-api-us-central-1-e2-highcpu-4-nod-dfe5c3a6-c0jy"    
  }
  type: "Warning"   
 }
 logName: "projects/questions-279902/logs/events"  
 receiveTimestamp: "2021-04-26T23:13:16.918764734Z"  
 resource: {
  labels: {
   cluster_name: "api-us-central-1"    
   location: "us-central1-a"    
   node_name: "gke-api-us-central-1-e2-highcpu-4-nod-dfe5c3a6-c0jy"    
   project_id: "questions-279902"    
  }
  type: "k8s_node"   
 }
 severity: "WARNING"  
 timestamp: "2021-04-26T23:13:13Z"  
}

Kubernetes semble avoir presque immédiatement tué le conteneur pour avoir utilisé 1 Go de mémoire. Mais là encore, les mesures montrent que le conteneur n'utilise que 2 Mo de mémoire :

enter image description here

Une fois de plus, je reste perplexe, car même sous charge, ce binaire n'utilise pas plus de 80 Mo lorsque je l'exécute localement.

J'ai aussi essayé de lancer go tool pprof <url>/debug/pprof/heap . Il a montré plusieurs valeurs différentes alors que Kubernetes ne cessait d'attaquer le conteneur. Mais aucune n'était supérieure à ~20MB et l'utilisation de la mémoire ne sortait pas de l'ordinaire.

Edit 04/27

J'ai essayé de définir request=limit pour les deux conteneurs dans le pod :

 requests:
   cpu: "1"
   memory: "1024Mi"
 limits:
   cpu: "1"
   memory: "1024Mi"
...
requests:
  cpu: "100m"
  memory: "200Mi"
limits:
  cpu: "100m"
  memory: "200Mi"

Mais ça n'a pas marché non plus :

Memory cgroup out of memory: Killed process 2662217 (main) total-vm:1800900kB, anon-rss:1042888kB, file-rss:10384kB, shmem-rss:0kB, UID:0 pgtables:2224kB oom_score_adj:-998

Et les mesures de la mémoire montrent toujours une utilisation à un chiffre de Mo.

Mise à jour 04/30

J'ai localisé la modification qui semblait causer ce problème en vérifiant minutieusement mes derniers commits un par un.

Dans le commit incriminé, j'avais quelques lignes comme

type Pic struct {
        image.Image
        Proto *pb.Image
}
...

pic.Image = picture.Resize(pic, sz.Height, sz.Width)
...

picture.Resize appelle éventuellement resize.Resize . Je l'ai changé en :

type Pic struct {
        Img   image.Image
        Proto *pb.Image
 }
...
pic.Img = picture.Resize(pic.Img, sz.Height, sz.Width)

Cela résout mon problème immédiat et le conteneur fonctionne bien maintenant. Mais cela ne répond pas à ma question initiale :

  1. Pourquoi ces lignes ont-elles entraîné la fermeture du conteneur par GKE ?
  2. Et pourquoi les mesures de la mémoire de GKE ont-elles montré que tout allait bien ?

5voto

paul Points 1103

Je suppose que cela a été causé par Classe QoS Pod

Lorsque le système est surchargé, les classes de QoS déterminent quel pod est tué en premier afin que les ressources libérées puissent être attribuées à des pods plus prioritaires.

Dans votre cas, la QoS de votre pod serait de Burstable

Chaque processus en cours d'exécution a un score d'OutOfMemory (OOM). Le système sélectionne le processus à tuer en comparant le score OOM de tous les processus en cours d'exécution. Lorsque la mémoire doit être libérée, le processus ayant le score le plus élevé est tué. Pour plus de détails sur la façon dont le score est calculé, veuillez vous référer à Comment est calculé le score du kernel oom ? .

Quelle cosse sera tuée en premier si les deux sont dans la Burstable classe ?

En bref, le système tuera celui qui utilise plus de mémoire demandée que l'autre en pourcentage.

Pod A

used: 90m
requests: 100m
limits: 200m

Pod B

used: 150m
requests: 200m
limits: 400m

Pod A sera tué avant Pod B car il utilise 90 % de la mémoire demandée alors que Pod B n'utilise que 75 % de la mémoire demandée.

2voto

Ryan Siu Points 83

La spécification des ressources est ici la cause première de l'OOM.

Dans Kubernetes, la mémoire requise et la mémoire limitée sont définies différemment. La mémoire requise est la mémoire must-have . La mémoire limitée est la mémoire dans laquelle le conteneur peut être éclaté. Mais la mémoire limitée ne garantit pas que le conteneur puisse disposer de ces ressources.

Dans la plupart des systèmes de production, il n'est pas recommandé que la ressource limitée et la ressource requise diffèrent trop. Par exemple, dans votre cas,

requests:
  cpu: "150m"
  memory: "80Mi"
limits:
  cpu: "1"
  memory: "1024Mi"

Le conteneur ne peut avoir que 80Mi de mémoire garantie mais il peut en quelque sorte éclater en 1024Mi. Le nœud peut ne pas avoir assez de mémoire pour le conteneur et le conteneur lui-même passera en OOM.

Donc, si vous voulez améliorer cette situation, vous devez configurer la ressource de la manière suivante.

requests:
  cpu: "150m"
  memory: "1024Mi"
limits:
  cpu: "1"
  memory: "1024Mi"

Notez que le CPU est parfait car le processus ne sera pas tué si le CPU est faible. Mais l'OOM entraînera l'arrêt du processus.

Comme la réponse ci-dessus l'a mentionné, cela est lié à la qualité du service dans le pod. En général, pour la plupart des utilisateurs finaux, vous devriez toujours configurer votre conteneur en tant que classe garantie, c'est-à-dire demandé == limité. Vous pouvez avoir besoin d'une justification avant de le configurer en classe "bursted".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X