6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Sa paglipas ng mga taon ng paggamit ng Kubernetes sa produksyon, nakaipon kami ng maraming kawili-wiling kwento kung paano humantong ang mga bug sa iba't ibang bahagi ng system sa hindi kasiya-siya at/o hindi maintindihan na mga kahihinatnan na nakakaapekto sa pagpapatakbo ng mga container at pod. Sa artikulong ito nakagawa kami ng seleksyon ng ilan sa mga pinakakaraniwan o kawili-wili. Kahit na hindi ka mapalad na makatagpo ng mga ganitong sitwasyon, ang pagbabasa tungkol sa mga maiikling kwento ng tiktik - lalo na ang "first-hand" - ay palaging kawili-wili, hindi ba?..

Kuwento 1. Supercronic at Docker na nakabitin

Sa isa sa mga kumpol, pana-panahon kaming nakatanggap ng isang nakapirming Docker, na nakakasagabal sa normal na paggana ng kumpol. Kasabay nito, ang mga sumusunod ay naobserbahan sa mga log ng 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

…

Ang pinaka-interesante sa amin tungkol sa error na ito ay ang mensahe: pthread_create failed: No space left on device. Mabilis na Pag-aaral dokumentasyon ipinaliwanag na hindi maaaring i-fork ng Docker ang isang proseso, kaya naman pana-panahon itong nagyelo.

Sa pagsubaybay, ang sumusunod na larawan ay tumutugma sa kung ano ang nangyayari:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Ang isang katulad na sitwasyon ay sinusunod sa iba pang mga node:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Sa parehong mga node nakikita natin:

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>

Ito ay lumabas na ang pag-uugali na ito ay bunga ng pod na nagtatrabaho sa supercronic (isang Go utility na ginagamit namin para magpatakbo ng mga cron job sa mga pod):

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

Ang problema ay ito: kapag ang isang gawain ay pinapatakbo sa supercronic, ang proseso ay nabuo nito hindi maaaring wakasan ng tama, nagiging zombie.

Nota: Upang maging mas tumpak, ang mga proseso ay pinamumunuan ng mga gawain ng cron, ngunit ang supercronic ay hindi isang init system at hindi maaaring "mag-ampon" ng mga proseso na iniluwal ng mga anak nito. Kapag nakataas ang mga signal ng SIGHUP o SIGTERM, hindi ito naipapasa sa mga proseso ng bata, na nagreresulta sa hindi pagwawakas ng mga proseso ng bata at nananatili sa status ng zombie. Maaari kang magbasa nang higit pa tungkol sa lahat ng ito, halimbawa, sa ganoong artikulo.

Mayroong ilang mga paraan upang malutas ang mga problema:

  1. Bilang pansamantalang solusyon - dagdagan ang bilang ng mga PID sa system sa isang punto ng oras:
           /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 ilunsad ang mga gawain sa supercronic hindi direkta, ngunit gamit ang pareho tini, na magagawang wakasan ang mga proseso nang tama at hindi mag-spawn ng mga zombie.

Kuwento 2. "Mga Zombie" kapag nagde-delete ng cgroup

Ang Kubelet ay nagsimulang gumamit ng maraming CPU:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Walang magugustuhan nito, kaya armado kami perpekto at nagsimulang harapin ang problema. Ang mga resulta ng imbestigasyon ay ang mga sumusunod:

  • Gumugugol ang Kubelet ng higit sa isang katlo ng oras ng CPU nito sa pagkuha ng data ng memorya mula sa lahat ng cgroup:

    6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

  • Sa mailing list ng mga developer ng kernel mahahanap mo pagtalakay sa suliranin. Sa madaling salita, ang punto ay bumababa dito: iba't ibang mga tmpfs file at iba pang katulad na mga bagay ay hindi ganap na naalis sa system kapag nagde-delete ng cgroup, ang tinatawag na memcg sombi. Maaga o huli sila ay tatanggalin mula sa cache ng pahina, ngunit mayroong maraming memorya sa server at hindi nakikita ng kernel ang punto sa pag-aaksaya ng oras sa pagtanggal ng mga ito. Kaya naman patuloy silang nagtatambak. Bakit nangyayari pa ito? Ito ay isang server na may mga cron job na patuloy na lumilikha ng mga bagong trabaho, at kasama nila ang mga bagong pod. Kaya, ang mga bagong cgroup ay nilikha para sa mga lalagyan sa mga ito, na malapit nang matanggal.
  • Bakit nag-aaksaya ng maraming oras ang cAdvisor sa kubelet? Ito ay madaling makita sa pinakasimpleng pagpapatupad time cat /sys/fs/cgroup/memory/memory.stat. Kung sa isang malusog na makina ang operasyon ay tumatagal ng 0,01 segundo, pagkatapos ay sa may problemang cron02 ito ay tumatagal ng 1,2 segundo. Ang bagay ay ang cAdvisor, na nagbabasa ng data mula sa sysfs nang napakabagal, ay sumusubok na isaalang-alang ang memorya na ginagamit sa mga zombie cgroup.
  • Upang piliting alisin ang mga zombie, sinubukan naming i-clear ang mga cache gaya ng inirerekomenda sa LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - ngunit ang kernel ay naging mas kumplikado at nag-crash sa kotse.

