6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

در طول سال‌ها استفاده از Kubernetes در تولید، ما داستان‌های جالب زیادی از این که چگونه اشکالات در اجزای مختلف سیستم منجر به عواقب ناخوشایند و/یا غیرقابل درک بر عملکرد ظروف و غلاف‌ها می‌شود، جمع‌آوری کرده‌ایم. در این مقاله تعدادی از رایج ترین یا جالب ترین آنها را انتخاب کرده ایم. حتی اگر هرگز آنقدر خوش شانس نباشید که با چنین موقعیت هایی روبرو شوید، خواندن در مورد چنین داستان های پلیسی کوتاه - به ویژه "دست اول" - همیشه جالب است، اینطور نیست؟

داستان 1. Supercronic و Docker حلق آویز

در یکی از خوشه‌ها، ما به صورت دوره‌ای یک Docker منجمد دریافت می‌کردیم که در عملکرد عادی خوشه اختلال ایجاد می‌کرد. در همان زمان موارد زیر در لاگ های 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

…

چیزی که ما را در مورد این خطا بیشتر علاقه مند می کند این پیام است: pthread_create failed: No space left on device. مطالعه سریع مستندات توضیح داد که داکر نمی تواند فرآیندی را انشعاب کند، به همین دلیل است که به طور دوره ای منجمد می شود.

در مانیتورینگ، تصویر زیر مربوط به آنچه در حال وقوع است:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

وضعیت مشابهی در سایر گره ها مشاهده می شود:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

در همان گره ها می بینیم:

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>

