حالت تاریک
فصل ۹ — ReplicaSetها
قبلاً پوشش دادیم چگونه containerهای منفرد را بهعنوان Pod اجرا کنیم. اما این Podها اساساً singletonهای یکبارمصرف هستند. اغلب میخواهید چندین replica از یک container در زمان مشخص در حال اجرا باشد. دلایل متنوعی برای این نوع replication وجود دارد:
Redundancy چندین نمونهٔ در حال اجرا یعنی failure قابل تحمل است.
Scale چندین نمونهٔ در حال اجرا یعنی requestهای بیشتری handle میشود.
Sharding replicaهای مختلف میتوانند بخشهای مختلف یک محاسبه را بهصورت موازی handle کنند.
البته میتوانید بهصورت دستی چندین کپی از Pod با manifestهای Pod مختلف (اگرچه تا حد زیادی مشابه) بسازید، اما این هم خستهکننده و هم مستعد خطاست. از نظر منطقی، کاربری که مجموعهٔ replicatedی از Podها را مدیریت میکند آنها را بهعنوان یک موجودیت واحد برای تعریف و مدیریت در نظر میگیرد. دقیقاً همین کاری است که ReplicaSet انجام میدهد. ReplicaSet بهعنوان Pod manager سراسری cluster عمل میکند و اطمینان میدهد تعداد و نوع درست Podها همیشه در حال اجرا باشند.
چون ReplicaSetها ساخت و مدیریت مجموعههای replicated Pod را آسان میکنند، بلوکهای ساختمانی برای توصیف الگوهای رایج deploy برنامه و زیرساخت self-healing برنامهها در سطح infrastructure هستند. Podهای مدیریتشده توسط ReplicaSetها تحت شرایط خاص failure مانند node failure و network partition بهطور خودکار reschedule میشوند.
سادهترین راه تفکر به ReplicaSet این است که cutter کوکی و تعداد دلخواه کوکی را در یک شیء API واحد ترکیب میکند. وقتی ReplicaSet تعریف میکنیم، specification برای Podهایی که میخواهیم بسازیم («cutter کوکی») و تعداد replica دلخواه را تعریف میکنیم. علاوه بر این، باید راهی برای پیدا کردن Podهایی که ReplicaSet باید کنترل کند تعریف کنیم. عمل واقعی مدیریت Podهای replicated نمونهای از reconciliation loop است. چنین loopهایی اساس بیشتر طراحی و پیادهسازی Kubernetes هستند.
تصمیم تعبیهٔ تعریف Pod داخل ReplicaSet (و deployment، و job و…) یکی از تصمیمات جالبتر در Kubernetes است. در retrospect، احتمالاً بهتر بود به جای تعبیه مستقیم از reference به شیء PodTemplate استفاده شود.
Reconciliation Loopها
مفهوم مرکزی پشت reconciliation loop، مفهوم desired state در برابر observed یا current state است. desired state وضعیتی است که میخواهید. با ReplicaSet، تعداد replica دلخواه و تعریف Pod برای replicate است. مثلاً «desired state این است که سه replica از Pod در حال اجرای سرور kuard وجود داشته باشد.»
در مقابل، current state وضعیت فعلی مشاهدهشدهٔ سیستم است. مثلاً «فقط دو Pod kuard در حال اجرا هستند.»
reconciliation loop مداوم در حال اجراست، وضعیت فعلی جهان را مشاهده میکند و اقدام میکند تا observed state را با desired state مطابقت دهد. مثلاً با مثالهای قبلی، reconciliation loop Pod kuard جدید میسازد تا observed state را با desired state سه replica مطابقت دهد.
مزایای زیادی برای رویکرد reconciliation loop در مدیریت state وجود دارد. سیستمی ذاتاً goal-driven و self-healing است، اما اغلب در چند خط کد بهراحتی بیان میشود.
بهعنوان مثال ملموس، توجه کنید reconciliation loop برای ReplicaSetها یک loop واحد است، اما هم اقدامات کاربر برای scale up یا scale down ReplicaSet و هم node failure یا بازگشت nodeها به cluster پس از غیبت را handle میکند.
در ادامهٔ کتاب نمونههای متعددی از reconciliation loopها در عمل خواهیم دید.
ارتباط Podها و ReplicaSetها
یکی از تمهای کلیدی در Kubernetes decoupling است. بهویژه مهم است همهٔ مفاهیم اصلی Kubernetes نسبت به یکدیگر modular باشند و با اجزای دیگر قابل تعویض و جایگزینی باشند. در این روح، رابطهٔ ReplicaSetها و Podها loosely coupled است. اگرچه ReplicaSetها Pod میسازند و مدیریت میکنند، Podهایی که میسازند را مالک نیستند. ReplicaSetها از queryهای label برای شناسایی مجموعهٔ Podهایی که باید مدیریت کنند استفاده میکنند. سپس همان Pod APIای را که در فصل ۵ مستقیماً استفاده کردید برای ساخت Podهایی که مدیریت میکنند به کار میبرند. این مفهوم «ورود از در جلو» مفهوم طراحی مرکزی دیگری در Kubernetes است. در decoupling مشابه، ReplicaSetهایی که چند Pod میسازند و serviceهایی که به آن Podها load-balance میکنند هم کاملاً جدا و decoupled هستند. علاوه بر پشتیبانی از modularity، decoupling Podها و ReplicaSetها چند رفتار مهم را ممکن میسازد که در بخشهای زیر بحث میشود.
Adopt کردن Containerهای موجود
با وجود ارزش قرار دادن بر declarative configuration نرمافزار، گاهی ساخت imperative آسانتر است. بهویژه در ابتدا ممکن است فقط یک Pod با image container بدون ReplicaSet مدیریتکننده deploy کنید. اما در نقطهای ممکن است بخواهید singleton container را به service replicated گسترش دهید و آرایهای از containerهای مشابه بسازید و مدیریت کنید. ممکن است حتی load balancer تعریف کرده باشید که ترافیک به همان Pod میدهد. اگر ReplicaSetها Podهایی که میسازند را مالک بودند، تنها راه شروع replicate کردن Pod حذف و سپس راهاندازی مجدد از طریق ReplicaSet بود. این میتواند disruptive باشد، چون لحظهای وجود دارد که هیچ کپی container در حال اجرا نیست. اما چون ReplicaSetها از Podهایی که مدیریت میکنند decoupled هستند، میتوانید ReplicaSet بسازید که Pod موجود را «adopt» کند و کپیهای اضافی scale out کند. به این ترتیب بهصورت یکپارچه از یک Pod imperative منفرد به مجموعهٔ replicated Podهای مدیریتشده توسط ReplicaSet منتقل میشوید.
Quarantine کردن Containerها
اغلب وقتی سرور بد رفتار میکند، health check سطح Pod بهطور خودکار آن Pod را restart میکند. اما اگر health checkهای شما ناقص باشند، Pod میتواند بد رفتار کند اما همچنان بخشی از replicated set باشد. در این موقعیتها، اگرچه kill کردن Pod کار میکند، توسعهدهندگان فقط log برای debug دارند. بهجای آن میتوانید مجموعهٔ label روی Pod بیمار را تغییر دهید. این آن را از ReplicaSet (و service) جدا میکند تا Pod را debug کنید. ReplicaSet controller متوجه میشود Pod گم شده و کپی جدید میسازد، اما چون Pod هنوز در حال اجراست برای توسعهدهندگان برای interactive debugging در دسترس است که از logها بسیار ارزشمندتر است.
طراحی با ReplicaSetها
ReplicaSetها برای نمایش یک microservice تکی و scalable در معماری شما طراحی شدهاند. ویژگی کلیدی ReplicaSetها این است که هر Pod که ReplicaSet controller میسازد کاملاً homogeneous است. معمولاً این Podها سپس با service load balancer Kubernetes front میشوند که ترافیک را بین Podهای سرویس پخش میکند. بهطور کلی ReplicaSetها برای serviceهای stateless (یا تقریباً stateless) طراحی شدهاند. عناصری که ReplicaSet میسازد قابل تعویضاند؛ وقتی ReplicaSet scale down میشود، Pod دلخواهی برای حذف انتخاب میشود. رفتار برنامهتان نباید بهخاطر چنین scale-down تغییر کند.
ReplicaSet Spec
مثل همهٔ اشیاء Kubernetes، ReplicaSetها با specification تعریف میشوند. همهٔ ReplicaSetها باید نام یکتا (با فیلد metadata.name)، بخش spec که تعداد Podهایی (replicas) که باید در هر زمان در سطح cluster در حال اجرا باشند را توصیف میکند، و Pod template که Pod ساختهشده هنگام برآورده نشدن تعداد replica تعریفشده را توصیف میکند داشته باشند. مثال ۹-۱ تعریف minimal ReplicaSet را نشان میدهد.
مثال ۹-۱. kuard-rs.yaml
yaml
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: kuard
spec:
replicas: 1
template:
metadata:
labels:
app: kuard
version: "2"
spec:
containers:
- name: kuard
image: "gcr.io/kuar-demo/kuard-amd64:green"Pod Templateها
همانطور که قبلاً گفته شد، وقتی تعداد Podها در current state کمتر از desired state است، ReplicaSet controller Podهای جدید میسازد. Podها با Pod template که در specification ReplicaSet است ساخته میشوند. Podها دقیقاً همانطور که Pod را از فایل YAML در فصلهای قبل ساختید ساخته میشوند، اما بهجای فایل، ReplicaSet controller Kubernetes manifest Pod را بر اساس Pod template مستقیماً به API server ارسال میکند.
مثال زیر Pod template در ReplicaSet را نشان میدهد:
yaml
template:
metadata:
labels:
app: helloworld
version: v1
spec:
containers:
- name: helloworld
image: kelseyhightower/helloworld:v1
ports:
- containerPort: 80Labelها
در هر cluster با اندازهٔ معقول، Podهای زیادی در هر زمان در حال اجرا هستند — پس ReplicaSet reconciliation loop چگونه مجموعهٔ Podها را برای ReplicaSet خاص پیدا میکند؟ ReplicaSetها وضعیت cluster را با مجموعهای از Pod labelها نظارت میکنند. labelها برای فیلتر کردن فهرست Podها و ردیابی Podهای در حال اجرا در cluster استفاده میشوند. هنگام ایجاد اولیه، ReplicaSet فهرست Pod را از API Kubernetes میگیرد و نتایج را با label فیلتر میکند. بر اساس تعداد Podهای برگشتی از query، ReplicaSet Pod حذف یا میسازد تا به تعداد replica دلخواه برسد. labelهای استفادهشده برای فیلتر در بخش spec ReplicaSet تعریف میشوند و کلید درک نحوهٔ کار ReplicaSetها هستند.
selector در ReplicaSet spec باید زیرمجموعهٔ proper از labelهای Pod template باشد.
ایجاد ReplicaSet
ReplicaSetها با ارسال شیء ReplicaSet به API Kubernetes ساخته میشوند. در این بخش ReplicaSet را با فایل پیکربندی و دستور kubectl apply میسازیم.
فایل پیکربندی ReplicaSet در مثال ۹-۱ اطمینان میدهد یک کپی از container gcr.io/kuar-demo/kuard-amd64:green در هر زمان در حال اجرا باشد.
از دستور kubectl apply برای ارسال ReplicaSet kuard به API Kubernetes استفاده کنید:
bash
$ kubectl apply -f kuard-rs.yaml
replicaset "kuard" createdپس از پذیرش ReplicaSet kuard، ReplicaSet controller تشخیص میدهد هیچ Pod kuard در حال اجرا که با desired state مطابقت داشته باشد نیست و Pod kuard جدید بر اساس محتوای Pod template ساخته میشود:
bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kuard-yvzgd 1/1 Running 0 11sبررسی ReplicaSet
مثل Podها و اشیای API Kubernetes دیگر، اگر به جزئیات بیشتر ReplicaSet علاقهمندید، دستور describe اطلاعات بیشتری دربارهٔ وضعیت آن میدهد. مثال استفاده از describe برای جزئیات ReplicaSetی که قبلاً ساختیم:
bash
$ kubectl describe rs kuard
Name: kuard
Namespace: default
Image(s): kuard:1.9.15
Selector: app=kuard,version=2
Labels: app=kuard,version=2
Replicas: 1 current / 1 desired
Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed
No volumes.میتوانید label selector برای ReplicaSet و همچنین وضعیت همهٔ replicaهای مدیریتشده توسط ReplicaSet را ببینید.
پیدا کردن ReplicaSet از Pod
گاهی ممکن است بپرسید آیا Pod توسط ReplicaSet مدیریت میشود و اگر بله، کدام ReplicaSet.
برای این کشف، ReplicaSet controller annotation به هر Podی که میسازد اضافه میکند. key برای annotation kubernetes.io/created-by است. اگر دستور زیر را اجرا کنید، entry kubernetes.io/created-by را در بخش annotations ببینید:
bash
$ kubectl get pods <pod-name> -o yamlدر صورت وجود، نام ReplicaSetی که این Pod را مدیریت میکند فهرست میشود. توجه کنید چنین annotationهایی best-effort هستند؛ فقط هنگام ایجاد Pod توسط ReplicaSet ساخته میشوند و کاربر Kubernetes میتواند هر زمان حذفشان کند.
پیدا کردن مجموعهٔ Podها برای ReplicaSet
همچنین میتوانید مجموعهٔ Podهای مدیریتشده توسط ReplicaSet را تعیین کنید. ابتدا میتوانید مجموعهٔ labelها را با دستور kubectl describe بگیرید. در مثال قبلی، label selector app=kuard,version=2 بود. برای پیدا کردن Podهای مطابق این selector از flag --selector یا مخفف -l استفاده کنید:
bash
$ kubectl get pods -l app=kuard,version=2این دقیقاً همان queryای است که ReplicaSet برای تعیین تعداد فعلی Podها اجرا میکند.
Scale کردن ReplicaSetها
ReplicaSetها با بهروزرسانی key spec.replicas روی شیء ReplicaSet ذخیرهشده در Kubernetes scale up یا down میشوند. وقتی ReplicaSet scale up میشود، Podهای جدید با Pod template تعریفشده روی ReplicaSet به API Kubernetes ارسال میشوند.
Scale کردن Imperative با kubectl scale
سادهترین راه دستور scale در kubectl است. مثلاً برای scale up به چهار replica:
bash
$ kubectl scale replicasets kuard --replicas=4چنین دستورات imperative برای نمایش و واکنش سریع به موقعیت اضطراری (مثلاً در پاسخ به افزایش ناگهانی بار) مفیدند، اما مهم است پیکربندیهای فایل متنی را هم با تعداد replicaای که با دستور scale imperative تنظیم کردید مطابقت دهید. دلیلش در سناریوی زیر واضح میشود:
Alice on call است و ناگهان بار زیادی روی serviceای که مدیریت میکند میآید. Alice با دستور scale تعداد سرورهای پاسخدهنده به requestها را به ۱۰ افزایش میدهد و مشکل حل میشود. اما Alice فراموش میکند پیکربندی ReplicaSetهای check-in شده در source control را بهروز کند. چند روز بعد Bob rolloutهای هفتگی را آماده میکند. Bob پیکربندی ReplicaSetهای version control را برای image container جدید ویرایش میکند، اما متوجه نمیشود تعداد replica در فایل فعلاً ۵ است نه ۱۰ که Alice در پاسخ به افزایش بار تنظیم کرده. Bob rollout را ادامه میدهد که هم image container را بهروز میکند و هم تعداد replica را نصف میکند و بلافاصله overload یا outage ایجاد میکند.
امیدواریم این نیاز را نشان دهد که هر تغییر imperative بلافاصله با تغییر declarative در source control دنبال شود. در واقع اگر فوری نیست، معمولاً فقط تغییرات declarative توصیه میشود که در بخش بعد توضیح داده میشود.
Scale کردن Declarative با kubectl apply
در دنیای declarative، با ویرایش فایل پیکربندی در version control تغییر میدهیم و سپس آن تغییرات را روی cluster اعمال میکنیم. برای scale کردن ReplicaSet kuard، فایل پیکربندی kuard-rs.yaml را ویرایش کنید و replicas را روی ۳ تنظیم کنید:
yaml
...
spec:
replicas: 3
...در محیط چندکاربره، میخواهید code review مستند این تغییر داشته باشید و در نهایت در version control check-in شود. به هر حال، سپس از kubectl apply برای ارسال ReplicaSet kuard بهروزشده به API server استفاده کنید:
bash
$ kubectl apply -f kuard-rs.yaml
replicaset "kuard" configuredاکنون که ReplicaSet kuard بهروز شده، ReplicaSet controller تشخیص میدهد تعداد Podهای دلخواه تغییر کرده و باید اقدام کند تا desired state محقق شود. اگر در بخش قبل از دستور scale imperative استفاده کردید، ReplicaSet controller یک Pod را destroy میکند تا تعداد به سه برسد. در غیر این صورت دو Pod جدید با Pod template تعریفشده روی ReplicaSet kuard به API Kubernetes ارسال میکند. به هر حال، با kubectl get pods Podهای kuard در حال اجرا را فهرست کنید. باید خروجی شبیه زیر ببینید:
bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kuard-3a2sb 1/1 Running 0 26s
kuard-wuq9v 1/1 Running 0 26s
kuard-yvzgd 1/1 Running 0 2mAutoscaling ReplicaSet
اگرچه گاهی کنترل صریح تعداد replica در ReplicaSet میخواهید، اغلب فقط «به اندازهٔ کافی» replica میخواهید. تعریف بسته به نیاز containerها در ReplicaSet متفاوت است. مثلاً با web server مانند NGINX ممکن است بهخاطر مصرف CPU scale کنید. برای in-memory cache ممکن است با مصرف memory scale کنید. در برخی موارد ممکن است به metricهای سفارشی برنامه scale کنید. Kubernetes همهٔ این سناریوها را از طریق Horizontal Pod Autoscaling (HPA) handle میکند.
HPA نیاز به وجود Pod heapster روی cluster شما دارد. heapster metricها را ردیابی میکند و API برای مصرف metricها که HPA هنگام تصمیم scale استفاده میکند فراهم میکند. بیشتر نصبهای Kubernetes heapster را بهطور پیشفرض شامل میشوند. حضور آن را با فهرست Podها در namespace kube-system تأیید کنید:
bash$ kubectl get pods --namespace=kube-systemباید Podی به نام heapster در آن فهرست ببینید. اگر نمیبینید، autoscaling درست کار نمیکند.
«Horizontal Pod Autoscaling» کمی طولانی است و ممکن است بپرسید چرا فقط «autoscaling» نامیده نمیشود. Kubernetes بین horizontal scaling که شامل ساخت replica اضافی Pod است و vertical scaling که شامل افزایش منابع مورد نیاز یک Pod خاص است (مثلاً افزایش CPU مورد نیاز Pod) تمایز قائل میشود. vertical scaling فعلاً در Kubernetes پیادهسازی نشده اما برنامهریزی شده. علاوه بر این، بسیاری از راهحلها cluster autoscaling را هم فعال میکنند که تعداد ماشینها در cluster را بر اساس نیاز منابع scale میکند، اما این راهحل اینجا پوشش داده نمیشود.
Autoscaling بر اساس CPU
Scale بر اساس مصرف CPU رایجترین use case برای Pod autoscaling است. معمولاً برای سیستمهای request-based که CPU را متناسب با تعداد requestهایی که دریافت میکنند مصرف میکنند، در حالی که مقدار نسبتاً ثابتی memory استفاده میکنند، مفیدترین است.
برای scale کردن ReplicaSet میتوانید دستوری شبیه زیر اجرا کنید:
bash
$ kubectl autoscale rs kuard --min=2 --max=5 --cpu-percent=80این autoscalerای میسازد که بین دو و پنج replica با آستانهٔ CPU ۸۰٪ scale میکند. برای مشاهده، تغییر یا حذف این resource میتوانید از دستورات استاندارد kubectl و resource horizontalpodautoscalers استفاده کنید. horizontalpodautoscalers برای تایپ کردن زیاد است، اما میتوان به hpa مخفف کرد:
bash
$ kubectl get hpaبهخاطر ماهیت decoupled Kubernetes، لینک مستقیمی بین HPA و ReplicaSet نیست. این برای modularity و composition عالی است، اما anti-patternهایی هم ممکن میسازد. بهویژه ترکیب autoscaling با مدیریت imperative یا declarative تعداد replica ایدهٔ بدی است. اگر هم شما و هم autoscaler سعی در تغییر تعداد replica دارند، احتمال clash و رفتار غیرمنتظره بسیار زیاد است.
حذف ReplicaSetها
وقتی ReplicaSet دیگر لازم نیست میتوان با kubectl delete حذف کرد. بهطور پیشفرض Podهای مدیریتشده توسط ReplicaSet هم حذف میشوند:
bash
$ kubectl delete rs kuard
replicaset "kuard" deletedاجرای kubectl get pods نشان میدهد همهٔ Podهای kuard ساختهشده توسط ReplicaSet kuard هم حذف شدهاند:
bash
$ kubectl get podsاگر نمیخواهید Podهای مدیریتشده توسط ReplicaSet حذف شوند، میتوانید flag --cascade را false تنظیم کنید تا فقط شیء ReplicaSet حذف شود نه Podها:
bash
$ kubectl delete rs kuard --cascade=falseخلاصه
ترکیب Podها با ReplicaSetها پایهٔ ساخت برنامههای robust با failover خودکار است و deploy آن برنامهها را با الگوهای deploy scalable و منطقی آسان میکند. ReplicaSetها باید برای هر Podی که به آن اهمیت میدهید استفاده شوند، حتی اگر Pod تکی باشد! برخی حتی بهطور پیشفرض به ReplicaSet بهجای Pod استفاده میکنند. یک cluster معمولی ReplicaSetهای زیادی دارد، پس آزادانه در ناحیهٔ مربوطه اعمال کنید.