![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
V letih uporabe Kubernetesa v produkciji smo nabrali veliko zanimivih zgodb o tem, kako so hrošči v različnih komponentah sistema vodili do neprijetnih in/ali nerazumljivih posledic, ki vplivajo na delovanje vsebnikov in podov. V tem članku smo izbrali nekaj najbolj pogostih ali zanimivih. Tudi če nikoli nimate sreče, da se srečate s takšnimi situacijami, je branje takšnih kratkih detektivskih zgodb – še posebej iz »prve roke« – vedno zanimivo, kajne?..
Zgodba 1. Supercronic in Docker visi
Na eni od gruč smo občasno prejemali zamrznjen Docker, ki je motil normalno delovanje gruče. Istočasno je bilo v dnevnikih Docker opaženo naslednje:
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
… Pri tej napaki nas najbolj zanima sporočilo: pthread_create failed: No space left on device. Hitra študija pojasnil, da Docker ni mogel razcepiti procesa, zato je občasno zamrznil.
Pri spremljanju dogajanju ustreza naslednja slika:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
Podobno stanje opazimo na drugih vozliščih:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
Na istih vozliščih vidimo:
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] <defunct>
root 17398 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 16852 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 9473 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 4664 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 30571 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 24113 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 16475 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 7176 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>
root 1090 0.0 0.0 0 0 ? Z Feb06 0:00 | _ [curl] <defunct>Izkazalo se je, da je to vedenje posledica dela stroka (pripomoček Go, ki ga uporabljamo za izvajanje opravil cron v podih):
_ 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] <defunct>
| _ [curl] <defunct>
| _ [curl] <defunct>
| _ [curl] <defunct>
…Težava je naslednja: ko se naloga izvaja v supercronicu, je proces, ki ga je sprožila ne more pravilno zaključiti, spreminjanje v .
Obvestilo: Če smo natančnejši, procese sprožijo opravila cron, vendar supercronic ni init sistem in ne more »posvojiti« procesov, ki so jih ustvarili njegovi otroci. Ko se sprožijo signali SIGHUP ali SIGTERM, se ne prenesejo na podrejene procese, zaradi česar se podrejeni procesi ne zaključijo in ostanejo v statusu zombija. Več o vsem tem lahko preberete na primer v .
Obstaja nekaj načinov za reševanje težav:
- Kot začasna rešitev - povečajte število PID-jev v sistemu naenkrat:
/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 - Ali pa zaženite naloge v supercronicu ne neposredno, ampak z istim , ki lahko pravilno prekine procese in ne ustvari zombijev.
Zgodba 2. "Zombiji" pri brisanju cgroup
Kubelet je začel porabljati veliko procesorja:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
To nikomur ne bo všeč, zato smo se oborožili in se začel ukvarjati s težavo. Rezultati preiskave so bili naslednji:
- Kubelet porabi več kot tretjino svojega procesorskega časa za pridobivanje podatkov iz pomnilnika iz vseh skupin c:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20241'%3E%3C/svg%3E)
- Na poštnem seznamu razvijalcev jedra lahko najdete . Skratka, poanta se spusti na tole: razne datoteke tmpfs in druge podobne zadeve niso popolnoma odstranjene iz sistema pri brisanju cgroup, t.i zombi. Prej ali slej bodo izbrisani iz predpomnilnika strani, vendar je na strežniku veliko pomnilnika in jedro ne vidi smisla izgubljati časa z njihovim brisanjem. Zato se kar kopičijo. Zakaj se to sploh dogaja? To je strežnik s cron opravili, ki nenehno ustvarja nova delovna mesta in z njimi nove pode. Tako se ustvarijo nove cgroup za vsebnike v njih, ki se kmalu izbrišejo.
- Zakaj cAdvisor v kubeletu izgublja toliko časa? To je enostavno videti z najpreprostejšo izvedbo
time cat /sys/fs/cgroup/memory/memory.stat. Če na zdravem stroju operacija traja 0,01 sekunde, potem na problematičnem cron02 traja 1,2 sekunde. Stvar je v tem, da cAdvisor, ki zelo počasi bere podatke iz sysfs, poskuša upoštevati pomnilnik, ki se uporablja v zombi cgroups. - Za prisilno odstranitev zombijev smo poskusili počistiti predpomnilnike, kot je priporočeno v LKML:
sync; echo 3 > /proc/sys/vm/drop_caches, - toda jedro se je izkazalo za bolj zapleteno in je sesulo avto.
Kaj storiti? Težava se odpravlja (, za opis pa glej ) posodobitev jedra Linux do različice 4.16.
Zgodovina 3. Systemd in njegova namestitev
Spet kubelet porablja preveč virov na nekaterih vozliščih, toda tokrat porablja preveč pomnilnika:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
Izkazalo se je, da je prišlo do težave s systemd, ki se uporablja v Ubuntu 16.04, in se pojavi pri upravljanju priklopov, ki so ustvarjeni za povezavo subPath iz ConfigMap ali Secret's. Ko je pod končal svoje delo storitev systemd in njen priklop storitve ostaneta v sistemu. Sčasoma se jih nabere ogromno. Obstajajo celo težave na to temo:
- ;
- .
... zadnji se nanaša na PR v systemd: (težava v systemd - ).
Težave ni več Ubuntu 18.04, če pa želite še naprej uporabljati Ubuntu 16.04, morda vam bo naša rešitev za to temo koristna.
Tako smo naredili naslednji DaemonSet:
---
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... in uporablja naslednji skript:
#!/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... in teče vsakih 5 minut z uporabo prej omenjenega supercronica. Njegova datoteka Dockerfile je videti takole:
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"]Zgodba 4. Konkurenčnost pri razporejanju pods
Ugotovljeno je bilo, da: če imamo enoto postavljeno na vozlišče in njeno sliko črpamo zelo dolgo, bo druga enota, ki je "zadela" isto vozlišče, preprosto ne začne vleči slike novega pod. Namesto tega počaka, da se potegne slika prejšnjega sklopa. Posledično bo pod, ki je bil že načrtovan in katerega sliko bi lahko prenesli v samo minuti, končal v statusu containerCreating.
Dogodki bodo izgledali nekako takole:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"Izkazalo se je, da ena sama slika iz počasnega registra lahko blokira uvajanje na vozlišče.
Na žalost ni veliko izhodov iz situacije:
- Poskusite uporabiti svoj register Docker neposredno v gruči ali neposredno z gručo (na primer register GitLab, Nexus itd.);
- Uporabite pripomočke, kot je npr .
Zgodba 5. Vozlišča visijo zaradi pomanjkanja pomnilnika
Med delovanjem različnih aplikacij smo naleteli tudi na situacijo, ko vozlišče popolnoma preneha biti dostopno: SSH se ne odziva, vsi nadzorni demoni odpadejo, nato pa v dnevnikih ni nič (ali skoraj nič) nepravilnega.
Povedal vam bom v slikah na primeru enega vozlišča, kjer je deloval MongoDB.
Tako izgleda vrh za nesreče:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
In tako - po nesreče:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
Pri spremljanju je tudi oster skok, pri katerem vozlišče preneha biti na voljo:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
Tako je iz posnetkov zaslona jasno, da:
- RAM na stroju je blizu konca;
- Pride do močnega skoka porabe RAM-a, po katerem je dostop do celotnega stroja nenadoma onemogočen;
- Na Mongo pride velika naloga, ki prisili proces DBMS, da uporabi več pomnilnika in aktivno bere z diska.
Izkaže se, da če v Linux zmanjka prostega pomnilnika (pojavi se pritisk na pomnilnik) in ni zamenjave, potem za Ko pride morilec OOM, lahko pride do ravnovesja med metanjem strani v predpomnilnik strani in zapisovanjem nazaj na disk. To naredi kswapd, ki pogumno sprosti čim več pomnilniških strani za kasnejšo distribucijo.
Na žalost z veliko V/I obremenitvijo skupaj z majhno količino prostega pomnilnika, kswapd postane ozko grlo celotnega sistema, saj so na to vezani Vsi dodelitve (napake strani) pomnilniških strani v sistemu. To lahko traja zelo dolgo, če procesi ne želijo več uporabljati pomnilnika, ampak so pritrjeni na sam rob prepada OOM-killer.
Naravno vprašanje je: zakaj OOM morilec pride tako pozno? V svoji trenutni ponovitvi je ubijalec OOM izjemno neumen: uničil bo proces šele, ko poskus dodelitve pomnilniške strani ne uspe, tj. če napaka strani ne uspe. To se ne zgodi prav dolgo, ker kswapd pogumno osvobodi pomnilniške strani in vrže predpomnilnik strani (pravzaprav celoten V/I disk v sistemu) nazaj na disk. Podrobneje z opisom korakov, potrebnih za odpravo takšnih težav v jedru, lahko preberete .
To vedenje z jedrom Linux 4.6 +.
Zgodba 6. Stroki se zataknejo v stanju čakanja
V nekaterih grozdih, v katerih deluje res veliko podov, smo začeli opažati, da jih večina zelo dolgo »visi« v stanju Pending, čeprav se sami vsebniki Docker že izvajajo na vozliščih in je z njimi mogoče delati ročno.
Poleg tega v describe nič ni narobe:
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 containerPo nekaj kopanju smo domnevali, da kubelet preprosto nima časa za pošiljanje vseh informacij o stanju podov in testih živahnosti/pripravljenosti strežniku API.
In po študiju pomoči smo našli naslednje parametre:
--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)Kot je razvidno, privzete vrednosti so precej majhne, in v 90% pokrivajo vse potrebe... Vendar v našem primeru to ni bilo dovolj. Zato smo postavili naslednje vrednosti:
--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40... in znova zagnali kubelets, nakar smo v grafih klicev na API strežnik videli naslednjo sliko:
![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
... in ja, vse je začelo leteti!
PS
Za njihovo pomoč pri zbiranju hroščev in pripravi tega članka se globoko zahvaljujem številnim inženirjem našega podjetja, še posebej pa svojemu kolegu iz naše ekipe za raziskave in razvoj Andreju Klimentjevu ().
PPS
Preberite tudi na našem blogu:
- «".
- Zanka nasvetov in trikov Kubernetes:
- «»;
- «»;
- «»;
- «".
Vir: www.habr.com

![6 zabavnih sistemskih hroščev v delovanju Kubernetesa [in njihova rešitev]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)