6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

Al llarg dels anys d'utilitzar Kubernetes en producció, hem acumulat moltes històries interessants de com els errors en diversos components del sistema van provocar conseqüències desagradables i/o incomprensibles que afectaven el funcionament dels contenidors i les beines. En aquest article hem fet una selecció d'alguns dels més comuns o interessants. Fins i tot si mai no tens la sort de trobar-te amb aquestes situacions, llegir sobre històries de detectius tan breus, especialment "de primera mà", sempre és interessant, oi?...

Història 1. Supercronic i Docker penjats

En un dels clústers, rebíem periòdicament un Docker congelat, que interferia amb el funcionament normal del clúster. Al mateix temps, es va observar el següent als registres de 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

…

El que més ens interessa d'aquest error és el missatge: pthread_create failed: No space left on device. Estudi ràpid documentació va explicar que Docker no podia bifurcar un procés, motiu pel qual es congelava periòdicament.

En el seguiment, la imatge següent correspon al que està passant:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

Una situació similar s'observa en altres nodes:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

Als mateixos nodes veiem:

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>

Va resultar que aquest comportament és una conseqüència del treball amb la beina supercrònica (una utilitat Go que fem servir per executar treballs cron en 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>
…

El problema és aquest: quan una tasca s'executa en supercrònica, el procés genera no pot acabar correctament, convertint-se en zombi.

Nota: Per ser més precisos, els processos es generen per tasques cron, però supercronic no és un sistema d'inici i no pot "adoptar" els processos que van generar els seus fills. Quan s'eleven els senyals SIGHUP o SIGTERM, no es transmeten als processos fills, cosa que fa que els processos fills no acabin i romanguin en estat zombi. Podeu llegir més sobre tot això, per exemple, a tal article.

Hi ha un parell de maneres de resoldre problemes:

  1. Com a solució temporal: augmenteu el nombre de PID al sistema en un sol moment:
           /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 llançar tasques en supercronic no directament, sinó utilitzant el mateix tini, que és capaç d'acabar els processos correctament i no generar zombis.

Història 2. "Zombis" quan s'elimina un grup c

Kubelet va començar a consumir molta CPU:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

Això no li agradarà a ningú, així que ens vam armar perfecte i va començar a tractar el problema. Els resultats de la investigació van ser els següents:

  • Kubelet gasta més d'un terç del seu temps de CPU extreu dades de memòria de tots els grups c:

    6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

  • A la llista de correu dels desenvolupadors del nucli es pot trobar discussió del problema. En resum, la qüestió es redueix a això: diversos fitxers tmpfs i altres coses semblants no s'eliminen completament del sistema en eliminar un cgroup, l'anomenat memcg zombi. Tard o d'hora s'eliminaran de la memòria cau de la pàgina, però hi ha molta memòria al servidor i el nucli no veu el sentit de perdre el temps en esborrar-los. Per això es continuen acumulant. Per què passa això? Aquest és un servidor amb tasques cron que constantment crea nous llocs de treball, i amb ells nous pods. Així, es creen nous cgroups per als contenidors que hi ha, que aviat s'eliminen.
  • Per què cAdvisor a kubelet perd tant de temps? Això és fàcil de veure amb l'execució més senzilla time cat /sys/fs/cgroup/memory/memory.stat. Si en una màquina sana l'operació triga 0,01 segons, a la cron02 problemàtica triga 1,2 segons. El cas és que cAdvisor, que llegeix dades de sysfs molt lentament, intenta tenir en compte la memòria utilitzada en els cgroups zombies.
  • Per eliminar els zombis amb força, hem provat d'esborrar la memòria cau tal com es recomana a LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - però el nucli va resultar més complicat i va estavellar el cotxe.

Què fer? El problema s'està solucionant (comprometre's, i per a una descripció vegeu missatge de llançament) actualitzant el nucli de Linux a la versió 4.16.

Història 3. Systemd i el seu muntatge

Un cop més, el kubelet està consumint massa recursos en alguns nodes, però aquesta vegada està consumint massa memòria:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

Va resultar que hi ha un problema en systemd utilitzat a Ubuntu 16.04 i es produeix quan es gestionen muntatges que es creen per a la connexió subPath des de ConfigMaps o secrets. Un cop el pod ha completat el seu treball el servei systemd i el seu muntatge de servei romanen en sistema. Amb el temps, s'acumulen un gran nombre. Fins i tot hi ha problemes sobre aquest tema:

  1. #5916;
  2. kubernetes #57345.

...l'últim dels quals es refereix al PR a systemd: # 7811 (problema a systemd - # 7798).

El problema ja no existeix a Ubuntu 18.04, però si voleu continuar utilitzant Ubuntu 16.04, és possible que la nostra solució alternativa sobre aquest tema sigui útil.

Així que vam fer el següent 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

... i utilitza el següent script:

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

