Skip to content

فصل ۱۱ — 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/containers

DaemonSetها به 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      1d

Node 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 اضافه می‌کند اضافه می‌کند.