6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Kubernetesin tuotannossa käytettyjen vuosien aikana olemme keränneet monia mielenkiintoisia tarinoita siitä, kuinka järjestelmän eri komponenttien virheet johtivat epämiellyttäviin ja/tai käsittämättömiin seurauksiin, jotka vaikuttavat säiliöiden ja koteloiden toimintaan. Tässä artikkelissa olemme valinneet joitain yleisimmistä tai mielenkiintoisimmista. Vaikka et koskaan olisikaan onnekas kohtaamaan tällaisia ​​tilanteita, tällaisten lyhyiden dekkarien lukeminen - varsinkin "ensimmäisestä" - on aina mielenkiintoista, eikö niin?

Tarina 1. Supercronic ja Docker roikkuvat

Yhdessä klusterissa saimme ajoittain jäätyneen Dockerin, joka häiritsi klusterin normaalia toimintaa. Samaan aikaan Dockerin lokeissa havaittiin seuraavaa:

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

…

Meitä kiinnostaa eniten tästä virheestä seuraava viesti: pthread_create failed: No space left on device. Pikatutkimus dokumentointi selitti, että Docker ei pystynyt muodostamaan prosessia, minkä vuoksi se ajoittain jäätyi.

Seurannassa seuraava kuva vastaa tapahtuvaa:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Samanlainen tilanne havaitaan muissa solmuissa:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Samoissa solmuissa näemme:

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>

Kävi ilmi, että tämä käyttäytyminen on seurausta podin kanssa työskentelemisestä superkrooninen (Go-apuohjelma, jota käytämme cron-töiden suorittamiseen podissa):

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

Ongelma on tämä: kun tehtävä suoritetaan supercronicissa, prosessi synnyttää sen ei voi lopettaa oikein, muuttumassa zombie.

Huomata: Tarkemmin sanottuna prosesseja synnyttävät cron-tehtävät, mutta supercronic ei ole aloitusjärjestelmä, eikä se voi "omaksua" prosesseja, joita sen lapset synnyttivät. Kun SIGHUP- tai SIGTERM-signaaleja nostetaan, niitä ei välitetä lapsiprosesseille, minkä seurauksena lapsiprosessit eivät pääty ja pysyvät zombie-tilassa. Tästä kaikesta voit lukea lisää mm sellainen artikkeli.

On olemassa pari tapaa ratkaista ongelmia:

  1. Väliaikaisena kiertotapana lisää PID-tunnusten määrää järjestelmässä yhdellä kertaa:
           /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. Tai käynnistä tehtäviä supercronicissa ei suoraan, vaan käyttämällä samaa tini, joka pystyy lopettamaan prosessit oikein eikä synnytä zombeja.

Tarina 2. "Zombit" c-ryhmää poistettaessa

Kubelet alkoi kuluttaa paljon prosessoria:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Kukaan ei pidä tästä, joten aseistauduimme teho ja alkoi käsitellä ongelmaa. Tutkinnan tulokset olivat seuraavat:

  • Kubelet käyttää yli kolmanneksen CPU-ajastaan ​​muistitietojen hakemiseen kaikista c-ryhmistä:

    6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

  • Ytimen kehittäjien postituslistalta löydät keskustelua ongelmasta. Lyhyesti sanottuna pointti menee tähän: erilaisia ​​tmpfs-tiedostoja ja muita vastaavia asioita ei poisteta kokonaan järjestelmästä c-ryhmää poistettaessa ns memcg zombie. Ennemmin tai myöhemmin ne poistetaan sivun välimuistista, mutta palvelimella on paljon muistia ja ydin ei näe järkeä tuhlata aikaa niiden poistamiseen. Siksi niitä kasaantuu jatkuvasti. Miksi tämä edes tapahtuu? Tämä on palvelin, jossa on cron-työt ja joka luo jatkuvasti uusia työpaikkoja ja niiden mukana uusia podeja. Siten niissä oleville säilöille luodaan uusia c-ryhmiä, jotka poistetaan pian.
  • Miksi cAdvisor kubeletissa tuhlaa niin paljon aikaa? Tämä on helppo nähdä yksinkertaisimmalla suorituksella time cat /sys/fs/cgroup/memory/memory.stat. Jos terveellä koneella toiminta kestää 0,01 sekuntia, niin ongelmallisella cron02:lla se kestää 1,2 sekuntia. Asia on siinä, että cAdvisor, joka lukee dataa sysfsistä hyvin hitaasti, yrittää ottaa huomioon zombie-c-ryhmissä käytetyn muistin.
  • Zombien poistamiseksi väkisin yritimme tyhjentää välimuistit LKML:n suositusten mukaisesti: sync; echo 3 > /proc/sys/vm/drop_caches, - mutta ydin osoittautui monimutkaisemmaksi ja kaatui auton.

