Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Kubernetes’in istehsalda istifadəsi illəri ərzində, müxtəlif sistem komponentlərindəki xətaların konteynerlərə və pod-lara təsir edən xoşagəlməz və ya başa düşülməz nəticələrə səbəb olduğu bir çox maraqlı hekayə toplandı. Bu məqalədə onların bəzilərinin ən çox rast gəlinən və ya maraqlı olanlarını topladıq. Hətta belə bir vəziyyətlə heç vaxt rastlaşmasanız belə, bu cür qısa detektiv hekayələri oxumaq — xüsusən də «birbaşa» — həmişə maraqlıdır, elmi də deyilmi?..

Hekayə 1. Supercronic və dayanan Docker

Küçük klasterlərimizdən birində periodik olaraq «dayanan» Docker ilə rastlaşırdıq ki, bu da klasterin normal fəaliyyətinə mane olurdu. Bu zaman Docker loglarında aşağıdakıları görürdük.

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 xətada bizi daha çox maraqlandıran mesajdır: pthread_create failed: No space left on device. Tez bir araşdırma sənəd Docker-in prosesi fork-laması mümkün olmadığını, buna görə də periodik olaraq «dayandığını» aydınlaşdırdı.

Müşahidə olunan monitorinqdə aşağıdakı görünüş var:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Oxşar vəziyyət digər düyünlərdə də müşahidə olunur:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Eyni düyünlərdə 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] 
root     17398  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root     16852  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root      9473  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root      4664  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root     30571  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root     24113  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root     16475  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root      7176  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] 
root      1090  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl]

Məlum oldu ki, bu cür davranış — pod-un supercronic (pod-larda cron tapşırıqlarını icra etmək üçün istifadə etdiyimiz Go proqramı) işi ilə bağlıdır:

 _ 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] 
|       _ [curl] 
|       _ [curl] 
|       _ [curl] 
…

Problemin mahiyyəti budur: supercronic-da tapşırıq işə salındıqda, onun yaratdığı proses düzgün şəkildə başa çatmır, zombiyə çevrilərək zombi.

Qeyd: Dəqiq desək, proseslər cron tapşırıqları tərəfindən yaradılır, lakin supercronic init sistemi deyil və ona öz övladlarını "adopte etmək" imkanı vermir. SIGHUP və ya SIGTERM siqnalları meydana gəldikdə, onlar yaradılmış proseslərə ötürülmür, nəticədə övlad prosesləri tamamlanmır, zombi vəziyyətində qalır. Bunun haqqında daha ətraflı oxumaq üçün, məsələn, belə bir məqaləyə.

Var bir neçə həll yolu:

  1. Müvəqqəti bir workaround olaraq — sistemdə eyni anda PID-lərin sayını artırmaq:
           /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. Yoxsa tapşırıqları supercronic-da birbaşa deyil, eyni tini, prosesi düzgün tamamlayaraq zombi yaratmayan bir vasitə vasitəsilə işə salmaq.

Hekayə 2. Cgroup-ları silərkən "zombilər"

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

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Bu, heç kəsə xoş gəlməz, ona görə biz perf ilə problemi araşdırmağa başladıq. Araşdırmanın yekunları belə oldu:

Bəs nə etməli? Problemin həlli (komit, və təsvirini burada tapın:) Linux nüvəsini 4.16 versiyasına yeniləməklə əldə edilir.

Tarix 3. Systemd və onun mount’ı

Yenidən kubelet bəzi düyünlərdə çoxlu resurs istehlak edir, amma bu dəfə — artıq yaddaş:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Məlum oldu ki, Ubuntu 16.04-də istifadə olunan systemd-də problem var və bu, mount’ların idarə edilməsi zamanı yaranır ConfigMap’lardan və ya secret’lardan yaradılan . Pod bitdikdən sonra systemd xidməti və onun yardımçı mount’u sistemdə qalır. Zamanla onların sayı çoxalır. Bu barədə məsələlər də mövcuddur:

  1. kops #5916;
  2. kubernetes #57345.

… ən sonuncusunda systemd-dəki PR-ə istinad edilir: #7811 (systemd-də məsələlər — #7798).

Problem artıq Ubuntu 18.04-də yoxdur, amma əgər Ubuntu 16.04-dən istifadə etməyə davam etmək istəyirsinizsə, bu barədə workaround-mız yaара bilər.

Beləliklə, biz aşağıdakı DaemonSet-i yaratdıq:

