6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Í gegnum árin sem við höfum notað Kubernetes í framleiðslu höfum við safnað mörgum áhugaverðum sögum af því hvernig villur í ýmsum kerfishlutum leiddu til óþægilegra og/eða óskiljanlegra afleiðinga sem höfðu áhrif á rekstur íláta og fræbelgja. Í þessari grein höfum við gert úrval af þeim algengustu eða áhugaverðustu. Jafnvel þótt þú sért aldrei svo heppinn að lenda í slíkum aðstæðum, þá er alltaf áhugavert að lesa um svona stuttar leynilögreglusögur - sérstaklega „frá fyrstu hendi“, er það ekki?

Saga 1. Supercronic og Docker hangandi

Á einum klasanum fengum við reglulega frosinn Docker, sem truflaði eðlilega starfsemi klasans. Á sama tíma kom eftirfarandi fram í Docker logs:

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

…

Það sem vekur mestan áhuga okkar við þessa villu eru skilaboðin: pthread_create failed: No space left on device. Fljótt nám skjöl útskýrði að Docker gæti ekki gafflað ferli, þess vegna frosið það reglulega.

Í eftirliti samsvarar eftirfarandi mynd því sem er að gerast:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Svipað ástand sést á öðrum hnútum:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Á sömu hnútum sjáum 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>

Það kom í ljós að þessi hegðun er afleiðing af því að belgurinn vinnur með ofurkrónískur (Go tól sem við notum til að keyra cron störf í belg):

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

Vandamálið er þetta: þegar verkefni er keyrt í supercronic, fer ferlið af stað af því getur ekki hætt á réttan hátt, breytast í uppvakningur.

Athugið: Til að vera nákvæmari, ferlar eru sprottnar af cron verkefnum, en supercronic er ekki init kerfi og getur ekki "samþykkt" ferla sem börnin þess fæddu. Þegar SIGHUP eða SIGTERM merki eru birt, eru þau ekki send til barnaferlanna, sem leiðir til þess að barnaferlarnir hætta ekki og verða áfram í uppvakningastöðu. Þú getur lesið meira um þetta allt, til dæmis í svona grein.

Það eru nokkrar leiðir til að leysa vandamál:

  1. Sem tímabundin lausn - fjölga PID í kerfinu á einum tímapunkti:
           /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. Eða ræstu verkefni í supercronic ekki beint, heldur með því að nota það sama Pínulítið, sem er fær um að stöðva ferla á réttan hátt og ekki hrogna zombie.

Saga 2. „Zombies“ þegar cgroup er eytt

Kubelet byrjaði að neyta mikið af örgjörva:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Engum mun líka við þetta, svo við vopnuðum okkur fullkominn og fór að takast á við vandamálið. Niðurstöður rannsóknarinnar voru sem hér segir:

  • Kubelet eyðir meira en þriðjungi af CPU tíma sínum í að draga minnisgögn frá öllum cgroups:

    6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

  • Í póstlista kjarnaforritara er hægt að finna umfjöllun um vandamálið. Í stuttu máli kemur málið niður á þetta: ýmsar tmpfs skrár og annað svipað er ekki alveg fjarlægt úr kerfinu þegar verið er að eyða cgroup, svokallaða memcg Zombie. Fyrr eða síðar verður þeim eytt úr skyndiminni síðunnar, en það er mikið minni á þjóninum og kjarninn sér ekki tilganginn í að eyða tíma í að eyða þeim. Þess vegna hlaðast þeir áfram. Hvers vegna er þetta jafnvel að gerast? Þetta er þjónn með cron störfum sem skapar stöðugt ný störf og með þeim nýja pods. Þannig verða til nýir cgroups fyrir gáma í þeim, sem fljótlega er eytt.
  • Af hverju eyðir cAdvisor í kubelet svona miklum tíma? Þetta er auðvelt að sjá með einföldustu framkvæmd time cat /sys/fs/cgroup/memory/memory.stat. Ef á heilbrigðri vél tekur aðgerðin 0,01 sekúndu, þá á erfiða cron02 tekur það 1,2 sekúndur. Málið er að cAdvisor, sem les gögn frá sysfs mjög hægt, reynir að taka tillit til minnisins sem notað er í zombie cgroups.
  • Til að fjarlægja uppvakninga af krafti reyndum við að hreinsa skyndiminni eins og mælt er með í LKML: sync; echo 3 > /proc/sys/vm/drop_caches,- en kjarninn reyndist flóknari og hrundi bílnum.

Hvað skal gera? Verið er að laga vandamálið (skuldbinda sig, og fyrir lýsingu sjá gefa út skilaboð) að uppfæra Linux kjarnann í útgáfu 4.16.

Saga 3. Systemd og festing þess

Aftur, kúbelet eyðir of mörgum auðlindum á sumum hnútum, en að þessu sinni eyðir það of miklu minni:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Það kom í ljós að það er vandamál í systemd sem er notað í Ubuntu 16.04 og það kemur upp þegar stjórnað er festingum sem eru búnar til fyrir tengingu subPath frá ConfigMaps eða leyndarmálum. Eftir að belgurinn hefur lokið störfum systemd þjónustan og þjónustufesting hennar eru eftir í kerfi. Með tímanum safnast mikill fjöldi þeirra upp. Það eru jafnvel vandamál um þetta efni:

  1. #5916;
  2. kubernetes #57345.

...síðasta sem vísar til PR í systemd: # 7811 (vandamál í systemd - # 7798).

Vandamálið er ekki lengur til í Ubuntu 18.04, en ef þú vilt halda áfram að nota Ubuntu 16.04 gætirðu fundið lausn okkar á þessu efni gagnleg.

