ProHoster > Bloc > Administració > 6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]
6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]
Al llarg dels anys d'utilitzar Kubernetes en producció, hem acumulat moltes històries interessants de com els errors en diversos components del sistema van provocar conseqüències desagradables i/o incomprensibles que afectaven el funcionament dels contenidors i les beines. En aquest article hem fet una selecció d'alguns dels més comuns o interessants. Fins i tot si mai no tens la sort de trobar-te amb aquestes situacions, llegir sobre històries de detectius tan breus, especialment "de primera mà", sempre és interessant, oi?...
Història 1. Supercronic i Docker penjats
En un dels clústers, rebíem periòdicament un Docker congelat, que interferia amb el funcionament normal del clúster. Al mateix temps, es va observar el següent als registres de Docker:
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
…
El que més ens interessa d'aquest error és el missatge: pthread_create failed: No space left on device. Estudi ràpid documentació va explicar que Docker no podia bifurcar un procés, motiu pel qual es congelava periòdicament.
En el seguiment, la imatge següent correspon al que està passant:
Va resultar que aquest comportament és una conseqüència del treball amb la beina supercrònica (una utilitat Go que fem servir per executar treballs cron en pods):
El problema és aquest: quan una tasca s'executa en supercrònica, el procés genera no pot acabar correctament, convertint-se en zombi.
Nota: Per ser més precisos, els processos es generen per tasques cron, però supercronic no és un sistema d'inici i no pot "adoptar" els processos que van generar els seus fills. Quan s'eleven els senyals SIGHUP o SIGTERM, no es transmeten als processos fills, cosa que fa que els processos fills no acabin i romanguin en estat zombi. Podeu llegir més sobre tot això, per exemple, a tal article.
Hi ha un parell de maneres de resoldre problemes:
Com a solució temporal: augmenteu el nombre de PID al sistema en un sol 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
O llançar tasques en supercronic no directament, sinó utilitzant el mateix tini, que és capaç d'acabar els processos correctament i no generar zombis.
Història 2. "Zombis" quan s'elimina un grup c
Kubelet va començar a consumir molta CPU:
Això no li agradarà a ningú, així que ens vam armar perfecte i va començar a tractar el problema. Els resultats de la investigació van ser els següents:
Kubelet gasta més d'un terç del seu temps de CPU extreu dades de memòria de tots els grups c:
A la llista de correu dels desenvolupadors del nucli es pot trobar discussió del problema. En resum, la qüestió es redueix a això: diversos fitxers tmpfs i altres coses semblants no s'eliminen completament del sistema en eliminar un cgroup, l'anomenat memcg zombi. Tard o d'hora s'eliminaran de la memòria cau de la pàgina, però hi ha molta memòria al servidor i el nucli no veu el sentit de perdre el temps en esborrar-los. Per això es continuen acumulant. Per què passa això? Aquest és un servidor amb tasques cron que constantment crea nous llocs de treball, i amb ells nous pods. Així, es creen nous cgroups per als contenidors que hi ha, que aviat s'eliminen.
Per què cAdvisor a kubelet perd tant de temps? Això és fàcil de veure amb l'execució més senzilla time cat /sys/fs/cgroup/memory/memory.stat. Si en una màquina sana l'operació triga 0,01 segons, a la cron02 problemàtica triga 1,2 segons. El cas és que cAdvisor, que llegeix dades de sysfs molt lentament, intenta tenir en compte la memòria utilitzada en els cgroups zombies.
Per eliminar els zombis amb força, hem provat d'esborrar la memòria cau tal com es recomana a LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - però el nucli va resultar més complicat i va estavellar el cotxe.
Què fer? El problema s'està solucionant (comprometre's, i per a una descripció vegeu missatge de llançament) actualitzant el nucli de Linux a la versió 4.16.
Història 3. Systemd i el seu muntatge
Un cop més, el kubelet està consumint massa recursos en alguns nodes, però aquesta vegada està consumint massa memòria:
Va resultar que hi ha un problema en systemd utilitzat a Ubuntu 16.04 i es produeix quan es gestionen muntatges que es creen per a la connexió subPath des de ConfigMaps o secrets. Un cop el pod ha completat el seu treball el servei systemd i el seu muntatge de servei romanen en sistema. Amb el temps, s'acumulen un gran nombre. Fins i tot hi ha problemes sobre aquest tema:
...l'últim dels quals es refereix al PR a systemd: # 7811 (problema a systemd - # 7798).
El problema ja no existeix a Ubuntu 18.04, però si voleu continuar utilitzant Ubuntu 16.04, és possible que la nostra solució alternativa sobre aquest tema sigui útil.
#!/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
... i funciona cada 5 minuts utilitzant el supercronic esmentat anteriorment. El seu Dockerfile té aquest aspecte:
Història 4. Competitivitat a l'hora de programar pods
Es va observar que: si tenim una beina col·locada en un node i la seva imatge es bombeja durant molt de temps, llavors una altra beina que "xoca" el mateix node simplement no comença a treure la imatge de la nova beina. En comptes d'això, espera fins que es tregui la imatge de la beina anterior. Com a resultat, un pod que ja estava programat i la imatge del qual s'hauria pogut descarregar en només un minut acabarà en l'estat de containerCreating.
Els esdeveniments tindran un aspecte com aquest:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"
Resulta que una sola imatge d'un registre lent pot bloquejar el desplegament per node.
Malauradament, no hi ha moltes maneres de sortir de la situació:
Intenteu utilitzar el vostre registre Docker directament al clúster o directament amb el clúster (per exemple, GitLab Registry, Nexus, etc.);
Història 5. Els nodes es pengen per falta de memòria
Durant el funcionament de diverses aplicacions, també ens hem trobat amb una situació en què un node deixa de ser completament accessible: SSH no respon, tots els dimonis de monitorització cauen i, aleshores, no hi ha res (o gairebé res) anòmal als registres.
T'ho diré en imatges utilitzant l'exemple d'un node on funcionava MongoDB.
Això és el que sembla al cim до accidents:
I així - després accidents:
En el seguiment, també hi ha un salt brusc, en què el node deixa d'estar disponible:
Així, a partir de les captures de pantalla queda clar que:
La memòria RAM de la màquina està a prop del final;
Hi ha un fort salt en el consum de memòria RAM, després del qual l'accés a tota la màquina es desactiva bruscament;
Arriba una gran tasca a Mongo, que obliga el procés DBMS a utilitzar més memòria i llegir activament des del disc.
Resulta que si Linux es queda sense memòria lliure (la pressió de memòria s'instal·la) i no hi ha intercanvi, aleshores до Quan arriba l'assassí OOM, pot sorgir un acte d'equilibri entre llançar pàgines a la memòria cau de la pàgina i escriure-les de nou al disc. Això ho fa kswapd, que allibera de valent tantes pàgines de memòria com sigui possible per a la seva posterior distribució.
Malauradament, amb una gran càrrega d'E/S juntament amb una petita quantitat de memòria lliure, kswapd es converteix en el coll d'ampolla de tot el sistema, perquè hi estan lligats tots assignacions (errors de pàgina) de pàgines de memòria del sistema. Això pot durar molt de temps si els processos ja no volen utilitzar la memòria, però es fixen a la vora de l'abisme assassí OOM.
La pregunta natural és: per què l'assassí OOM arriba tan tard? En la seva iteració actual, l'assassí OOM és extremadament estúpid: matarà el procés només quan falla l'intent d'assignar una pàgina de memòria, és a dir. si l'error de pàgina falla. Això no passa durant molt de temps, perquè kswapd allibera amb valentia les pàgines de memòria, abocant la memòria cau de pàgines (de fet, tota l'E/S del disc del sistema) al disc. Amb més detall, amb una descripció dels passos necessaris per eliminar aquests problemes al nucli, podeu llegir aquí.
Història 6. Les beines s'enganxen a l'estat Pendent
En alguns clústers, en els quals realment hi ha moltes beines en funcionament, vam començar a notar que la majoria d'ells "pengen" durant molt de temps a l'estat. Pending, tot i que els mateixos contenidors de Docker ja s'estan executant als nodes i es poden treballar manualment.
A més, en describe no hi ha res dolent:
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
Després d'excavar una mica, vam suposar que el kubelet simplement no té temps d'enviar tota la informació sobre l'estat dels pods i les proves de vivacitat/preparació al servidor de l'API.
I després d'estudiar l'ajuda, hem trobat els paràmetres següents:
--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)
Com es veu, els valors per defecte són força petits, i en un 90% cobreixen totes les necessitats... No obstant això, en el nostre cas això no va ser suficient. Per tant, establim els valors següents:
... i vam reiniciar els kubelets, després de la qual cosa vam veure la següent imatge als gràfics de trucades al servidor de l'API:
... i sí, tot va començar a volar!
PS
Per la seva ajuda per recollir errors i preparar aquest article, expresso el meu profund agraïment als nombrosos enginyers de la nostra empresa, i especialment al meu col·lega del nostre equip d'R+D Andrey Klimentyev (zuzzes).