حالت تاریک
فصل ۱۱ — DaemonSetها
Deploymentها و ReplicaSetها عموماً برای ساخت سرویسی (مثلاً web server) با replicaهای متعدد برای redundancy هستند. اما تنها دلیل replicate کردن مجموعهای از Podها در یک cluster نیست. دلیل دیگر replicate کردن Podها، زمانبندی یک Pod روی هر node در cluster است. عموماً انگیزه replicate کردن Pod به هر node، قرار دادن نوعی agent یا daemon روی هر node است، و شیء Kubernetes برای تحقق این کار DaemonSet است.
DaemonSet تضمین میکند یک کپی Pod روی مجموعهای از nodeها در cluster Kubernetes اجرا شود. DaemonSetها برای deploy کردن system daemonهایی مثل log collectorها و monitoring agentها استفاده میشوند که معمولاً باید روی هر node اجرا شوند. DaemonSetها عملکرد مشابه ReplicaSetها دارند؛ هر دو Podهایی میسازند که انتظار میرود سرویسهای long-running باشند و desired state و observed state cluster مطابقت داشته باشد.
با توجه به شباهت DaemonSetها و ReplicaSetها، مهم است بدانید چه موقع یکی را بهجای دیگری استفاده کنید. ReplicaSetها وقتی برنامهتان کاملاً از node جدا است و میتوانید چند کپی روی یک node بدون ملاحظه خاص اجرا کنید. DaemonSetها وقتی یک کپی برنامه باید روی همه یا زیرمجموعهای از nodeهای cluster اجرا شود.
بهطور کلی نباید از محدودیتهای scheduling یا پارامترهای دیگر استفاده کنید تا Podها روی یک node colocate نشوند. اگر میخواهید یک Pod per node، DaemonSet resource درست Kubernetes است. همینطور اگر سرویس replicated همگن برای سرو user traffic میسازید، ReplicaSet احتمالاً resource درست است.
میتوانید از labelها استفاده کنید تا Podهای DaemonSet روی nodeهای خاص اجرا شوند؛ مثلاً ممکن است بخواهید نرمافزار intrusion-detection تخصصی روی nodeهایی که به edge network expose شدهاند اجرا کنید.
همچنین میتوانید از DaemonSetها برای نصب نرمافزار روی nodeها در cluster مبتنی بر cloud استفاده کنید. برای بسیاری از سرویسهای cloud، upgrade یا scale cluster میتواند virtual machineها را حذف و/یا دوباره بسازد. این رویکرد immutable infrastructure پویا میتواند مشکل ایجاد کند اگر میخواهید (یا IT مرکزی الزام کرده) نرمافزار خاصی روی هر machine باشد. برای اطمینان از نصب نرمافزار خاص روی هر machine با وجود upgrade و scale، DaemonSet رویکرد درست است. حتی میتوانید host filesystem را mount کنید و scriptهایی اجرا کنید که بستههای RPM/DEB را روی host operating system نصب کنند. اینطور cluster cloud-native دارید که همچنان الزامات enterprise IT را برآورده میکند.
Scheduler DaemonSet
بهطور پیشفرض DaemonSet یک کپی Pod روی هر node میسازد مگر node selector استفاده شود که nodeهای واجد شرایط را به nodeهایی با labelهای matching محدود میکند. DaemonSetها تعیین میکنند Pod روی کدام node اجرا شود در زمان ساخت Pod با مشخص کردن فیلد nodeName در spec Pod. در نتیجه، Podهای ساختهشده توسط DaemonSetها توسط Kubernetes scheduler نادیده گرفته میشوند.
مثل ReplicaSetها، DaemonSetها توسط control loop reconciliation مدیریت میشوند که desired state (Pod روی همه nodeها) را با observed state (آیا Pod روی node خاص هست?) مقایسه میکند. با این اطلاعات، DaemonSet controller روی هر node که Pod matching ندارد یک Pod میسازد.
اگر node جدید به cluster اضافه شود، DaemonSet controller میبیند Pod ندارد و Pod را به node جدید اضافه میکند.
DaemonSetها و ReplicaSetها نمایش عالی ارزش معماری decoupled Kubernetes هستند. ممکن است به نظر برسد طراحی درست این باشد ReplicaSet مالک Podهای تحت مدیریتش باشد و Podها subresource ReplicaSet باشند. همینطور Podهای تحت مدیریت DaemonSet subresource آن DaemonSet باشند. اما این encapsulation نیاز میکرد ابزارهای کار با Pod دو بار نوشته شوند، یک بار برای DaemonSet و یک بار برای ReplicaSet. در عوض Kubernetes رویکرد decoupled دارد که Podها top-level object هستند. یعنی هر ابزاری که برای introspection Podها در context ReplicaSet یاد گرفتید (مثلاً
kubectl logs <pod-name>) برای Podهای ساختهشده توسط DaemonSetها هم قابل استفاده است.
ایجاد DaemonSetها
DaemonSetها با submit کردن پیکربندی DaemonSet به Kubernetes API server ساخته میشوند. DaemonSet در مثال ۱۱-۱ یک fluentd logging agent روی هر node در cluster هدف میسازد.
مثال ۱۱-۱. fluentd.yaml
yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: fluentd
labels:
app: fluentd
spec:
template:
metadata:
labels:
app: fluentd
spec:
containers:
- name: fluentd
image: fluent/fluentd:v0.14.10
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containersDaemonSetها به name یکتا در همه DaemonSetها در namespace Kubernetes مشخص نیاز دارند. هر DaemonSet باید Pod template spec داشته باشد که برای ساخت Podها در صورت نیاز استفاده میشود. اینجا شباهت ReplicaSetها و DaemonSetها تمام میشود. برخلاف ReplicaSetها، DaemonSetها بهطور پیشفرض Pod روی هر node در cluster میسازند مگر node selector استفاده شود.
وقتی پیکربندی DaemonSet معتبر دارید، با kubectl apply DaemonSet را به Kubernetes API submit کنید. در این بخش DaemonSet میسازیم تا fluentd HTTP server روی هر node در cluster اجرا شود:
$ kubectl apply -f fluentd.yaml
daemonset "fluentd" createdوقتی DaemonSet fluentd با موفقیت به Kubernetes API submit شد، state فعلی را با kubectl describe query کنید:
$ kubectl describe daemonset fluentd
Name: fluentd
Image(s): fluent/fluentd:v0.14.10
Selector: app=fluentd
Node-Selector: <none>
Labels: app=fluentd
Desired Number of Nodes Scheduled: 3
Current Number of Nodes Scheduled: 3
Number of Nodes Misscheduled: 0
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failedاین خروجی نشان میدهد Pod fluentd با موفقیت روی هر سه node در cluster deploy شده. با kubectl get pods و flag -o برای چاپ nodeهایی که هر Pod fluentd به آن assign شده verify کنید:
$ kubectl get pods -o wide
NAME AGE NODE
fluentd-1q6c6 13m k0-default-pool-35609c18-z7tb
fluentd-mwi7h 13m k0-default-pool-35609c18-ydae
fluentd-zr6l7 13m k0-default-pool-35609c18-pol3با DaemonSet fluentd، اضافه کردن node جدید به cluster باعث deploy خودکار Pod fluentd روی آن node میشود:
$ kubectl get pods -o wide
NAME AGE NODE
fluentd-1q6c6 13m k0-default-pool-35609c18-z7tb
fluentd-mwi7h 13m k0-default-pool-35609c18-ydae
fluentd-oipmq 43s k0-default-pool-35609c18-0xnl
fluentd-zr6l7 13m k0-default-pool-35609c18-pol3این دقیقاً رفتاری است که برای مدیریت logging daemonها و سرویسهای cluster-wide میخواهید. اقدامی از سمت ما لازم نبود؛ اینطور DaemonSet controller Kubernetes observed state را با desired state ما reconcile میکند.
محدود کردن DaemonSetها به Nodeهای خاص
رایجترین use case DaemonSetها اجرای Pod روی هر node در cluster Kubernetes است. اما مواردی هست که میخواهید Pod را فقط روی زیرمجموعهای از nodeها deploy کنید. مثلاً workloadای دارید که GPU یا دسترسی به storage سریع فقط روی زیرمجموعهای از nodeها لازم است. در چنین مواردی node labelها برای tag کردن nodeهایی که الزامات workload را برآورده میکنند استفاده میشوند.
افزودن Label به Nodeها
اولین قدم محدود کردن DaemonSetها به nodeهای خاص، افزودن labelهای مورد نظر به زیرمجموعهای از nodeهاست. با دستور kubectl label انجام میشود. دستور زیر label ssd=true را به یک node اضافه میکند:
$ kubectl label nodes k0-default-pool-35609c18-z7tb ssd=true
node "k0-default-pool-35609c18-z7tb" labeledمثل resourceهای دیگر Kubernetes، لیست nodeها بدون label selector همه nodeهای cluster را برمیگرداند:
$ kubectl get nodes
NAME STATUS AGE
k0-default-pool-35609c18-0xnl Ready 23m
k0-default-pool-35609c18-pol3 Ready 1d
k0-default-pool-35609c18-ydae Ready 1d
k0-default-pool-35609c18-z7tb Ready 1dبا label selector میتوانید nodeها را بر اساس label فیلتر کنید. برای لیست فقط nodeهایی که label ssd روی true است، kubectl get nodes با flag --selector:
$ kubectl get nodes --selector ssd=true
NAME STATUS AGE
k0-default-pool-35609c18-z7tb Ready 1dNode Selectorها
Node selectorها میتوانند محدود کنند Pod روی کدام nodeها در cluster Kubernetes اجرا شود. Node selectorها بخشی از spec Pod هنگام ساخت DaemonSet تعریف میشوند. پیکربندی DaemonSet در مثال ۱۱-۲ NGINX را فقط روی nodeهایی با label ssd=true محدود میکند.
مثال ۱۱-۲. nginx-fast-storage.yaml
yaml
apiVersion: extensions/v1beta1
kind: "DaemonSet"
metadata:
labels:
app: nginx
ssd: "true"
name: nginx-fast-storage
spec:
template:
metadata:
labels:
app: nginx
ssd: "true"
spec:
nodeSelector:
ssd: "true"
containers:
- name: nginx
image: nginx:1.10.0ببینیم وقتی DaemonSet nginx-fast-storage را به Kubernetes API submit میکنیم چه میشود:
$ kubectl apply -f nginx-fast-storage.yaml
daemonset "nginx-fast-storage" createdچون فقط یک node با label ssd=true داریم، Pod nginx-fast-storage فقط روی آن node اجرا میشود:
$ kubectl get pods -o wide
NAME STATUS NODE
nginx-fast-storage-7b90t Running k0-default-pool-35609c18-z7tbافزودن label ssd=true به nodeهای بیشتر باعث deploy Pod nginx-fast-storage روی آن nodeها میشود. برعکس هم صادق است: اگر label لازم از node حذف شود، Pod توسط DaemonSet controller حذف میشود.
حذف label از node که DaemonSet برای node selectorش لازم دارد، باعث حذف Pod تحت مدیریت آن DaemonSet از node میشود.
بهروزرسانی یک DaemonSet
DaemonSetها برای deploy سرویس در کل cluster عالی هستند، اما upgrade چطور؟ قبل از Kubernetes 1.6، تنها راه بهروزرسانی Podهای تحت مدیریت DaemonSet بهروزرسانی DaemonSet و سپس حذف دستی هر Pod بود تا با پیکربندی جدید دوباره ساخته شود. با انتشار Kubernetes 1.6، DaemonSetها معادلی از شیء Deployment برای مدیریت rollout DaemonSet داخل cluster گرفتند.
Rolling Update یک DaemonSet
DaemonSetها میتوانند با همان استراتژی RollingUpdate که Deploymentها استفاده میکنند rollout شوند. استراتژی بهروزرسانی را با فیلد spec.updateStrategy.type پیکربندی کنید که باید مقدار RollingUpdate داشته باشد. وقتی DaemonSet استراتژی بهروزرسانی RollingUpdate دارد، هر تغییر در فیلد spec.template (یا subfieldهایش) rolling update را آغاز میکند.
مثل rolling updateهای Deployment (فصل ۱۰ را ببینید)، استراتژی RollingUpdate تدریجاً اعضای DaemonSet را بهروز میکند تا همه Podها پیکربندی جدید را اجرا کنند. دو پارامتر rolling update DaemonSet را کنترل میکنند:
spec.minReadySecondsکه تعیین میکند Pod چقدر باید «ready» باشد قبل از اینکه rolling update به Podهای بعدی upgrade کندspec.updateStrategy.rollingUpdate.maxUnavailableکه نشان میدهد rolling update چند Pod را میتواند همزمان بهروز کند
احتمالاً spec.minReadySeconds را روی مقدار reasonably long، مثلاً ۳۰–۶۰ ثانیه، میگذارید تا مطمئن شوید Pod واقعاً healthy است قبل از ادامه rollout.
تنظیم spec.updateStrategy.rollingUpdate.maxUnavailable بیشتر application-dependent است. گذاشتن روی ۱ استراتژی general-purpose ایمن است، اما rollout طول میکشد (تعداد nodeها × minReadySeconds). افزایش maximum unavailability rollout را سریعتر میکند، اما «blast radius» rollout ناموفق را بزرگتر میکند. ویژگیهای برنامه و محیط cluster مقادیر نسبی سرعت در برابر ایمنی را تعیین میکنند. رویکرد خوب ممکن است maxUnavailable را ۱ بگذارید و فقط اگر کاربران یا administratorها از سرعت rollout DaemonSet شکایت کردند افزایش دهید.
وقتی rolling update شروع شد، با دستورات kubectl rollout وضعیت فعلی rollout DaemonSet را میبینید.
مثلاً kubectl rollout status daemonSets my-daemon-set وضعیت rollout فعلی DaemonSet بهنام my-daemon-set را نشان میدهد.
حذف یک DaemonSet
حذف DaemonSet با kubectl delete بسیار ساده است. فقط name درست DaemonSet را بدهید:
$ kubectl delete -f fluentd.yamlحذف DaemonSet همه Podهای تحت مدیریت آن DaemonSet را هم حذف میکند. flag
--cascadeراfalseبگذارید تا فقط DaemonSet حذف شود نه Podها.
خلاصه
DaemonSetها abstraction آسان برای اجرای مجموعهای Pod روی هر node در cluster Kubernetes، یا در صورت نیاز روی زیرمجموعهای از nodeها بر اساس label، فراهم میکنند. DaemonSet controller و scheduler خودش را دارد تا سرویسهای کلیدی مثل monitoring agentها همیشه روی nodeهای درست cluster در حال اجرا باشند.
برای بعضی برنامهها فقط تعداد مشخصی replica میخواهید؛ مهم نیست کجا اجرا شوند تا resource کافی و distribution برای عمل reliable داشته باشند. اما دسته دیگری از برنامهها، مثل agentها و monitoring applicationها، هستند که باید روی هر machine در cluster باشند تا درست کار کنند. این DaemonSetها واقعاً serving application سنتی نیستند، بلکه capability و feature اضافی به خود cluster Kubernetes میافزایند.
چون DaemonSet شیء declarative فعال تحت مدیریت controller است، اعلام intent که agent روی هر machine اجرا شود بدون قرار دادن explicit روی هر machine آسان میشود. این بهخصوص در context cluster Kubernetes autoscaled مفید است که nodeها مدام اضافه و حذف میشوند بدون مداخله کاربر. در چنین مواردی DaemonSet خودکار agentهای مناسب را به هر node که autoscaler به cluster اضافه میکند اضافه میکند.