ProHoster > Blog > administration > 6 bogues système amusants dans le fonctionnement de Kubernetes [et leur solution]
6 bogues système amusants dans le fonctionnement de Kubernetes [et leur solution]
Au fil des années d'utilisation de Kubernetes en production, nous avons accumulé de nombreuses histoires intéressantes sur la façon dont des bugs dans divers composants du système ont entraîné des conséquences désagréables et/ou incompréhensibles affectant le fonctionnement des conteneurs et des pods. Dans cet article, nous avons sélectionné quelques-uns des plus courants ou des plus intéressants. Même si l’on n’a jamais la chance de rencontrer de telles situations, lire des romans policiers aussi courts – surtout « de première main » – est toujours intéressant, n’est-ce pas ?
Histoire 1. Supercronic et Docker suspendus
Sur l'un des clusters, nous recevions périodiquement un Docker gelé, ce qui interférait avec le fonctionnement normal du cluster. Dans le même temps, les éléments suivants ont été observés dans les journaux Docker :
level=error msg="containerd: start init process" error="exit status 2: "runtime/cgo: pthread_create failed: No space left on device
SIGABRT: abort
PC=0x7f31b811a428 m=0
goroutine 0 [idle]:
goroutine 1 [running]:
runtime.systemstack_switch() /usr/local/go/src/runtime/asm_amd64.s:252 fp=0xc420026768 sp=0xc420026760
runtime.main() /usr/local/go/src/runtime/proc.go:127 +0x6c fp=0xc4200267c0 sp=0xc420026768
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4200267c8 sp=0xc4200267c0
goroutine 17 [syscall, locked to thread]:
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1
…
Ce qui nous intéresse le plus dans cette erreur est le message : pthread_create failed: No space left on device. Étude rapide documentation a expliqué que Docker ne pouvait pas lancer un processus, c'est pourquoi il se bloquait périodiquement.
En monitoring, l’image suivante correspond à ce qui se passe :
Une situation similaire est observée sur d’autres nœuds :
Il s'est avéré que ce comportement est une conséquence du fait que le pod fonctionne avec supercronique (un utilitaire Go que nous utilisons pour exécuter des tâches cron dans les pods) :
Le problème est le suivant : lorsqu'une tâche est exécutée en supercronique, le processus engendré par celle-ci ne peut pas se terminer correctement, se transformer en zombi.
Noter: Pour être plus précis, les processus sont générés par les tâches cron, mais supercronic n'est pas un système d'initialisation et ne peut pas « adopter » les processus générés par ses enfants. Lorsque les signaux SIGHUP ou SIGTERM sont émis, ils ne sont pas transmis aux processus enfants, ce qui fait que les processus enfants ne se terminent pas et restent dans le statut zombie. Vous pouvez en savoir plus sur tout cela, par exemple, dans un tel article.
Il existe plusieurs façons de résoudre les problèmes :
Comme solution de contournement temporaire : augmentez le nombre de PID dans le système à un moment donné :
/proc/sys/kernel/pid_max (since Linux 2.5.34)
This file specifies the value at which PIDs wrap around (i.e., the value in this file is one greater than the maximum PID). PIDs greater than this value are not allo‐
cated; thus, the value in this file also acts as a system-wide limit on the total number of processes and threads. The default value for this file, 32768, results in the
same range of PIDs as on earlier kernels
Ou lancez des tâches dans supercronic pas directement, mais en utilisant le même tini, qui est capable de terminer correctement les processus et de ne pas générer de zombies.
Histoire 2. « Zombies » lors de la suppression d'un groupe de contrôle
Kubelet a commencé à consommer beaucoup de CPU :
Personne n'aimera ça, alors nous nous sommes armés Perf et j'ai commencé à régler le problème. Les résultats de l'enquête ont été les suivants :
Kubelet passe plus d'un tiers de son temps CPU à extraire les données mémoire de tous les groupes de contrôle :
Dans la liste de diffusion des développeurs du noyau, vous pouvez trouver discussion du problème. Bref, le problème se résume à ceci : divers fichiers tmpfs et autres éléments similaires ne sont pas complètement supprimés du système lors de la suppression d'un groupe de contrôle, ce qu'on appelle memcg zombie. Tôt ou tard, elles seront supprimées du cache des pages, mais il y a beaucoup de mémoire sur le serveur et le noyau ne voit pas l'intérêt de perdre du temps à les supprimer. C'est pourquoi ils continuent de s'accumuler. Pourquoi est-ce que cela se produit ? Il s'agit d'un serveur avec des tâches cron qui crée constamment de nouvelles tâches, et avec elles de nouveaux pods. Ainsi, de nouveaux groupes de contrôle sont créés pour les conteneurs qu'ils contiennent, qui sont rapidement supprimés.
Pourquoi cAdvisor dans Kubelet perd-il autant de temps ? C'est facile à voir avec l'exécution la plus simple time cat /sys/fs/cgroup/memory/memory.stat. Si sur une machine saine, l'opération prend 0,01 seconde, alors sur le cron02 problématique, cela prend 1,2 seconde. Le fait est que cAdvisor, qui lit très lentement les données de sysfs, essaie de prendre en compte la mémoire utilisée dans les groupes de contrôle zombies.
Pour supprimer de force les zombies, nous avons essayé de vider les caches comme recommandé dans LKML : sync; echo 3 > /proc/sys/vm/drop_caches, - mais le noyau s'est avéré plus compliqué et a fait planter la voiture.
Ce qu'il faut faire? Le problème est en cours de résolution (commettre, et pour une description, voir message de libération) mettant à jour le noyau Linux vers la version 4.16.
Historique 3. Systemd et son montage
Encore une fois, le kubelet consomme trop de ressources sur certains nœuds, mais cette fois il consomme trop de mémoire :
Il s'est avéré qu'il y avait un problème dans systemd utilisé dans Ubuntu 16.04, et cela se produit lors de la gestion des montages créés pour la connexion. subPath à partir de ConfigMap ou de secrets. Une fois que le pod a terminé son travail le service systemd et son service mount restent dans le système. Au fil du temps, un grand nombre d'entre eux s'accumulent. Il y a même des problèmes sur ce sujet :
... dont le dernier fait référence au PR dans systemd : #7811 (problème dans systemd - #7798).
Le problème n'existe plus dans Ubuntu 18.04, mais si vous souhaitez continuer à utiliser Ubuntu 16.04, notre solution de contournement sur ce sujet peut vous être utile.
#!/bin/bash
# we will work only on xenial
hostrelease="/etc/lsb-release-host"
test -f ${hostrelease} && grep xenial ${hostrelease} > /dev/null || exit 0
# sleeping max 30 minutes to dispense load on kube-nodes
sleep $((RANDOM % 1800))
stoppedCount=0
# counting actual subpath units in systemd
countBefore=$(systemctl list-units | grep subpath | grep "run-" | wc -l)
# let's go check each unit
for unit in $(systemctl list-units | grep subpath | grep "run-" | awk '{print $1}'); do
# finding description file for unit (to find out docker container, who born this unit)
DropFile=$(systemctl status ${unit} | grep Drop | awk -F': ' '{print $2}')
# reading uuid for docker container from description file
DockerContainerId=$(cat ${DropFile}/50-Description.conf | awk '{print $5}' | cut -d/ -f6)
# checking container status (running or not)
checkFlag=$(docker ps | grep -c ${DockerContainerId})
# if container not running, we will stop unit
if [[ ${checkFlag} -eq 0 ]]; then
echo "Stopping unit ${unit}"
# stoping unit in action
systemctl stop $unit
# just counter for logs
((stoppedCount++))
# logging current progress
echo "Stopped ${stoppedCount} systemd units out of ${countBefore}"
fi
done
... et il s'exécute toutes les 5 minutes en utilisant le supercronic mentionné précédemment. Son Dockerfile ressemble à ceci :
Histoire 4. Compétitivité lors de la planification des pods
Il a été remarqué que : si nous avons un pod placé sur un nœud et que son image est pompée pendant très longtemps, alors un autre pod qui « frappe » le même nœud va simplement ne commence pas à tirer l'image du nouveau pod. Au lieu de cela, il attend que l'image du pod précédent soit extraite. De ce fait, un pod qui était déjà programmé et dont l'image aurait pu être téléchargée en une minute seulement se retrouvera au statut de containerCreating.
Les événements ressembleront à ceci :
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"
Il s'avère que une seule image provenant d'un registre lent peut bloquer le déploiement par nœud.
Malheureusement, il n'y a pas beaucoup de solutions pour sortir de la situation :
Essayez d'utiliser votre Docker Registry directement dans le cluster ou directement avec le cluster (par exemple, GitLab Registry, Nexus, etc.) ;
Histoire 5. Les nœuds se bloquent en raison d'un manque de mémoire
Lors du fonctionnement de diverses applications, nous avons également rencontré une situation où un nœud cesse complètement d'être accessible : SSH ne répond pas, tous les démons de surveillance tombent, et puis il n'y a rien (ou presque rien) d'anormal dans les logs.
Je vais vous le dire en images en utilisant l'exemple d'un nœud sur lequel MongoDB fonctionnait.
Voilà à quoi ressemble le sommet à les accidents:
Et comme ça - après les accidents:
Lors de la surveillance, il y a également un saut brusque, auquel le nœud cesse d'être disponible :
Ainsi, d'après les captures d'écran, il ressort clairement que :
La RAM de la machine est proche de la fin ;
Il y a une forte augmentation de la consommation de RAM, après quoi l'accès à l'ensemble de la machine est brusquement désactivé ;
Une tâche volumineuse arrive sur Mongo, ce qui oblige le processus SGBD à utiliser plus de mémoire et à lire activement à partir du disque.
Il s'avère que si Linux manque de mémoire libre (la pression de la mémoire s'installe) et qu'il n'y a pas d'échange, alors à Lorsque le tueur de MOO arrive, un équilibre peut survenir entre le lancement de pages dans le cache de pages et leur réécriture sur le disque. Ceci est fait par kswapd, qui libère courageusement autant de pages mémoire que possible pour une distribution ultérieure.
Malheureusement, avec une charge d'E/S importante associée à une petite quantité de mémoire libre, kswapd devient le goulot d'étranglement de tout le système, parce qu'ils y sont liés tous allocations (défauts de page) des pages mémoire dans le système. Cela peut durer très longtemps si les processus ne veulent plus utiliser de mémoire, mais sont fixés au bord même de l'abîme du tueur de MOO.
La question naturelle est : pourquoi le tueur OOM arrive-t-il si tard ? Dans son itération actuelle, le tueur de MOO est extrêmement stupide : il ne tuera le processus que lorsque la tentative d'allocation d'une page mémoire échoue, c'est-à-dire si le défaut de page échoue. Cela ne se produit pas avant assez longtemps, car kswapd libère courageusement des pages de mémoire, en vidant le cache des pages (l'intégralité des E/S disque du système, en fait) sur le disque. Plus en détail, avec une description des étapes nécessaires pour éliminer de tels problèmes dans le noyau, vous pouvez lire ici.
Histoire 6. Les pods restent bloqués dans l'état En attente
Dans certains clusters, dans lesquels de très nombreux pods fonctionnent, nous avons commencé à remarquer que la plupart d'entre eux « pendent » très longtemps dans l'état Pending, bien que les conteneurs Docker eux-mêmes soient déjà exécutés sur les nœuds et puissent être utilisés manuellement.
Avec cela dans describe il n'y a rien de mal:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned sphinx-0 to ss-dev-kub07
Normal SuccessfulAttachVolume 1m attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "sphinx-config"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "default-token-fzcsf"
Normal SuccessfulMountVolume 49s (x2 over 51s) kubelet, ss-dev-kub07 MountVolume.SetUp succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal Pulled 43s kubelet, ss-dev-kub07 Container image "registry.example.com/infra/sphinx-exporter/sphinx-indexer:v1" already present on machine
Normal Created 43s kubelet, ss-dev-kub07 Created container
Normal Started 43s kubelet, ss-dev-kub07 Started container
Normal Pulled 43s kubelet, ss-dev-kub07 Container image "registry.example.com/infra/sphinx/sphinx:v1" already present on machine
Normal Created 42s kubelet, ss-dev-kub07 Created container
Normal Started 42s kubelet, ss-dev-kub07 Started container
Après quelques recherches, nous avons supposé que le kubelet n'avait tout simplement pas le temps d'envoyer toutes les informations sur l'état des pods et les tests d'activité/préparation au serveur API.
Et après avoir étudié l'aide, nous avons trouvé les paramètres suivants :
--kube-api-qps - QPS to use while talking with kubernetes apiserver (default 5)
--kube-api-burst - Burst to use while talking with kubernetes apiserver (default 10)
--event-qps - If > 0, limit event creations per second to this value. If 0, unlimited. (default 5)
--event-burst - Maximum size of a bursty event records, temporarily allows event records to burst to this number, while still not exceeding event-qps. Only used if --event-qps > 0 (default 10)
--registry-qps - If > 0, limit registry pull QPS to this value.
--registry-burst - Maximum size of bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry-qps. Only used if --registry-qps > 0 (default 10)
Comme vous pouvez le voir les valeurs par défaut sont assez petites, et à 90% ils couvrent tous les besoins... Cependant, dans notre cas, cela n'était pas suffisant. Par conséquent, nous définissons les valeurs suivantes :
... et redémarré les kubelets, après quoi nous avons vu l'image suivante dans les graphiques des appels au serveur API :
... et oui, tout s'est mis à voler !
PS
Pour leur aide dans la collecte des bugs et la préparation de cet article, j'exprime ma profonde gratitude aux nombreux ingénieurs de notre entreprise, et notamment à mon collègue de notre équipe R&D Andrey Klimentyev (zuzzas).