Skip to content

فصل ۱۰ — Deploymentها

تا اینجا دیدید چگونه برنامه‌های خود را به‌صورت container بسته‌بندی کنید، مجموعه‌های تکراری container بسازید و از Ingress controllerها برای load-balance کردن ترافیک به سرویس‌هایتان استفاده کنید. همه این اشیاء (Pod، ReplicaSet و Service) برای ساخت یک نمونه از برنامه‌تان به کار می‌روند. با این حال، در مدیریت ریتم روزانه یا هفتگی انتشار نسخه‌های جدید برنامه کمکی نمی‌کنند. در واقع، انتظار می‌رود Podها و ReplicaSetها به imageهای container مشخصی متصل باشند که تغییر نمی‌کنند.

شیء Deployment برای مدیریت انتشار نسخه‌های جدید وجود دارد. Deploymentها برنامه‌های مستقرشده را به‌گونه‌ای نمایندگی می‌کنند که فراتر از هر نسخه خاصی باشد. علاوه بر این، Deploymentها به شما امکان می‌دهند به‌سادگی از یک نسخه کد به نسخه بعدی بروید. این فرایند «rollout» قابل مشخص‌سازی و محتاطانه است. بین به‌روزرسانی Podهای فردی به‌مدت قابل پیکربندی توسط کاربر منتظر می‌ماند. همچنین از health checkها استفاده می‌کند تا مطمئن شود نسخه جدید برنامه درست کار می‌کند، و اگر شکست‌های زیادی رخ دهد rollout را متوقف می‌کند.

با Deploymentها می‌توانید نسخه‌های جدید نرم‌افزار را بدون downtime یا خطا به‌سادگی و قابل اعتماد rollout کنید. مکانیک واقعی rollout نرم‌افزار توسط deployment controllerای انجام می‌شود که در خود cluster Kubernetes اجرا می‌شود. یعنی می‌توانید rollout را بدون نظارت ادامه دهید و همچنان درست و ایمن عمل کند. این ادغام Deploymentها با ابزارها و سرویس‌های continuous delivery متعدد را آسان می‌کند. علاوه بر این، اجرای سمت سرور rollout را از مکان‌هایی با اتصال اینترنت ضعیف یا متناوب ایمن می‌کند. تصور کنید نسخه جدید نرم‌افزار را از گوشی‌تان در مترو rollout می‌کنید. Deploymentها این کار را ممکن و ایمن می‌کنند!

وقتی Kubernetes برای اولین بار منتشر شد، یکی از محبوب‌ترین نمایش‌های قدرت آن «rolling update» بود که نشان می‌داد چگونه با یک دستور می‌توانید یک برنامه در حال اجرا را بدون downtime و بدون از دست دادن درخواست‌ها به‌روز کنید. این demo اولیه بر پایه دستور kubectl rolling-update بود که هنوز در ابزار خط فرمان موجود است، اگرچه عملکردش تا حد زیادی توسط شیء Deployment جایگزین شده است.

اولین Deployment شما

مثل همه اشیاء Kubernetes، یک Deployment را می‌توان به‌صورت شیء YAML declarative نمایش داد که جزئیات آنچه می‌خواهید اجرا کنید را مشخص می‌کند. در مورد زیر، Deployment یک نمونه از برنامه kuard درخواست می‌کند:

yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kuard
spec:
  selector:
    matchLabels:
      run: kuard
  replicas: 1
  template:
    metadata:
      labels:
        run: kuard
    spec:
      containers:
      - name: kuard
        image: gcr.io/kuar-demo/kuard-amd64:blue

این فایل YAML را به‌نام kuard-deployment.yaml ذخیره کنید، سپس می‌توانید آن را با این دستور بسازید:

$ kubectl create -f kuard-deployment.yaml

درون‌کاری Deploymentها

بیایید ببینیم Deploymentها واقعاً چگونه کار می‌کنند. همان‌طور که یاد گرفتیم ReplicaSetها Podها را مدیریت می‌کنند، Deploymentها ReplicaSetها را مدیریت می‌کنند. مثل همه روابط در Kubernetes، این رابطه با labelها و label selector تعریف می‌شود. می‌توانید label selector را با نگاه به شیء Deployment ببینید:

$ kubectl get deployments kuard \
  -o jsonpath --template {.spec.selector.matchLabels}

