Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Kubernetes-ті өндірісте пайдаланған жылдар ішінде біз әртүрлі жүйе құрамдас бөліктеріндегі қателердің контейнерлер мен бұршақтардың жұмысына әсер ететін жағымсыз және/немесе түсініксіз салдарға әкелгені туралы көптеген қызықты оқиғаларды жинақтадық. Бұл мақалада біз ең көп таралған немесе қызықты нәрселердің кейбірін таңдадық. Мұндай жағдайларға кезікпесеңіз де, мұндай қысқа детективтер туралы оқу, әсіресе «бірінші қолмен» - әрқашан қызықты, солай емес пе?..

Әңгіме 1. Supercronic және Docker ілулі

Кластерлердің бірінде біз мезгіл-мезгіл кластердің қалыпты жұмысына кедергі келтіретін мұздатылған Docker алдық. Сонымен қатар, 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

…

Бұл қате туралы бізді ең қызықтыратыны мына хабарлама: pthread_create failed: No space left on device. Жылдам оқу құжаттама Докер процесті тоқтата алмайтынын түсіндірді, сондықтан ол мезгіл-мезгіл қатып қалады.

Мониторинг кезінде келесі сурет болып жатқан жағдайға сәйкес келеді:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Ұқсас жағдай басқа түйіндерде байқалады:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Kubernetes жұмысындағы 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>

Бұл мінез-құлық поддонмен жұмыс істеудің салдары болып шықты суперкроникалық (подтардағы cron тапсырмаларын орындау үшін қолданатын Go утилитасы):

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

Мәселе мынада: тапсырма суперкроникада іске қосылғанда, процесс одан пайда болады дұрыс аяқтай алмайды, айналады зомби.

ескерту: Дәлірек айтқанда, процестер cron тапсырмаларымен туындайды, бірақ суперкроника бастама жүйесі емес және оның балалары пайда болған процестерді «қабылдай» алмайды. SIGHUP немесе SIGTERM сигналдары көтерілгенде, олар еншілес процестерге берілмейді, нәтижесінде еншілес процестер аяқталмайды және зомби күйінде қалады. Мұның бәрі туралы көбірек оқи аласыз, мысалы, мына жерден мұндай мақала.

Мәселені шешудің бірнеше жолы бар:

  1. Уақытша шешім ретінде - бір уақытта жүйедегі PID санын көбейтіңіз:
           /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-әңгіме. Топты жою кезіндегі «Зомбилер».

Kubelet көп процессорды тұтына бастады:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Бұл ешкімге ұнамайды, сондықтан біз қаруландық Perf және мәселемен айналыса бастады. Тергеу нәтижелері келесідей болды:

  • Kubelet процессорлық уақытының үштен бірінен астамын барлық топтардан жад деректерін алуға жұмсайды:

    Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

  • Ядро әзірлеушілерінің жіберу тізімінде сіз таба аласыз мәселені талқылау. Қысқасы, мәселе мынаған келіп тіреледі: әртүрлі tmpfs файлдары және басқа ұқсас нәрселер жүйеден толығымен жойылмайды топты жою кезінде, деп аталатын memcg зомби. Ерте ме, кеш пе, олар бет кэшінен жойылады, бірақ серверде жад көп және ядро ​​​​оларды жоюға уақыт жоғалтудың мәнін көрмейді. Сондықтан олар үйіліп жатыр. Неге бұл тіпті болып жатыр? Бұл үнемі жаңа жұмыс орындарын және олармен бірге жаңа подкасттарды жасайтын cron тапсырмалары бар сервер. Осылайша, олардағы контейнерлер үшін жаңа топтар құрылады, олар көп ұзамай жойылады.
  • Неліктен kubelet ішіндегі cAdvisor көп уақытты босқа өткізеді? Мұны қарапайым орындау арқылы байқау оңай time cat /sys/fs/cgroup/memory/memory.stat. Егер сау машинада операция 0,01 секундқа созылса, проблемалы cron02-де 1,2 секунд кетеді. Мәселе мынада, sysfs деректерін өте баяу оқитын cAdvisor зомби топтарында қолданылатын жадты есепке алуға тырысады.
  • Зомбилерді күштеп жою үшін біз LKML-де ұсынылған кэштерді тазалауға тырыстық: sync; echo 3 > /proc/sys/vm/drop_caches, - бірақ ядросы күрделірек болып шықты және көлікті соқтырды.

Енді не істеу керек? Мәселе шешілуде (міндеттеу, және сипаттаманы қараңыз хабарды шығару) Linux ядросын 4.16 нұсқасына жаңарту.

Тарих 3. Systemd және оның тірегі

Тағы да, kubelet кейбір түйіндерде тым көп ресурстарды тұтынады, бірақ бұл жолы ол тым көп жадты тұтынады:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Ubuntu 16.04 жүйесінде қолданылатын жүйеде ақаулық бар екені анықталды және ол қосылу үшін жасалған қондырғыларды басқару кезінде пайда болады. subPath ConfigMap немесе құпиядан. Бұршақ жұмысын аяқтағаннан кейін systemd қызметі және оның қызметтік қондырғысы қалады жүйеде. Уақыт өте келе олардың үлкен саны жинақталады. Бұл тақырыпта тіпті мәселелер бар:

  1. №5916;
  2. kubernetes №57345.

