![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
Im Laufe der Jahre, in denen wir Kubernetes in der Produktion eingesetzt haben, haben wir viele interessante Geschichten gesammelt, wie Bugs in verschiedenen Systemkomponenten zu unangenehmen und/oder unverstĂ€ndlichen Konsequenzen fĂŒhrten, die die Funktionsweise von Containern und Pods beeinflussten. In diesem Artikel haben wir eine Sammlung einiger der hĂ€ufigsten oder interessantesten Geschichten zusammengestellt. Selbst wenn Sie nie das GlĂŒck haben sollten, mit solchen Situationen konfrontiert zu werden, ist es immer interessant, ĂŒber solche kurzen Detektive zu lesen â besonders "aus erster Hand", nicht wahr? ..
Geschichte 1. Supercronic und hÀngender Docker
In einem unserer Cluster hatten wir gelegentlich "hÀngenden" Docker, was die normale Funktion des Clusters beeintrÀchtigte. In den Docker-Logs konnten wir Folgendes beobachten:
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
⊠An dieser Fehlermeldung interessiert uns am meisten die Mitteilung: pthread_create failed: No space left on device. Eine flĂŒchtige Untersuchung zeigte, dass Docker den Prozess nicht abzweigen kann, weshalb er gelegentlich "hĂ€ngengeblieben" ist.
In der Ăberwachung ergibt sich folgendes Bild:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
Eine Àhnliche Situation zeigt sich auch auf anderen Knoten:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
Auf diesen Knoten sehen wir:
root@kube-node-1 ~ # ps auxfww | grep curl -c
19782
root@kube-node-1 ~ # ps auxfww | grep curl | head
root 16688 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 17398 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 16852 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 9473 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 4664 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 30571 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 24113 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 16475 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 7176 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]
root 1090 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl]Es stellte sich heraus, dass dieses Verhalten die Folge der Arbeit des Pods mit (einem Go-Tool, das wir zum AusfĂŒhren von Cron-Jobs in Pods verwenden) ist.
_ docker-containerd-shim 833b60bb9ff4c669bb413b898a5fd142a57a21695e5dc42684235df907825567 /var/run/docker/libcontainerd/833b60bb9ff4c669bb413b898a5fd142a57a21695e5dc42684235df907825567 docker-runc
| _ /usr/local/bin/supercronic -json /crontabs/cron
| _ /usr/bin/newrelic-daemon --agent --pidfile /var/run/newrelic-daemon.pid --logfile /dev/stderr --port /run/newrelic.sock --tls --define utilization.detect_aws=true --define utilization.detect_azure=true --define utilization.detect_gcp=true --define utilization.detect_pcf=true --define utilization.detect_docker=true
| | _ /usr/bin/newrelic-daemon --agent --pidfile /var/run/newrelic-daemon.pid --logfile /dev/stderr --port /run/newrelic.sock --tls --define utilization.detect_aws=true --define utilization.detect_azure=true --define utilization.detect_gcp=true --define utilization.detect_pcf=true --define utilization.detect_docker=true -no-pidfile
| _ [newrelic-daemon]
| _ [curl]
| _ [curl]
| _ [curl]
| _ [curl]
âŠDas Problem ist folgendes: Wenn die Aufgabe in supercronic gestartet wird, kann der von ihm erzeugte Prozess nicht korrekt beendet werden, wodurch er sich in .
Hinweis: Genauer gesagt werden Prozesse von cron-Aufgaben erzeugt, aber supercronic ist kein Init-System und kann die Prozesse, die seine Kinder erzeugt haben, nicht âadoptierenâ. Bei Auftreten von SIGHUP- oder SIGTERM-Signalen werden diese nicht an die erzeugten Prozesse weitergegeben, sodass die untergeordneten Prozesse nicht beendet werden und im Zombie-Status verbleiben. Mehr dazu kann man beispielsweise in .
Es gibt ein paar AnsÀtze zur Problemlösung:
- Als temporĂ€ren Workaround â die Anzahl der PIDs im System zu einem bestimmten Zeitpunkt zu erhöhen:
/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 - Oder die Aufgaben in supercronic nicht direkt, sondern mit Hilfe von , das in der Lage ist, Prozesse korrekt zu beenden und keine Zombies zu erzeugen.
Geschichte 2. âZombiesâ beim Löschen der cgroup
Kubelet begann, viel CPU zu verbrauchen:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
Das wird niemandem gefallen, also haben wir uns gerĂŒstet und uns mit dem Problem beschĂ€ftigt. Die Ergebnisse der Untersuchung waren folgende:
- Kubelet verbraucht mehr als ein Drittel der CPU-Zeit fĂŒr das Abrufen von Speicherdaten aus allen cgroups:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20600%20241%22%3E%3C%2Fsvg%3E)
- In der Mailingliste der Kernel-Entwickler findet man . Kurz gesagt, der Kern des Problems liegt darin, dass verschiedene tmpfs-Dateien und andere Ă€hnliche Dinge nicht vollstĂ€ndig aus dem System entfernt werden bei der Löschung der cgroup â es bleiben so genannte Zombies.FrĂŒher oder spĂ€ter werden sie aus dem Page-Cache gelöscht, doch der Server hat viel Speicher, und das Kern sieht keinen Sinn darin, Zeit mit ihrer Löschung zu verschwenden. Deshalb sammeln sie sich weiter an. Warum passiert das ĂŒberhaupt? Es handelt sich um einen Server mit Cron-Jobs, der stĂ€ndig neue Jobs erstellt, und damit auch neue Pods. So werden fĂŒr die Container neue cgroups erstellt, die bald wieder gelöscht werden.
- Warum benötigt cAdvisor in kubelet so viel Zeit? Das lÀsst sich leicht mit dem einfachsten Befehl erkennen:
time cat /sys/fs/cgroup/memory/memory.stat. Wenn auf einem gesunden System die Operation 0,01 Sekunden dauert, sind es auf dem problematischen cron02 bereits 1,2 Sekunden. Das Problem ist, dass cAdvisor, das sehr langsam Daten aus sysfs liest, versucht, den verwendeten Speicher und auch in Zombie-cgroups zu berĂŒcksichtigen. - Um die Zombies gewaltsam zu entfernen, haben wir versucht, den Cache zu leeren, wie es in LKML empfohlen wird:
sync; echo 3 > /proc/sys/vm/drop_caches, â aber das Kernel war komplizierter und hat die Maschine zum Absturz gebracht.
Was ist zu tun? Das Problem wird behoben (, die Beschreibung finden Sie in ) durch ein Update des Linux-Kernels auf Version 4.16.
Geschichte 3. Systemd und sein Mount
Wieder verbraucht kubelet zu viele Ressourcen auf einigen Knoten, diesmal â bereits Speicher:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
Es stellte sich heraus, dass es ein Problem mit systemd in Ubuntu 16.04 gibt, das auftritt, wenn es um die Verwaltung von Mounts geht, die zur Anbindung subPath aus ConfigMaps oder Secrets erstellt werden. Nach dem Abschluss des Pods bleibt der Systemd-Dienst und sein Hilfs-Mount im System. Mit der Zeit sammeln sich davon enorme Mengen an. Zu diesem Thema gibt es sogar Issues:
- ;
- .
⊠auf die im letzten verwiesen wird, auf PR in systemd: (Issue in systemd â ).
Das Problem besteht nicht mehr in Ubuntu 18.04, aber wenn Sie weiterhin Ubuntu 16.04 verwenden möchten, könnte unser Workaround zu diesem Thema hilfreich sein.
Also haben wir den folgenden DaemonSet erstellt:
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
labels:
app: systemd-slices-cleaner
name: systemd-slices-cleaner
namespace: kube-system
spec:
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: systemd-slices-cleaner
template:
metadata:
labels:
app: systemd-slices-cleaner
spec:
containers:
- command:
- /usr/local/bin/supercronic
- -json
- /app/crontab
Image: private-registry.org/systemd-slices-cleaner/systemd-slices-cleaner:v0.1.0
imagePullPolicy: Always
name: systemd-slices-cleaner
resources: {}
securityContext:
privileged: true
volumeMounts:
- name: systemd
mountPath: /run/systemd/private
- name: docker
mountPath: /run/docker.sock
- name: systemd-etc
mountPath: /etc/systemd
- name: systemd-run
mountPath: /run/systemd/system/
- name: lsb-release
mountPath: /etc/lsb-release-host
imagePullSecrets:
- name: antiopa-registry
priorityClassName: cluster-low
tolerations:
- operator: Exists
volumes:
- name: systemd
hostPath:
path: /run/systemd/private
- name: docker
hostPath:
path: /run/docker.sock
- name: systemd-etc
hostPath:
path: /etc/systemd
- name: systemd-run
hostPath:
path: /run/systemd/system/
- name: lsb-release
hostPath:
path: /etc/lsb-release⊠und darin wird folgendes Skript verwendet:
#!/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⊠und es wird alle 5 Minuten mit dem bereits erwĂ€hnten supercronic gestartet. Seine Dockerfile sieht folgendermaĂen aus:
FROM ubuntu:16.04
COPY rootfs /
WORKDIR /app
RUN apt-get update &&
apt-get upgrade -y &&
apt-get install -y gnupg curl apt-transport-https software-properties-common wget
RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" &&
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - &&
apt-get update &&
apt-get install -y docker-ce=17.03.0*
RUN wget https://github.com/aptible/supercronic/releases/download/v0.1.6/supercronic-linux-amd64 -O
/usr/local/bin/supercronic && chmod +x /usr/local/bin/supercronic
ENTRYPOINT ["/bin/bash", "-c", " /usr/local/bin/supercronic -json /app/crontab"]Geschichte 4. Konkurrenz bei der Pod-Planung
Es wurde festgestellt, dass: Wenn wir einen Pod auf einen Knoten platzieren und dessen Image sehr lange heruntergeladen wird, dann wird ein anderer Pod, der "auf" denselben Knoten kommt, einfach nicht beginnen, das Image des neuen Pods herunterzuladen.Stattdessen wartet er, bis das Image des vorherigen Pods heruntergeladen ist. Infolgedessen wird der Pod, der bereits geplant war und dessen Image in nur einer Minute heruntergeladen werden könnte, fĂŒr lĂ€ngere Zeit im Status containerCreating.
Die Ereignisse werden ungefÀhr folgendes zeigen:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"Es stellt sich heraus, dass ein einziges Image aus einem langsamen Register kann die Bereitstellung auf den Knoten blockieren.
Leider gibt es nicht viele Lösungen fĂŒr dieses Problem:
- Versuchen Sie, Ihr Docker-Registry direkt im Cluster oder direkt mit dem Cluster zu verwenden (zum Beispiel GitLab-Registry, Nexus usw.);
- Nutzen Sie Utilities wie .
Geschichte 5. Knoten hÀngen bei Speichermangel
Im Laufe der Nutzung verschiedener Anwendungen hatten wir auch die Situation, dass ein Knoten vollstĂ€ndig unzugĂ€nglich wurde: keine SSH-Antwort, alle Ăberwachungsdienste fallen aus, und in den Logs gibt es dann nichts (oder fast nichts) Anomalisches.
Ich werde anhand von Bildern an einem Beispiel eines Knotens erzÀhlen, auf dem MongoDB funktionierte.
So sieht atop aus bis UnfÀlle:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
Und so â nach UnfĂ€lle:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
In der Ăberwachung zeigt sich auch ein plötzlicher Anstieg, bei dem der Knoten unzugĂ€nglich wird:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
Somit zeigt sich aus den Screenshots, dass:
- Der Arbeitsspeicher der Maschine geht zur Neige;
- Es gibt einen plötzlichen Anstieg des Arbeitsspeicherverbrauchs, wonach der Zugriff auf die gesamte Maschine abrupt abgeschaltet wird;
- An Mongo wird eine groĂe Aufgabe gestellt, die den DBMS-Prozess dazu zwingt, mehr Speicher zu verwenden und aktiv von der Festplatte zu lesen.
Es stellt sich heraus, dass, wenn unter Linux der freie Speicher ausgeht (memory pressure eintritt) und kein Swap vorhanden ist, bis das Eintreffen des OOM-Killers zu einem Gleichgewicht zwischen dem Einkapseln von Seiten in den Page Cache und dem ZurĂŒckschreiben auf die Festplatte fĂŒhren kann. Damit beschĂ€ftigt sich kswapd, das mutig so viele Seiten wie möglich fĂŒr die spĂ€tere Zuteilung freigibt.
Leider wird bei hoher Eingabe/Ausgabe-Last zusammen mit wenig freiem Speicher kswapd zum Flaschenhals des gesamten Systems, weil sich daran Ressourcen) abgeschlossen ist, lösen wir Seitenfehler (page faults) im System festmachen. Das kann sehr lange dauern, wenn Prozesse nicht bereit sind, mehr Speicher zu verwenden, und am Rand des OOM-Killer-Abgrundes verharren.
Die Frage ist berechtigt: Warum kommt der OOM-Killer so spĂ€t? In seiner aktuellen Iteration ist der OOM-Killer extrem dumm: Er beendet den Prozess erst, wenn der Versuch, eine Seite Speicher zuzuweisen, fehlschlĂ€gt, d.h. wenn der Seitenfehler mit einem Fehler endet. Das geschieht lange nicht, weil kswapd mutig Speicherseiten freigibt, indem es den Page Cache (sie ist im Grunde alle Disk I/O im System) wieder auf die Festplatte zurĂŒckschreibt. Detaillierte Informationen mit einer Beschreibung der Schritte, die erforderlich sind, um solche Probleme im Kernel zu beheben, finden Sie .
Dieses Verhalten mit dem Kernel Linux 4.6+.
Geschichte 6. Pods hÀngen im Status Pending
In einigen Clustern, in denen wirklich viele Pods betrieben werden, haben wir begonnen zu bemerken, dass ein GroĂteil von ihnen sehr lange im Status Pending, obwohl die Docker-Container bereits auf den Knoten gestartet sind und man manuell mit ihnen arbeiten kann.
Dabei ist in describe nichts Schlechtes:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Erfolgreich sphinx-0 auf ss-dev-kub07 zugewiesen
Normal SuccessfulAttachVolume 1m attachdetach-controller AttachVolume.Attach war erfolgreich fĂŒr Volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp war erfolgreich fĂŒr Volume "sphinx-config"
Normal SuccessfulMountVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp war erfolgreich fĂŒr Volume "default-token-fzcsf"
Normal SuccessfulMountVolume 49s (x2 ĂŒber 51s) kubelet, ss-dev-kub07 MountVolume.SetUp war erfolgreich fĂŒr Volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal Pulled 43s kubelet, ss-dev-kub07 Container-Image "registry.example.com/infrа/sphinx-exporter/sphinx-indexer:v1" bereits auf der Maschine vorhanden
Normal Created 43s kubelet, ss-dev-kub07 Container erstellt
Normal Started 43s kubelet, ss-dev-kub07 Container gestartet
Normal Pulled 43s kubelet, ss-dev-kub07 Container-Image "registry.example.com/infrа/sphinx/sphinx:v1" bereits auf der Maschine vorhanden
Normal Created 42s kubelet, ss-dev-kub07 Container erstellt
Normal Started 42s kubelet, ss-dev-kub07 Container gestartetNachdem wir recherchiert haben, haben wir die Vermutung geĂ€uĂert, dass kubelet einfach nicht schnell genug ist, um dem API-Server alle Informationen ĂŒber den Status der Pods, liveness/readiness-Probe, zu senden.
Und nachdem wir die Hilfe untersucht haben, fanden wir die folgenden Parameter:
--kube-api-qps - QPS, die beim Sprechen mit dem Kubernetes-Api-Server verwendet werden soll (Standard 5)
--kube-api-burst - Burst, den man beim Sprechen mit dem Kubernetes-Api-Server verwenden kann (Standard 10)
--event-qps - Wenn > 0, die Erstellung von Ereignissen pro Sekunde auf diesen Wert begrenzen. Wenn 0, unbegrenzt. (Standard 5)
--event-burst - Maximale GröĂe von bursty Ereignisaufzeichnungen, ermöglicht vorĂŒbergehend, dass Ereignisaufzeichnungen auf diese Zahl ansteigen, wĂ€hrend sie weiterhin event-qps nicht ĂŒberschreiten. Nur verwendet, wenn --event-qps > 0 (Standard 10)
--registry-qps - Wenn > 0, die QPS beim Registrieren auf diesen Wert begrenzen.
--registry-burst - Maximale GröĂe der burstigen Downloads, ermöglicht vorĂŒbergehend, dass Downloads auf diese Zahl ansteigen, wĂ€hrend sie weiterhin registry-qps nicht ĂŒberschreiten. Nur verwendet, wenn --registry-qps > 0 (Standard 10)Wie man sieht, sind die Standardwerte recht niedrig, und in 90 % der FĂ€lle decken sie alle BedĂŒrfnisse ab⊠Allerdings war das in unserem Fall nicht genug. Daher haben wir die folgenden Werte festgelegt:
--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40⊠und haben die Kubelets neu gestartet, nach dem wir folgende Grafik der API-Server-Anfragen gesehen haben:
![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
⊠und ja, alles begann zu fliegen!
P.S.
Ich möchte den zahlreichen Ingenieuren unseres Unternehmens, insbesondere meinem Kollegen aus unserem R&D-Team, Andrej Klimentjew, fĂŒr die UnterstĂŒtzung bei der Fehlersammlung und der Vorbereitung des Artikels herzlich danken.).
P.P.S.
Lesen Sie auch in unserem Blog:
- «».
- Kubernetes-Tipps & Tricks:
- «»;
- «»;
- «»;
- «».
Quelle: habr.com

![6 interessante systematische Fehler bei der Nutzung von Kubernetes [und deren Lösung]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)