6 забавних системских грешака у раду Кубернетеса [и њихово решење]

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

Током година коришћења Кубернетеса у производњи, сакупили смо много занимљивих прича о томе како су грешке у различитим компонентама система довеле до непријатних и/или несхватљивих последица које утичу на рад контејнера и подова. У овом чланку смо направили избор неких од најчешћих или најзанимљивијих. Чак и ако никада немате среће да наиђете на такве ситуације, читање о таквим кратким детективским причама – посебно „из прве руке“ – увек је занимљиво, зар не?..

Прича 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 забавних системских грешака у раду Кубернетеса [и њихово решење]

Слична ситуација се примећује и на другим чворовима:

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

На истим чворовима видимо:

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>
…

Проблем је у следећем: када се задатак покрене у суперкроници, процес је покренут из њега не може исправно да се заврши, претвара у zombi.

Приметити: Да будемо прецизнији, процеси су покренути црон задацима, али суперцрониц није инит систем и не може да „усвоји“ процесе које су његова деца покренула. Када се подигну сигнали СИГХУП или СИГТЕРМ, они се не прослеђују подређеним процесима, што резултира тиме да се подређени процеси не завршавају и остају у статусу зомбија. Више о свему томе можете прочитати, на пример, у такав чланак.

Постоји неколико начина за решавање проблема:

  1. Као привремено решење - повећајте број ПИД-ова у систему у једном тренутку:
           /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. Или покрените задатке у суперкронику не директно, већ користећи исти тини, који је у стању да исправно оконча процесе и не ствара зомбије.

Прича 2. „Зомбији“ приликом брисања цгрупе

Кубелет је почео да троши много ЦПУ-а:

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

Ово се никоме неће допасти, па смо се наоружали перф и почео да се бави проблемом. Резултати истраге су били следећи:

  • Кубелет троши више од трећине свог ЦПУ времена извлачећи меморијске податке из свих цгрупа:

    6 забавних системских грешака у раду Кубернетеса [и њихово решење]

  • Можете пронаћи на мејлинг листи програмера кернела дискусија о проблему. Укратко, поента се своди на ово: разне тмпфс датотеке и друге сличне ствари нису у потпуности уклоњене из система при брисању цгрупе, тзв мемцг зомби. Пре или касније они ће бити избрисани из кеша страница, али на серверу има доста меморије и кернел не види смисао да губи време на њихово брисање. Зато се стално гомилају. Зашто се ово уопште дешава? Ово је сервер са црон пословима који стално ствара нове послове, а са њима и нове подове. Тако се за контејнере у њима креирају нове цгрупе, које се убрзо бришу.
  • Зашто цАдвисор у кубелет-у губи толико времена? Ово је лако видети најједноставнијим извођењем 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 забавних системских грешака у раду Кубернетеса [и њихово решење]

Испоставило се да је постојао проблем са systemd-ом који се користи у Ubuntu 16.04, и то се дешава приликом управљања монтирањима која су креирана за повезивање subPath из ЦонфигМапс-а или тајни. Након што је под завршила свој посао системд сервис и његов сервисни моунт остају у систему. Временом се накупља огроман број њих. Постоје чак и проблеми на ову тему:

  1. #5916;
  2. кубернетес #57345.

...од којих се последње односи на ПР у системд: #КСНУМКС (проблем у системд-у - #КСНУМКС).

Проблем више није ту 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"

Испоставило се да једна слика из спорог регистра може блокирати примену по чвору.

Нажалост, нема много излаза из ситуације:

  1. Покушајте да користите свој Доцкер регистар директно у кластеру или директно са кластером (на пример, ГитЛаб Регистри, Некус, итд.);
  2. Користите услужне програме као што су Кракен.

Прича 5. Чворови висе због недостатка меморије

Током рада различитих апликација, такође смо наишли на ситуацију да чвор потпуно престаје да буде доступан: ССХ не реагује, сви демони за праћење отпадају, а онда нема ничег (или скоро ништа) аномалија у логовима.

Рећи ћу вам на сликама користећи пример једног чвора где је функционисао МонгоДБ.

Овако изгледа на врху до несреће:

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

И овако - после несреће:

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

У праћењу постоји и оштар скок, при којем чвор престаје да буде доступан:

6 забавних системских грешака у раду Кубернетеса [и њихово решење]

Дакле, из снимака екрана је јасно да:

  1. РАМ на машини је близу краја;
  2. Постоји оштар скок потрошње РАМ-а, након чега је приступ целој машини нагло онемогућен;
  3. На Монго стиже велики задатак, који приморава ДБМС процес да користи више меморије и активно чита са диска.

Испоставља се да ако у 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 забавних системских грешака у раду Кубернетеса [и њихово решење]

... и да, све је почело да лети!

ПС

За њихову помоћ у прикупљању грешака и припреми овог чланка, изражавам дубоку захвалност бројним инжењерима наше компаније, а посебно мом колеги из нашег Р&Д тима Андреју Климентјеву (зуззас).

Ппс

Прочитајте и на нашем блогу:

Извор: ввв.хабр.цом

Купите поуздан хостинг за сајтове са ДДоС заштитом, ВПС ВДС сервере 🔥 Купите поуздан веб хостинг са DDoS заштитом, VPS VDS сервере | ProHoster