![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/bed059552ed86580939aa18fbdf1553e.jpg)
در طول سالها استفاده از 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 [و راه حل آنها]](/wp-content/uploads/2019/03/bd778052c87b338493bae54b26830ef3.jpg)
وضعیت مشابهی در سایر گره ها مشاهده می شود:
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/ef512532a95ca982e4342071115dbe9f.jpg)
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/43c32ebca78755dde348ed5e7ac75c79.jpg)
در همان گره ها می بینیم:
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 بالا میآیند، به پردازشهای فرزند منتقل نمیشوند و در نتیجه فرآیندهای فرزند پایان نمییابند و در وضعیت زامبی باقی میمانند. شما می توانید در مورد همه اینها بیشتر بخوانید، به عنوان مثال، در .
چند راه برای حل مشکلات وجود دارد:
- به عنوان یک راه حل موقت - تعداد 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 - یا وظایف را در supercronic راه اندازی کنید نه مستقیم، بلکه با استفاده از همان ، که قادر است فرآیندها را به درستی خاتمه دهد و زامبی ها را تولید نکند.
داستان 2. "زامبی ها" هنگام حذف یک cgroup
Kubelet شروع به مصرف مقدار زیادی CPU کرد:
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/6140058330faaa3785b089dcba857056.jpg)
هیچ کس این را دوست ندارد، بنابراین ما خودمان را مسلح کردیم و شروع به رسیدگی به مشکل کرد. نتایج تحقیقات به شرح زیر بود:
- Kubelet بیش از یک سوم از زمان CPU خود را صرف استخراج داده های حافظه از همه cgroup ها می کند:
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20241'%3E%3C/svg%3E)
- در لیست پستی توسعه دهندگان هسته می توانید پیدا کنید . به طور خلاصه، موضوع به این نتیجه می رسد: فایل های مختلف tmpfs و موارد مشابه دیگر به طور کامل از سیستم حذف نمی شوند هنگام حذف یک cgroup، به اصطلاح انسان زنده شد. دیر یا زود آنها از کش صفحه حذف می شوند، اما حافظه زیادی روی سرور وجود دارد و هسته فایده ای در اتلاف وقت برای حذف آنها نمی بیند. به همین دلیل مدام انباشته می شوند. اصلا چرا این اتفاق می افتد؟ این یک سرور با 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 [و راه حل آنها]](/wp-content/uploads/2019/03/044c4e23a772c61a6206b9b20aa67c1d.jpg)
معلوم شد که مشکلی در systemd مورد استفاده وجود دارد Ubuntu 16.04، و هنگام مدیریت مونتهایی که برای اتصال ایجاد شدهاند، رخ میدهد. subPath از ConfigMap's یا Secret's. بعد از اینکه غلاف کار خود را کامل کرد سرویس systemd و مانت سرویس آن باقی می مانند در سیستم با گذشت زمان، تعداد زیادی از آنها جمع می شوند. حتی مسائلی در این زمینه وجود دارد:
- ;
- .
... که آخرین آنها به روابط عمومی در systemd اشاره دارد: (مساله در systemd - ).
مشکل دیگه وجود نداره 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"معلوم است که یک تصویر از یک رجیستری کند می تواند استقرار را مسدود کند در هر گره
متأسفانه راه های زیادی برای خروج از این وضعیت وجود ندارد:
- سعی کنید از رجیستری Docker خود مستقیماً در کلاستر یا مستقیماً با کلاستر (به عنوان مثال، رجیستری GitLab، Nexus و غیره) استفاده کنید.
- استفاده از ابزارهای کاربردی مانند .
داستان 5. گره ها به دلیل کمبود حافظه آویزان می شوند
در طول عملکرد برنامه های مختلف، ما همچنین با وضعیتی مواجه شدیم که در آن یک گره به طور کامل در دسترس نیست: SSH پاسخ نمی دهد، تمام دیمون های نظارتی سقوط می کنند، و سپس هیچ چیز (یا تقریباً هیچ چیز) غیرعادی در گزارش ها وجود ندارد.
من در تصاویر با استفاده از مثال یک گره که MongoDB در آن کار می کرد به شما خواهم گفت.
این چیزی است که در بالا به نظر می رسد به تصادفات:
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/5de916d270a862cbcbb5ed23c31f698e.jpg)
و مانند این - پس از تصادفات:
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/0f32bf1113204cf19f4639a297e40348.jpg)
در نظارت، یک پرش شدید نیز وجود دارد که در آن گره در دسترس نیست:
![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/31e770cac5be32bb7f95cfbbc6b9f1ae.jpg)
بنابراین، از اسکرین شات ها مشخص است که:
- RAM دستگاه به انتهای آن نزدیک است.
- یک جهش شدید در مصرف RAM وجود دارد که پس از آن دسترسی به کل دستگاه به طور ناگهانی غیرفعال می شود.
- یک کار بزرگ به 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 [و راه حل آنها]](/wp-content/uploads/2019/03/b2ae099729e55a686f6bec3012b96195.jpg)
... و بله، همه چیز شروع به پرواز کرد!
PS
برای کمک آنها در جمع آوری اشکالات و تهیه این مقاله، تشکر عمیق خود را از مهندسان متعدد شرکت ما و به ویژه از همکارم از تیم تحقیق و توسعه ما آندری کلیمنتیف ابراز می کنم.).
PPS
در وبلاگ ما نیز بخوانید:
- «'.
- حلقه نکات و ترفندهای Kubernetes:
- «"؛
- «"؛
- «"؛
- «'.
منبع: www.habr.com

![6 اشکال سرگرم کننده سیستم در عملکرد Kubernetes [و راه حل آنها]](/wp-content/uploads/2019/03/0d15d1de17cd6838fc1cad19615af218.jpg)