نه نکته عملکرد Kubernetes

نه نکته عملکرد Kubernetes

سلام به همه! نام من اولگ سیدورنکوف است، من در DomClick به عنوان رهبر تیم زیرساخت کار می کنم. بیش از سه سال است که از مکعب برای فروش استفاده می کنیم و در این مدت لحظات جالب و متفاوتی را با آن تجربه کرده ایم. امروز به شما خواهم گفت که چگونه با رویکرد درست، می توانید عملکرد بیشتری از وانیلی Kubernetes را برای خوشه خود کاهش دهید. آماده برو پیوسته!

همه شما به خوبی می دانید که Kubernetes یک سیستم منبع باز مقیاس پذیر برای ارکستراسیون کانتینر است. خوب، یا 5 باینری که با مدیریت چرخه عمر میکروسرویس های شما در محیط سرور جادو می کنند. علاوه بر این، این یک ابزار نسبتاً انعطاف پذیر است که می تواند مانند سازنده لگو برای حداکثر سفارشی سازی برای کارهای مختلف مونتاژ شود.

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

1. منابع تیم و برنامه را پیگیری کنید

نه نکته عملکرد Kubernetes

یکی از پیش پا افتاده ترین اما موثرترین روش ها، معرفی درخواست ها/محدودیت ها است. برنامه ها را بر اساس فضاهای نام و فضاهای نام را توسط تیم های توسعه جدا کنید. برنامه را قبل از استقرار مقادیر برای مصرف زمان پردازنده، حافظه، ذخیره سازی زودگذر تنظیم کنید.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

با تجربه، به این نتیجه رسیدیم: ارزش ندارد که درخواست های محدودیت ها را بیش از دو برابر افزایش دهیم. حجم خوشه بر اساس درخواست ها محاسبه می شود و اگر تفاوت منابع را مثلاً 5-10 برابر روی برنامه ها قرار دهید، تصور کنید وقتی گره شما با پادها پر می شود و ناگهان بار دریافت می کند چه اتفاقی می افتد. . هیچ چیز خوبی نیست. حداقل، دریچه گاز، و به عنوان حداکثر، با کارگر خداحافظی کنید و پس از شروع حرکت غلاف ها، بار چرخه ای را روی بقیه گره ها دریافت کنید.

علاوه بر این، با کمک limitranges می توانید مقادیر منبع را برای ظرف در ابتدا تنظیم کنید - حداقل، حداکثر و پیش فرض:

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

به یاد داشته باشید که منابع فضای نام را محدود کنید تا یک دستور نتواند تمام منابع خوشه را بگیرد:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

همانطور که از توضیحات می بینید resourcequotas، اگر دستور ops بخواهد پادهایی را مستقر کند که 10 cpu دیگر مصرف کند، زمانبند اجازه نمی دهد این کار انجام شود و یک خطا صادر می کند:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

برای حل یک مشکل مشابه، می توانید یک ابزار، به عنوان مثال، به عنوان این، که می تواند وضعیت منابع دستوری را ذخیره و متعهد کند.

2. بهترین ذخیره سازی فایل را انتخاب کنید

نه نکته عملکرد Kubernetes

در اینجا می خواهم به موضوع حجم های پایدار و زیرسیستم دیسک گره های کارگر Kubernetes بپردازم. من امیدوارم که هیچ کس از "مکعب" در HDD در تولید استفاده نکند، اما گاهی اوقات حتی یک SSD معمولی در حال حاضر کافی نیست. ما با چنین مشکلی روبرو شدیم که لاگ ها با عملیات I / O دیسک را می کشند و راه حل های زیادی در اینجا وجود ندارد:

  • از SSD های با کارایی بالا استفاده کنید یا به NVMe بروید (اگر سخت افزار خود را مدیریت می کنید).

  • سطح ورود به سیستم را کاهش دهید.

  • تعادل "هوشمندانه" غلاف هایی که به دیسک تجاوز می کنند را انجام دهید (podAntiAffinity).

اسکرین شات بالا نشان می دهد که در هنگام فعال شدن ورود به سیستم Access_logs (~12k logs/sec) تحت کنترلر nginx-ingress-با یک دیسک چه اتفاقی می افتد. البته چنین حالتی می تواند منجر به تخریب همه برنامه های کاربردی در این گره شود.