...соның соңғысы systemd ішіндегі PR-ға қатысты: #7811 (жүйедегі мәселе - #7798).

Мәселе Ubuntu 18.04 нұсқасында енді жоқ, бірақ Ubuntu 16.04 пайдалануды жалғастырғыңыз келсе, осы тақырып бойынша біздің уақытша шешімімізді пайдалы деп таба аласыз.

Осылайша біз келесі 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

... және ол келесі сценарийді пайдаланады:

#!/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 минут сайын жұмыс істейді. Оның Dockerfile келесідей көрінеді:

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. Docker тізілімін тікелей кластерде немесе тікелей кластермен бірге пайдалануға тырысыңыз (мысалы, GitLab Registry, Nexus және т.б.);
  2. сияқты утилиталарды пайдаланыңыз Kraken.

5-әңгіме. Жадының жетіспеушілігінен түйіндер ілінеді

Әртүрлі қосымшалардың жұмысы кезінде біз түйін толығымен қол жетімділікті тоқтататын жағдайға тап болдық: SSH жауап бермейді, барлық бақылау демондары құлап кетеді, содан кейін журналдарда аномальды ештеңе (немесе ештеңе дерлік) жоқ.

Мен MongoDB жұмыс істейтін бір түйіннің мысалын пайдаланып суреттерде айтып беремін.

Төбесі осылай көрінеді қарай апаттар:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Және осылай - после апаттар:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Мониторингте түйін қол жетімді болмайтын күрт секіріс бар:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

Осылайша, скриншоттардан анық:

  1. Құрылғыдағы жедел жады соңына жақын;
  2. ЖЖҚ тұтынуында күрт секіру бар, содан кейін бүкіл машинаға қол жеткізу кенеттен ажыратылады;
  3. Mongo-ға үлкен тапсырма келеді, ол ДҚБЖ процесін көбірек жадты пайдалануға және дискіден белсенді оқуға мәжбүр етеді.

Егер Linux-тың бос жады бітсе (жад қысымы орнатылса) және айырбастау болмаса, онда қарай OOM өлтірушісі келгенде, беттерді бет кэшіне тастау және оларды дискіге қайта жазу арасында теңдестіру әрекеті туындауы мүмкін. Бұл kswapd арқылы жасалады, ол кейіннен тарату үшін мүмкіндігінше көп жад беттерін босатады.

Өкінішке орай, үлкен енгізу/шығару жүктемесі және шағын бос жад көлемімен, kswapd бүкіл жүйенің тар мойнына айналады, өйткені олар оған байланған барлық жүйедегі жад беттерінің бөлінуі (бет ақаулары). Бұл процесстер жадты бұдан былай пайдаланғысы келмесе, бірақ OOM-өлтіруші тұңғиықтың ең шетінде бекітілген болса, бұл өте ұзақ уақытқа созылуы мүмкін.

Табиғи сұрақ: OOM өлтіруші неге кеш келеді? Қазіргі итерациясында OOM өлтірушісі өте ақымақ: ол жад бетін бөлу әрекеті сәтсіз болғанда ғана процесті жояды, яғни. егер бет қатесі орындалмаса. Бұл ұзақ уақыт бойы болмайды, өйткені kswapd жад беттерін батыл босатып, бет кэшін (жүйедегі бүкіл диск енгізу/шығару, шын мәнінде) дискіге қайтарады. Толығырақ, ядродағы осындай проблемаларды жою үшін қажетті қадамдардың сипаттамасымен сіз оқи аласыз осында.

Бұл мінез жақсарту керек Linux ядросы 4.6+.

6-оқиға. Тұтқалар күту күйінде тұрып қалады

Кейбір кластерлерде, оларда шын мәнінде көп жұмыс істейді, біз олардың көпшілігі штатта өте ұзақ уақыт «ілулі тұрғанын» байқадық. Pending, дегенмен Docker контейнерлерінің өзі түйіндерде жұмыс істеп тұр және олармен қолмен жұмыс істеуге болады.

Оның үстіне, ішке 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

Біраз іздегеннен кейін біз кубелеттің API серверіне подкасттардың күйі және өмірлік/дайындық сынақтары туралы барлық ақпаратты жіберуге уақыты жоқ деген болжам жасадық.

Ал анықтаманы зерттегеннен кейін біз келесі параметрлерді таптық:

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

... және кубелеттерді қайта іске қостық, содан кейін API серверіне қоңыраулар графиктерінде келесі суретті көрдік:

Kubernetes жұмысындағы 6 қызықты жүйелік қателер [және олардың шешімі]

... және иә, бәрі ұша бастады!

PS

Қателерді жинауға және осы мақаланы дайындауға көмектескені үшін мен компаниямыздың көптеген инженерлеріне, әсіресе ҒЗТКЖ тобымыздағы әріптесім Андрей Климентьевке үлкен алғысымды білдіремін (зузалар).

PPS

Біздің блогта да оқыңыз:

Ақпарат көзі: www.habr.com

пікір қалдыру