معلوم شد که این رفتار نتیجه کار غلاف با آن است سوپرکرونیک (یک ابزار Go که ما از آن برای اجرای cron jobها در 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>
…

مشکل این است: وقتی یک کار در سوپرکرونیک اجرا می شود، فرآیند توسط آن ایجاد می شود نمی تواند به درستی خاتمه یابد، تبدیل شدن به زامبی.

یادداشت: به بیان دقیق‌تر، فرآیندها توسط وظایف cron ایجاد می‌شوند، اما سوپرکرونیک یک سیستم اولیه نیست و نمی‌تواند فرآیندهایی را که فرزندانش ایجاد کرده‌اند «پذیرش» کند. وقتی سیگنال‌های SIGHUP یا SIGTERM بالا می‌آیند، به پردازش‌های فرزند منتقل نمی‌شوند و در نتیجه فرآیندهای فرزند پایان نمی‌یابند و در وضعیت زامبی باقی می‌مانند. شما می توانید در مورد همه اینها بیشتر بخوانید، به عنوان مثال، در چنین مقاله ای.

چند راه برای حل مشکلات وجود دارد:

  1. به عنوان یک راه حل موقت - تعداد PID ها را در سیستم در یک نقطه از زمان افزایش دهید:
           /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. یا وظایف را در supercronic راه اندازی کنید نه مستقیم، بلکه با استفاده از همان تینی، که قادر است فرآیندها را به درستی خاتمه دهد و زامبی ها را تولید نکند.

داستان 2. "زامبی ها" هنگام حذف یک cgroup

Kubelet شروع به مصرف مقدار زیادی CPU کرد:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

هیچ کس این را دوست ندارد، بنابراین ما خودمان را مسلح کردیم پرفیوم و شروع به رسیدگی به مشکل کرد. نتایج تحقیقات به شرح زیر بود:

  • Kubelet بیش از یک سوم از زمان CPU خود را صرف استخراج داده های حافظه از همه cgroup ها می کند:

    6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

  • در لیست پستی توسعه دهندگان هسته می توانید پیدا کنید بحث در مورد مشکل. به طور خلاصه، موضوع به این نتیجه می رسد: فایل های مختلف tmpfs و موارد مشابه دیگر به طور کامل از سیستم حذف نمی شوند هنگام حذف یک cgroup، به اصطلاح memcg انسان زنده شد. دیر یا زود آنها از کش صفحه حذف می شوند، اما حافظه زیادی روی سرور وجود دارد و هسته فایده ای در اتلاف وقت برای حذف آنها نمی بیند. به همین دلیل مدام انباشته می شوند. اصلا چرا این اتفاق می افتد؟ این یک سرور با cron job است که دائماً مشاغل جدید و همراه با آنها pod های جدید ایجاد می کند. بنابراین cgroup های جدیدی برای کانتینرهای موجود در آنها ایجاد می شود که به زودی حذف می شوند.
  • چرا cAdvisor در kubelet زمان زیادی را تلف می کند؟ با ساده ترین اجرا به راحتی قابل مشاهده است time cat /sys/fs/cgroup/memory/memory.stat. اگر در یک ماشین سالم این عملیات 0,01 ثانیه طول می کشد، سپس در cron02 مشکل دار 1,2 ثانیه طول می کشد. موضوع این است که cAdvisor که داده ها را از sysfs بسیار آهسته می خواند، سعی می کند حافظه استفاده شده در cgroup های زامبی را در نظر بگیرد.
  • برای حذف اجباری زامبی‌ها، سعی کردیم حافظه پنهان را همانطور که در LKML توصیه می‌شود پاک کنیم: sync; echo 3 > /proc/sys/vm/drop_caches، - اما هسته پیچیده تر بود و ماشین را تصادف کرد.

چه باید کرد؟ مشکل در حال رفع شدن است (مرتکب شدن، و برای توضیحات رجوع کنید پیام انتشار) به‌روزرسانی هسته Linux تا نسخه 4.16

تاریخچه 3. Systemd و mount آن

باز هم، kubelet منابع زیادی را در برخی گره ها مصرف می کند، اما این بار حافظه زیادی مصرف می کند:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

معلوم شد که مشکلی در systemd مورد استفاده وجود دارد Ubuntu 16.04، و هنگام مدیریت مونت‌هایی که برای اتصال ایجاد شده‌اند، رخ می‌دهد. subPath از ConfigMap's یا Secret's. بعد از اینکه غلاف کار خود را کامل کرد سرویس systemd و مانت سرویس آن باقی می مانند در سیستم با گذشت زمان، تعداد زیادی از آنها جمع می شوند. حتی مسائلی در این زمینه وجود دارد:

  1. #5916;
  2. kubernetes #57345.

... که آخرین آنها به روابط عمومی در systemd اشاره دارد: #7811 (مساله در systemd - #7798).

مشکل دیگه وجود نداره Ubuntu 18.04، اما اگر می‌خواهید به استفاده از آن ادامه دهید Ubuntu ۱۶.۰۴، ممکن است راه حل ما در این مورد برای شما مفید باشد.

بنابراین ما 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

... و از اسکریپت زیر استفاده می کند:

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

... و هر 5 دقیقه با استفاده از سوپرکرونیک که قبلا ذکر شد اجرا می شود. Dockerfile آن به شکل زیر است:

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

داستان 4. رقابت در هنگام برنامه ریزی غلاف

توجه شد که: اگر یک غلاف روی یک گره قرار دهیم و تصویر آن برای مدت طولانی پمپ شود، غلاف دیگری که به همان گره ضربه بزند، به سادگی شروع به کشیدن تصویر غلاف جدید نمی کند. در عوض منتظر می ماند تا تصویر غلاف قبلی کشیده شود. در نتیجه، یک پاد که از قبل برنامه ریزی شده بود و تصویر آن می توانست تنها در یک دقیقه بارگیری شود، به وضعیت پایان می رسد containerCreating.

رویدادها چیزی شبیه به این خواهند بود:

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

معلوم است که یک تصویر از یک رجیستری کند می تواند استقرار را مسدود کند در هر گره

متأسفانه راه های زیادی برای خروج از این وضعیت وجود ندارد:

  1. سعی کنید از رجیستری Docker خود مستقیماً در کلاستر یا مستقیماً با کلاستر (به عنوان مثال، رجیستری GitLab، Nexus و غیره) استفاده کنید.
  2. استفاده از ابزارهای کاربردی مانند kraken.

داستان 5. گره ها به دلیل کمبود حافظه آویزان می شوند

در طول عملکرد برنامه های مختلف، ما همچنین با وضعیتی مواجه شدیم که در آن یک گره به طور کامل در دسترس نیست: SSH پاسخ نمی دهد، تمام دیمون های نظارتی سقوط می کنند، و سپس هیچ چیز (یا تقریباً هیچ چیز) غیرعادی در گزارش ها وجود ندارد.

من در تصاویر با استفاده از مثال یک گره که MongoDB در آن کار می کرد به شما خواهم گفت.

این چیزی است که در بالا به نظر می رسد به تصادفات:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

و مانند این - پس از تصادفات:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

در نظارت، یک پرش شدید نیز وجود دارد که در آن گره در دسترس نیست:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

بنابراین، از اسکرین شات ها مشخص است که:

  1. RAM دستگاه به انتهای آن نزدیک است.
  2. یک جهش شدید در مصرف RAM وجود دارد که پس از آن دسترسی به کل دستگاه به طور ناگهانی غیرفعال می شود.
  3. یک کار بزرگ به Mongo می رسد، که فرآیند DBMS را مجبور می کند از حافظه بیشتری استفاده کند و به طور فعال از روی دیسک بخواند.

معلوم می‌شود که اگر در Linux حافظه آزاد تمام می‌شود (فشار حافظه رخ می‌دهد) و هیچ مبادله‌ای وجود ندارد، پس به هنگامی که قاتل OOM می رسد، ممکن است یک عمل متعادل کننده بین پرتاب صفحات به حافظه پنهان صفحه و بازگرداندن آنها به دیسک ایجاد شود. این کار توسط kswapd انجام می شود، که شجاعانه تا آنجا که ممکن است صفحات حافظه را برای توزیع بعدی آزاد می کند.

متأسفانه، با یک بار ورودی/خروجی زیاد همراه با مقدار کمی حافظه آزاد، kswapd به گلوگاه کل سیستم تبدیل می شود، زیرا به آن گره خورده اند همه تخصیص (عیوب صفحه) صفحات حافظه در سیستم. اگر فرآیندها دیگر نمی خواهند از حافظه استفاده کنند، اما در لبه پرتگاه قاتل OOM ثابت شده اند، این می تواند برای مدت طولانی ادامه یابد.

سوال طبیعی این است: چرا قاتل OOM اینقدر دیر می آید؟ در تکرار فعلی، قاتل OOM بسیار احمقانه است: فقط زمانی فرآیند را از بین می برد که تلاش برای تخصیص یک صفحه حافظه با شکست مواجه شود، یعنی. اگر خطای صفحه از کار بیفتد. این برای مدت زمان طولانی اتفاق نمی افتد، زیرا kswapd شجاعانه صفحات حافظه را آزاد می کند و کش صفحه (در واقع کل ورودی/خروجی دیسک در سیستم) را به دیسک باز می گرداند. در ادامه با شرح مراحل لازم برای رفع اینگونه مشکلات در کرنل می توانید مطالعه نمایید اینجا.

این رفتار باید بهبود یابد با هسته Linux 4.6 +

داستان 6. غلاف ها در وضعیت معلق گیر می کنند

در برخی از خوشه ها، که در آنها واقعاً غلاف های زیادی در حال کار هستند، متوجه شدیم که بیشتر آنها برای مدت طولانی در ایالت "آویزان" هستند. Pending، اگرچه خود کانتینرهای Docker در حال حاضر روی گره ها در حال اجرا هستند و می توان با آنها به صورت دستی کار کرد.

علاوه بر این ، در describe عیبی نداره:

  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

پس از مدتی حفاری، ما این فرض را ایجاد کردیم که kubelet به سادگی زمان ارسال تمام اطلاعات مربوط به وضعیت پادها و تست‌های زنده‌گی/آمادگی را به سرور API ندارد.

و پس از مطالعه کمک، پارامترهای زیر را پیدا کردیم:

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

همانطور که دیدیم، مقادیر پیش فرض بسیار کوچک هستند، و در 90٪ آنها تمام نیازها را پوشش می دهند ... اما در مورد ما این کافی نبود. بنابراین، مقادیر زیر را تعیین می کنیم:

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

... و Kubelets را مجدداً راه اندازی کرد و پس از آن تصویر زیر را در نمودارهای فراخوانی به سرور API مشاهده کردیم:

6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]

... و بله، همه چیز شروع به پرواز کرد!

PS

برای کمک آنها در جمع آوری اشکالات و تهیه این مقاله، تشکر عمیق خود را از مهندسان متعدد شرکت ما و به ویژه از همکارم از تیم تحقیق و توسعه ما آندری کلیمنتیف ابراز می کنم.زوزاها).

PPS

در وبلاگ ما نیز بخوانید:

منبع: www.habr.com

خرید هاست قابل اعتماد برای سایت های دارای حفاظت DDoS، سرورهای VPS VDS 🔥 خرید هاستینگ معتبر با محافظت در برابر حملات DDoS، سرورهای VPS و VDS | ProHoster