ProHoster > Blogi > antaminen > 6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]
6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]
Kubernetesin tuotannossa käytettyjen vuosien aikana olemme keränneet monia mielenkiintoisia tarinoita siitä, kuinka järjestelmän eri komponenttien virheet johtivat epämiellyttäviin ja/tai käsittämättömiin seurauksiin, jotka vaikuttavat säiliöiden ja koteloiden toimintaan. Tässä artikkelissa olemme valinneet joitain yleisimmistä tai mielenkiintoisimmista. Vaikka et koskaan olisikaan onnekas kohtaamaan tällaisia tilanteita, tällaisten lyhyiden dekkarien lukeminen - varsinkin "ensimmäisestä" - on aina mielenkiintoista, eikö niin?
Tarina 1. Supercronic ja Docker roikkuvat
Yhdessä klusterissa saimme ajoittain jäätyneen Dockerin, joka häiritsi klusterin normaalia toimintaa. Samaan aikaan Dockerin lokeissa havaittiin seuraavaa:
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
…
Meitä kiinnostaa eniten tästä virheestä seuraava viesti: pthread_create failed: No space left on device. Pikatutkimus dokumentointi selitti, että Docker ei pystynyt muodostamaan prosessia, minkä vuoksi se ajoittain jäätyi.
Kävi ilmi, että tämä käyttäytyminen on seurausta podin kanssa työskentelemisestä superkrooninen (Go-apuohjelma, jota käytämme cron-töiden suorittamiseen podissa):
Ongelma on tämä: kun tehtävä suoritetaan supercronicissa, prosessi synnyttää sen ei voi lopettaa oikein, muuttumassa zombie.
Huomata: Tarkemmin sanottuna prosesseja synnyttävät cron-tehtävät, mutta supercronic ei ole aloitusjärjestelmä, eikä se voi "omaksua" prosesseja, joita sen lapset synnyttivät. Kun SIGHUP- tai SIGTERM-signaaleja nostetaan, niitä ei välitetä lapsiprosesseille, minkä seurauksena lapsiprosessit eivät pääty ja pysyvät zombie-tilassa. Tästä kaikesta voit lukea lisää mm sellainen artikkeli.
On olemassa pari tapaa ratkaista ongelmia:
Väliaikaisena kiertotapana lisää PID-tunnusten määrää järjestelmässä yhdellä kertaa:
/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
Tai käynnistä tehtäviä supercronicissa ei suoraan, vaan käyttämällä samaa tini, joka pystyy lopettamaan prosessit oikein eikä synnytä zombeja.
Tarina 2. "Zombit" c-ryhmää poistettaessa
Kubelet alkoi kuluttaa paljon prosessoria:
Kukaan ei pidä tästä, joten aseistauduimme teho ja alkoi käsitellä ongelmaa. Tutkinnan tulokset olivat seuraavat:
Kubelet käyttää yli kolmanneksen CPU-ajastaan muistitietojen hakemiseen kaikista c-ryhmistä:
Ytimen kehittäjien postituslistalta löydät keskustelua ongelmasta. Lyhyesti sanottuna pointti menee tähän: erilaisia tmpfs-tiedostoja ja muita vastaavia asioita ei poisteta kokonaan järjestelmästä c-ryhmää poistettaessa ns memcg zombie. Ennemmin tai myöhemmin ne poistetaan sivun välimuistista, mutta palvelimella on paljon muistia ja ydin ei näe järkeä tuhlata aikaa niiden poistamiseen. Siksi niitä kasaantuu jatkuvasti. Miksi tämä edes tapahtuu? Tämä on palvelin, jossa on cron-työt ja joka luo jatkuvasti uusia työpaikkoja ja niiden mukana uusia podeja. Siten niissä oleville säilöille luodaan uusia c-ryhmiä, jotka poistetaan pian.
Miksi cAdvisor kubeletissa tuhlaa niin paljon aikaa? Tämä on helppo nähdä yksinkertaisimmalla suorituksella time cat /sys/fs/cgroup/memory/memory.stat. Jos terveellä koneella toiminta kestää 0,01 sekuntia, niin ongelmallisella cron02:lla se kestää 1,2 sekuntia. Asia on siinä, että cAdvisor, joka lukee dataa sysfsistä hyvin hitaasti, yrittää ottaa huomioon zombie-c-ryhmissä käytetyn muistin.
Zombien poistamiseksi väkisin yritimme tyhjentää välimuistit LKML:n suositusten mukaisesti: sync; echo 3 > /proc/sys/vm/drop_caches, - mutta ydin osoittautui monimutkaisemmaksi ja kaatui auton.
Mitä tehdä? Ongelmaa korjataan (tehdä, ja kuvaus löytyy vapauttaa viesti) päivittämällä Linux-ytimen versioon 4.16.
Historia 3. Systemd ja sen kiinnitys
Jälleen kubelet kuluttaa liian paljon resursseja joissakin solmuissa, mutta tällä kertaa se kuluttaa liikaa muistia:
Kävi ilmi, että Ubuntu 16.04:ssä käytetyssä systemd:ssä on ongelma, ja se ilmenee hallittaessa liitoksia, jotka on luotu yhteyttä varten subPath ConfigMapista tai salaisuudesta. Sen jälkeen, kun kotelo on suorittanut työnsä systemd-palvelu ja sen huoltokiinnitys säilyvät järjestelmässä. Ajan myötä niistä kertyy valtava määrä. Tästä aiheesta on jopa ongelmia:
#!/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
... ja se toimii 5 minuutin välein käyttämällä aiemmin mainittua supercronic-ohjelmaa. Sen Docker-tiedosto näyttää tältä:
Huomattiin, että: jos meillä on pod asetettu solmuun ja sen kuva pumpataan ulos hyvin pitkään, niin toinen samaan solmuun " osuva pod" yksinkertaisesti ei ala vetää kuvaa uudesta kotelosta. Sen sijaan se odottaa, kunnes edellisen kotelon kuva vedetään. Tämän seurauksena pod, joka oli jo ajoitettu ja jonka kuva olisi voitu ladata minuutissa, päätyy tilaan containerCreating.
Tapahtumat näyttävät suunnilleen tältä:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"
On käynyt ilmi, että yksi kuva hitaasta rekisteristä voi estää käyttöönoton solmukohtaisesti.
Valitettavasti tilanteesta ei ole montaa ulospääsyä:
Yritä käyttää Docker-rekisteriäsi suoraan klusterissa tai suoraan klusterin kanssa (esimerkiksi GitLab-rekisteri, Nexus jne.);
Tarina 5. Solmut roikkuvat muistin puutteen vuoksi
Eri sovellusten toiminnan aikana törmäsimme myös tilanteeseen, jossa solmu lakkaa olemasta täysin saavutettavissa: SSH ei vastaa, kaikki valvontademonit putoavat, ja sitten lokeissa ei ole mitään (tai melkein mitään) poikkeavaa.
Kerron sinulle kuvissa käyttämällä esimerkkiä yhdestä solmusta, jossa MongoDB toimi.
Tältä ylhäällä näyttää до onnettomuudet:
Ja näin - jälkeen onnettomuudet:
Seurannassa tapahtuu myös jyrkkä hyppy, jossa solmu lakkaa olemasta saatavilla:
Näin ollen kuvakaappauksista on selvää, että:
Koneen RAM-muisti on lähellä loppua;
RAM-muistin kulutuksessa on jyrkkä hyppy, jonka jälkeen pääsy koko koneeseen estetään äkillisesti;
Mongoon saapuu suuri tehtävä, joka pakottaa DBMS-prosessin käyttämään enemmän muistia ja lukemaan aktiivisesti levyltä.
Osoittautuu, että jos Linuxista loppuu vapaa muisti (muistin paine alkaa) eikä vaihtoa ole, до Kun OOM-tappaja saapuu, sivujen välimuistiin heittämisen ja takaisin levylle kirjoittamisen välillä voi syntyä tasapaino. Tämän tekee kswapd, joka vapauttaa rohkeasti mahdollisimman monta muistisivua myöhempää jakelua varten.
Valitettavasti suuri I/O-kuorma yhdistettynä pieneen määrään vapaata muistia, kswapd:sta tulee koko järjestelmän pullonkaula, koska ne on sidottu siihen kaikki muistisivujen varaukset (sivuvirheet) järjestelmässä. Tämä voi jatkua hyvin pitkään, jos prosessit eivät enää halua käyttää muistia, vaan ne on kiinnitetty OOM-killer-kuilun reunaan.
Luonnollinen kysymys kuuluu: miksi OOM-tappaja tulee niin myöhään? Nykyisessä iteraatiossaan OOM-killer on äärimmäisen tyhmä: se tappaa prosessin vain, kun yritys varata muistisivu epäonnistuu, ts. jos sivuvika epäonnistuu. Tätä ei tapahdu pitkään aikaan, koska kswapd vapauttaa rohkeasti muistisivuja ja tyhjentää sivuvälimuistin (itse asiassa koko levyn I/O:n järjestelmässä) takaisin levylle. Voit lukea yksityiskohtaisemmin kuvauksen vaiheista, joita tarvitaan tällaisten ytimen ongelmien poistamiseksi täällä.
Joissakin klustereissa, joissa toimii todella paljon paloja, aloimme huomata, että useimmat niistä "roikkuvat" hyvin pitkään osavaltiossa Pending, vaikka itse Docker-säilöt ovat jo käynnissä solmuissa ja niitä voidaan käsitellä manuaalisesti.
Lisäksi sisään describe ei ole mitään vikaa:
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
Pienen kaivamisen jälkeen teimme oletuksen, että kubeletillä ei yksinkertaisesti ole aikaa lähettää kaikkea tietoa podien tilasta ja elävyydestä/valmiustesteistä API-palvelimelle.
Ja tutkittuamme apua, löysimme seuraavat parametrit:
--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)
Nähtynä, oletusarvot ovat melko pieniä, ja 90 %:ssa ne kattavat kaikki tarpeet... Meidän tapauksessamme tämä ei kuitenkaan riittänyt. Siksi asetamme seuraavat arvot:
... ja käynnistimme kubeletit uudelleen, minkä jälkeen näimme seuraavan kuvan API-palvelimen kutsujen kaavioissa:
... ja kyllä, kaikki alkoi lentää!
PS.
Esitän syvän kiitokseni heidän avustaan virheiden keräämisessä ja tämän artikkelin valmistelussa yrityksemme lukuisille insinööreille ja erityisesti kollegalleni T&K-tiimistämme Andrei Klimentjeville (zuzzat).