ProHoster > Log > administrasjon > 6 underholdende systemfeil i driften av Kubernetes [og deres løsning]
6 underholdende systemfeil i driften av Kubernetes [og deres løsning]
Gjennom årene med bruk av Kubernetes i produksjon, har vi samlet mange interessante historier om hvordan feil i ulike systemkomponenter førte til ubehagelige og/eller uforståelige konsekvenser som påvirket driften av containere og pods. I denne artikkelen har vi laget et utvalg av noen av de mest vanlige eller interessante. Selv om du aldri er heldig nok til å møte slike situasjoner, er det alltid interessant å lese om slike korte detektivhistorier - spesielt "førstehånds" - ikke sant?
Historie 1. Supercronic og Docker hengende
På en av klyngene mottok vi med jevne mellomrom en frossen Docker, som forstyrret den normale funksjonen til klyngen. Samtidig ble følgende observert i Docker-loggene:
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
…
Det som interesserer oss mest med denne feilen er meldingen: pthread_create failed: No space left on device. Rask studie dokumentasjon forklarte at Docker ikke kunne splitte en prosess, og derfor frøs den med jevne mellomrom.
Ved overvåking tilsvarer følgende bilde det som skjer:
Problemet er dette: når en oppgave kjøres i supercronic, ble prosessen skapt av den kan ikke avsluttes riktig, blir til zombie.
Note: For å være mer presis, prosesser skapt av cron-oppgaver, men supercronic er ikke et init-system og kan ikke "adoptere" prosesser som barna har skapt. Når SIGHUP- eller SIGTERM-signaler heves, sendes de ikke videre til barneprosessene, noe som resulterer i at barneprosessene ikke avsluttes og forblir i zombiestatus. Alt dette kan du lese mer om, for eksempel i en slik artikkel.
Det er et par måter å løse problemer på:
Som en midlertidig løsning – øk antallet PID-er i systemet på et enkelt tidspunkt:
/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
Eller start oppgaver i supercronic ikke direkte, men ved å bruke det samme tini, som er i stand til å avslutte prosesser på riktig måte og ikke skape zombier.
Historie 2. "Zombies" når du sletter en cgroup
Kubelet begynte å bruke mye CPU:
Ingen vil like dette, så vi bevæpnet oss perf og begynte å håndtere problemet. Resultatene av undersøkelsen var som følger:
Kubelet bruker mer enn en tredjedel av CPU-tiden sin på å trekke minnedata fra alle cgroups:
I kjerneutviklernes e-postliste kan du finne diskusjon av problemet. Kort fortalt kommer poenget ned til dette: ulike tmpfs-filer og andre lignende ting fjernes ikke fullstendig fra systemet ved sletting av en cgruppe, den såkalte memcg zombie. Før eller siden vil de bli slettet fra sidebufferen, men det er mye minne på serveren og kjernen ser ikke poenget med å kaste bort tid på å slette dem. Det er derfor de stadig hoper seg opp. Hvorfor skjer dette i det hele tatt? Dette er en server med cron-jobber som stadig skaper nye jobber, og med dem nye poder. Dermed opprettes det nye cgroups for containere i dem, som snart blir slettet.
Hvorfor kaster cAdvisor i kubelet bort så mye tid? Dette er lett å se med den enkleste utførelse time cat /sys/fs/cgroup/memory/memory.stat. Hvis operasjonen på en frisk maskin tar 0,01 sekunder, tar den på den problematiske cron02 1,2 sekunder. Saken er at cAdvisor, som leser data fra sysfs veldig sakte, prøver å ta hensyn til minnet som brukes i zombie cgroups.
For å fjerne zombier med makt, prøvde vi å tømme cacher som anbefalt i LKML: sync; echo 3 > /proc/sys/vm/drop_caches,- men kjernen viste seg å være mer komplisert og krasjet bilen.
Hva å gjøre? Problemet blir fikset (begå, og for en beskrivelse se slipp melding) oppdaterer Linux-kjernen til versjon 4.16.
Historie 3. Systemd og dets montering
Igjen, kubelet bruker for mange ressurser på noen noder, men denne gangen bruker den for mye minne:
Det viste seg at det er et problem i systemd brukt i Ubuntu 16.04, og det oppstår når du administrerer monteringer som er opprettet for tilkobling subPath fra ConfigMap's eller secret's. Etter at poden har fullført arbeidet systemd-tjenesten og dens servicemontering gjenstår i systemet. Over tid akkumuleres et stort antall av dem. Det er til og med problemer om dette emnet:
#!/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
... og den kjører hvert 5. minutt ved å bruke den tidligere nevnte supercronic. Dockerfilen ser slik ut:
Historie 4. Konkurranseevne når du planlegger pods
Det ble lagt merke til at: hvis vi har en pod plassert på en node og bildet pumpes ut i veldig lang tid, vil en annen pod som "treffer" den samme noden ganske enkelt begynner ikke å trekke bildet av den nye poden. I stedet venter den til bildet av den forrige poden er trukket. Som et resultat vil en pod som allerede var planlagt og hvis bilde kunne ha blitt lastet ned på bare et minutt havne i statusen til containerCreating.
Arrangementene vil se omtrent slik ut:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"
Det viser seg at et enkelt bilde fra et tregt register kan blokkere distribusjon per node.
Dessverre er det ikke mange veier ut av situasjonen:
Prøv å bruke Docker Registry direkte i klyngen eller direkte med klyngen (for eksempel GitLab Registry, Nexus, etc.);
Historie 5. Noder henger på grunn av mangel på hukommelse
Under driften av forskjellige applikasjoner møtte vi også en situasjon der en node slutter å være tilgjengelig fullstendig: SSH reagerer ikke, alle overvåkingsdemoner faller av, og så er det ingenting (eller nesten ingenting) unormalt i loggene.
Jeg skal fortelle deg i bilder ved å bruke eksemplet på en node der MongoDB fungerte.
Slik ser det ut på toppen til ulykker:
Og sånn - etter ulykker:
I overvåking er det også et skarpt hopp, der noden slutter å være tilgjengelig:
Så fra skjermbildene er det klart at:
RAM-en på maskinen er nær slutten;
Det er et kraftig hopp i RAM-forbruket, hvoretter tilgangen til hele maskinen brått deaktiveres;
En stor oppgave kommer til Mongo, som tvinger DBMS-prosessen til å bruke mer minne og aktivt lese fra disk.
Det viser seg at hvis Linux går tom for ledig minne (minnetrykket setter inn) og det ikke er noe bytte, så til Når OOM-morderen ankommer, kan det oppstå en balansegang mellom å kaste sider inn i sidebufferen og skrive dem tilbake til disken. Dette gjøres av kswapd, som modig frigjør så mange minnesider som mulig for senere distribusjon.
Dessverre, med en stor I/O-belastning kombinert med en liten mengde ledig minne, kswapd blir flaskehalsen i hele systemet, fordi de er knyttet til det alle allokeringer (sidefeil) av minnesider i systemet. Dette kan pågå i svært lang tid hvis prosessene ikke ønsker å bruke minne lenger, men er fikset helt på kanten av OOM-killer-avgrunnen.
Det naturlige spørsmålet er: hvorfor kommer OOM-morderen så sent? I sin nåværende iterasjon er OOM-morderen ekstremt dum: den vil drepe prosessen bare når forsøket på å tildele en minneside mislykkes, dvs. hvis sidefeilen mislykkes. Dette skjer ikke på ganske lenge, fordi kswapd modig frigjør minnesider, og dumper sidebufferen (faktisk hele disk I/O i systemet) tilbake til disken. Mer detaljert, med en beskrivelse av trinnene som kreves for å eliminere slike problemer i kjernen, kan du lese her.
Denne oppførselen bør forbedres med Linux-kjerne 4.6+.
Historie 6. Pods blir sittende fast i ventende tilstand
I noen klynger, der det virkelig er mange pods som opererer, begynte vi å legge merke til at de fleste av dem "henger" veldig lenge i staten Pending, selv om selve Docker-beholderne allerede kjører på nodene og kan arbeides med manuelt.
Dessuten, i describe Det er ikke noe galt:
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
Etter litt graving antok vi at kubelet rett og slett ikke har tid til å sende all informasjon om tilstanden til podene og liveness/beredskapstester til API-serveren.
Og etter å ha studert hjelp, fant vi følgende parametere:
--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)
Som sett, standardverdiene er ganske små, og i 90 % dekker de alle behov... Men i vårt tilfelle var ikke dette nok. Derfor setter vi følgende verdier:
... og startet kubelets på nytt, hvoretter vi så følgende bilde i grafene av kall til API-serveren:
... og ja, alt begynte å fly!
PS
For deres hjelp med å samle feil og forberede denne artikkelen, uttrykker jeg min dype takknemlighet til de mange ingeniørene i selskapet vårt, og spesielt til min kollega fra vårt FoU-team Andrey Klimentyev (zuzzas).