---
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ə burada belə bir skript istifadə olunur:

#!/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ə bu, artıq qeyd edilən supercronic ilə hər 5 dəqiqədən bir işə düşür. 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"]

Tarix 4. Pod’ların planlaşdırılması zamanı rəqabət

Müşahidə edildi ki: əgər bizim bir pod düyünə yerləşdirilirsə və onun imici yüklənməsi çox uzun sürərsə, digər pod, həmin düyünə 'düşərsə' sadəcə yeni pod’un imicini yükləməyə başlamır.Bunun yerine, önceki pod'un görüntüsünün 'çekilmesini' bekliyor. Sonuç olarak, daha önce planlanmış ve yalnızca bir dakikada çekilebilecek bir pod, uzun süre 'containerCreating' durumunda kalacak. containerCreating.

Olaylarda yaklaşık olarak şu şekilde olacak:

Normal  Pulling    8m    kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal  görüntüyü çekiyor "registry.example.com/infra/openvpn/openvpn:master"

Yani tek bir yavaş registry'den gelen bir görüntü, dağıtımı engelleyebilir düğümde.

Maalesef, durumdan çıkış yolu çok değil:

  1. Kendi Docker Registry'nizi doğrudan kümede veya küme ile birlikte kullanmaya çalışın (örneğin, GitLab Registry, Nexus vb.);
  2. Böyle araçlardan yararlanın, kraken.

Hikaye 5. Bellek yetersizliğinde düğüm donması

Farklı uygulamaların kullanım süresince, bir düğümün tamamen erişilemez hale geldiği bir durumu da yaşadık: SSH yanıt vermiyor, tüm izleme daemonları devre dışı kalıyor ve loglarda sonra hiçbir (veya neredeyse hiçbir) anomali yok.

MongoDB'nin çalıştığı bir düğüm örneğinde, bunu görsellerle anlatacağım.

Atop böyle görünüyor ile kaza:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Ve işte böyle — sonra kaza:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

İzlemede düğümün erişilemez hale geldiği keskin bir artış da gözlemleniyor:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

Bu şekilde, ekran görüntülerinden şunlar görülüyor:

  1. Makinedeki RAM neredeyse bitmiş;
  2. RAM tüketiminde keskin bir artış gözlemleniyor, ardından makinaya erişim aniden kesiliyor;
  3. Mongo'ya büyük bir görev geliyor, bu da veritabanı sürecinin daha fazla RAM kullanmasına ve diskte aktif olarak okuma yapmasına neden oluyor.

Görülüyor ki, Linux'ta boş bellek kalmadığında (bellek baskısı gerçekleştiğinde) ve swap yoksa, ile OOM killer'ın gelişi, sayfaları page cache'e atma ile bunları diske geri yazma arasında bir dengede gerçekleşebilir. Bunu, mümkün olduğunca fazla bellek sayfasını serbest bırakmak için kswapd yapar.

Maalesef, girdi/çıktı üzerindeki büyük yük ile birlikte az sayıda boş bellek olduğunda, kswapd, tüm sistemin şişme noktası haline geliyor, çünkü sistemdeki bellek sayfalarının tahsislerini (page faults) etkiliyor. İşlemler daha fazla bellek kullanmak istemezse ve OOM killer çukurunun kenarında takılı kalırlarsa, bu çok uzun sürebilir. tüm Soru şudur: OOM killer neden bu kadar geç geliyor? Şu anki iterasyonunda, OOM killer oldukça aptaldır: bir bellek sayfası tahsis etmeye çalışırken başarısız olduğunda yani bir page fault hatası aldığında işlemi sonlandırır. Bunun gerçekleşmesi uzun sürmez, çünkü kswapd cesurca bellek sayfalarını serbest bırakır ve page cache'i (temel olarak sistemdeki tüm disk I/O) diske geri gönderir. Benzer sorunların çözümü için gerekli adımları daha ayrıntılı olarak açıklayan bilgiler okuyabilirsiniz.

Bu davranış burada.

geliştirilmelidir. должно улучшиться Linux 4.6+ nüvəsi ilə.

6. Tarix. Pod’lar Pending vəziyyətində asılı qaldı

Həqiqətən çox sayda pod’un fəaliyyət göstərdiyi bəzi klasterlərdə, əksər pod-ların çox uzun müddət "asılı" qaldığını qeyd etməyə başladıq. Pending, halbuki Docker konteynerləri artıq nodlarda başlandı və onlarla əllə işləmək mümkündür.