map[run:kuard]

از این می‌بینید Deployment در حال مدیریت ReplicaSetای با label run=kuard است. می‌توانیم از این در یک query با label selector روی ReplicaSetها استفاده کنیم تا آن ReplicaSet مشخص را پیدا کنیم:

$ kubectl get replicasets --selector=run=kuard

NAME               DESIRED    CURRENT    READY     AGE
kuard-1128242161   1          1          1         13m

حالا رابطه Deployment و ReplicaSet را در عمل ببینیم. می‌توانیم Deployment را با دستور imperative scale تغییر اندازه دهیم:

$ kubectl scale deployments kuard --replicas=2

deployment.extensions/kuard scaled

حالا اگر دوباره آن ReplicaSet را لیست کنیم، باید ببینیم:

$ kubectl get replicasets --selector=run=kuard

NAME               DESIRED    CURRENT    READY     AGE
kuard-1128242161   2          2          2         13m

scale کردن Deployment، ReplicaSet تحت کنترلش را هم scale کرده است.

حالا برعکس را امتحان کنیم؛ ReplicaSet را scale کنیم:

$ kubectl scale replicasets kuard-1128242161 --replicas=1

replicaset "kuard-1128242161" scaled

حالا دوباره آن ReplicaSet را بگیرید:

$ kubectl get replicasets --selector=run=kuard

NAME               DESIRED    CURRENT    READY     AGE
kuard-1128242161   2          2          2         13m

عجیب است. با وجود scale کردن ReplicaSet به یک replica، هنوز دو replica به‌عنوان desired state دارد. چه خبر است؟ به یاد داشته باشید Kubernetes یک سیستم آنلاین و self-healing است. شیء Deployment سطح بالا این ReplicaSet را مدیریت می‌کند. وقتی تعداد replicaها را به یک تنظیم می‌کنید، دیگر با desired state Deployment که replicas روی ۲ است مطابقت ندارد. deployment controller این را می‌بیند و اقدام می‌کند تا observed state با desired state یکی شود؛ در این مورد تعداد replicaها را دوباره به دو برمی‌گرداند.

اگر بخواهید مستقیماً آن ReplicaSet را مدیریت کنید، باید Deployment را حذف کنید (به یاد داشته باشید --cascade را false بگذارید، وگرنه ReplicaSet و Podها هم حذف می‌شوند!).

ایجاد Deploymentها

البته، همان‌طور که جای دیگر گفته شده، باید مدیریت declarative پیکربندی‌های Kubernetes را ترجیح دهید. یعنی نگه‌داشتن state Deploymentها در فایل‌های YAML یا JSON روی دیسک.

به‌عنوان نقطه شروع، این Deployment را در یک فایل YAML دانلود کنید:

$ kubectl get deployments kuard --export -o yaml > kuard-deployment.yaml
$ kubectl replace -f kuard-deployment.yaml --save-config

اگر در فایل نگاه کنید، چیزی شبیه این می‌بینید:

yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: null
  generation: 1
  labels:
    run: kuard
  name: kuard
  selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/kuard
spec:
  progressDeadlineSeconds: 2147483647
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      run: kuard
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: kuard
    spec:
      containers:
      - image: gcr.io/kuar-demo/kuard-amd64:blue
        imagePullPolicy: IfNotPresent
        name: kuard
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status: {}

بسیاری از فیلدهای read-only و پیش‌فرض در فهرست بالا برای اختصار حذف شدند. همچنین باید kubectl replace --save-config را اجرا کنید. این یک annotation اضافه می‌کند تا در اعمال تغییرات آینده، kubectl بداند آخرین پیکربندی اعمال‌شده چه بوده تا merge هوشمندانه‌تری انجام دهد. اگر همیشه از kubectl apply استفاده می‌کنید، این مرحله فقط بعد از اولین بار ساخت Deployment با kubectl create -f لازم است.

spec Deployment ساختار بسیار مشابه spec ReplicaSet دارد. یک Pod template وجود دارد که تعدادی container برای هر replica تحت مدیریت Deployment می‌سازد. علاوه بر مشخصات Pod، یک شیء strategy هم هست:

yaml
...
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
...

شیء strategy تعیین می‌کند rollout نرم‌افزار جدید چگونه می‌تواند پیش برود. Deploymentها از دو استراتژی پشتیبانی می‌کنند: Recreate و RollingUpdate. این‌ها بعداً در این فصل با جزئیات بحث می‌شوند.

