Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Kwa miaka mingi ya kutumia Kubernetes katika uzalishaji, tumekusanya hadithi nyingi za kuvutia za jinsi hitilafu katika vipengele mbalimbali vya mfumo zilisababisha matokeo yasiyofurahisha na/au yasiyoeleweka yaliyoathiri utendakazi wa vyombo na maganda. Katika makala hii tumefanya uteuzi wa baadhi ya kawaida au ya kuvutia. Hata kama huna bahati ya kukutana na hali kama hizi, kusoma juu ya hadithi fupi za upelelezi - haswa "mkono wa kwanza" - inavutia kila wakati, sivyo?

Hadithi ya 1. Supercronic na Docker kunyongwa

Kwenye mojawapo ya makundi, mara kwa mara tulipokea Docker iliyogandishwa, ambayo iliingilia utendaji wa kawaida wa nguzo. Wakati huo huo, yafuatayo yalizingatiwa kwenye kumbukumbu za 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

…

Kinachotuvutia zaidi kuhusu kosa hili ni ujumbe: pthread_create failed: No space left on device. Utafiti wa Haraka nyaraka alielezea kuwa Docker hakuweza kufanya mchakato, ndiyo sababu iliganda mara kwa mara.

Katika ufuatiliaji, picha ifuatayo inalingana na kile kinachotokea:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Hali kama hiyo inazingatiwa kwenye nodi zingine:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Katika nodi sawa tunaona:

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>

Ilibadilika kuwa tabia hii ni matokeo ya pod kufanya kazi nayo supercronic (huduma ya Go tunayotumia kuendesha kazi za cron kwenye maganda):

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

Shida ni hii: wakati kazi inaendeshwa kwa hali ya juu zaidi, mchakato unasababishwa nayo haiwezi kukomesha kwa usahihi, kugeuka kuwa zombie.

Kumbuka: Ili kuwa sahihi zaidi, michakato husababishwa na kazi za cron, lakini supercronic sio mfumo wa init na hauwezi "kupitisha" michakato ambayo watoto wake walizalisha. Wakati mawimbi ya SIGHUP au SIGTERM yanapoinuliwa, hayapitishwi kwa michakato ya mtoto, na hivyo kusababisha michakato ya mtoto kutokatiza na kubaki katika hali ya zombie. Unaweza kusoma zaidi juu ya haya yote, kwa mfano, katika makala kama hiyo.

Kuna njia kadhaa za kutatua shida:

  1. Kama suluhisho la muda - ongeza idadi ya PID kwenye mfumo kwa wakati mmoja:
           /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. Au kuzindua kazi katika supercronic si moja kwa moja, lakini kwa kutumia sawa tini, ambayo inaweza kusitisha michakato kwa usahihi na sio kuzaa Riddick.

Hadithi ya 2. "Zombies" wakati wa kufuta kikundi

Kubelet alianza kutumia CPU nyingi:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Hakuna mtu atakayependa hii, kwa hivyo tulijihami perf na kuanza kushughulikia shida. Matokeo ya uchunguzi yalikuwa kama ifuatavyo:

  • Kubelet hutumia zaidi ya theluthi moja ya wakati wake wa CPU kuvuta data ya kumbukumbu kutoka kwa vikundi vyote:

    Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

  • Katika orodha ya barua ya watengenezaji wa kernel unaweza kupata mjadala wa tatizo. Kwa kifupi, hoja inakuja kwa hii: faili mbalimbali za tmpfs na vitu vingine vinavyofanana hazijaondolewa kabisa kwenye mfumo wakati wa kufuta kikundi, kinachojulikana memcg zombie. Hivi karibuni au baadaye watafutwa kutoka kwa kashe ya ukurasa, lakini kuna kumbukumbu nyingi kwenye seva na kernel haioni maana ya kupoteza muda kwa kuifuta. Ndio maana wanaendelea kujirundika. Kwa nini hata hii inatokea? Hii ni seva iliyo na kazi za cron ambazo hutengeneza kazi mpya kila wakati, na pamoja na maganda mapya. Kwa hivyo, vikundi vipya vinaundwa kwa vyombo ndani yao, ambavyo vinafutwa hivi karibuni.
  • Kwa nini cAdvisor kwenye kubelet inapoteza muda mwingi? Hii ni rahisi kuona na utekelezaji rahisi zaidi time cat /sys/fs/cgroup/memory/memory.stat. Ikiwa kwenye mashine yenye afya operesheni inachukua sekunde 0,01, basi kwenye cron02 yenye shida inachukua sekunde 1,2. Jambo ni kwamba cAdvisor, ambayo inasoma data kutoka kwa sysfs polepole sana, inajaribu kuzingatia kumbukumbu inayotumiwa katika makundi ya zombie.
  • Ili kuondoa Riddick kwa nguvu, tulijaribu kufuta kache kama inavyopendekezwa katika LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - lakini kernel iligeuka kuwa ngumu zaidi na ikaanguka gari.

