![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
Im Laufe der Jahre, in denen wir Kubernetes in der Produktion eingesetzt haben, haben sich zahlreiche interessante Geschichten angesammelt, wie Bugs in verschiedenen Systemkomponenten zu unangenehmen und/oder unverständlichen Konsequenzen führten, die die Funktionalität von Containern und Pods beeinflussten. In diesem Artikel haben wir einige der häufigsten oder interessantesten Beispiele zusammengestellt. Selbst wenn Sie nie in solche Situationen geraten sollten, ist es immer spannend, über solche kurzen Detektive zu lesen - vor allem ‚aus erster Hand‘ - oder nicht?..
Geschichte 1. Supercronic und ein hängender Docker
In einem unserer Cluster hatten wir gelegentlich einen ‚hängenden‘ Docker, was die normale Funktionalität des Clusters beeinträchtigte. In den Docker-Protokollen wurde Folgendes beobachtet:
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
… Von diesem Fehler interessiert uns besonders die Nachricht: pthread_create failed: No space left on device. Eine flüchtige Untersuchung es wurde erklärt, dass Docker nicht in der Lage ist, Prozesse zu forken, was gelegentlich zu einem "Einfrieren" führte.
Die Überwachung des Geschehens zeigt folgendes Bild:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
Eine ähnliche Situation wird auch bei anderen Knoten beobachtet:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
An 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 06Feb 0:00 | _ [curl]
root 17398 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 16852 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 9473 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 4664 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 30571 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 24113 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 16475 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 7176 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]
root 1090 0.0 0.0 0 0 ? Z 06Feb 0:00 | _ [curl]Es stellte sich heraus, dass dieses Verhalten eine Folge der Arbeit des Pods mit (ein Go-Tool, das wir zum Ausführen von Cron-Jobs in Pods verwenden):
_ 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 ein Job in supercronic gestartet wird, kann der von ihm erzeugte Prozess nicht korrekt beendet werden, wodurch er in .
Hinweisumwandelt: Genauer gesagt, Prozesse werden durch Cron-Jobs erzeugt, jedoch ist supercronic kein Init-System und kann die Prozesse, die seine Kinder erzeugt haben, nicht ‚adoptieren‘. Wenn SIGHUP- oder SIGTERM-Signale auftreten, werden diese nicht an die erzeugten Prozesse weitergeleitet, sodass die untergeordneten Prozesse nicht beendet werden und im Zombie-Status verbleiben. Mehr dazu kann man beispielsweise in .
Es gibt einige Möglichkeiten zur Problemlösung:
- Ein vorübergehender Workaround besteht darin, 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 Sie können die Aufgaben in supercronic nicht direkt, sondern mithilfe von , was in der Lage ist, Prozesse ordnungsgemäß zu beenden und keine Zombies zu erzeugen.
Geschichte 2. „Zombies“ beim Löschen von cgroups
Kubelet begann, eine große Menge an CPU zu verbrauchen:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
Das gefällt niemandem, also haben wir uns mit auseinandergesetzt und das Problem analysiert. Die Ergebnisse unserer Untersuchung sind wie folgt:
- Kubelet benötigt mehr als ein Drittel der Prozessorezeit, um Daten zum Speicher aus allen cgroups abzurufen:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20241'%3E%3C/svg%3E)
- In den Mitteilungen der Kernel-Entwickler kann man eine Kurz gesagt, es geht darum, dass verschiedene tmpfs-Dateien und ähnliche Objekte nicht vollständig aus dem System entfernt werden beim Löschen von cgroups – die sogenannten Zombies bleiben bestehen.. Sooner or later, they will be removed from the page cache, but the server has plenty of memory, and the kernel sees no reason to spend time removing them. Therefore, they continue to accumulate. Why does this happen at all? This is a server with cron jobs that constantly creates new jobs, along with new pods. As a result, new cgroups are created for the containers, which are soon deleted.
- Why does cAdvisor spend so much time in kubelet? This is easily seen by simply executing
time cat /sys/fs/cgroup/memory/memory.stat. If on a healthy machine the operation takes 0.01 seconds, then on the problematic cron02 it takes 1.2 seconds. The issue is that cAdvisor, which reads data from sysfs very slowly, tries to account for the memory used in zombie cgroups. - To forcibly remove the zombies, we tried to clean the caches as recommended in LKML:
sync; echo 3 > /proc/sys/vm/drop_caches, — but the kernel turned out to be more complicated and hung the machine.
What to do? The problem can be fixed (, and the description can be found in ) by updating the Linux kernel to version 4.16.
Story 3. Systemd and its mount
Again, kubelet consumes too many resources on some nodes, but this time — memory:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
Es stellte sich heraus, dass es ein Problem mit systemd in Ubuntu 16.04 gibt, das beim Verwalten von Mounts auftritt, die für die Anbindung erstellt werden. subPath aus ConfigMaps oder Secrets. Nach dem Abschluss des Pods bleibt der systemd-Dienst und dessen Hilfs-Mount im System. Im Laufe der Zeit sammeln sich enorm viele an. Zu diesem Thema gibt es sogar Issues:
- ;
- .
… in letzterem wird auf einen PR in systemd verwiesen: (Issue in systemd — ).
Das Problem besteht nicht mehr in Ubuntu 18.04, aber wenn Sie weiterhin Ubuntu 16.04 nutzen möchten, könnte unser Workaround zu diesem Thema nützlich 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 es wird ein solcher 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 läuft alle 5 Minuten mit dem bereits zuvor erwähnten supercronic. Sein Dockerfile sieht so aus:
VON 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. Konkurrenzfähigkeit bei der Planung von Pods
Es wurde festgestellt, dass: wenn ein Pod auf einem Knoten bereitgestellt wird und sein Image sehr lange heruntergeladen wird, ein anderer Pod, der auf demselben Knoten gestartet wird, einfach nicht beginnt, das Image des neuen Pods herunterzuladen.Stattdessen wartet er, bis das Image des vorherigen Pods heruntergeladen wurde. Infolgedessen wird ein Pod, der bereits geplant war und dessen Image in nur einer Minute heruntergeladen werden könnte, für längere Zeit im Status containerCreating..
In den Ereignissen wird ungefähr Folgendes stehen:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal Bild "registry.example.com/infra/openvpn/openvpn:master" wird heruntergeladen.Es stellt sich heraus, dass ein einziges Image aus einem langsamen Registry den Deployment auf den Knoten blockieren kann.
Leider gibt es nicht viele Optionen:
- Versuchen Sie, Ihr Docker-Registry direkt im Cluster oder in direkter Verbindung zum Cluster zu verwenden (zum Beispiel GitLab Registry, Nexus usw.);
- Nutzen Sie solche Tools wie .
Geschichte 5. Knoten hängen bei Speichermangel
Im Verlauf der Nutzung verschiedener Anwendungen hatten wir auch Situationen, in denen ein Knoten vollständig nicht mehr verfügbar ist: SSH reagiert nicht, alle Monitoring-Dienste fallen aus, und in den Logs sind später nichts (oder fast nichts) Auffälliges zu finden.
Ich werde dies anhand von Bildern am Beispiel eines Knotens zeigen, auf dem MongoDB funktionierte.
So sieht atop aus bis zu Unfälle:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
Und so — nach Unfälle:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
Im Monitoring ist ebenfalls ein plötzlicher Anstieg zu beobachten, bei dem der Knoten nicht mehr verfügbar ist:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
Anhand der Screenshots ist zu erkennen, dass:
- Der Arbeitsspeicher der Maschine fast erschöpft ist;
- Ein plötzlicher Anstieg des Arbeitsspeicherverbrauchs auftritt, nach dem der Zugriff auf die gesamte Maschine abrupt abgeschaltet wird;
- Ein großes Task trifft Mongo, was den DBMS-Prozess zwingt, mehr Speicher zu verwenden und aktiv von der Festplatte zu lesen.
Es stellt sich heraus, dass, wenn der freie Speicher in Linux zur Neige geht (memory pressure eintritt) und kein Swap vorhanden ist, dann bis zu Bei Eintreffen des OOM-Killers kann ein Gleichgewicht zwischen dem Speichern von Seiten im Page-Cache und dem Zurückschreiben auf die Festplatte entstehen. Dies wird von kswapd übernommen, das tapfer versucht, so viele Speicherseiten wie möglich für die nachfolgende Verteilung freizugeben.
Leider wird kswapd bei hoher E/A-Belastung in Kombination mit einem geringen verfügbaren Speicher zum Flaschenhals des gesamten Systems. Das gilt insbesondere, weil sichdie Seitenfehler (page faults) im System an ihn binden. alle Das kann sehr lange dauern, wenn die Prozesse nicht mehr bereit sind, Speicher zu verwenden und am Rand des OOM-Killers verharren.
Die Frage stellt sich: Warum kommt der OOM-Killer so spät? In seiner aktuellen Iteration ist der OOM-Killer äußerst ineffizient: Er beendet einen Prozess nur, wenn der Versuch, eine Speicherseite zuzuweisen, fehlschlägt, also wenn der Page Fault mit einem Fehler endet. Das passiert lange nicht, weil kswapd tapfer Speicherseiten freigibt, indem es den Page-Cache (im Grunde alle Disk-I/O in dem System) wieder auf die Festplatte schreibt. Eine detaillierte Beschreibung der notwendigen Schritte zur Behebung solcher Probleme im Kernel kann man .
Dieses Verhalten mit Linux-Kern 4.6+.
Historie 6. Pods hängen im Zustand Pending
In einigen Clustern, in denen wirklich viele Pods betrieben werden, haben wir begonnen zu bemerken, dass der Großteil von ihnen sehr lange im Zustand Pending, obwohl die Docker-Container bereits auf den Knoten laufen und man manuell mit ihnen arbeiten kann.
Dabei gibt es describe nichts Schlechtes:
Typ Grund Alter Von Nachricht
---- ------ ---- ---- -------
Normal Geplant 1m default-scheduler Erfolgreich sphinx-0 dem ss-dev-kub07 zugewiesen
Normal ErfolgreichesAnfügenVolume 1m attachdetach-controller AttachVolume.Attach erfolgreich für Volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal ErfolgreichesEinbindenVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp erfolgreich für Volume "sphinx-config"
Normal ErfolgreichesEinbindenVolume 1m kubelet, ss-dev-kub07 MountVolume.SetUp erfolgreich für Volume "default-token-fzcsf"
Normal ErfolgreichesEinbindenVolume 49s (x2 über 51s) kubelet, ss-dev-kub07 MountVolume.SetUp erfolgreich für Volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normal Heruntergeladen 43s kubelet, ss-dev-kub07 Container-Image "registry.example.com/infa/sphinx-exporter/sphinx-indexer:v1" bereits auf der Maschine vorhanden
Normal Erstellt 43s kubelet, ss-dev-kub07 Container erstellt
Normal Gestartet 43s kubelet, ss-dev-kub07 Container gestartet
Normal Heruntergeladen 43s kubelet, ss-dev-kub07 Container-Image "registry.example.com/infa/sphinx/sphinx:v1" bereits auf der Maschine vorhanden
Normal Erstellt 42s kubelet, ss-dev-kub07 Container erstellt
Normal Gestartet 42s kubelet, ss-dev-kub07 Container gestartetNach eingehender Analyse haben wir die Vermutung, dass kubelet einfach nicht schnell genug ist, um dem API-Server alle Informationen über den Status der Pods sowie die liveness/readiness-Probes zu senden.
Und nach Durchsicht der Hilfe fanden wir die folgenden Parameter:
--kube-api-qps - QPS, die beim Sprechen mit dem Kubernetes API-Server verwendet wird (Standard 5)
--kube-api-burst - Burst, die beim Sprechen mit dem Kubernetes API-Server verwendet wird (Standard 10)
--event-qps - Wenn > 0, die Anzahl der Ereigniserstellungen pro Sekunde auf diesen Wert begrenzen. Wenn 0, unbegrenzt. (Standard 5)
--event-burst - Maximale Größe von sprunghaften Ereignisprotokollen, erlaubt vorübergehend, dass Ereignisprotokolle auf diese Anzahl ansteigen, ohne das event-qps zu überschreiten. Wird nur verwendet, wenn --event-qps > 0 (Standard 10)
--registry-qps - Wenn > 0, die QPS für das Abrufen aus dem Registry auf diesen Wert begrenzen.
--registry-burst - Maximale Größe von sprunghaften Abrufen, erlaubt vorübergehend, dass Abrufe auf diese Anzahl ansteigen, ohne das registry-qps zu überschreiten. Wird nur verwendet, wenn --registry-qps > 0 (Standard 10)Wie man sieht, sind die Standardwerte recht klein, und sie decken 90 % aller Anforderungen ab… In unserem Fall waren sie jedoch nicht ausreichend. Daher haben wir folgende Werte eingestellt:
--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, woraufhin wir in den Grafiken der API-Server-Abfragen folgendes Bild sahen:
![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
… und ja, jetzt läuft alles reibungslos!
P.S.
Ich möchte vielen Ingenieuren unseres Unternehmens für ihre Hilfe bei der Fehlersuche und der Vorbereitung dieses Artikels danken, insbesondere meinem Kollegen aus unserem R&D-Team, Andrey Klimentyev ().
P.P.S.
Lesen Sie auch in unserem Blog:
- «».
- Kubernetes Tipps & Tricks:
- «»;
- «»;
- «»;
- «».
Quelle: habr.com

![6 interessante Systemfehler beim Betrieb von Kubernetes [und deren Lösungen]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)