Bu arada describe pis bir şey yoxdur:

  Tip    Səbəb                      Yaş           Gələn                   Mesaj
  ----    ------                     ----           ----                     -------
  Normal  Cədvəli                      1d              default-scheduler       müvəffəqiyyətlə sphinx-0 i ss-dev-kub07 yerinə təyin etdi
  Normal  Uğurlu əlavə həcmi   1d              attachdetach-controller  AttachVolume.Attach müvəffəqiyyətlə "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b" üçün həcmi əlavə etdi
  Normal  Uğurlu montaj həcmi    1d              kubelet, ss-dev-kub07   MountVolume.SetUp müvəffəqiyyətlə "sphinx-config" üçün həcmi qurdu
  Normal  Uğurlu montaj həcmi    1d              kubelet, ss-dev-kub07   MountVolume.SetUp müvəffəqiyyətlə "default-token-fzcsf" üçün həcmi qurdu
  Normal  Uğurlu montaj həcmi    49s (x2 51s ərzində)  kubelet, ss-dev-kub07   MountVolume.SetUp müvəffəqiyyətlə "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b" üçün həcmi qurdu
  Normal  Çəkildi                     43s               kubelet, ss-dev-kub07   Kontainer şəkli "registry.example.com/infra/sphinx-exporter/sphinx-indexer:v1" artıq maşında mövcuddur
  Normal  Yaradıldı                  43s               kubelet, ss-dev-kub07   Kontainer yaradıldı
  Normal  Başlandı                   43s               kubelet, ss-dev-kub07   Kontainer başlandı
  Normal  Çəkildi                     43s               kubelet, ss-dev-kub07   Kontainer şəkli "registry.example.com/infra/sphinx/sphinx:v1" artıq maşında mövcuddur
  Normal  Yaradıldı                  42s               kubelet, ss-dev-kub07   Kontainer yaradıldı
  Normal  Başlandı                   42s               kubelet, ss-dev-kub07   Kontainer başlandı

Araşdırmalar apararaq, kubelet-in sadəcə pod-ların vəziyyəti haqqında API-serverə bütün məlumatları göndərməyə yetərincə vaxtı olmadığını düşündük.

Və kömək bildirərək aşağıdakı parametrləri tapdıq:

--kube-api-qps - kubernetes apiserveri ilə danışarkən istifadə etmək üçün QPS (default 5)
--kube-api-burst  - kubernetes apiserveri ilə danışarkən istifadə olunan Burst (default 10) 
--event-qps - 0-dan böyükdürsə, saniyədə bu dəyərə qədər hadisələrin yaradılmasını məhdudlaşdırır. 0 olanlar, sərhəd yoxdur. (default 5)
--event-burst - Partlayıcı hadisə qeydiyyatlarının maksimum ölçüsü, hadisə qeydiyyatlarının bu sayına qədər partlamasına icazə verir, hələ də event-qps-i aşmadan. Yalnız --event-qps > 0 olduqda istifadə olunur (default 10) 
--registry-qps - 0-dan böyükdürsə, registri çəkmək üçün QPS-i bu dəyərə qədər məhdudlaşdırır.
--registry-burst - Partlayıcı çəkmələrin maksimum ölçüsü, registri çəkməyə mənsub olan registri-qps kateqoriyasını aşmadan bu sayına qədər çəkməyə icazə verir. Yalnız --registry-qps > 0 olduqda istifadə olunur (default 10)

Göründüyü kimi, default dəyərləri - olduqca kiçikdir, və 90 % hallarda bütün tələbləri qarşılayır… Lakin bizim vəziyyətimizdə bu yetərsiz oldu. Bu səbəbdən aşağıdakı dəyərləri təqdim etdik:

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

… və kubelet-ləri yenidən işə saldıq, bundan sonra API-serverə müraciətlərin qrafiklərində aşağıdakı mənzərəni gördük:

Kubernetes-də baş verən 6 maraqlı sistem xətası [və onların həlli]

… və bəli, hər şey uçdu!

P.S.

Xətaların toplanması və məqalənin hazırlanması üçün böyük təşəkkürümü bildirmək istəyirəm, xüsusilə də R&D komandamızdan həmkarım Andrey Klimentyevə (zuzzas).

P.P.S.

Blogumuzda oxuyun:

Mənbə: habr.com

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster