6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

Gjennom Ärene med bruk av Kubernetes i produksjon, har vi samlet mange interessante historier om hvordan feil i ulike systemkomponenter fÞrte til ubehagelige og/eller uforstÄelige konsekvenser som pÄvirket driften av containere og pods. I denne artikkelen har vi laget et utvalg av noen av de mest vanlige eller interessante. Selv om du aldri er heldig nok til Ä mÞte slike situasjoner, er det alltid interessant Ä lese om slike korte detektivhistorier - spesielt "fÞrstehÄnds" - ikke sant?

Historie 1. Supercronic og Docker hengende

PĂ„ en av klyngene mottok vi med jevne mellomrom en frossen Docker, som forstyrret den normale funksjonen til klyngen. Samtidig ble fĂžlgende observert i Docker-loggene:

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




Det som interesserer oss mest med denne feilen er meldingen: pthread_create failed: No space left on device. Rask studie dokumentasjon forklarte at Docker ikke kunne splitte en prosess, og derfor frĂžs den med jevne mellomrom.

Ved overvÄking tilsvarer fÞlgende bilde det som skjer:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

En lignende situasjon observeres pÄ andre noder:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

PĂ„ de samme nodene ser vi:

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>

Det viste seg at denne oppfĂžrselen er en konsekvens av at poden jobber med superkronisk (et Go-verktĂžy som vi bruker til Ă„ kjĂžre cron-jobber i 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>



Problemet er dette: nÄr en oppgave kjÞres i supercronic, ble prosessen skapt av den kan ikke avsluttes riktig, blir til zombie.

Note: For Ä vÊre mer presis, prosesser skapt av cron-oppgaver, men supercronic er ikke et init-system og kan ikke "adoptere" prosesser som barna har skapt. NÄr SIGHUP- eller SIGTERM-signaler heves, sendes de ikke videre til barneprosessene, noe som resulterer i at barneprosessene ikke avsluttes og forblir i zombiestatus. Alt dette kan du lese mer om, for eksempel i en slik artikkel.

Det er et par mÄter Ä lÞse problemer pÄ:

  1. Som en midlertidig lĂžsning – Ăžk antallet PID-er i systemet pĂ„ et enkelt tidspunkt:
           /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. Eller start oppgaver i supercronic ikke direkte, men ved Ä bruke det samme tini, som er i stand til Ä avslutte prosesser pÄ riktig mÄte og ikke skape zombier.

Historie 2. "Zombies" nÄr du sletter en cgroup

Kubelet begynte Ă„ bruke mye CPU:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

Ingen vil like dette, sÄ vi bevÊpnet oss perf og begynte Ä hÄndtere problemet. Resultatene av undersÞkelsen var som fÞlger:

  • Kubelet bruker mer enn en tredjedel av CPU-tiden sin pĂ„ Ă„ trekke minnedata fra alle cgroups:

    6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

  • I kjerneutviklernes e-postliste kan du finne diskusjon av problemet. Kort fortalt kommer poenget ned til dette: ulike tmpfs-filer og andre lignende ting fjernes ikke fullstendig fra systemet ved sletting av en cgruppe, den sĂ„kalte memcg zombie. FĂžr eller siden vil de bli slettet fra sidebufferen, men det er mye minne pĂ„ serveren og kjernen ser ikke poenget med Ă„ kaste bort tid pĂ„ Ă„ slette dem. Det er derfor de stadig hoper seg opp. Hvorfor skjer dette i det hele tatt? Dette er en server med cron-jobber som stadig skaper nye jobber, og med dem nye poder. Dermed opprettes det nye cgroups for containere i dem, som snart blir slettet.
  • Hvorfor kaster cAdvisor i kubelet bort sĂ„ mye tid? Dette er lett Ă„ se med den enkleste utfĂžrelse time cat /sys/fs/cgroup/memory/memory.stat. Hvis operasjonen pĂ„ en frisk maskin tar 0,01 sekunder, tar den pĂ„ den problematiske cron02 1,2 sekunder. Saken er at cAdvisor, som leser data fra sysfs veldig sakte, prĂžver Ă„ ta hensyn til minnet som brukes i zombie cgroups.
  • For Ă„ fjerne zombier med makt, prĂžvde vi Ă„ tĂžmme cacher som anbefalt i LKML: sync; echo 3 > /proc/sys/vm/drop_caches,- men kjernen viste seg Ă„ vĂŠre mer komplisert og krasjet bilen.

Hva Ä gjÞre? Problemet blir fikset (begÄ, og for en beskrivelse se slipp melding) kjerneoppdatering Linux opp til versjon 4.16.

Historie 3. Systemd og dets montering

Igjen, kubelet bruker for mange ressurser pÄ noen noder, men denne gangen bruker den for mye minne:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

Det viste seg at det var et problem med systemd som ble brukt i Ubuntu 16.04, og det skjer nÄr du administrerer monteringer som er opprettet for tilkobling subPath fra ConfigMap's eller secret's. Etter at poden har fullfÞrt arbeidet systemd-tjenesten og dens servicemontering gjenstÄr i systemet. Over tid akkumuleres et stort antall av dem. Det er til og med problemer om dette emnet:

  1. #5916;
  2. kubernetes #57345.

...den siste refererer til PR i systemd: #7811 (problem i systemd - #7798).

Problemet er ikke lenger der Ubuntu 18.04, men hvis du vil fortsette Ä bruke Ubuntu 16.04, kan det hende du synes lÞsningen vÄr pÄ dette emnet er nyttig.

SĂ„ vi laget fĂžlgende 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

... og den bruker fĂžlgende skript:

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

... og den kjĂžrer hvert 5. minutt ved Ă„ bruke den tidligere nevnte supercronic. Dockerfilen ser slik ut:

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

Historie 4. Konkurranseevne nÄr du planlegger pods

Det ble lagt merke til at: hvis vi har en pod plassert pÄ en node og bildet pumpes ut i veldig lang tid, vil en annen pod som "treffer" den samme noden ganske enkelt begynner ikke Ä trekke bildet av den nye poden. I stedet venter den til bildet av den forrige poden er trukket. Som et resultat vil en pod som allerede var planlagt og hvis bilde kunne ha blitt lastet ned pÄ bare et minutt havne i statusen til containerCreating.

Arrangementene vil se omtrent slik ut:

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

Det viser seg at et enkelt bilde fra et tregt register kan blokkere distribusjon per node.

Dessverre er det ikke mange veier ut av situasjonen:

  1. PrĂžv Ă„ bruke Docker Registry direkte i klyngen eller direkte med klyngen (for eksempel GitLab Registry, Nexus, etc.);
  2. Bruk verktĂžy som f.eks Kraken.

Historie 5. Noder henger pÄ grunn av mangel pÄ hukommelse

Under driften av forskjellige applikasjoner mÞtte vi ogsÄ en situasjon der en node slutter Ä vÊre tilgjengelig fullstendig: SSH reagerer ikke, alle overvÄkingsdemoner faller av, og sÄ er det ingenting (eller nesten ingenting) unormalt i loggene.

Jeg skal fortelle deg i bilder ved Ä bruke eksemplet pÄ en node der MongoDB fungerte.

Slik ser det ut pÄ toppen til ulykker:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

Og sÄnn - etter ulykker:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

I overvÄking er det ogsÄ et skarpt hopp, der noden slutter Ä vÊre tilgjengelig:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

SĂ„ fra skjermbildene er det klart at:

  1. RAM-en pÄ maskinen er nÊr slutten;
  2. Det er et kraftig hopp i RAM-forbruket, hvoretter tilgangen til hele maskinen brÄtt deaktiveres;
  3. En stor oppgave kommer til Mongo, som tvinger DBMS-prosessen til Ă„ bruke mer minne og aktivt lese fra disk.

Det viser seg at hvis i Linux ledig minne gÄr tom (minnetrykk oppstÄr) og det ikke er noen bytte, da til NÄr OOM-morderen ankommer, kan det oppstÄ en balansegang mellom Ä kaste sider inn i sidebufferen og skrive dem tilbake til disken. Dette gjÞres av kswapd, som modig frigjÞr sÄ mange minnesider som mulig for senere distribusjon.

Dessverre, med en stor I/O-belastning kombinert med en liten mengde ledig minne, kswapd blir flaskehalsen i hele systemet, fordi de er knyttet til det alle allokeringer (sidefeil) av minnesider i systemet. Dette kan pÄgÄ i svÊrt lang tid hvis prosessene ikke Þnsker Ä bruke minne lenger, men er fikset helt pÄ kanten av OOM-killer-avgrunnen.

Det naturlige spÞrsmÄlet er: hvorfor kommer OOM-morderen sÄ sent? I sin nÄvÊrende iterasjon er OOM-morderen ekstremt dum: den vil drepe prosessen bare nÄr forsÞket pÄ Ä tildele en minneside mislykkes, dvs. hvis sidefeilen mislykkes. Dette skjer ikke pÄ ganske lenge, fordi kswapd modig frigjÞr minnesider, og dumper sidebufferen (faktisk hele disk I/O i systemet) tilbake til disken. Mer detaljert, med en beskrivelse av trinnene som kreves for Ä eliminere slike problemer i kjernen, kan du lese her.

Denne oppfĂžrselen bĂžr forbedres med en kjerne Linux 4.6 +.

Historie 6. Pods blir sittende fast i ventende tilstand

I noen klynger, der det virkelig er mange pods som opererer, begynte vi Ä legge merke til at de fleste av dem "henger" veldig lenge i staten Pending, selv om selve Docker-beholderne allerede kjÞrer pÄ nodene og kan arbeides med manuelt.

Dessuten, i describe Det er ikke noe galt:

  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

Etter litt graving antok vi at kubelet rett og slett ikke har tid til Ă„ sende all informasjon om tilstanden til podene og liveness/beredskapstester til API-serveren.

Og etter Ă„ ha studert hjelp, fant vi fĂžlgende parametere:

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

Som sett, standardverdiene er ganske smÄ, og i 90 % dekker de alle behov... Men i vÄrt tilfelle var ikke dette nok. Derfor setter vi fÞlgende verdier:

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

... og startet kubelets pÄ nytt, hvoretter vi sÄ fÞlgende bilde i grafene av kall til API-serveren:

6 underholdende systemfeil i driften av Kubernetes [og deres lĂžsning]

... og ja, alt begynte Ă„ fly!

PS

For deres hjelp med Ä samle feil og forberede denne artikkelen, uttrykker jeg min dype takknemlighet til de mange ingeniÞrene i selskapet vÄrt, og spesielt til min kollega fra vÄrt FoU-team Andrey Klimentyev (zuzzas).

PPS

Les ogsÄ pÄ bloggen vÄr:

Kilde: www.habr.com

KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KjĂžp pĂ„litelig webhotell med DDoS-beskyttelse, VPS VDS-servere | ProHoster