Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Kubernetes-dən istehsalda istifadə etdiyimiz illər ərzində müxtəlif sistem komponentlərindəki səhvlərin qabların və podların işinə təsir edən xoşagəlməz və/və ya anlaşılmaz nəticələrə necə gətirib çıxardığına dair çoxlu maraqlı hekayələr topladıq. Bu yazıda ən çox yayılmış və ya maraqlı olanlardan bəzilərini seçdik. Heç vaxt belə hallarla rastlaşmasanız belə, belə qısa detektiv hekayələri oxumaq, xüsusən də “birinci əl” - həmişə maraqlıdır, elə deyilmi?..

Hekayə 1. Supercronic və Docker asma

Klasterlərdən birində biz vaxtaşırı klasterin normal fəaliyyətinə mane olan dondurulmuş Docker aldıq. Eyni zamanda, Docker qeydlərində aşağıdakılar müşahidə edildi:

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

…

Bu səhvlə bağlı bizi ən çox maraqlandıran mesajdır: pthread_create failed: No space left on device. Sürətli öyrənmə sənədləşdirmə izah etdi ki, Docker bir prosesi çəngəlləyə bilmədi, buna görə də vaxtaşırı dondu.

Monitorinq zamanı baş verənlərə aşağıdakı şəkil uyğun gəlir:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Bənzər bir vəziyyət digər qovşaqlarda da müşahidə olunur:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Eyni qovşaqlarda biz görürük:

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>

Məlum oldu ki, bu davranış pod ilə işləməyin nəticəsidir superkronik (podlarda cron işlərini yerinə yetirmək üçün istifadə etdiyimiz Go yardım proqramı):

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

Problem belədir: tapşırıq superkronikdə icra edildikdə, proses onun tərəfindən yaranır düzgün bitirə bilməz, çevrilir zombi.

Qeyd: Daha dəqiq desək, proseslər cron tapşırıqları ilə əmələ gəlir, lakin supercronic başlanğıc sistemi deyil və uşaqlarının yaratdığı prosesləri “qəbul edə” bilməz. SIGHUP və ya SIGTERM siqnalları qaldırıldıqda, onlar uşaq proseslərə ötürülmür, nəticədə uşaq prosesləri dayandırılmır və zombi statusunda qalır. Bütün bunlar haqqında daha çox oxuya bilərsiniz, məsələn, belə bir məqalə.

Problemləri həll etməyin bir neçə yolu var:

  1. Müvəqqəti həll yolu kimi - bir anda sistemdəki PID-lərin sayını artırın:
           /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. Və ya superkronikdə tapşırıqları birbaşa deyil, eyni istifadə edərək işə salın tini, prosesləri düzgün şəkildə dayandıra bilən və zombi yaratmayan.

Hekayə 2. Qrupu silərkən “Zombilər”

Kubelet çox CPU istehlak etməyə başladı:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Bu heç kimin xoşuna gəlməyəcək, ona görə də özümüzü silahlandırdıq perf və problemlə məşğul olmağa başladı. Araşdırmanın nəticələri aşağıdakı kimi olub:

  • Kubelet CPU vaxtının üçdə birindən çoxunu bütün qruplardan yaddaş məlumatlarını çəkməyə sərf edir:

    Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

  • Kernel tərtibatçılarının poçt siyahısında tapa bilərsiniz problemin müzakirəsi. Qısacası, məsələ belədir: müxtəlif tmpfs faylları və digər oxşar şeylər sistemdən tamamilə silinmir bir qrup silərkən, sözdə memcg zombi. Gec-tez onlar səhifənin ön yaddaşından silinəcəklər, lakin serverdə çoxlu yaddaş var və nüvə onları silmək üçün vaxt itirməyin mənasını görmür. Ona görə də yığışmağa davam edirlər. Niyə belə baş verir? Bu, daim yeni iş yerləri və onlarla birlikdə yeni podlar yaradan cron işləri olan bir serverdir. Beləliklə, onlarda olan konteynerlər üçün tezliklə silinən yeni qruplar yaradılır.
  • Niyə kubeletdəki cAdvisor bu qədər vaxt itirir? Bunu ən sadə icra ilə görmək asandır time cat /sys/fs/cgroup/memory/memory.stat. Sağlam bir maşında əməliyyat 0,01 saniyə çəkirsə, problemli cron02-də 1,2 saniyə çəkir. İş ondadır ki, sysfs-dən məlumatları çox yavaş oxuyan cAdvisor, zombi qruplarında istifadə olunan yaddaşı nəzərə almağa çalışır.
  • Zombiləri zorla silmək üçün LKML-də tövsiyə edildiyi kimi keşləri təmizləməyə çalışdıq: sync; echo 3 > /proc/sys/vm/drop_caches, - lakin ləpə daha mürəkkəb olduğu ortaya çıxdı və maşını qəzaya uğratdı.