مدیریت Deploymentها

مثل همه اشیاء Kubernetes، می‌توانید اطلاعات دقیق Deployment را با دستور kubectl describe بگیرید:

$ kubectl describe deployments kuard

Name:                     kuard
Namespace:                default
CreationTimestamp:        Tue, 16 Apr 2019 21:43:25 -0700
Labels:                   run=kuard
Annotations:              deployment.kubernetes.io/revision: 1
Selector:                 run=kuard
Replicas:                 2 desired | 2 updated | 2 total | 2 available | 0 ...
StrategyType:             RollingUpdate
MinReadySeconds:          0
RollingUpdateStrategy:    1 max unavailable, 1 max surge
Pod Template:
  Labels: run=kuard
  Containers:
   kuard:
    Image:         gcr.io/kuar-demo/kuard-amd64:blue
    Port:          <none>
    Host Port:     <none>
    Environment: <none>
    Mounts:        <none>
  Volumes:         <none>
Conditions:
  Type            Status Reason
  ----            ------ ------
  Available       True    MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet:    kuard-6d69d9fc5c (2/2 replicas created)
Events:
  Type     Reason             Age                    From                Message
  ----     ------             ----                   ----                -------
  Normal ScalingReplicaSet 4m6s                      deployment-con...   ...
  Normal ScalingReplicaSet 113s (x2 over 3m20s) deployment-con...        ...

در خروجی describe اطلاعات مهمی زیاد است. دو مورد از مهم‌ترین‌ها OldReplicaSets و NewReplicaSet هستند. این فیلدها به ReplicaSetهایی که Deployment در حال حاضر مدیریت می‌کند اشاره می‌کنند. اگر Deployment وسط rollout باشد، هر دو فیلد مقدار دارند. اگر rollout کامل شده باشد، OldReplicaSets روی <none> تنظیم می‌شود.

علاوه بر describe، دستور kubectl rollout هم برای Deploymentها وجود دارد. بعداً جزئیات بیشتری می‌دهیم، اما فعلاً بدانید می‌توانید با kubectl rollout history تاریخچه rolloutهای مرتبط با یک Deployment خاص را بگیرید. اگر rollout جاری دارید، با kubectl rollout status وضعیت فعلی rollout را می‌بینید.

به‌روزرسانی Deploymentها

Deploymentها اشیاء declarative هستند که یک برنامه مستقرشده را توصیف می‌کنند. دو عملیات رایج روی Deployment، scale کردن و به‌روزرسانی برنامه است.

Scale کردن یک Deployment

اگرچه قبلاً نشان دادیم چگونه imperative با kubectl scale یک Deployment را scale کنید، بهترین روش مدیریت declarative از طریق فایل‌های YAML و سپس به‌روزرسانی Deployment با آن فایل‌هاست.

برای scale up، فایل YAML را ویرایش کنید و تعداد replicaها را افزایش دهید:

yaml
...
spec:
  replicas: 3
...

بعد از ذخیره و commit این تغییر، Deployment را با kubectl apply به‌روز کنید:

$ kubectl apply -f kuard-deployment.yaml

این desired state Deployment را به‌روز می‌کند، ReplicaSet تحت مدیریتش را بزرگ‌تر می‌کند و در نهایت Pod جدیدی می‌سازد:

$ kubectl get deployments kuard

NAME        DESIRED   CURRENT    UP-TO-DATE    AVAILABLE    AGE
kuard       3         3          3             3            4m

به‌روزرسانی Image کانتینر

کاربرد رایج دیگر به‌روزرسانی Deployment، rollout نسخه جدید نرم‌افزار در یک یا چند container است. برای این کار هم فایل YAML Deployment را ویرایش کنید، اما این بار image container را به‌روز می‌کنید نه تعداد replicaها:

yaml
...
        containers:
        - image: gcr.io/kuar-demo/kuard-amd64:green
          imagePullPolicy: Always
...

همچنین یک annotation در template Deployment می‌گذاریم تا اطلاعاتی درباره به‌روزرسانی ثبت شود:

yaml
...
spec:
  ...
  template:
    metadata:
      annotations:
        kubernetes.io/change-cause: "Update to green kuard"
...

