6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

Duranti l'anni di usu di Kubernetes in a produzzione, avemu accumulatu parechje storie interessanti di cumu i bug in diversi cumpunenti di u sistema anu purtatu à cunsequenze spiacevoli è / o incomprensibili chì affettanu l'operazione di cuntenituri è pods. In questu articulu avemu fattu una selezzione di alcuni di i più cumuni o interessanti. Ancu s'è ùn avete mai a furtuna di scuntrà tali situazioni, a lettura di tali brevi storie di detective - in particulare "di prima mano" - hè sempre interessante, ùn hè micca?...

Storia 1. Supercronic è Docker appiccicatu

Nantu à unu di i clusters, avemu ricevutu periodicamente un Docker congelatu, chì interferiscenu cù u funziunamentu normale di u cluster. À u listessu tempu, i seguenti sò stati osservati in i logs 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

…

Ciò chì ci interessa più nantu à questu errore hè u missaghju: pthread_create failed: No space left on device. Studiu rapidu ducumentazione spiegò chì Docker ùn pudia micca fork un prucessu, chì hè per quessa ch'ellu si congelava periodicamente.

In u monitoraghju, a seguente stampa currisponde à ciò chì succede:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

Una situazione simile hè osservata in altri nodi:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

À i stessi nodi vedemu:

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>

Hè risultatu chì stu cumpurtamentu hè una cunsequenza di u pod di travaglià supercronicu (una utilità Go chì usemu per eseguisce cron jobs in pods):

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

U prublema hè questu: quandu un compitu hè eseguitu in supercronicu, u prucessu hà generatu da questu ùn pò micca finisce currettamente, si trasforma in zombie.

Vita: Per esse più precisu, i prucessi sò spawned by cron tasks, ma supercronic ùn hè micca un sistema init è ùn pò micca "aduttà" prucessi chì i so figlioli spawned. Quandu i signali SIGHUP o SIGTERM sò risuscitati, ùn sò micca trasmessi à i prucessi di u zitellu, u risultatu in i prucessi di u zitellu ùn finiscinu è restanu in u statu di zombie. Pudete leghje più nantu à tuttu questu, per esempiu, in un tali articulu.

Ci hè parechje manere di risolve i prublemi:

  1. Cum'è una soluzione temporale - aumenta u numeru di PID in u sistema in un unicu puntu in u tempu:
           /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. O lanciari compiti in supercronicu micca direttamente, ma cù u listessu tini, chì hè capace di finisce i prucessi currettamente è micca spawn zombies.

Storia 2. "Zombies" quandu sguassate un cgroup

Kubelet hà cuminciatu à cunsumà assai CPU:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

Nimu ùn piacerà questu, cusì avemu armatu perfetta è hà cuminciatu à trattà cù u prublema. I risultati di l'inchiesta eranu i seguenti:

  • Kubelet spende più di un terzu di u so tempu CPU trattendu dati di memoria da tutti i cgroups:

    6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

  • In a mailing list di i sviluppatori di u kernel pudete truvà discussione di u prublema. In breve, u puntu si riduce à questu: Diversi schedarii tmpfs è altre cose simili ùn sò micca sguassate completamente da u sistema quandu sguassate un cgroup, u cusì chjamatu memcg Zombie. Prima o dopu seranu sguassati da a cache di a pagina, ma ci hè assai memoria in u servitore è u kernel ùn vede micca u puntu di perde u tempu per sguassà. Hè per quessa chì cuntinueghjanu à accumulà. Perchè ancu questu succede? Questu hè un servitore cù cron jobs chì crea constantemente novi impieghi, è cun elli novi pods. Cusì, novi cgroups sò creati per cuntenituri in elli, chì sò prontu sguassati.
  • Perchè cAdvisor in kubelet perde tantu tempu? Questu hè faciule da vede cù l'esekzione più simplice time cat /sys/fs/cgroup/memory/memory.stat. Se nantu à una macchina sana, l'operazione dura 0,01 seconde, dopu nantu à u cron02 problematicu ci vole 1,2 seconde. A cosa hè chì cAdvisor, chì leghje e dati da sysfs assai lentamente, prova di piglià in contu a memoria utilizata in i gruppi di zombie.
  • Per caccià cù forza i zombies, avemu pruvatu à sguassà i cache cum'è cunsigliatu in LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - ma u kernel hè diventatu più cumplicatu è hà sbattutu a vittura.

Chì fà ? U prublema hè risolta (impegnà, è per una descrizzione vede messagiu liberatu) aghjurnà u kernel Linux à a versione 4.16.

Storia 3. Systemd è a so muntagna

Di novu, u kubelet cunsuma troppu risorse nantu à certi nodi, ma sta volta hè cunsuma troppu memoria:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

Hè risultatu chì ci hè un prublema in systemd utilizatu in Ubuntu 16.04, è si trova quandu si gestisce i monti chì sò creati per a cunnessione. subPath da ConfigMap's o secret's. Dopu chì u pod hà finitu u so travagliu u serviziu systemd è a so muntagna di serviziu restanu in sistema. Au fil du temps, un grand nombre d'entre elles s'accumulent. Ci sò ancu prublemi nantu à questu tema:

  1. #5916;
  2. kubernetes #57345.

...l'ultimu di quale si riferisce à u PR in systemd: #7811 (issue in systemd - #7798).

U prublema ùn esiste più in Ubuntu 18.04, ma sè vo vulete cuntinuà aduprà Ubuntu 16.04, pudete truvà a nostra soluzione nantu à questu tema utile.