در مورد PV، متأسفانه، من همه چیز را امتحان نکردم. نمایش ها حجم های ماندگار از بهترین گزینه ای که مناسب شماست استفاده کنید. به طور تاریخی در کشور ما اتفاق افتاده است که بخش کوچکی از سرویس ها به حجم های RWX نیاز دارند و از مدت ها قبل شروع به استفاده از ذخیره سازی NFS برای این کار کردند. ارزان و ... به اندازه کافی. البته ما باهاش ​​گند خوردیم - سالم باش، اما یاد گرفتیم چطوری کوک کنیم و سرش دیگر درد نمی کند. و در صورت امکان، به S3 SXNUMX Storage تغییر دهید.

3. تصاویر بهینه سازی شده بسازید

نه نکته عملکرد Kubernetes

بهتر است از تصاویر بهینه سازی شده برای کانتینر استفاده کنید تا Kubernetes بتواند آنها را سریعتر واکشی کند و آنها را کارآمدتر اجرا کند. 

بهینه سازی به این معنی است که تصاویر:

  • فقط یک برنامه کاربردی داشته باشد یا فقط یک عملکرد را انجام دهد.

  • اندازه کوچک، زیرا تصاویر بزرگ بدتر از طریق شبکه منتقل می شوند.

  • دارای نقاط پایانی سلامت و آمادگی هستند که Kubernetes می تواند از آنها برای انجام اقدامات در صورت خرابی استفاده کند.

  • از سیستم عامل های سازگار با کانتینر (مانند Alpine یا CoreOS) که در برابر خطاهای پیکربندی مقاوم تر هستند استفاده کنید.

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

ابزارها و خدمات زیادی وجود دارد که به شما امکان می دهد تصاویر را در لحظه بررسی و بهینه سازی کنید. مهم است که همیشه آنها را به روز و ایمن نگه دارید. در نتیجه، شما دریافت می کنید:

  1. کاهش بار شبکه در کل خوشه.

  2. کاهش زمان راه اندازی کانتینر

  3. اندازه کوچکتر کل رجیستری Docker شما.

4. از کش DNS استفاده کنید

نه نکته عملکرد Kubernetes

اگر در مورد بارهای زیاد صحبت کنیم، بدون تنظیم سیستم DNS خوشه، زندگی بسیار کثیف است. روزی روزگاری، توسعه دهندگان Kubernetes از راه حل kube-dns خود پشتیبانی کردند. این نرم افزار در کشور ما نیز اجرا شد، اما این نرم افزار به طور خاص تنظیم نشد و عملکرد مورد نیاز را ارائه نکرد، اگرچه به نظر می رسد کار ساده است. سپس coredns ظاهر شد، که ما به آن سوئیچ کردیم و غم را نمی دانستیم، بعداً این سرویس DNS پیش فرض در K8s شد. در مقطعی، ما تا 40 هزار دور در ثانیه به سیستم DNS رشد کردیم و این راه حل نیز کافی نبود. اما، به طور شانسی، Nodelocaldns بیرون آمد، با نام مستعار node local cache، با نام مستعار NodeLocal DNSCache.

چرا از آن استفاده می کنیم؟ یک اشکال در هسته لینوکس وجود دارد که وقتی چندین دسترسی از طریق NAT از طریق UDP انجام می شود، منجر به شرایط مسابقه برای نوشتن در جداول conntrack می شود و بخشی از ترافیک از طریق NAT از بین می رود (هر سفر از طریق سرویس NAT است). Nodelocaldns این مشکل را با خلاص شدن از شر NAT و ارتقای اتصال TCP به DNS بالادستی و همچنین کش کردن جستجوهای DNS بالادستی به صورت محلی (شامل یک کش منفی 5 ثانیه ای) حل می کند.

5. مقیاس غلاف به صورت افقی و عمودی به طور خودکار

نه نکته عملکرد Kubernetes