مطمئن شوید این annotation را به template اضافه کنید نه خود Deployment، چون kubectl apply از این فیلد در شیء Deployment استفاده می‌کند. همچنین هنگام عملیات scale ساده، annotation change-cause را به‌روز نکنید. تغییر change-cause تغییر مهمی در template است و rollout جدید را trigger می‌کند.

دوباره می‌توانید با kubectl apply Deployment را به‌روز کنید:

$ kubectl apply -f kuard-deployment.yaml

بعد از به‌روزرسانی Deployment، rollout trigger می‌شود که می‌توانید با kubectl rollout monitor کنید:

$ kubectl rollout status deployments kuard
deployment kuard successfully rolled out

ReplicaSetهای قدیم و جدید تحت مدیریت Deployment را همراه imageهای استفاده‌شده می‌بینید. هر دو ReplicaSet نگه داشته می‌شوند در صورت نیاز به rollback:

$ kubectl get replicasets -o wide

NAME                    DESIRED   CURRENT   READY   ...   IMAGE(S)            ...
kuard-1128242161        0         0         0       ...   gcr.io/kuar-demo/   ...
kuard-1128635377        3         3         3       ...   gcr.io/kuar-demo/   ...

اگر وسط rollout هستید و می‌خواهید موقتاً متوقفش کنید (مثلاً رفتار عجیب در سیستم دیدید و می‌خواهید بررسی کنید)، از دستور pause استفاده کنید:

$ kubectl rollout pause deployments kuard
deployment "kuard" paused

اگر بعد از بررسی فکر می‌کنید rollout می‌تواند ادامه یابد، با resume از همان جا ادامه دهید:

$ kubectl rollout resume deployments kuard
deployment "kuard" resumed

تاریخچه Rollout

Deploymentهای Kubernetes تاریخچه rolloutها را نگه می‌دارند که هم برای فهم state قبلی Deployment و هم rollback به نسخه خاص مفید است.

تاریخچه Deployment را با این دستور می‌بینید:

$ kubectl rollout history deployment kuard

deployment.extensions/kuard
REVISION CHANGE-CAUSE
1         <none>
2         Update to green kuard

تاریخچه revision از قدیمی‌ترین به جدیدترین است. برای هر rollout جدید یک شماره revision یکتا افزایش می‌یابد. تا اینجا دو تا داریم: Deployment اولیه و به‌روزرسانی image به kuard:1.9.10.

اگر جزئیات بیشتری درباره revision خاص می‌خواهید، flag --revision را اضافه کنید:

$ kubectl rollout history deployment kuard --revision=2

deployment.extensions/kuard with revision #2
Pod Template:
  Labels:       pod-template-hash=54b74ddcd4
        run=kuard
  Annotations: kubernetes.io/change-cause: Update to green kuard
  Containers:
   kuard:
    Image:      gcr.io/kuar-demo/kuard-amd64:green
    Port:        <none>
    Host Port: <none>
    Environment:        <none>
    Mounts:      <none>
  Volumes:       <none>

یک به‌روزرسانی دیگر برای این مثال انجام دهیم. نسخه kuard را با تغییر شماره نسخه container و به‌روزرسانی annotation change-cause به blue برگردانید. با kubectl apply اعمال کنید. تاریخچه باید سه entry داشته باشد:

$ kubectl rollout history deployment kuard

deployment.extensions/kuard
REVISION CHANGE-CAUSE
1         <none>
2         Update to green kuard
3         Update to blue kuard

فرض کنید مشکلی در آخرین release هست و می‌خواهید rollback کنید تا بررسی کنید. می‌توانید ساده آخرین rollout را undo کنید:

$ kubectl rollout undo deployments kuard
deployment "kuard" rolled back

دستور undo صرف‌نظر از مرحله rollout کار می‌کند. هم rolloutهای نیمه‌تمام و هم کامل را می‌توان undo کرد. undo در واقع rollout معکوس است (مثلاً از v2 به v1 به‌جای v1 به v2) و همان سیاست‌های استراتژی rollout برای undo هم اعمال می‌شوند. Deployment ساده desired replica count در ReplicaSetهای مدیریت‌شده را تنظیم می‌کند:

$ kubectl get replicasets -o wide