Allora avemu fattu u DaemonSet seguente:

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

... è usa u script seguente:

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

... è corre ogni 5 minuti utilizendu u supercronicu citatu prima. U so Dockerfile s'assumiglia cusì:

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

Storia 4. Competitività quandu pianificate pods

Hè statu nutatu chì: se avemu un podu pusatu nantu à un node è a so maghjina hè pompata per un tempu assai longu, allora un altru pod chì "colpà" u stessu node serà simplicemente. ùn principia micca à tirà l'imaghjini di u novu pod. Invece, aspetta finu à chì l'imaghjini di u pod precedente hè tiratu. In u risultatu, un pod chì era digià pianificatu è chì a so maghjina puderia esse scaricata in solu un minutu finirà in u statutu di containerCreating.

L'avvenimenti pareranu cusì cusì:

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

Questu hè chì una sola maghjina da un registru lento pò bluccà a implementazione per node.

Sfortunatamente, ùn ci sò parechje manere di esce da a situazione:

  1. Pruvate aduprà u vostru Docker Registru direttamente in u cluster o direttamente cù u cluster (per esempiu, GitLab Registru, Nexus, etc.);
  2. Aduprate utilità cum'è kraken.

Storia 5. Nodes hang per mancanza di memoria

Duranti u funziunamentu di diverse applicazioni, avemu ancu scontru una situazione induve un node cessà cumplettamente di esse accessibile: SSH ùn risponde micca, tutti i demoni di surviglianza cascanu, è dopu ùn ci hè nunda (o quasi nunda) anomalu in i logs.

Vi dicu in l'imaghjini cù l'esempiu di un node induve MongoDB hà funzionatu.

Questu hè ciò chì pare in cima à accidenti:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

È cusì - после accidenti:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

In u monitoraghju, ci hè ancu un saltu forte, à u quale u node cessa di esse dispunibule:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

Cusì, da i screenshots hè chjaru chì:

  1. A RAM in a macchina hè vicinu à a fine;
  2. Ci hè un forte saltu in u cunsumu di RAM, dopu chì l'accessu à tutta a macchina hè abruptamente disattivatu;
  3. Un grande compitu ghjunghje à Mongo, chì forza u prucessu DBMS à utilizà più memoria è leghje attivamente da u discu.

Risulta chì se Linux manca di memoria libera (a pressione di memoria si stabilisce) è ùn ci hè micca swap, allora à Quandu l'assassinu di l'OOM ghjunghje, un attu di equilibriu pò esse nascendu trà e pagine in u cache di a pagina è scrivele in u discu. Questu hè fattu da kswapd, chì bravamente libera quantunque pagine di memoria pussibule per a distribuzione successiva.

Sfortunatamente, cù una grande carica I / O accumpagnata da una piccula quantità di memoria libera, kswapd diventa u collu di buttiglia di tuttu u sistema, perchè sò ligati à questu tutte e allocations (page faults) di pagine di memoria in u sistema. Questu pò andà per un tempu assai longu, se i prucessi ùn volenu più usà a memoria, ma sò fissati à a fine di l'abissu OOM-assassinu.

A quistione naturale hè: perchè l'assassinu OOM vene cusì tardi? In a so iterazione attuale, l'assassinu OOM hè estremamente stupidu: ucciderà u prucessu solu quandu u tentativu di assignà una pagina di memoria falla, i.e. se l'errore di a pagina falla. Questu ùn succede micca per un bellu pezzu, perchè kswapd libera valentemente e pagine di memoria, scaricate a cache di a pagina (l'interu discu I / O in u sistema, in fattu) torna à u discu. In più detail, cù una descrizzione di i passi necessarii per eliminà tali prublemi in u kernel, pudete leghje ccà.

Stu cumpurtamentu duverebbe migliurà cù u kernel Linux 4.6+.

Storia 6. I baccelli si fermanu in u statu Pending

In certi clusters, in quale ci sò veramente assai baccelli chì operanu, avemu cuminciatu à nutà chì a maiò parte di elli "pennu" per un tempu assai longu in u statu. Pending, ancu s'è i cuntenituri Docker stessi sò digià in esecuzione nantu à i nodi è ponu esse travagliatu manualmente.

Inoltre, in describe ùn ci hè nunda di male:

  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

Dopu qualchì scavà, avemu fattu l'assunzione chì u kubelet simpricimenti ùn hà micca tempu per mandà tutte l'infurmazioni nantu à u statu di i pods è e teste di vivacità / prontezza à u servitore API.

È dopu avè studiatu l'aiutu, truvamu i seguenti parametri:

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

Comu vistu, i valori predeterminati sò abbastanza chjuchi, è in 90% coprenu tutti i bisogni... Tuttavia, in u nostru casu, questu ùn era micca abbastanza. Dunque, avemu stabilitu i seguenti valori:

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

... è ripigliate i kubelets, dopu chì avemu vistu a seguente stampa in i grafici di chjamate à u servitore API:

6 bug di sistema divertenti in l'operazione di Kubernetes [è a so suluzione]

... è iè, tuttu hà cuminciatu à vulà !

PS

Per u so aiutu in a cullizzioni di bug è a preparazione di stu articulu, aghju espresu a mo profonda gratitudine à i numerosi ingegneri di a nostra cumpagnia, è soprattuttu à u mo cullega da a nostra squadra di R&D Andrey Klimentyev (zuzza).

PPS

Leghjite puru nant'à u nostru blog:

Source: www.habr.com

Add a comment