Anong gagawin? Ang problema ay inaayos (mangako, at para sa isang paglalarawan tingnan maglabas ng mensahe) pag-update ng Linux kernel sa bersyon 4.16.

Kasaysayan 3. Systemd at ang mount nito

Muli, ang kubelet ay kumokonsumo ng napakaraming mapagkukunan sa ilang mga node, ngunit sa pagkakataong ito ito ay gumagamit ng masyadong maraming memorya:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Ito ay lumabas na may problema sa systemd na ginamit sa Ubuntu 16.04, at ito ay nangyayari kapag pinamamahalaan ang mga mount na nilikha para sa koneksyon subPath mula sa ConfigMap's o secret's. Matapos makumpleto ng pod ang trabaho nito mananatili ang systemd service at ang service mount nito sa sistema. Sa paglipas ng panahon, ang isang malaking bilang ng mga ito ay naipon. Mayroong kahit na mga isyu sa paksang ito:

  1. #5916;
  2. kubernetes #57345.

...ang huli ay tumutukoy sa PR sa systemd: #7811 (isyu sa systemd - #7798).

Ang problema ay wala na sa Ubuntu 18.04, ngunit kung gusto mong magpatuloy sa paggamit ng Ubuntu 16.04, maaari mong makitang kapaki-pakinabang ang aming solusyon sa paksang ito.

Kaya ginawa namin ang sumusunod na 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

... at ginagamit nito ang sumusunod na 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

... at ito ay tumatakbo tuwing 5 minuto gamit ang naunang nabanggit na supercronic. Ang Dockerfile nito ay ganito ang hitsura:

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

Kuwento 4. Pagiging mapagkumpitensya kapag nag-iiskedyul ng mga pod

Napansin na: kung mayroon kaming isang pod na nakalagay sa isang node at ang imahe nito ay na-pump out nang napakatagal, pagkatapos ay isa pang pod na "hit" sa parehong node ay simpleng ay hindi nagsisimulang hilahin ang imahe ng bagong pod. Sa halip, naghihintay ito hanggang sa ma-pull ang imahe ng nakaraang pod. Bilang resulta, ang isang pod na nakaiskedyul na at kung saan ang larawan ay maaaring ma-download sa loob lamang ng isang minuto ay mapupunta sa katayuan ng containerCreating.

Magiging ganito ang hitsura ng mga kaganapan:

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

Ito ay lumiliko ang na ang isang imahe mula sa isang mabagal na pagpapatala ay maaaring harangan ang pag-deploy bawat node.

Sa kasamaang palad, walang maraming paraan sa labas ng sitwasyon:

  1. Subukang gamitin ang iyong Docker Registry nang direkta sa cluster o direkta sa cluster (halimbawa, GitLab Registry, Nexus, atbp.);
  2. Gumamit ng mga kagamitan tulad ng kraken.

Kuwento 5. Nakasabit ang mga node dahil sa kakulangan ng memorya

Sa panahon ng pagpapatakbo ng iba't ibang mga application, nakatagpo din kami ng isang sitwasyon kung saan ang isang node ay ganap na hindi na ma-access: SSH ay hindi tumugon, lahat ng mga daemon sa pagsubaybay ay nahuhulog, at pagkatapos ay wala (o halos walang) anomalya sa mga log.

Sasabihin ko sa iyo sa mga larawan gamit ang halimbawa ng isang node kung saan gumana ang MongoDB.

