OOMkiller dans Docker est plus difficile que vous ne le pensez

Rebonjour. En prévision du début du cours, "Java Developer" a préparé une traduction d'un autre petit matériel.




Récemment, l'un des utilisateurs de Plumbr APM a eu un étrange problème avec le crash du conteneur docker avec le code 137. La configuration était simple avec plusieurs conteneurs imbriqués et machines virtuelles, semblable à une poupée imbriquée:

  • propre serveur de fer avec Ubuntu;
  • de nombreux conteneurs docker avec Ubuntu à l'intérieur;
  • Machine virtuelle Java à l'intérieur des conteneurs Docker.

Au cours de l'enquête sur le problème, nous avons trouvé la documentation Docker sur ce sujet. Il est devenu clair que la raison était soit un arrêt manuel du conteneur ou un manque de mémoire et l'intervention ultérieure de oomkiller (Out-Of-Memory Killer).

Nous regardons syslog et voyons qu'en effet, oomkiller s'appelait :

[138805.608851] java invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0
[138805.608887] [<ffffffff8116d20e>] oom_kill_process+0x24e/0x3b0
[138805.608916] Task in /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71 killed as a result of limit of /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71
[138805.608902] [<ffffffff8116da84>] pagefault_out_of_memory+0x14/0x90
[138805.608918] memory: usage 3140120kB, limit 3145728kB, failcnt 616038
[138805.608940] memory+swap: usage 6291456kB, limit 6291456kB, failcnt 2837
[138805.609043] Memory cgroup out of memory: Kill process 20611 (java) score 1068 or sacrifice child

Comme vous pouvez le voir, le processus java a atteint une limite de 3145728 Ko (environ 3 Go), ce qui a provoqué l'arrêt du conteneur. C'est assez étrange, car le docker lui-même a été lancé avec une limite de 4 Go (dans le fichier docker-compose).

Vous savez probablement que la JVM impose également ses limites sur l'utilisation de la mémoire. Bien que Docker lui-même ait été défini sur une limite de 4 Go, la JVM a été lancée avec l'option Xmx=3GB. Cela peut être encore plus déroutant, mais gardez à l'esprit que la JVM peut utiliser plus de mémoire que ce qui était spécifié dans -Xmx (voir l'article sur l' analyse de l'utilisation de la mémoire dans la JVM ).

Jusqu'à ce que nous comprenions ce qui se passe. Docker devrait autoriser l'utilisation de 4 Go. Alors pourquoi OOMkiller a-t-il fonctionné sur 3 Go? Une recherche plus approfondie d'informations nous a conduit au fait qu'il existe une autre limitation de mémoire dans le système d'exploitation, qui a été déployé sur le matériel.

Dites merci aux groupes de contrôle (groupes de contrôle). cgroups est un moteur de noyau Linux pour restreindre, contrôler et comptabiliser l'utilisation des ressources par les groupes de processus. Par rapport à d'autres solutions (en équipe niceou /etc/security/limits.conf), les cgroups offrent plus de flexibilité car ils peuvent travailler avec des (sous) ensembles de processus.

Dans notre situation, cgroups a limité l'utilisation de la mémoire à 3 Go (via memory.limit_in_bytes). Nous avons des progrès!

Une étude de la mémoire GC et des événements utilisant Plumbr a montré que la plupart du temps, la machine virtuelle Java utilisait environ 700 Mo. L'exception ne s'est produite qu'immédiatement avant l'arrêt, lors d'une forte augmentation de l'allocation de mémoire. Il a été suivi d'une longue pause de GC. Il semble donc que les événements suivants se soient produits:

  • Le code Java exécuté dans la JVM essaie d'obtenir beaucoup de mémoire.
  • La JVM, après avoir vérifié que la limite Xmx de 3 Go est encore loin, demande d'allouer de la mémoire au système d'exploitation.
  • Docker vérifie également et voit que sa limite de 4 Go n'a pas été atteinte non plus.
  • Le noyau du système d'exploitation vérifie la limite de groupe de 3 Go et tue le conteneur.
  • La machine virtuelle Java s'arrête avec le conteneur avant de pouvoir traiter le sien OutOfMemoryError.

Comprenant cela, nous avons configuré toutes les limitations de 2,5 Go pour Docker et 1,5 pour Java. Après cela, la JVM pourrait gérer OutOfMemoryErroret lever une exception OutOfMemoryError. Cela a permis à Plumbr de faire sa magie - d'obtenir un instantané de la mémoire avec les vidages de pile correspondants et de montrer qu'il y avait une requête dans la base de données, qui dans une certaine situation a essayé de charger presque toute la base de données.

résultats


Même dans une situation aussi simple, il y avait trois limitations de mémoire:

  • JVM via paramètre -Xmx
  • Docker à travers les options du fichier docker-compose
  • Système d'exploitation via paramètre memory.limit_in_bytes cgroups

Ainsi, lorsque vous rencontrez un tueur OOM, vous devez faire attention à toutes les limitations de mémoire impliquées.

Une autre conclusion pour les développeurs de dockers. Il semble que cela n'a aucun sens de permettre à de telles «poupées imbriquées» de s'exécuter dans lesquelles la limite de mémoire du conteneur imbriqué est supérieure à la limite des groupes de contrôle. Une simple vérification de cela lors du démarrage du conteneur avec un avertissement approprié peut économiser des centaines d'heures de débogage pour vos utilisateurs.

C'est tout. Nous vous attendons sur le parcours .

All Articles