Nini cha kufanya? Tatizo linarekebishwa (kujitolea, na kwa maelezo tazama toa ujumbe) kusasisha kernel ya Linux hadi toleo la 4.16.

Historia 3. Systemd na mlima wake

Tena, kubelet inatumia rasilimali nyingi kwenye nodi zingine, lakini wakati huu inatumia kumbukumbu nyingi sana:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Ilibadilika kuwa kuna shida katika systemd inayotumiwa katika Ubuntu 16.04, na inatokea wakati wa kudhibiti milipuko ambayo imeundwa kwa unganisho. subPath kutoka kwa ConfigMap au siri. Baada ya ganda kumaliza kazi yake huduma ya mfumo na mlima wake wa huduma unabaki katika mfumo. Kwa wakati, idadi kubwa yao hujilimbikiza. Kuna masuala hata juu ya mada hii:

  1. #5916;
  2. kubernetes #57345.

...ya mwisho ambayo inarejelea PR katika systemd: #7811 (toleo katika systemd - #7798).

Tatizo halipo tena katika Ubuntu 18.04, lakini ikiwa unataka kuendelea kutumia Ubuntu 16.04, unaweza kupata suluhisho letu kuhusu mada hii kuwa muhimu.

Kwa hivyo tulifanya DaemonSet ifuatayo:

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

... na hutumia hati ifuatayo:

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

... na inaendesha kila dakika 5 kwa kutumia supercronic iliyotajwa hapo awali. Dockerfile yake inaonekana kama hii:

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

Hadithi ya 4. Ushindani wakati wa kupanga maganda

Iligunduliwa kuwa: ikiwa tuna ganda lililowekwa kwenye nodi na picha yake inasukumwa kwa muda mrefu sana, basi ganda lingine ambalo "lilipiga" nodi hiyo hiyo itakuwa rahisi. haianza kuvuta taswira ya ganda jipya. Badala yake, inasubiri hadi picha ya ganda la awali ivutwe. Kama matokeo, ganda ambalo tayari lilikuwa limepangwa na ambalo picha yake inaweza kupakuliwa kwa dakika moja itaishia katika hali ya containerCreating.

Matukio yataonekana kama hii:

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

Ni zinageuka kuwa picha moja kutoka kwa usajili wa polepole inaweza kuzuia kupelekwa kwa nodi.

Kwa bahati mbaya, hakuna njia nyingi za kutoka kwa hali hiyo:

  1. Jaribu kutumia Usajili wako wa Docker moja kwa moja kwenye nguzo au moja kwa moja na nguzo (kwa mfano, Usajili wa GitLab, Nexus, nk);
  2. Tumia huduma kama vile Kraken.

Hadithi ya 5. Nodes hutegemea kwa sababu ya ukosefu wa kumbukumbu

Wakati wa uendeshaji wa maombi mbalimbali, pia tulikutana na hali ambapo node inakoma kabisa kupatikana: SSH haijibu, daemons zote za ufuatiliaji huanguka, na kisha hakuna kitu (au karibu chochote) cha ajabu katika magogo.

Nitakuambia kwenye picha kwa kutumia mfano wa nodi moja ambapo MongoDB ilifanya kazi.

Hivi ndivyo atop inaonekana kwa ajali:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Na kama hii - baada ya ajali:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Katika ufuatiliaji, pia kuna kuruka mkali, ambayo node huacha kupatikana:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

