![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
Током година коришћења Кубернетеса у производњи, сакупили смо много занимљивих прича о томе како су грешке у различитим компонентама система довеле до непријатних и/или несхватљивих последица које утичу на рад контејнера и подова. У овом чланку смо направили избор неких од најчешћих или најзанимљивијих. Чак и ако никада немате среће да наиђете на такве ситуације, читање о таквим кратким детективским причама – посебно „из прве руке“ – увек је занимљиво, зар не?..
Прича 1. Суперкроник и Докер висе
На једном од кластера смо периодично добијали замрзнути Доцкер, који је ометао нормално функционисање кластера. Истовремено, у евиденцији Доцкер-а је примећено следеће:
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
… Оно што нас највише занима у вези са овом грешком је порука: pthread_create failed: No space left on device. Куицк Студи објаснио је да Доцкер не може форкирати процес, због чега се периодично замрзава.
У праћењу, следећа слика одговара ономе што се дешава:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
Слична ситуација се примећује и на другим чворовима:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
На истим чворовима видимо:
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>Испоставило се да је ово понашање последица рада махуна (услужни програм Го који користимо за покретање црон послова у подовима):
_ 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>
…Проблем је у следећем: када се задатак покрене у суперкроници, процес је покренут из њега не може исправно да се заврши, претвара у .
Приметити: Да будемо прецизнији, процеси су покренути црон задацима, али суперцрониц није инит систем и не може да „усвоји“ процесе које су његова деца покренула. Када се подигну сигнали СИГХУП или СИГТЕРМ, они се не прослеђују подређеним процесима, што резултира тиме да се подређени процеси не завршавају и остају у статусу зомбија. Више о свему томе можете прочитати, на пример, у .
Постоји неколико начина за решавање проблема:
- Као привремено решење - повећајте број ПИД-ова у систему у једном тренутку:
/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 - Или покрените задатке у суперкронику не директно, већ користећи исти , који је у стању да исправно оконча процесе и не ствара зомбије.
Прича 2. „Зомбији“ приликом брисања цгрупе
Кубелет је почео да троши много ЦПУ-а:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
Ово се никоме неће допасти, па смо се наоружали и почео да се бави проблемом. Резултати истраге су били следећи:
- Кубелет троши више од трећине свог ЦПУ времена извлачећи меморијске податке из свих цгрупа:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20241'%3E%3C/svg%3E)
- Можете пронаћи на мејлинг листи програмера кернела . Укратко, поента се своди на ово: разне тмпфс датотеке и друге сличне ствари нису у потпуности уклоњене из система при брисању цгрупе, тзв зомби. Пре или касније они ће бити избрисани из кеша страница, али на серверу има доста меморије и кернел не види смисао да губи време на њихово брисање. Зато се стално гомилају. Зашто се ово уопште дешава? Ово је сервер са црон пословима који стално ствара нове послове, а са њима и нове подове. Тако се за контејнере у њима креирају нове цгрупе, које се убрзо бришу.
- Зашто цАдвисор у кубелет-у губи толико времена? Ово је лако видети најједноставнијим извођењем
time cat /sys/fs/cgroup/memory/memory.stat. Ако на здравој машини операција траје 0,01 секунду, онда на проблематичној црон02 траје 1,2 секунде. Ствар је у томе што цАдвисор, који врло споро чита податке из сисфс-а, покушава да узме у обзир меморију која се користи у зомби цгрупама. - Да бисмо насилно уклонили зомбије, покушали смо да обришемо кеш као што је препоручено у ЛКМЛ-у:
sync; echo 3 > /proc/sys/vm/drop_caches, - али се испоставило да је кернел компликованији и срушио је аутомобил.
Шта да радим? Проблем се решава (, а за опис види ) ажурирање језгра Linux до верзије 4.16.
Историја 3. Системд и његов моунт
Опет, кубелет троши превише ресурса на неким чворовима, али овај пут троши превише меморије:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
Испоставило се да је постојао проблем са systemd-ом који се користи у Ubuntu 16.04, и то се дешава приликом управљања монтирањима која су креирана за повезивање subPath из ЦонфигМапс-а или тајни. Након што је под завршила свој посао системд сервис и његов сервисни моунт остају у систему. Временом се накупља огроман број њих. Постоје чак и проблеми на ову тему:
- ;
- .
...од којих се последње односи на ПР у системд: (проблем у системд-у - ).
Проблем више није ту Ubuntu 18.04, али ако желите да наставите да користите Ubuntu 16.04, наше решење за ову тему би вам могло бити корисно.
Тако смо направили следећи ДаемонСет:
---
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... и користи следећу скрипту:
#!/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... и ради сваких 5 минута користећи претходно поменути суперкроник. Његов Доцкерфиле изгледа овако:
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"]Прича 4. Конкурентност при заказивању под
Примећено је да: ако имамо махуну постављену на чвор и њен имиџ се испумпава веома дуго, онда ће друга капсула која „удари“ у исти чвор једноставно не почиње да вуче слику нове махуне. Уместо тога, чека док се не повуче слика претходне махуне. Као резултат тога, под који је већ заказан и чија је слика могла бити преузета за само минут завршиће у статусу containerCreating.
Догађаји ће изгледати отприлике овако:
Normal Pulling 8m kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal pulling image "registry.example.com/infra/openvpn/openvpn:master"Испоставило се да једна слика из спорог регистра може блокирати примену по чвору.
Нажалост, нема много излаза из ситуације:
- Покушајте да користите свој Доцкер регистар директно у кластеру или директно са кластером (на пример, ГитЛаб Регистри, Некус, итд.);
- Користите услужне програме као што су .
Прича 5. Чворови висе због недостатка меморије
Током рада различитих апликација, такође смо наишли на ситуацију да чвор потпуно престаје да буде доступан: ССХ не реагује, сви демони за праћење отпадају, а онда нема ничег (или скоро ништа) аномалија у логовима.
Рећи ћу вам на сликама користећи пример једног чвора где је функционисао МонгоДБ.
Овако изгледа на врху до несреће:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
И овако - после несреће:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
У праћењу постоји и оштар скок, при којем чвор престаје да буде доступан:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
Дакле, из снимака екрана је јасно да:
- РАМ на машини је близу краја;
- Постоји оштар скок потрошње РАМ-а, након чега је приступ целој машини нагло онемогућен;
- На Монго стиже велики задатак, који приморава ДБМС процес да користи више меморије и активно чита са диска.
Испоставља се да ако у Linux понестане слободне меморије (долази до притиска на меморију) и нема замене, онда до Када дође убица ООМ-а, може доћи до балансирања између убацивања страница у кеш страница и њиховог уписивања назад на диск. То ради ксвапд, који храбро ослобађа што више меморијских страница за каснију дистрибуцију.
Нажалост, са великим И/О оптерећењем заједно са малом количином слободне меморије, ксвапд постаје уско грло целог система, јер су везани за то све алокације (грешке странице) меморијских страница у систему. Ово може потрајати веома дуго ако процеси више не желе да користе меморију, већ су фиксирани на самој ивици понора ООМ-убице.
Природно питање је: зашто убица ООМ долази тако касно? У својој тренутној итерацији, ООМ убица је изузетно глуп: убиће процес само када покушај доделе меморијске странице не успе, тј. ако грешка странице не успе. Ово се не дешава дуго времена, јер ксвапд храбро ослобађа меморијске странице, избацујући кеш странице (у ствари, цео диск И/О у систему) назад на диск. Детаљније, са описом корака потребних за отклањање таквих проблема у кернелу, можете прочитати .
Ово понашање са језгром Linux КСНУМКС +.
Прича 6. Махуне се заглављују у стању чекања
У неким кластерима, у којима ради заиста много махуна, почели смо да примећујемо да већина њих „виси“ веома дуго у држави Pending, иако сами Доцкер контејнери већ раде на чворовима и са њима се може радити ручно.
Штавише, у describe све је у реду:
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После извесног копања, претпоставили смо да кубелет једноставно нема времена да пошаље све информације о стању подова и тестовима живости/спремности на АПИ сервер.
И након проучавања помоћи, пронашли смо следеће параметре:
--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)Као што се види, подразумеване вредности су прилично мале, а у 90% покривају све потребе... Међутим, у нашем случају то није било довољно. Због тога постављамо следеће вредности:
--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40... и поново покренули кубелетс, након чега смо видели следећу слику на графиконима позива АПИ серверу:
![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
... и да, све је почело да лети!
ПС
За њихову помоћ у прикупљању грешака и припреми овог чланка, изражавам дубоку захвалност бројним инжењерима наше компаније, а посебно мом колеги из нашег Р&Д тима Андреју Климентјеву ().
Ппс
Прочитајте и на нашем блогу:
- «'.
- Кубернетес петља савета и трикова:
- «";
- «";
- «";
- «'.
Извор: ввв.хабр.цом

![6 забавних системских грешака у раду Кубернетеса [и њихово решење]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)