Nə etməli? Problem həll olunur (törətmək, və təsvir üçün baxın buraxılış mesajı) Linux nüvəsinin 4.16 versiyasına yenilənməsi.

Tarix 3. Systemd və onun montajı

Yenə kubelet bəzi qovşaqlarda həddən artıq çox resurs istehlak edir, lakin bu dəfə çox yaddaş sərf edir:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Məlum oldu ki, Ubuntu 16.04-də istifadə olunan systemd-də problem var və bu, əlaqə üçün yaradılmış montajları idarə edərkən baş verir. subPath ConfigMap-dan və ya gizlidən. Pod öz işini bitirdikdən sonra systemd xidməti və onun xidmət qurğusu qalır sistemdə. Zamanla onların çoxu yığılır. Bu mövzuda hətta problemlər var:

  1. #5916;
  2. kubernetes #57345.

...sonuncusu systemd-də PR-ə aiddir: # 7811 (systemd-də məsələ - # 7798).

Problem artıq Ubuntu 18.04-də yoxdur, lakin Ubuntu 16.04-dən istifadə etməyə davam etmək istəyirsinizsə, bu mövzuda həll yolumuzu faydalı tapa bilərsiniz.

Beləliklə, aşağıdakı DaemonSet-i etdik:

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

... və aşağıdakı skriptdən istifadə edir:

#!/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

... və əvvəllər qeyd olunan superkronikdən istifadə edərək hər 5 dəqiqədən bir işləyir. Onun Dockerfile belə görünür:

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

Hekayə 4. Podları planlaşdırarkən rəqabət qabiliyyəti

Diqqət edildi ki, əgər bir qovşaq üzərində yerləşdirilmiş bir pod varsa və onun təsviri çox uzun müddət çıxarılırsa, eyni node "vuran" başqa bir pod sadəcə olaraq yeni podun şəklini çəkməyə başlamaz. Bunun əvəzinə, əvvəlki podun şəkli çəkilənə qədər gözləyir. Nəticədə, artıq planlaşdırılan və şəkli bir dəqiqə ərzində endirilə bilən pod statusunda sona çatacaq. containerCreating.

Hadisələr belə görünəcək:

Normal  Pulling    8m    kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal  pulling image "registry.example.com/infra/openvpn/openvpn:master"

O çıxır ki, yavaş reyestrdən bir şəkil yerləşdirməni blok edə bilər node başına.

Təəssüf ki, vəziyyətdən çıxış yolları çox deyil:

  1. Docker Registry-ni birbaşa klasterdə və ya birbaşa klasterlə birlikdə istifadə etməyə çalışın (məsələn, GitLab Registry, Nexus və s.);
  2. kimi kommunal vasitələrdən istifadə edin Kraken.

Hekayə 5. Yaddaşın olmaması səbəbindən qovşaqlar asılır

Müxtəlif proqramların işləməsi zamanı bir node tamamilə əlçatan olmağı dayandırdığı bir vəziyyətlə də qarşılaşdıq: SSH cavab vermir, bütün monitorinq demonları düşür və sonra qeydlərdə anormal bir şey (və ya demək olar ki, heç bir şey) yoxdur.

MongoDB-nin işlədiyi bir node nümunəsindən istifadə edərək şəkillərdə sizə xəbər verəcəyəm.

Bu, yuxarıdakı kimi görünür üzrə qəzalar:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Və belə - sonra qəzalar:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Monitorinqdə, düyünün mövcud olmağı dayandırdığı kəskin bir atlama da var:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

