![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
Door de jaren heen hebben we bij het gebruik van Kubernetes in productie veel interessante verhalen verzameld, waarin fouten in verschillende systeemcomponenten leidden tot vervelende en/of onbegrijpelijke gevolgen die de werking van containers en pods beïnvloedden. In dit artikel hebben we een selectie gemaakt van enkele van de meest voorkomende of interessante. Zelfs als je nooit geluk hebt om in dergelijke situaties terecht te komen, is het altijd boeiend om over zulke korte detectives te lezen - vooral 'uit de eerste hand' - nietwaar?
Verhaal 1. Supercronic en vastlopende Docker
Op een van de clusters kregen we af en toe een 'vastgelopen' Docker, wat het normale functioneren van het cluster verstoorde. In de logs van Docker zagen we het volgende:
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
… Het belangrijkste dat ons in deze fout interesseert, is de melding: pthread_create failed: No space left on device. Een vluchtige studie verklaarde dat Docker geen proces kan fork'en, waardoor het periodiek 'vastliep'.
In de monitoring komt er het volgende in beeld:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
Een soortgelijke situatie is ook op andere knooppunten te zien:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
Op deze knooppunten zien we:
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]Het bleek dat dit gedrag een gevolg was van het werken van de pod met (een tool in Go die we gebruiken voor het uitvoeren van cron-taken in pods):
_ 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]
…Het probleem is als volgt: wanneer een taak wordt uitgevoerd in supercronic, kan het proces dat door hem is gestart, niet correct worden beëindigd, waardoor het verandert in .
Opmerking: Om precies te zijn, worden de processen gestart door cron-taken, maar supercronic is geen init-systeem en kan de processen die zijn kinderen zijn niet 'adopteren'. Bij het ontvangen van SIGHUP- of SIGTERM-signalen worden deze niet doorgegeven aan de processen die zijn gestart, waardoor de kindprocessen niet worden beëindigd en in de status van zombie blijven. Meer hierover kan onder andere worden gelezen in .
Er zijn een paar manieren om de problemen op te lossen:
- Als tijdelijke workaround — vergroot het aantal PID's in het systeem op een bepaald moment:
/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 - Of laat de taken in supercronic niet direct starten, maar met behulp van dezelfde , die in staat is om processen correct te beëindigen en geen zombie's te produceren.
Verhaal 2. 'Zombies' bij het verwijderen van cgroup
Kubelet begon een grote hoeveelheid CPU te verbruiken:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
Dat valt niemand aan te raden, dus bewapenden we ons en begonnen we het probleem te onderzoeken. De uitkomsten van het onderzoek waren als volgt:
- Kubelet besteedt meer dan een derde van de CPU-tijd aan het ophalen van gegevens over geheugen uit alle cgroup:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](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 de mailinglijst van de kernelontwikkelaars kun je een . Kortom komt het erop neer dat verschillende tmpfs-bestanden en andere vergelijkbare dingen niet volledig uit het systeem worden verwijderd bij het verwijderen van cgroup — er blijven zogenaamde zombies over. vroeg of laat zullen ze uit de pagina-cache verdwijnen, maar de server heeft veel geheugen en de kernel ziet geen zin in het besteden van tijd aan hun verwijdering. Daarom blijven ze zich ophopen. Waarom gebeurt dit überhaupt? Dit is een server met cron-taken die constant nieuwe taken genereert, en daarmee — nieuwe pods. Hierdoor worden er nieuwe cgroups voor de containers aangemaakt, die snel weer worden verwijderd.
- Waarom verbruikt cAdvisor in kubelet zoveel tijd? Dit is gemakkelijk te zien door simpelweg uit te voeren
time cat /sys/fs/cgroup/memory/memory.stat. Als de operatie op een gezonde machine 0,01 seconde duurt, dan duurt die op de probleemmachine cron02 — 1,2 seconde. Het probleem is dat cAdvisor, dat zeer langzaam gegevens uit sysfs leest, probeert het gebruikte geheugen in zombie cgroups te berekenen. - Om de zombies gedwongen te verwijderen, hebben we geprobeerd de caches op te schonen, zoals aanbevolen in LKML:
sync; echo 3 > /proc/sys/vm/drop_caches, — maar de kernel bleek ingewikkelder en heeft de machine vastgelopen.
Wat moeten we doen? Het probleem kan worden opgelost (, zie de beschrijving in ) door de Linux-kernel bij te werken naar versie 4.16.
Verhaal 3. Systemd en zijn mounts
Wederom verbruikt kubelet te veel middelen op sommige knooppunten, maar dit keer — al het geheugen:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
Het bleek dat er een probleem is met systemd, dat in Ubuntu 16.04 wordt gebruikt, en dit ontstaat bij het beheren van mounts die zijn aangemaakt voor het koppelen van subPath uit ConfigMaps of secrets. Na het beëindigen van de pod blijft de systemd-service en zijn service-mount in het systeem. In de loop der tijd accumuleert zich een enorme hoeveelheid. Over dit onderwerp zijn er zelfs issues:
- ;
- .
… waarin naar een PR in systemd wordt verwezen: (issue in systemd — ).
Het probleem is er niet meer in Ubuntu 18.04, maar als je Ubuntu 16.04 wilt blijven gebruiken, kan onze workaround hiervoor nuttig zijn.
Dus hebben we de volgende DaemonSet gemaakt:
---
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… en daarin wordt deze script gebruikt:
#!/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… en het wordt elke 5 minuten uitgevoerd met behulp van eerder genoemde supercronic. Zijn Dockerfile ziet er als volgt uit:
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"]Hoofdstuk 4. Concurrentie bij het plannen van pods
Het is opgemerkt dat: als we een pod op een node plaatsen en zijn image lang duurt om te downloaden, dan zal een andere pod die "op dezelfde node terechtkomt", gewoon niet beginnen met het pullen van de image van de nieuwe pod. In plaats daarvan wacht hij totdat de image van de vorige pod is gedownload. Hierdoor zal de pod die al was gepland en waarvan de image in slechts een minuut had kunnen worden gedownload, voor langere tijd in de status containerCreating.
In de evenementen zal het ongeveer als volgt zijn:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"Het blijkt dat één enkele image uit een trage registry de deployment kan blokkeren op de node.
Helaas zijn er niet veel oplossingen voor deze situatie:
- Probeer uw Docker Registry rechtstreeks in het cluster of direkt met het cluster te gebruiken (bijvoorbeeld GitLab Registry, Nexus, enz.);
- Gebruik tools zoals .
Verhaal 5. Vastlopen van knooppunten bij geheugen tekort
Tijdens het gebruik van verschillende applicaties kwamen we ook situaties tegen waarin een knooppunt volledig onbereikbaar werd: SSH reageert niet, alle monitoringdemonen vallen uit en in de logs staat daarna niets (of bijna niets) abnormaals.
Ik zal het aan de hand van afbeeldingen uitleggen met als voorbeeld een knooppunt waar MongoDB actief was.
Zo ziet atop eruit tot crashes:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
En zo — worden toegevoegd na crashes:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
In de monitoring is ook een scherpe piek te zien, waarbij het knooppunt onbereikbaar wordt:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
Hieruit blijkt uit de screenshots dat:
- Het RAM op de machine bijna op is;
- Er is een scherpe piek in het geheugenverbruik, waarna de toegang tot de hele machine abrupt wordt afgesloten;
- Er komt een grote taak naar Mongo die ervoor zorgt dat het DBMS-proces meer geheugen gebruikt en actief van de schijf leest.
Het blijkt dat als op Linux het vrije geheugen op is (er ontstaat memory pressure) en er geen swap is, dan tot bij de komst van de OOM killer er een evenwicht kan ontstaan tussen het laden van pagina's in de page cache en het terugschrijven naar de schijf. Dit wordt uitgevoerd door kswapd, die moedig zoveel mogelijk geheugenpagina's vrijmaakt voor toekomstige distributie.
Helaas, bij een hoge I/O belasting gecombineerd met een klein aantal vrije geheugen, wordt kswapd de bottleneck van het hele systeem, omdat het zorgt voor all de toewijzingen (page faults) van geheugenpagina's in het systeem. Dit kan heel lang doorgaan als processen niet meer geheugen willen gebruiken en vastlopen op de rand van de OOM-killer-afgrond.
De vraag rijst: waarom komt de OOM killer zo laat? In zijn huidige iteratie is de OOM killer extreem dom: hij zal een proces pas beëindigen wanneer de poging om een geheugenpagina toe te wijzen mislukt, d.w.z. als de page fault met een fout eindigt. Dit gebeurt genoeg tijd niet, omdat kswapd moedig geheugenpagina's vrijmaakt door de page cache (al het schijf I/O in het systeem, in wezen) terug naar de schijf te schrijven. Voor een gedetailleerdere uitleg, met beschrijving van de stappen die nodig zijn om dergelijke problemen in de kernel op te lossen, kan men lezen .
Dit gedrag met kernel Linux 4.6+.
Geschiedenis 6. Pods blijven hangen in de status Pending
In sommige clusters waar echt veel pods draaien, hebben we opgemerkt dat het merendeel ervan heel lang in de status Pending, terwijl de Docker-containers al op de knooppunten draaien en we er handmatig mee kunnen werken.
In beschrijven is niets verkeerd:
Type Reden Leeftijd Van Bericht
---- ------ ---- ---- -------
Normaal Gepland 1m default-scheduler Succesvol toegewezen aan sphinx-0 op ss-dev-kub07
Normaal SuccesvolVolumeBevestigen 1m attachdetach-controller AttachVolume.Attach is succesvol voor volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normaal SuccesvolVolumeMonteren 1m kubelet, ss-dev-kub07 MountVolume.SetUp is succesvol voor volume "sphinx-config"
Normaal SuccesvolVolumeMonteren 1m kubelet, ss-dev-kub07 MountVolume.SetUp is succesvol voor volume "default-token-fzcsf"
Normaal SuccesvolVolumeMonteren 49s (x2 over 51s) kubelet, ss-dev-kub07 MountVolume.SetUp is succesvol voor volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
Normaal Getrokken 43s kubelet, ss-dev-kub07 Containerafbeelding "registry.example.com/infrastructure/sphinx-exporter/sphinx-indexer:v1" is al aanwezig op de machine
Normaal Aangemaakt 43s kubelet, ss-dev-kub07 Container is aangemaakt
Normaal Gestart 43s kubelet, ss-dev-kub07 Container is gestart
Normaal Getrokken 43s kubelet, ss-dev-kub07 Containerafbeelding "registry.example.com/infrastructure/sphinx/sphinx:v1" is al aanwezig op de machine
Normaal Aangemaakt 42s kubelet, ss-dev-kub07 Container is aangemaakt
Normaal Gestart 42s kubelet, ss-dev-kub07 Container is gestartNa wat onderzoek deden we de aanname dat kubelet gewoon niet in staat is om de API-server alle informatie over de status van de pods, liveness/readiness-probes, tijdig te sturen.
En na het bestuderen van de help, vonden we de volgende parameters:
--kube-api-qps - QPS om te gebruiken bij het communiceren met de Kubernetes API-server (standaard 5)
--kube-api-burst - Burst om te gebruiken bij het communiceren met de Kubernetes API-server (standaard 10)
--event-qps - Als > 0, beperk het aantal evenementen per seconde tot deze waarde. Als 0, onbeperkt. (standaard 5)
--event-burst - Maximale grootte van een bursty evenementrecords, stelt tijdelijk evenementenrecords in staat om tot dit aantal te stijgen, terwijl ze nog steeds niet meer dan event-qps overschrijden. Alleen gebruikt als --event-qps > 0 (standaard 10)
--registry-qps - Als > 0, beperk registratie pull QPS tot deze waarde.
--registry-burst - Maximale grootte van bursty pulls, stelt tijdelijk pulls in staat om tot dit aantal te stijgen, terwijl ze nog steeds niet meer dan registry-qps overschrijden. Alleen gebruikt als --registry-qps > 0 (standaard 10)Zoals je kunt zien, standaardwaarden zijn vrij klein, en in 90% van de gevallen dekken ze alle behoeften... Maar in ons geval was dat niet genoeg. Daarom stelden we de volgende waarden in:
--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40… en herstartten we de kubelets, waarna we op de grafieken van de API-server het volgende zagen:
![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
… en ja, alles begon te vliegen!
P.S.
Voor de hulp bij het verzamelen van bugs en het voorbereiden van het artikel wil ik mijn grote dank uitspreken aan de vele ingenieurs van ons bedrijf, met name aan mijn collega uit ons R&D-team, Andrey Klimentyev ().
P.P.S.
Lees ook op onze blog:
- «».
- Kubernetes tips & tricks cyclus:
- «»;
- «»;
- «»;
- «».
Bron: habr.com

![6 interessante systeemfouten bij het gebruik van Kubernetes [en hun oplossing]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)