Mitä tehdä? Ongelmaa korjataan (tehdä, ja kuvaus löytyy vapauttaa viesti) päivittämällä Linux-ytimen versioon 4.16.

Historia 3. Systemd ja sen kiinnitys

Jälleen kubelet kuluttaa liian paljon resursseja joissakin solmuissa, mutta tällä kertaa se kuluttaa liikaa muistia:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Kävi ilmi, että Ubuntu 16.04:ssä käytetyssä systemd:ssä on ongelma, ja se ilmenee hallittaessa liitoksia, jotka on luotu yhteyttä varten subPath ConfigMapista tai salaisuudesta. Sen jälkeen, kun kotelo on suorittanut työnsä systemd-palvelu ja sen huoltokiinnitys säilyvät järjestelmässä. Ajan myötä niistä kertyy valtava määrä. Tästä aiheesta on jopa ongelmia:

  1. #5916;
  2. kubernetes #57345.

...joista viimeinen viittaa PR:ään systemd:ssä: #7811 (ongelma järjestelmässä - #7798).

Ongelmaa ei enää ole Ubuntu 18.04:ssä, mutta jos haluat jatkaa Ubuntu 16.04:n käyttöä, tämän aiheen kiertotapamme saattaa olla hyödyllinen.

Joten teimme seuraavan DaemonSetin:

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

... ja se käyttää seuraavaa komentosarjaa:

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

... ja se toimii 5 minuutin välein käyttämällä aiemmin mainittua supercronic-ohjelmaa. Sen Docker-tiedosto näyttää tältä:

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

Tarina 4. Kilpailukyky podeja ajoitettaessa

Huomattiin, että: jos meillä on pod asetettu solmuun ja sen kuva pumpataan ulos hyvin pitkään, niin toinen samaan solmuun " osuva pod" yksinkertaisesti ei ala vetää kuvaa uudesta kotelosta. Sen sijaan se odottaa, kunnes edellisen kotelon kuva vedetään. Tämän seurauksena pod, joka oli jo ajoitettu ja jonka kuva olisi voitu ladata minuutissa, päätyy tilaan containerCreating.

Tapahtumat näyttävät suunnilleen tältä:

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

On käynyt ilmi, että yksi kuva hitaasta rekisteristä voi estää käyttöönoton solmukohtaisesti.

Valitettavasti tilanteesta ei ole montaa ulospääsyä:

  1. Yritä käyttää Docker-rekisteriäsi suoraan klusterissa tai suoraan klusterin kanssa (esimerkiksi GitLab-rekisteri, Nexus jne.);
  2. Käytä apuohjelmia, kuten Kraken.

Tarina 5. Solmut roikkuvat muistin puutteen vuoksi

Eri sovellusten toiminnan aikana törmäsimme myös tilanteeseen, jossa solmu lakkaa olemasta täysin saavutettavissa: SSH ei vastaa, kaikki valvontademonit putoavat, ja sitten lokeissa ei ole mitään (tai melkein mitään) poikkeavaa.

Kerron sinulle kuvissa käyttämällä esimerkkiä yhdestä solmusta, jossa MongoDB toimi.