NAME               DESIRED    CURRENT   READY   ...    IMAGE(S)               ...
kuard-1128242161   0          0         0       ...    gcr.io/kuar-demo/      ...
kuard-1570155864   0          0         0       ...    gcr.io/kuar-demo/      ...
kuard-2738859366   3          3         3       ...    gcr.io/kuar-demo/      ...

وقتی فایل‌های declarative سیستم‌های production را کنترل می‌کنید، تا حد ممکن manifestهای commit‌شده باید با آنچه واقعاً در cluster اجرا می‌شود مطابقت داشته باشد. با kubectl rollout undo state production را به‌گونه‌ای به‌روز می‌کنید که در source control منعکس نیست. راه جایگزین (و شاید ترجیحی) undo کردن rollout، revert فایل YAML و kubectl apply نسخه قبلی است. این‌طور «پیکربندی change-tracked» شما با آنچه واقعاً در cluster اجرا می‌شود هم‌راستاتر می‌ماند.

دوباره تاریخچه Deployment را ببینیم:

$ kubectl rollout history deployment kuard

deployment.extensions/kuard
REVISION CHANGE-CAUSE
1         <none>
3         Update to blue kuard
4         Update to green kuard

revision 2 غایب است! وقتی به revision قبلی rollback می‌کنید، Deployment ساده template را دوباره استفاده می‌کند و renumber می‌کند تا جدیدترین revision شود. آنچه قبلاً revision 2 بود حالا revision 4 شده.

قبلاً دیدیم می‌توانید با kubectl rollout undo به نسخه قبلی Deployment rollback کنید. علاوه بر این، با flag --to-revision می‌توانید به revision مشخص در تاریخچه rollback کنید:

$ kubectl rollout undo deployments kuard --to-revision=3
deployment "kuard" rolled back
$ kubectl rollout history deployment kuard
deployment.extensions/kuard
REVISION CHANGE-CAUSE
1         <none>
4         Update to green kuard
5         Update to blue kuard

دوباره undo، revision 3 را اعمال کرد و آن را revision 5 نام‌گذاری کرد.

revision 0 shorthand برای revision قبلی است. بنابراین kubectl rollout undo معادل kubectl rollout undo --to-revision=0 است.

به‌طور پیش‌فرض، تاریخچه کامل revision یک Deployment به خود شیء Deployment متصل می‌ماند. با گذشت زمان (مثلاً سال‌ها) این تاریخچه می‌تواند بزرگ شود، پس توصیه می‌شود برای Deploymentهایی که انتظار دارید مدت طولانی بمانند، حداکثر اندازه تاریخچه revision تنظیم کنید تا اندازه کل شیء Deployment محدود شود. مثلاً اگر به‌روزرسانی روزانه دارید، ممکن است تاریخچه revision را ۱۴ بگذارید تا حداکثر دو هفته revision (اگر انتظار rollback فراتر از دو هفته ندارید).

برای این کار از property revisionHistoryLimit در specification Deployment استفاده کنید:

yaml
...
spec:
  # We do daily rollouts, limit the revision history to two weeks of
  # releases as we don't expect to roll back beyond that.
  revisionHistoryLimit: 14
...

استراتژی‌های Deployment

وقتی زمان تغییر نسخه نرم‌افزار پیاده‌سازی سرویس‌تان برسد، Deployment Kubernetes از دو استراتژی rollout مختلف پشتیبانی می‌کند:

  • Recreate
  • RollingUpdate

استراتژی Recreate

استراتژی Recreate ساده‌تر دو استراتژی rollout است. ساده ReplicaSet تحت مدیریتش را به image جدید به‌روز می‌کند و همه Podهای مرتبط با Deployment را terminate می‌کند. ReplicaSet می‌بیند دیگر replica ندارد و همه Podها را با image جدید دوباره می‌سازد. وقتی Podها دوباره ساخته شدند، نسخه جدید را اجرا می‌کنند.

این استراتژی سریع و ساده است، اما یک نقص بزرگ دارد — بالقوه فاجعه‌بار است و تقریباً حتماً باعث downtime سایت می‌شود. به همین دلیل Recreate فقط برای Deploymentهای تست که سرویس user-facing نیست و مقدار کمی downtime قابل قبول است باید استفاده شود.

استراتژی RollingUpdate