Kwa hivyo, kutoka kwa picha za skrini ni wazi kuwa:

  1. RAM kwenye mashine iko karibu na mwisho;
  2. Kuna kuruka mkali katika matumizi ya RAM, baada ya hapo upatikanaji wa mashine nzima umezimwa ghafla;
  3. Kazi kubwa inafika kwa Mongo, ambayo inalazimisha mchakato wa DBMS kutumia kumbukumbu zaidi na kusoma kikamilifu kutoka kwa diski.

Inabadilika kuwa ikiwa Linux itaisha kumbukumbu ya bure (shinikizo la kumbukumbu linaingia) na hakuna ubadilishaji, basi. kwa Wakati muuaji wa OOM anapowasili, kitendo cha kusawazisha kinaweza kutokea kati ya kutupa kurasa kwenye kashe ya ukurasa na kuziandika kwenye diski. Hii inafanywa na kswapd, ambayo kwa ujasiri huweka huru kurasa nyingi za kumbukumbu iwezekanavyo kwa usambazaji unaofuata.

Kwa bahati mbaya, na mzigo mkubwa wa I/O pamoja na kumbukumbu ndogo ya bure, kswapt inakuwa kizuizi cha mfumo mzima, kwa sababu wamefungwa nayo wote mgao (makosa ya ukurasa) ya kurasa za kumbukumbu kwenye mfumo. Hii inaweza kuendelea kwa muda mrefu sana ikiwa michakato haitaki kutumia kumbukumbu tena, lakini imewekwa kwenye ukingo wa shimo la muuaji wa OOM.

Swali la asili ni: kwa nini muuaji wa OOM anachelewa sana? Katika iteration yake ya sasa, muuaji wa OOM ni mjinga sana: itaua mchakato tu wakati jaribio la kutenga ukurasa wa kumbukumbu linashindwa, i.e. ikiwa kosa la ukurasa litashindwa. Hii haifanyiki kwa muda mrefu, kwa sababu kswapd kwa ujasiri huachilia kurasa za kumbukumbu, ikitupa kashe ya ukurasa (diski nzima ya I/O kwenye mfumo, kwa kweli) kurudi kwenye diski. Kwa undani zaidi, kwa maelezo ya hatua zinazohitajika ili kuondoa matatizo hayo kwenye kernel, unaweza kusoma hapa.

Tabia hii inapaswa kuboresha na Linux kernel 4.6+.

Hadithi ya 6. Maganda yanakwama katika hali inayosubiri

Katika vikundi vingine, ambavyo kuna maganda mengi yanayofanya kazi, tulianza kugundua kuwa wengi wao "hutegemea" kwa muda mrefu sana katika jimbo. Pending, ingawa vyombo vya Docker vyenyewe tayari vinaendesha kwenye nodi na vinaweza kufanyiwa kazi kwa mikono.

Kwa kuongezea, katika describe hakuna kitu kibaya:

  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

Baada ya kuchimba kidogo, tulifanya dhana kuwa kubelet haina wakati wa kutuma habari zote kuhusu hali ya maganda na majaribio ya uhai/utayari kwa seva ya API.

Na baada ya kusoma msaada, tulipata vigezo vifuatavyo:

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

Kama inavyoonekana, maadili chaguo-msingi ni ndogo sana, na katika 90% hufunika mahitaji yote ... Hata hivyo, kwa upande wetu hii haitoshi. Kwa hivyo, tunaweka maadili yafuatayo:

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

... na kuanza tena kubelets, baada ya hapo tuliona picha ifuatayo kwenye grafu za simu kwa seva ya API:

Hitilafu 6 za mfumo wa burudani katika uendeshaji wa Kubernetes [na suluhisho lao]

... na ndiyo, kila kitu kilianza kuruka!

PS

Kwa msaada wao katika kukusanya mende na kuandaa nakala hii, ninatoa shukrani zangu za dhati kwa wahandisi wengi wa kampuni yetu, na haswa kwa mwenzangu kutoka kwa timu yetu ya R&D Andrey Klimentyev (zuza).

PPS

Soma pia kwenye blogi yetu:

Chanzo: mapenzi.com

Kuongeza maoni