Svo við gerðum eftirfarandi 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 það notar eftirfarandi skriftu:

#!/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 það keyrir á 5 mínútna fresti með því að nota áðurnefnda supercronic. Dockerfile þess lítur svona út:

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

Saga 4. Samkeppnishæfni við tímasetningu á belg

Það var tekið eftir því að: ef við erum með belg sem er settur á hnút og mynd hans er dælt út í mjög langan tíma, þá mun annar belg sem „slær“ í sama hnút einfaldlega byrjar ekki að draga myndina af nýja belgnum. Þess í stað bíður það þar til myndin af fyrri belgnum er dregin. Fyrir vikið mun pod sem þegar var á dagskrá og hefði hægt að hlaða niður myndinni á aðeins einni mínútu endar í stöðunni containerCreating.

Atburðirnir munu líta einhvern veginn svona út:

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

Það kemur í ljós að ein mynd úr hægfara skrásetningu getur hindrað dreifingu á hvern hnút.

Því miður eru ekki margar leiðir út úr ástandinu:

  1. Reyndu að nota Docker Registry beint í klasanum eða beint með klasanum (til dæmis GitLab Registry, Nexus osfrv.);
  2. Notaðu tól eins og Kraken.

Saga 5. Hnútar hanga vegna minnisskorts

Við rekstur ýmissa forrita lentum við líka í aðstæðum þar sem hnútur hættir alveg að vera aðgengilegur: SSH svarar ekki, allir vöktunarpúkar detta af og svo er ekkert (eða nánast ekkert) óeðlilegt í loggunum.

Ég skal segja þér það á myndum með því að nota dæmi um einn hnút þar sem MongoDB virkaði.

Svona lítur toppurinn út í slys:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Og svona - eftir slys:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Í eftirliti er einnig skarpt stökk, þar sem hnúturinn hættir að vera tiltækur:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

Af skjáskotunum er því ljóst að:

  1. Vinnsluminni á vélinni er nálægt endanum;
  2. Það er mikið stökk í vinnsluminni neyslu, eftir það er aðgangur að allri vélinni skyndilega gerður óvirkur;
  3. Stórt verkefni kemur á Mongo, sem neyðir DBMS ferlið til að nota meira minni og lesa virkan af diski.

Það kemur í ljós að ef Linux verður uppiskroppa með laust minni (minnisþrýstingur kemur inn) og það er engin skipti, þá í Þegar OOM morðinginn kemur getur jafnvægisaðgerð komið upp á milli þess að henda síðum í skyndiminni síðunnar og skrifa þær aftur á diskinn. Þetta er gert með kswapd, sem af hugrekki losar eins margar minnissíður og mögulegt er til síðari dreifingar.

Því miður, með miklu I/O álagi ásamt litlu magni af lausu minni, kswapd verður flöskuháls alls kerfisins, vegna þess að þeir eru bundnir við það allt úthlutun (síðuvillur) á minnissíðum í kerfinu. Þetta getur haldið áfram í mjög langan tíma ef ferlarnir vilja ekki nota minni lengur, heldur eru fastir við jaðar OOM-killer hyldýpsins.

Eðlilega spurningin er: hvers vegna kemur OOM morðinginn svona seint? Í núverandi endurtekningu sinni er OOM morðinginn afar heimskur: hann drepur ferlið aðeins þegar tilraunin til að úthluta minnissíðu mistekst, þ.e. ef síðuvillan mistekst. Þetta gerist ekki í mjög langan tíma, því kswapd losar hugrakkur minnissíður, dumpar síðu skyndiminni (allt diskinn I/O í kerfinu, reyndar) aftur á diskinn. Nánar, með lýsingu á þeim skrefum sem þarf til að útrýma slíkum vandamálum í kjarnanum, geturðu lesið hér.

Þessi hegðun ætti að bæta með Linux kjarna 4.6+.

Saga 6. Beygjur festast í biðstöðu

Í sumum klösum, þar sem það eru mjög margir fræbelgir starfandi, fórum við að taka eftir því að flestir þeirra „hanga“ í mjög langan tíma í ríkinu Pending, þó að Docker gámarnir sjálfir séu nú þegar í gangi á hnútunum og hægt er að vinna með þeim handvirkt.

Þar að auki, í describe það er ekkert að:

  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

Eftir smá pælingu gerðum við þá forsendu að kubelet hafi einfaldlega ekki tíma til að senda allar upplýsingar um stöðu fræbelganna og lífleika/viðbúnaðarprófanir til API netþjónsins.

Og eftir að hafa rannsakað hjálp fundum við eftirfarandi breytur:

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

Eins og sést, sjálfgefin gildi eru frekar lítil, og í 90% dekka þær allar þarfir... Hins vegar var þetta ekki nóg í okkar tilviki. Þess vegna setjum við eftirfarandi gildi:

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

... og endurræstu kubelets, eftir það sáum við eftirfarandi mynd á línuritum af símtölum á API-þjóninn:

6 skemmtilegar kerfisvillur í rekstri Kubernetes [og lausn þeirra]

... og já, allt fór að fljúga!

PS

Fyrir hjálp þeirra við að safna pöddum og undirbúa þessa grein, lýsi ég djúpu þakklæti mínu til fjölmargra verkfræðinga fyrirtækisins okkar, og sérstaklega til samstarfsmanns míns frá R&D teyminu okkar Andrey Klimentyev (zuzzas).

Pps

Lestu líka á blogginu okkar:

Heimild: www.habr.com

Bæta við athugasemd