Beləliklə, ekran görüntülərindən aydın olur ki:

  1. Maşındakı RAM sona yaxındır;
  2. RAM istehlakında kəskin bir sıçrayış var, bundan sonra bütün maşına giriş kəskin şəkildə dayandırılır;
  3. Mongo-ya böyük bir tapşırıq gəlir ki, bu da DBMS prosesini daha çox yaddaş istifadə etməyə və diskdən aktiv oxumağa məcbur edir.

Belə çıxır ki, əgər Linux-un boş yaddaşı tükənirsə (yaddaş təzyiqi işə düşür) və dəyişdirmə yoxdursa, onda üzrə OOM qatili gəldikdə, səhifələri səhifə keşinə atmaq və onları yenidən diskə yazmaq arasında balanslaşdırma aktı yarana bilər. Bu, sonrakı paylama üçün mümkün qədər çox yaddaş səhifəsini cəsarətlə azad edən kswapd tərəfindən edilir.

Təəssüf ki, böyük I/O yükü və az miqdarda boş yaddaşla, kswapd bütün sistemin darboğazına çevrilir, çünki onlar buna bağlıdırlar bütün sistemdə yaddaş səhifələrinin ayrılması (səhifə xətaları). Proseslər artıq yaddaşdan istifadə etmək istəmirsə, lakin OOM-qatil uçurumunun ən kənarında sabitləşərsə, bu çox uzun müddət davam edə bilər.

Təbii sual budur: OOM qatili niyə belə gec gəlir? Hazırkı iterasiyasında OOM qatili son dərəcə axmaqdır: o, prosesi yalnız yaddaş səhifəsini ayırmaq cəhdi uğursuz olduqda, yəni. səhifə xətası uğursuz olarsa. Bu, kifayət qədər uzun müddət baş vermir, çünki kswapd cəsarətlə yaddaş səhifələrini azad edir, səhifənin önbelleğini (əslində sistemdəki bütün disk I/O) diskə geri qaytarır. Daha ətraflı olaraq, nüvədəki bu cür problemləri aradan qaldırmaq üçün lazım olan addımların təsviri ilə oxuya bilərsiniz burada.

Bu davranış təkmilləşdirməlidir Linux kernel 4.6+ ilə.

Hekayə 6. Podlar Gözləmə vəziyyətində ilişib qalır

Həqiqətən çoxlu podların fəaliyyət göstərdiyi bəzi qruplarda, onların əksəriyyətinin dövlətdə çox uzun müddət "asıldığını" görməyə başladıq. Pending, baxmayaraq ki, Docker konteynerlərinin özləri artıq qovşaqlarda işləyir və əl ilə işlənə bilər.

Üstəlik, içində describe səhv bir şey yoxdur:

  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

Bəzi qazıntılardan sonra biz fərz etdik ki, kubeletin sadəcə olaraq podların vəziyyəti və canlılıq/hazırlıq testləri haqqında bütün məlumatları API serverinə göndərməyə vaxtı yoxdur.

Və köməyi öyrəndikdən sonra aşağıdakı parametrləri tapdıq:

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

Göründüyü kimi, default dəyərlər olduqca kiçikdir, 90%-də isə bütün ehtiyacları ödəyir... Ancaq bizim vəziyyətimizdə bu kifayət deyildi. Beləliklə, biz aşağıdakı dəyərləri təyin edirik:

--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40

... və kubeletləri yenidən başladın, bundan sonra API serverinə edilən zənglərin qrafiklərində aşağıdakı şəkli gördük:

Kubernetes əməliyyatında 6 əyləncəli sistem səhvi [və onların həlli]

... və bəli, hər şey uçmağa başladı!

PS

Səhvlərin toplanmasında və bu məqalənin hazırlanmasında göstərdikləri köməyə görə şirkətimizin çoxsaylı mühəndislərinə və xüsusən də R&D komandamızdakı həmkarım Andrey Klimentyevə dərin təşəkkürümü bildirirəm (zuzzalar).

PPS

Bloqumuzda da oxuyun:

Mənbə: www.habr.com

Добавить комментарий