آیا می توانید با اطمینان بگویید که همه میکروسرویس های شما برای افزایش دو تا سه برابری بار آماده هستند؟ چگونه منابع را به برنامه های خود به درستی تخصیص دهیم؟ کارکردن چند غلاف بیش از حجم کاری ممکن است زائد باشد و پشت سر هم نگه داشتن آنها باعث از کار افتادگی ناشی از افزایش ناگهانی ترافیک به سرویس می شود. میانگین طلایی به دستیابی به طلسم ضرب از جمله خدماتی مانند Autoscaler پاد افقی и مقیاس کننده خودکار غلاف عمودی.

VPA به شما این امکان را می دهد تا به طور خودکار درخواست ها/محدودیت های ظروف خود را در یک غلاف بر اساس استفاده واقعی افزایش دهید. چگونه می تواند مفید باشد؟ اگر پادهایی دارید که به دلایلی نمی‌توان آنها را به صورت افقی کوچک کرد (که کاملاً قابل اعتماد نیست)، می‌توانید برای تغییر منابع آن به VPA اعتماد کنید. ویژگی آن یک سیستم توصیه مبتنی بر داده‌های تاریخی و فعلی از سرور متریک است، بنابراین اگر نمی‌خواهید درخواست‌ها/محدودیت‌ها را به‌طور خودکار تغییر دهید، می‌توانید به سادگی منابع توصیه‌شده برای کانتینرهای خود را نظارت کنید و تنظیمات را برای ذخیره CPU و حافظه بهینه کنید. در خوشه

نه نکته عملکرد Kubernetesتصویر گرفته شده از https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

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

به عنوان مثال، در اینجا تنظیمات معمولی غلاف وجود دارد:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

موتور توصیه تعیین می کند که برنامه شما برای اجرای صحیح به 300 متر CPU و 500 Mi نیاز دارد. این تنظیمات را دریافت خواهید کرد:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

همانطور که در بالا ذکر شد، این مقیاس بندی متناسب بر اساس نسبت درخواست/محدودیت در مانیفست است:

  • CPU: 200m → 300m: نسبت 1:1.75;

  • حافظه: 250Mi → 500Mi: نسبت 1:2.

با توجه به اینکه HPA، سپس مکانیسم عملکرد شفاف تر است. آستانه‌ها برای معیارهایی مانند پردازنده و حافظه تنظیم می‌شوند و اگر میانگین همه نسخه‌ها از آستانه فراتر رود، برنامه با 1+ غلاف مقیاس می‌شود تا زمانی که مقدار به زیر آستانه می‌رسد یا تا زمانی که به حداکثر تعداد کپی‌ها برسد.

نه نکته عملکرد Kubernetesتصویر گرفته شده از https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

علاوه بر معیارهای معمولی مانند CPU و Memory، می‌توانید آستانه‌هایی را روی معیارهای Prometheus سفارشی خود تعیین کنید و اگر فکر می‌کنید این دقیق‌ترین راه برای تعیین زمان مقیاس‌سازی برنامه‌تان است، با آن‌ها کار کنید. هنگامی که برنامه در زیر آستانه متریک مشخص شده تثبیت شود، HPA شروع به کوچک کردن Pods تا حداقل تعداد تکرار یا تا زمانی که بار به آستانه برسد، می‌کند.

6. Node Affinity و Pod Affinity را فراموش نکنید

نه نکته عملکرد Kubernetes

همه گره ها روی یک سخت افزار اجرا نمی شوند و همه پادها نیازی به اجرای برنامه های محاسباتی فشرده ندارند. Kubernetes به شما اجازه می دهد تا با استفاده از گره ها و غلاف ها تخصص خود را مشخص کنید گره افینیتی и Pod Affinity.

اگر گره‌های مناسب برای عملیات محاسباتی دارید، برای حداکثر کارایی، بهتر است برنامه‌ها را به گره‌های مناسب متصل کنید. برای این کار استفاده کنید nodeSelector با برچسب گره

فرض کنید دو گره دارید: یکی با CPUType=HIGHFREQ و تعداد زیادی هسته سریع، دیگری با MemoryType=HIGHMEMORY حافظه بیشتر و عملکرد سریعتر ساده ترین راه این است که یک پاد استقرار را به یک گره اختصاص دهید HIGHFREQبا افزودن به بخش spec انتخابگر مانند این:

…
nodeSelector:
	CPUType: HIGHFREQ