Tältä ylhäällä näyttää до onnettomuudet:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Ja näin - jälkeen onnettomuudet:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Seurannassa tapahtuu myös jyrkkä hyppy, jossa solmu lakkaa olemasta saatavilla:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

Näin ollen kuvakaappauksista on selvää, että:

  1. Koneen RAM-muisti on lähellä loppua;
  2. RAM-muistin kulutuksessa on jyrkkä hyppy, jonka jälkeen pääsy koko koneeseen estetään äkillisesti;
  3. Mongoon saapuu suuri tehtävä, joka pakottaa DBMS-prosessin käyttämään enemmän muistia ja lukemaan aktiivisesti levyltä.

Osoittautuu, että jos Linuxista loppuu vapaa muisti (muistin paine alkaa) eikä vaihtoa ole, до Kun OOM-tappaja saapuu, sivujen välimuistiin heittämisen ja takaisin levylle kirjoittamisen välillä voi syntyä tasapaino. Tämän tekee kswapd, joka vapauttaa rohkeasti mahdollisimman monta muistisivua myöhempää jakelua varten.

Valitettavasti suuri I/O-kuorma yhdistettynä pieneen määrään vapaata muistia, kswapd:sta tulee koko järjestelmän pullonkaula, koska ne on sidottu siihen kaikki muistisivujen varaukset (sivuvirheet) järjestelmässä. Tämä voi jatkua hyvin pitkään, jos prosessit eivät enää halua käyttää muistia, vaan ne on kiinnitetty OOM-killer-kuilun reunaan.

Luonnollinen kysymys kuuluu: miksi OOM-tappaja tulee niin myöhään? Nykyisessä iteraatiossaan OOM-killer on äärimmäisen tyhmä: se tappaa prosessin vain, kun yritys varata muistisivu epäonnistuu, ts. jos sivuvika epäonnistuu. Tätä ei tapahdu pitkään aikaan, koska kswapd vapauttaa rohkeasti muistisivuja ja tyhjentää sivuvälimuistin (itse asiassa koko levyn I/O:n järjestelmässä) takaisin levylle. Voit lukea yksityiskohtaisemmin kuvauksen vaiheista, joita tarvitaan tällaisten ytimen ongelmien poistamiseksi täällä.

Tämä käytös pitäisi parantaa Linux-ytimen 4.6+ kanssa.

Tarina 6. Palot juuttuvat Odottaa-tilaan

Joissakin klustereissa, joissa toimii todella paljon paloja, aloimme huomata, että useimmat niistä "roikkuvat" hyvin pitkään osavaltiossa Pending, vaikka itse Docker-säilöt ovat jo käynnissä solmuissa ja niitä voidaan käsitellä manuaalisesti.

Lisäksi sisään describe ei ole mitään vikaa:

  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

Pienen kaivamisen jälkeen teimme oletuksen, että kubeletillä ei yksinkertaisesti ole aikaa lähettää kaikkea tietoa podien tilasta ja elävyydestä/valmiustesteistä API-palvelimelle.

Ja tutkittuamme apua, löysimme seuraavat parametrit:

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

Nähtynä, oletusarvot ovat melko pieniä, ja 90 %:ssa ne kattavat kaikki tarpeet... Meidän tapauksessamme tämä ei kuitenkaan riittänyt. Siksi asetamme seuraavat arvot:

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

... ja käynnistimme kubeletit uudelleen, minkä jälkeen näimme seuraavan kuvan API-palvelimen kutsujen kaavioissa:

6 viihdyttävää järjestelmävirhettä Kubernetesin toiminnassa [ja niiden ratkaisu]

... ja kyllä, kaikki alkoi lentää!

PS.

Esitän syvän kiitokseni heidän avustaan ​​virheiden keräämisessä ja tämän artikkelin valmistelussa yrityksemme lukuisille insinööreille ja erityisesti kollegalleni T&K-tiimistämme Andrei Klimentjeville (zuzzat).

PPS

Lue myös blogistamme:

Lähde: will.com

Lisää kommentti