... i funciona cada 5 minuts utilitzant el supercronic esmentat anteriorment. El seu Dockerfile té aquest aspecte:

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

Història 4. Competitivitat a l'hora de programar pods

Es va observar que: si tenim una beina col·locada en un node i la seva imatge es bombeja durant molt de temps, llavors una altra beina que "xoca" el mateix node simplement no comença a treure la imatge de la nova beina. En comptes d'això, espera fins que es tregui la imatge de la beina anterior. Com a resultat, un pod que ja estava programat i la imatge del qual s'hauria pogut descarregar en només un minut acabarà en l'estat de containerCreating.

Els esdeveniments tindran un aspecte com aquest:

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

Resulta que una sola imatge d'un registre lent pot bloquejar el desplegament per node.

Malauradament, no hi ha moltes maneres de sortir de la situació:

  1. Intenteu utilitzar el vostre registre Docker directament al clúster o directament amb el clúster (per exemple, GitLab Registry, Nexus, etc.);
  2. Utilitzeu utilitats com ara kraken.

Història 5. Els nodes es pengen per falta de memòria

Durant el funcionament de diverses aplicacions, també ens hem trobat amb una situació en què un node deixa de ser completament accessible: SSH no respon, tots els dimonis de monitorització cauen i, aleshores, no hi ha res (o gairebé res) anòmal als registres.

T'ho diré en imatges utilitzant l'exemple d'un node on funcionava MongoDB.

Això és el que sembla al cim до accidents:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

I així - després accidents:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

En el seguiment, també hi ha un salt brusc, en què el node deixa d'estar disponible:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

Així, a partir de les captures de pantalla queda clar que:

  1. La memòria RAM de la màquina està a prop del final;
  2. Hi ha un fort salt en el consum de memòria RAM, després del qual l'accés a tota la màquina es desactiva bruscament;
  3. Arriba una gran tasca a Mongo, que obliga el procés DBMS a utilitzar més memòria i llegir activament des del disc.

Resulta que si Linux es queda sense memòria lliure (la pressió de memòria s'instal·la) i no hi ha intercanvi, aleshores до Quan arriba l'assassí OOM, pot sorgir un acte d'equilibri entre llançar pàgines a la memòria cau de la pàgina i escriure-les de nou al disc. Això ho fa kswapd, que allibera de valent tantes pàgines de memòria com sigui possible per a la seva posterior distribució.

Malauradament, amb una gran càrrega d'E/S juntament amb una petita quantitat de memòria lliure, kswapd es converteix en el coll d'ampolla de tot el sistema, perquè hi estan lligats tots assignacions (errors de pàgina) de pàgines de memòria del sistema. Això pot durar molt de temps si els processos ja no volen utilitzar la memòria, però es fixen a la vora de l'abisme assassí OOM.

La pregunta natural és: per què l'assassí OOM arriba tan tard? En la seva iteració actual, l'assassí OOM és extremadament estúpid: matarà el procés només quan falla l'intent d'assignar una pàgina de memòria, és a dir. si l'error de pàgina falla. Això no passa durant molt de temps, perquè kswapd allibera amb valentia les pàgines de memòria, abocant la memòria cau de pàgines (de fet, tota l'E/S del disc del sistema) al disc. Amb més detall, amb una descripció dels passos necessaris per eliminar aquests problemes al nucli, podeu llegir aquí.

Aquest comportament hauria de millorar amb el nucli Linux 4.6+.

Història 6. Les beines s'enganxen a l'estat Pendent

En alguns clústers, en els quals realment hi ha moltes beines en funcionament, vam començar a notar que la majoria d'ells "pengen" durant molt de temps a l'estat. Pending, tot i que els mateixos contenidors de Docker ja s'estan executant als nodes i es poden treballar manualment.

A més, en describe no hi ha res dolent:

  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

Després d'excavar una mica, vam suposar que el kubelet simplement no té temps d'enviar tota la informació sobre l'estat dels pods i les proves de vivacitat/preparació al servidor de l'API.

I després d'estudiar l'ajuda, hem trobat els paràmetres següents:

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

Com es veu, els valors per defecte són força petits, i en un 90% cobreixen totes les necessitats... No obstant això, en el nostre cas això no va ser suficient. Per tant, establim els valors següents:

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

... i vam reiniciar els kubelets, després de la qual cosa vam veure la següent imatge als gràfics de trucades al servidor de l'API:

6 errors del sistema entretinguts en el funcionament de Kubernetes [i la seva solució]

... i sí, tot va començar a volar!

PS

Per la seva ajuda per recollir errors i preparar aquest article, expresso el meu profund agraïment als nombrosos enginyers de la nostra empresa, i especialment al meu col·lega del nostre equip d'R+D Andrey Klimentyev (zuzzes).

PPS

Llegeix també al nostre blog:

Font: www.habr.com

Afegeix comentari