استراتژی RollingUpdate برای هر سرویس user-facing عموماً ترجیحی‌تر است. کندتر از Recreate است، اما بسیار پیچیده‌تر و robust هم هست. با RollingUpdate می‌توانید نسخه جدید سرویس را در حالی که هنوز ترافیک کاربر دریافت می‌کند rollout کنید، بدون downtime.

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

مدیریت نسخه‌های متعدد سرویس شما

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

مثال برای اهمیت این موضوع:

وسط rollout نرم‌افزار frontend هستید؛ نیمی از سرورها نسخه ۱ و نیمی نسخه ۲ را اجرا می‌کنند. کاربر درخواست اولیه به سرویس می‌دهد و کتابخانه JavaScript سمت client که UI شما را پیاده می‌کند دانلود می‌کند. این درخواست توسط سرور نسخه ۱ سرو می‌شود و کاربر کتابخانه client نسخه ۱ را دریافت می‌کند. این کتابخانه client در مرورگر کاربر اجرا می‌شود و درخواست‌های API بعدی به سرویس می‌فرستد. این درخواست‌های API به سرور نسخه ۲ route می‌شوند؛ بنابراین کتابخانه JavaScript client نسخه ۱ با API server نسخه ۲ صحبت می‌کند. اگر سازگاری بین این نسخه‌ها را تضمین نکرده‌اید، برنامه درست کار نمی‌کند.

در ابتدا ممکن است بار اضافی به نظر برسد. اما در واقع همیشه این مشکل را داشتید؛ شاید متوجه نشده‌اید. کاربر می‌تواند در زمان t درست قبل از شروع به‌روزرسانی درخواست بدهد. این درخواست توسط سرور نسخه ۱ سرو می‌شود. در t_1 سرویس را به نسخه ۲ به‌روز می‌کنید. در t_2 کد client نسخه ۱ روی مرورگر کاربر اجرا می‌شود و به endpoint APIای که سرور نسخه ۲ اداره می‌کند می‌زند. مهم نیست نرم‌افزار را چگونه به‌روز کنید؛ برای به‌روزرسانی قابل اعتماد باید backward و forward compatibility حفظ کنید. ماهیت استراتژی RollingUpdate صرفاً این را واضح‌تر و صریح‌تر می‌کند.

توجه داشته باشید این فقط برای clientهای JavaScript نیست — برای کتابخانه‌های client که در سرویس‌های دیگر compile شده‌اند و به سرویس شما call می‌زنند هم صادق است. فقط چون شما به‌روز کردید به معنای به‌روزرسانی client library توسط آن‌ها نیست. این backward compatibility برای decouple کردن سرویس از سیستم‌های وابسته حیاتی است. اگر APIها را formalize نکنید و decouple نشوید، مجبورید rolloutها را با همه سیستم‌هایی که به سرویس شما call می‌زنند با دقت مدیریت کنید. این coupling سخت چابکی لازم برای push نرم‌افزار جدید هر هفته، چه برسد به هر ساعت یا هر روز، را بسیار سخت می‌کند. در معماری decoupled نشان‌داده‌شده در شکل ۱۰-۱، frontend از backend با contract API و load balancer جدا شده، در حالی که در معماری coupled، client ضخیم compile‌شده در frontend مستقیماً به backendها وصل می‌شود.

شکل ۱۰-۱. نمودارهای معماری برنامه decoupled (چپ) و coupled (راست)

پیکربندی rolling update

RollingUpdate استراتژی نسبتاً عمومی است؛ برای به‌روزرسانی انواع برنامه در تنظیمات مختلف قابل استفاده است. در نتیجه خود rolling update بسیار قابل پیکربندی است؛ می‌توانید رفتارش را برای نیازهای خاص tune کنید. دو پارامتر برای tune کردن رفتار rolling update دارید: maxUnavailable و maxSurge.

پارامتر maxUnavailable حداکثر تعداد Podهایی را که در rolling update می‌توانند unavailable باشند تنظیم می‌کند. می‌تواند عدد مطلق (مثلاً ۳، یعنی حداکثر سه Pod unavailable) یا درصد (مثلاً ۲۰٪، یعنی حداکثر ۲۰٪ replicaهای desired unavailable) باشد. عموماً درصد برای اکثر سервیس‌ها رویکرد خوبی است چون مقدار درست مستقل از تعداد replicaهای desired در Deployment اعمال می‌شود. اما گاهی عدد مطلق می‌خواهید (مثلاً محدود کردن حداکثر Podهای unavailable به یک).