یک راه پرهزینه تر و خاص تر برای انجام این کار استفاده از آن است nodeAffinity در زمینه affinity razdela spec. دو گزینه وجود دارد:

  • requiredDuringSchedulingIgnoredDuringExecution: تنظیمات سخت (زمان‌بندی‌کننده فقط غلاف‌ها را روی گره‌های خاص (و هیچ جای دیگر) مستقر می‌کند).

  • preferredDuringSchedulingIgnoredDuringExecution: تنظیم نرم (زمان‌بند سعی می‌کند در گره‌های خاص مستقر شود، و اگر موفق نشد، سعی می‌کند به گره موجود بعدی مستقر شود).

شما می توانید یک نحو خاص برای مدیریت برچسب های گره تعیین کنید، به عنوان مثال، In, NotIn, Exists, DoesNotExist, Gt یا Lt. با این حال، به یاد داشته باشید که روش های پیچیده در لیست های طولانی از برچسب ها، تصمیم گیری را در موقعیت های بحرانی کند می کند. به عبارت دیگر، بیش از حد پیچیده نکنید.

همانطور که در بالا ذکر شد، Kubernetes به شما این امکان را می دهد که اتصال پادهای فعلی را تنظیم کنید. یعنی می توانید کاری کنید که پادهای خاصی با پادهای دیگر در همان منطقه در دسترس (مرتبط با ابرها) یا گره ها با هم کار کنند.

В podAffinity حاشیه affinity razdela spec همان فیلدها موجود است که در مورد nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution. تنها تفاوت این است که matchExpressions پادها را به گره‌ای متصل می‌کند که قبلاً یک پاد را با آن برچسب اجرا می‌کند.

Kubernetes بیشتر زمینه ارائه می دهد podAntiAffinity، که در مقابل، یک pod را به یک گره با غلاف های خاص متصل نمی کند.

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

7. لکه ها و تحمل ها

راه دیگری برای مدیریت زمانبندی وجود دارد. اگر یک خوشه بزرگ با صدها گره و هزاران میکروسرویس دارید، جلوگیری از میزبانی پادهای خاص توسط گره های خاص بسیار دشوار است.

مکانیسم آلودگی - قوانین منع - به این امر کمک می کند. به عنوان مثال، می‌توانید از اجرای برخی از گره‌ها در سناریوهای خاص جلوگیری کنید. برای اعمال رنگ بر روی یک گره خاص، از گزینه استفاده کنید taint در کوبکتل کلید و مقدار را مشخص کنید و سپس مانند آن را لکه دار کنید NoSchedule یا NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

همچنین شایان ذکر است که مکانیسم لکه‌داری از سه اثر اصلی پشتیبانی می‌کند: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule به این معنی که تا زمانی که یک ورودی مربوطه در مشخصات غلاف وجود داشته باشد tolerations، نمی توان آن را در گره مستقر کرد (در این مثال node10).

  • PreferNoSchedule - نسخه ساده شده NoSchedule. در این مورد، زمان‌بند سعی می‌کند پادهایی را که ورودی تطبیقی ​​ندارند، اختصاص ندهد. tolerations در هر گره، اما این محدودیت سختی نیست. اگر هیچ منبعی در خوشه وجود نداشته باشد، پادها شروع به استقرار در این گره خواهند کرد.

  • NoExecute - این اثر باعث تخلیه فوری غلاف هایی می شود که ورودی تطبیقی ​​ندارند tolerations.

عجیب است که این رفتار را می توان با استفاده از مکانیسم تحمل ها خنثی کرد. این زمانی راحت است که یک گره "ممنوع" وجود داشته باشد و شما باید فقط خدمات زیرساختی را روی آن قرار دهید. چگونه انجامش بدهیم؟ فقط به غلاف هایی اجازه دهید که تحمل مناسبی برای آنها وجود دارد.

در اینجا مشخصات غلاف به نظر می رسد:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

این به این معنی نیست که در طول باز استقرار بعدی، غلاف دقیقاً به این گره برخورد می کند، این مکانیسم Node Affinity نیست و nodeSelector. اما با ترکیب چندین ویژگی، می‌توانید به یک تنظیم زمان‌بندی بسیار انعطاف‌پذیر دست پیدا کنید.

8. اولویت استقرار Pod را تنظیم کنید