Ito ang hitsura ng nasa ibabaw sa aksidente:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

At tulad nito - pagkatapos aksidente:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Sa pagsubaybay, mayroon ding isang matalim na pagtalon, kung saan ang node ay hindi na magagamit:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

Kaya, mula sa mga screenshot ay malinaw na:

  1. Ang RAM sa makina ay malapit sa dulo;
  2. Mayroong isang matalim na pagtalon sa pagkonsumo ng RAM, pagkatapos kung saan ang pag-access sa buong makina ay biglang hindi pinagana;
  3. Dumating ang isang malaking gawain sa Mongo, na pinipilit ang proseso ng DBMS na gumamit ng mas maraming memorya at aktibong magbasa mula sa disk.

Ito ay lumiliko na kung ang Linux ay naubusan ng libreng memorya (memory pressure set in) at walang swap, kung gayon sa Kapag dumating ang OOM killer, maaaring magkaroon ng pagbabalanse sa pagitan ng paghagis ng mga pahina sa cache ng pahina at pagsusulat ng mga ito pabalik sa disk. Ginagawa ito ng kswapd, na buong tapang na nagpapalaya ng maraming mga pahina ng memorya hangga't maaari para sa kasunod na pamamahagi.

Sa kasamaang palad, na may malaking I/O load na kasama ng maliit na halaga ng libreng memory, Ang kswapd ay nagiging bottleneck ng buong system, dahil nakatali sila dito lahat mga alokasyon (page faults) ng mga memory page sa system. Maaari itong magpatuloy nang napakatagal kung ang mga proseso ay hindi na gustong gumamit ng memorya, ngunit naayos sa pinakadulo ng OOM-killer abyss.

Ang natural na tanong ay: bakit nahuhuli ang pumapatay sa OOM? Sa kasalukuyang pag-ulit nito, ang OOM killer ay lubhang hangal: papatayin lamang nito ang proseso kapag nabigo ang pagtatangkang maglaan ng memory page, i.e. kung nabigo ang page fault. Hindi ito nangyayari sa loob ng mahabang panahon, dahil ang kswapd ay buong tapang na nagpapalaya sa mga pahina ng memorya, na nagtatapon ng cache ng pahina (ang buong disk I/O sa system, sa katunayan) pabalik sa disk. Sa mas detalyado, na may isang paglalarawan ng mga hakbang na kinakailangan upang maalis ang mga naturang problema sa kernel, maaari mong basahin dito.

Ang ugali na ito dapat pagbutihin na may Linux kernel 4.6+.

Kuwento 6. Natigil ang mga pod sa Nakabinbing estado

Sa ilang mga kumpol, kung saan mayroong talagang maraming mga pod na tumatakbo, nagsimula kaming mapansin na karamihan sa kanila ay "nakabitin" nang napakatagal sa estado. Pending, kahit na ang mga lalagyan ng Docker mismo ay tumatakbo na sa mga node at maaaring gamitin nang manu-mano.

Kasabay nito, sa describe walang mali:

  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

Pagkatapos ng ilang paghuhukay, ginawa namin ang pagpapalagay na ang kubelet ay walang oras para ipadala ang lahat ng impormasyon tungkol sa estado ng mga pod at liveness/readiness test sa API server.

At pagkatapos ng pag-aaral ng tulong, nakita namin ang mga sumusunod na parameter:

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

Tulad ng nakikita, ang mga default na halaga ay medyo maliit, at sa 90% sinasaklaw nila ang lahat ng pangangailangan... Gayunpaman, sa aming kaso hindi ito sapat. Samakatuwid, itinakda namin ang mga sumusunod na halaga:

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

... at i-restart ang mga kubelets, pagkatapos ay nakita namin ang sumusunod na larawan sa mga graph ng mga tawag sa API server:

6 na nakakaaliw na system bug sa pagpapatakbo ng Kubernetes [at ang kanilang solusyon]

... at oo, nagsimulang lumipad ang lahat!

PS

Para sa kanilang tulong sa pagkolekta ng mga bug at paghahanda ng artikulong ito, ipinapahayag ko ang aking matinding pasasalamat sa maraming mga inhinyero ng aming kumpanya, at lalo na sa aking kasamahan mula sa aming R&D team na si Andrey Klimentyev (zuzzas).

Pps

Basahin din sa aming blog:

Pinagmulan: www.habr.com

Magdagdag ng komento