در اصل، maxUnavailable به tune کردن سرعت rolling update کمک می‌کند. مثلاً اگر maxUnavailable را ۵۰٪ بگذارید، rolling update فوراً ReplicaSet قدیم را به ۵۰٪ اندازه اصلی scale down می‌کند. اگر چهار replica دارید، به دو replica scale down می‌کند. rolling update Podهای حذف‌شده را با scale up ReplicaSet جدید به دو replica جایگزین می‌کند، در مجموع چهار replica (دو قدیم، دو جدید). سپس ReplicaSet قدیم را به صفر replica scale down می‌کند، در مجموع دو replica جدید. در نهایت ReplicaSet جدید را به چهار replica scale up می‌کند و rollout کامل می‌شود. با maxUnavailable روی ۵۰٪، rollout در چهار مرحله کامل می‌شود، اما گاهی فقط ۵۰٪ ظرفیت سرویس دارید.

ببینید اگر maxUnavailable را ۲۵٪ بگذاریم چه می‌شود. در این حالت هر مرحله فقط با یک replica انجام می‌شود و rollout دو برابر مرحله می‌خواهد، اما availability حداقل به ۷۵٪ می‌رسد. این نشان می‌دهد maxUnavailable به ما اجازه می‌دهد سرعت rollout را با availability مبادله کنیم.

دقت‌کنندگان متوجه می‌شوند استراتژی Recreate معادل RollingUpdate با maxUnavailable روی ۱۰۰٪ است.

استفاده از ظرفیت کاهش‌یافته برای rollout موفق وقتی مفید است که سرویس الگوی ترافیک cyclical دارد (مثلاً شب ترافیک کمتر) یا منابع محدود دارید و scale بیشتر از حداکثر replicaهای فعلی ممکن نیست.

اما موقعیت‌هایی هست که نمی‌خواهید زیر ۱۰۰٪ ظرفیت بروید، اما موقتاً منابع اضافی برای rollout می‌پذیرید. در این موارد maxUnavailable را ۰٪ بگذارید و rollout را با maxSurge کنترل کنید. مثل maxUnavailable، maxSurge هم عدد مشخص یا درصد است.

maxSurge کنترل می‌کند چند resource اضافی برای تحقق rollout ساخته شود. برای illustration، سرویسی با ۱۰ replica تصور کنید. maxUnavailable را ۰ و maxSurge را ۲۰٪ می‌گذاریم. اولین کار rollout scale up ReplicaSet جدید به ۲ replica است، در مجموع ۱۲ (۱۲۰٪) در سرویس. سپس ReplicaSet قدیم را به ۸ replica scale down می‌کند، در مجموع ۱۰ (۸ قدیم، ۲ جدید). این فرایند تا کامل شدن rollout ادامه می‌یابد. در هر زمان ظرفیت سرویس حداقل ۱۰۰٪ تضمین شده و حداکثر resource اضافی برای rollout به ۲۰٪ منابع محدود است.

maxSurge روی ۱۰۰٪ معادل blue/green deployment است. deployment controller اول نسخه جدید را به ۱۰۰٪ نسخه قدیم scale up می‌کند. وقتی نسخه جدید healthy شد، فوراً نسخه قدیم را به ۰٪ scale down می‌کند.

کند کردن Rolloutها برای اطمینان از سلامت سرویس

هدف rollout مرحله‌ای اطمینان از این است که rollout به سرویس healthy و پایدار با نسخه جدید نرم‌افزار منجر شود. برای این کار، deployment controller همیشه تا وقتی Pod گزارش ready شد قبل از به‌روزرسانی Pod بعدی منتظر می‌ماند.

deployment controller وضعیت Pod را طبق readiness checkهایش بررسی می‌کند. readiness check بخشی از health probeهای Pod است و در فصل ۵ با جزئیات توضیح داده شده. اگر می‌خواهید Deploymentها نرم‌افزار را reliably rollout کنند، باید readiness health check برای containerهای Pod مشخص کنید. بدون این checkها، deployment controller کور کار می‌کند.