فقط به این دلیل که پیوندهای pod-to-node را پیکربندی کرده اید به این معنی نیست که همه غلاف ها باید با یک اولویت برخورد شوند. برای مثال، ممکن است بخواهید برخی از پادها را قبل از دیگران مستقر کنید.

Kubernetes راه های مختلفی را برای تنظیم Pod Priority و Preemption ارائه می دهد. تنظیم از چند بخش تشکیل شده است: شی PriorityClass و توضیحات میدانی priorityClassName در مشخصات غلاف به یک مثال توجه کنید:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"

ما ایجاد می کنیم PriorityClass، نام، توضیحات و ارزش را به آن بدهید. بالاتر value، اولویت بالاتر است. مقدار می تواند هر عدد صحیح 32 بیتی کمتر یا مساوی با 1 باشد. مقادیر بالاتر برای غلاف های سیستم حیاتی محفوظ است که معمولاً نمی توان از آنها جلوگیری کرد. اخراج تنها در صورتی رخ می دهد که غلاف با اولویت بالا جایی برای چرخش نداشته باشد، سپس برخی از غلاف ها از یک گره خاص تخلیه می شوند. اگر این مکانیسم برای شما خیلی سفت و سخت است، می توانید این گزینه را اضافه کنید preemptionPolicy: Never، و پس از آن هیچ پیش‌پرداختی وجود نخواهد داشت، غلاف اولین نفر در صف خواهد بود و منتظر می‌ماند تا زمان‌بندی منابع رایگان برای آن پیدا کند.

در مرحله بعد یک pod ایجاد می کنیم که در آن نام را مشخص می کنیم priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

می توانید هر تعداد کلاس اولویت ایجاد کنید، اگرچه توصیه می شود از این کار غافل نشوید (مثلاً خود را به اولویت کم، متوسط ​​و بالا محدود کنید).

بنابراین، در صورت لزوم، می توانید کارایی استقرار سرویس های مهم مانند nginx-ingress-controller، coredns و غیره را افزایش دهید.

9. خوشه ETCD خود را بهینه کنید

نه نکته عملکرد Kubernetes

ETCD را می توان مغز کل خوشه نامید. حفظ عملکرد این پایگاه داده در سطح بالا بسیار مهم است، زیرا سرعت عملیات در "مکعب" به آن بستگی دارد. یک راه حل نسبتا استاندارد و در عین حال خوب، نگه داشتن یک خوشه ETCD بر روی گره های اصلی به منظور داشتن حداقل تاخیر برای kube-apiserver است. اگر این امکان پذیر نیست، ETCD را تا حد امکان نزدیک و با پهنای باند خوب بین شرکت کنندگان قرار دهید. همچنین به این نکته توجه کنید که چه تعداد گره از ETCD می توانند بدون آسیب رساندن به خوشه خارج شوند.

نه نکته عملکرد Kubernetes

به خاطر داشته باشید که افزایش بیش از حد تعداد شرکت کنندگان در خوشه می تواند تحمل خطا را به بهای عملکرد افزایش دهد، همه چیز باید در حد اعتدال باشد.

اگر در مورد راه اندازی سرویس صحبت کنیم، توصیه های کمی وجود دارد:

  1. بر اساس اندازه خوشه سخت افزار خوبی داشته باشید (می توانید بخوانید اینجا).

  2. اگر خوشه‌ای را بین یک جفت DC یا شبکه و دیسک‌های خود پخش کرده‌اید، چند پارامتر را تغییر دهید (می‌توانید بخوانید اینجا).

نتیجه

این مقاله نکاتی را که تیم ما سعی در رعایت آنها دارد را شرح می دهد. این شرح گام به گام اقدامات نیست، بلکه گزینه هایی است که می تواند برای بهینه سازی سربار یک خوشه مفید باشد. واضح است که هر خوشه به روش خود منحصر به فرد است و راه حل های تنظیم می تواند بسیار متفاوت باشد، بنابراین دریافت بازخورد از شما جالب خواهد بود: چگونه خوشه Kubernetes خود را نظارت می کنید، چگونه عملکرد آن را بهبود می بخشید. تجربه خود را در نظرات به اشتراک بگذارید، دانستن آن جالب خواهد بود.

منبع: www.habr.com