گاهی اما فقط ready شدن Pod اعتماد کافی نمی‌دهد که Pod واقعاً درست رفتار می‌کند. بعضی خطاها فقط بعد از مدتی ظاهر می‌شوند. مثلاً memory leak جدی چند دقیقه طول می‌کشد تا خودش را نشان دهد، یا bugای که فقط با ۱٪ درخواست‌ها trigger می‌شود. در اکثر سناریوهای real-world می‌خواهید مدتی منتظر بمانید تا اطمینان بالایی از عملکرد درست نسخه جدید قبل از به‌روزرسانی Pod بعدی داشته باشید.

برای Deploymentها، این زمان انتظار با پارامتر minReadySeconds تعریف می‌شود:

yaml
...
spec:
  minReadySeconds: 60
...

minReadySeconds روی ۶۰ یعنی Deployment باید ۶۰ ثانیه بعد از healthy شدن Pod قبل از به‌روزرسانی Pod بعدی منتظر بماند.

علاوه بر انتظار برای healthy شدن Pod، timeoutای هم می‌خواهید که محدود کند سیستم چقدر منتظر بماند. فرض کنید نسخه جدید سرویس bug دارد و فوراً deadlock می‌شود. هرگز ready نمی‌شود و بدون timeout، deployment controller rollout را برای همیشه متوقف می‌کند.

رفتار درست timeout کردن rollout است. این rollout را failed علامت می‌زند. این وضعیت failure می‌تواند alerting trigger کند که به operator مشکل rollout را نشان دهد.

در نگاه اول، timeout rollout ممکن است پیچیدگی غیرضروری به نظر برسد. اما increasingly rolloutها توسط سیستم‌های fully automated با دخالت کم یا بدون انسان trigger می‌شوند. در چنین موقعیتی timeout یک exception حیاتی است که یا rollback خودکار release را trigger می‌کند یا ticket/eventی برای مداخله انسان می‌سازد.

برای تنظیم دوره timeout از پارامتر Deployment progressDeadlineSeconds استفاده می‌شود:

yaml
...
spec:
  progressDeadlineSeconds: 600
...

این مثال progress deadline را ۱۰ دقیقه می‌گذارد. اگر هر مرحله خاص در rollout در ۱۰ دقیقه progress نکند، Deployment failed علامت می‌خورد و همه تلاش‌ها برای جلو بردن Deployment متوقف می‌شوند.

مهم است timeout بر اساس progress Deployment است نه طول کل Deployment. در این context، progress هر زمان Deployment Pod بسازد یا حذف کند تعریف می‌شود. وقتی این اتفاق می‌افتد، ساعت timeout به صفر reset می‌شود. شکل ۱۰-۲ lifecycle Deployment را نشان می‌دهد.

شکل ۱۰-۲. lifecycle Deployment در Kubernetes

حذف یک Deployment

اگر بخواهید Deployment را حذف کنید، یا با دستور imperative:

$ kubectl delete deployments kuard

یا با فایل YAML declarative که قبلاً ساختیم:

$ kubectl delete -f kuard-deployment.yaml

در هر دو حالت، به‌طور پیش‌فرض حذف Deployment کل سرویس را حذف می‌کند. نه فقط Deployment، بلکه ReplicaSetهای تحت مدیریتش و Podهای تحت مدیریت ReplicaSetها. مثل ReplicaSetها، اگر این رفتار مطلوب نیست، flag --cascade=false فقط شیء Deployment را حذف می‌کند.

Monitor کردن یک Deployment

برای Deployment مهم است بدانید اگر بعد از مدتی progress نکند، timeout می‌شود. وقتی این اتفاق می‌افتد، status Deployment به failed transition می‌کند. این status از آرایه status.conditions گرفته می‌شود، جایی Conditionای با Type برابر Progressing و Status برابر False وجود دارد. Deployment در چنین stateای failed است و دیگر progress نمی‌کند. برای تعیین مدت انتظار deployment controller قبل از transition به این state، از فیلد spec.progressDeadlineSeconds استفاده کنید.

خلاصه

در نهایت، هدف اصلی Kubernetes این است که ساخت و deploy سیستم‌های توزیع‌شده reliable را برای شما آسان کند. یعنی نه فقط instantiate یک‌بار برنامه، بلکه مدیریت rollout منظم برنامه‌ریزی‌شده نسخه‌های جدید آن سرویس نرم‌افزاری. Deploymentها قطعه حیاتی rolloutهای reliable و مدیریت rollout برای سرویس‌های شما هستند.