সরাসরি পর্বে যান
পর্ব ৪৪ · ৮১-এর মধ্যে · মডিউল ৮

Deployment — ReplicaSet, rolling update আর rollback

Kubernetes Deployments — ReplicaSets, Rolling Updates and Rollback

Taught in Bangla, this lesson traces the Deployment, ReplicaSet and Pod ownership chain, the maxSurge and maxUnavailable arithmetic with a rollout simulator, kubectl rollout status, history, undo, pause and resume, a stuck rollout caused by a missing image tag, readiness as the rollout gate, Recreate, zero-downtime conditions, PodDisruptionBudget, and when DaemonSet, Job or CronJob fit better.

এই পর্বে যা শিখবেন

  • Deployment, ReplicaSet আর Pod-এর মালিকানার শিকল আসল ownerReferences দেখে পড়া
  • maxSurge আর maxUnavailable থেকে সর্বোচ্চ Pod, সর্বনিম্ন available আর rollout-এর সময় হিসাব করা
  • kubectl rollout-এর status, history, undo, pause আর resume ঠিক কী বদলায়
  • না-থাকা image tag দিলে কী হয়, আটকে যাওয়া rollout চেনা আর ফিরিয়ে আনা
  • Readiness, minReadySeconds, Recreate আর PodDisruptionBudget কখন কাজে লাগে, আর কোথায় লাগে না
  • DaemonSet, Job আর CronJob কখন Deployment-এর চেয়ে ঠিক

১ · Pod একা কেন যথেষ্ট নয়

পর্ব ৪৩-এ আপনি Pod দেখেছেন। হাতে বানানো একটি Pod মুছে গেলে বা তার node নষ্ট হলে সেটি ফিরে আসে না। নতুন version চালাতে হলে Pod-এর image বদলানোও সহজ পথ নয়, কারণ Pod মূলত অপরিবর্তনীয়ভাবে ব্যবহার করা হয়: বদলাতে হলে নতুন Pod লাগে। তাই আমরা চাই এমন একজন ব্যবস্থাপক, যাকে বলা যায় “এই রকম Pod ৪টি চালু রাখুন, আর নতুন version এলে অল্প অল্প করে বদলান।” এই ব্যবস্থাপকই Deployment.

একটি গার্মেন্টস কারখানা ভাবুন। Production manager বলেন, “এই শার্ট প্রতিদিন ৪ লাইনে চলবে।” প্রতিটি ডিজাইনের জন্য একজন আলাদা line in-charge আছেন, আর তিনি কর্মী বসান। নতুন ডিজাইন এলে manager নতুন in-charge নিয়োগ করেন, তাঁর লাইন বাড়ান আর পুরোনো in-charge-এর লাইন কমান। পুরোনো in-charge-কে বিদায় দেন না, কারণ নতুন ডিজাইনে গোলমাল হলে আবার তাঁকেই লাগবে। Deployment হলো manager, ReplicaSet হলো line in-charge, আর Pod হলো কর্মী।

নিচে আমাদের উদাহরণের পুরো YAML. এই পর্বের প্রতিটি transcript একটি আসল local single-node cluster-এ (Kubernetes ১.৩৪) চালানো। নাম, বয়স, IP আর সময় আপনার machine-এ আলাদা হবে। কমান্ডগুলো একটি আলাদা namespace-এ চলেছে, পাতায় -n অংশ বাদ দেখানো হয়েছে। progressDeadlineSeconds: 40 শুধু পরীক্ষা দ্রুত দেখানোর জন্য ছোট রাখা, default হলো 600.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 4
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 40
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: web
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 2
          resources:
            requests:
              cpu: 20m
              memory: 16Mi
            limits:
              cpu: 200m
              memory: 64Mi

সবচেয়ে গুরুত্বপূর্ণ অংশ হলো spec.template: Pod দেখতে কেমন হবে তার ছাঁচ। আর spec.selector বলে কোন Pod এই Deployment-এর নিজের। Kubernetes-এ Deployment আর ReplicaSet দুটোই apps/v1 API-র অংশ।

$ kubectl apply -f web.yaml
deployment.apps/web created

$ kubectl rollout status deploy/web
Waiting for deployment "web" rollout to finish: 0 of 4 updated replicas are available...
Waiting for deployment "web" rollout to finish: 1 of 4 updated replicas are available...
Waiting for deployment "web" rollout to finish: 2 of 4 updated replicas are available...
Waiting for deployment "web" rollout to finish: 3 of 4 updated replicas are available...
deployment "web" successfully rolled out

$ kubectl get deploy web
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    4/4     4            4           3s

$ kubectl get rs
NAME            DESIRED   CURRENT   READY   AGE
web-df7bd6557   4         4         4       3s

$ kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
web-df7bd6557-cbs4s   1/1     Running   0          3s
web-df7bd6557-fq29n   1/1     Running   0          3s
web-df7bd6557-m49rk   1/1     Running   0          3s
web-df7bd6557-snjmn   1/1     Running   0          3s
…

নামগুলো খেয়াল করুন: Deployment web, তার ReplicaSet web-df7bd6557, আর Pod web-df7bd6557-cbs4s. মাঝের অংশটি pod-template-hash, যা Pod template থেকে হিসাব করা হয়। মালিকানা শুধু নামে নয়, metadata-তেও লেখা থাকে:

# kubectl get pod web-df7bd6557-cbs4s -o yaml (ছাঁটা)
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: web-df7bd6557
    uid: ae844ce6-0df6-4324-b799-04c2fbaaeffd

# kubectl get rs web-df7bd6557 -o yaml (ছাঁটা)
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: Deployment
    name: web
    uid: ce65b353-c8e2-4682-85b2-fd1e4dbb1562

Pod-এর মালিক ReplicaSet, ReplicaSet-এর মালিক Deployment, আর Deployment-এর কোনো মালিক নেই। controller: true মানে এই মালিকই আসল নিয়ন্ত্রক। ownerReferences আছে বলেই Deployment মুছলে তার ReplicaSet আর Pod-ও garbage collection-এ চলে যায়। নাম নয়, uid মেলানো হয়, তাই একই নামে নতুন Deployment বানালেও পুরোনো মালিকানার ভুল হয় না।

Rolling update চলাকালে মালিকানার ছবি

One Deployment, two ReplicaSets, Pods of both versions

Deployment, দুটি ReplicaSet আর Pod Deployment: web replicas 4 · surge 1 · unavailable 1 বানায় ও চালায় পুরোনো ReplicaSet web-df7bd6557 nginx:1.27 · desired 3 নতুন ReplicaSet web-84c4bfd5dd nginx:1.28 · desired 2 PodReady PodReady PodReady PodReady Podতৈরি হচ্ছে Service · selector app=web traffic যায় শুধু Ready Pod-এ, দুই version-ই পাবে
ছবি ৪৪.১ — মোট Pod ৫ (৪ + surge ১), Ready Pod ৪। পুরোনো আর নতুন দুই version একই সময়ে অনুরোধ নিচ্ছে। Service-এর বিস্তারিত পর্ব ৪৫-এ।

২ · Scaling, label আর অপরিবর্তনীয় selector

Pod-এর সংখ্যা বদলানো সবচেয়ে সহজ কাজ। এটি Pod template বদলায় না, তাই নতুন ReplicaSet বা নতুন revision হয় না:

$ kubectl scale deploy/web --replicas=6
deployment.apps/web scaled

$ kubectl get rs
NAME            DESIRED   CURRENT   READY   AGE
web-df7bd6557   6         6         6       9s

$ kubectl scale deploy/web --replicas=4
deployment.apps/web scaled

$ kubectl get rs
NAME            DESIRED   CURRENT   READY   AGE
web-df7bd6557   4         4         4       12s

$ kubectl rollout history deploy/web
deployment.apps/web
REVISION  CHANGE-CAUSE
1         <none>

একই ReplicaSet-এর DESIRED ৪ থেকে ৬ হলো, আবার ৪-এ ফিরল। History-তে এখনো একটিই revision. নিয়মটি মনে রাখুন: শুধু spec.template বদলালেই rollout হয়। replicas, strategy বা minReadySeconds বদলালে Pod বদলায় না। যাঁরা পর্ব ১৭-এর autoscaling পড়েছেন, তাঁদের জন্য: Kubernetes-এর autoscaler-ও এই replicas সংখ্যাটিই বদলায় (পর্ব ৪৮)।

Label আর selector: কে কার

Label হলো object-এ লাগানো নামফলক, যেমন app=web. Selector হলো সেই নামফলক দেখে বাছাইয়ের নিয়ম। Deployment-এর selector তৈরির পর আর বদলানো যায় না। আর selector-কে অবশ্যই template-এর label-এর সাথে মিলতে হবে। আসল চেষ্টা:

$ kubectl patch deploy web -p '{"spec":{"selector":{"matchLabels":{"app":"shop"}}}}'
The Deployment "web" is invalid:
* spec.template.metadata.labels: Invalid value: {"app":"web"}: `selector` does not match template `labels`
* spec.selector: Invalid value: {"matchLabels":{"app":"shop"}}: field is immutable

দুটি ভুল একসাথে ধরা পড়েছে: selector আর template মিলছে না, আর selector immutable, মানে বদলানো নিষেধ। কারণ সহজ। Selector বদলালে আগের Pod-গুলো হঠাৎ “কারও না” হয়ে যেত। সত্যিই বদলাতে হলে নতুন নামে নতুন Deployment বানিয়ে ধাপে ধাপে সরতে হয়। Template-এ বাড়তি label যোগ করা যায়, শুধু selector-এর label থাকতে হবে।

Label যে সত্যিই বাঁধন, তার একটি মজার প্রমাণ। আলাদা একটি run-এ একটি Pod-এর label বদলে দেওয়া হয়েছিল:

$ kubectl label pod web-59d9464878-75t94 app=debug --overwrite
pod/web-59d9464878-75t94 labeled

$ kubectl get pods -L app
NAME                   READY   STATUS    RESTARTS   AGE     APP
web-59d9464878-75t94   1/1     Running   0          41s     debug
web-59d9464878-g49ql   1/1     Running   0          41s     web
web-59d9464878-hkcv8   1/1     Running   0          4s      web
web-59d9464878-hpf28   1/1     Running   0          41s     web
web-59d9464878-krxwg   1/1     Running   0          41s     web
…
# ওই Pod-এর ownerReferences এখন খালি: owners=[]

Label বদলাতেই Pod আর selector-এ মিলল না। ReplicaSet দেখল তার ৩টি আছে, চাই ৪টি, তাই ৪ সেকেন্ড বয়সের নতুন Pod hkcv8 বানাল। পুরোনো Pod চলতেই থাকল, কিন্তু মালিকহীন হয়ে গেল (owners=[])। Production-এ সমস্যাগ্রস্ত Pod-কে পরে খুঁটিয়ে দেখার জন্য এই কৌশল কাজে লাগে। শেষে ওই Pod হাতে মুছতে হবে, কারণ এখন আর কেউ তার দায়িত্বে নেই।

৩ · Rolling update-এর অঙ্ক: maxSurge আর maxUnavailable

একটি ব্যাংকের ৪টি কাউন্টারে দুপুরে শিফট বদল হচ্ছে। শাখা ব্যবস্থাপকের দুটি নিয়ম: (১) হলে একসাথে ৫ জনের বেশি কর্মী বসার জায়গা নেই, আর (২) কখনো ৩টির কম কাউন্টার খোলা রাখা চলবে না। নতুন কর্মী কম্পিউটারে লগইন শেষ করলেই পুরোনো একজন উঠতে পারেন। ৫ হলো replicas + maxSurge, ৩ হলো replicas − maxUnavailable.

Rolling update হলো Deployment-এর default কৌশল। দুটি সংখ্যা তাকে সীমা দেয়:

  • maxSurge: replicas-এর উপরে সর্বোচ্চ কতটি বাড়তি Pod থাকতে পারে। বেশি হলে দ্রুত, কিন্তু বাড়তি CPU আর memory লাগে।
  • maxUnavailable: replicas-এর নিচে সর্বোচ্চ কতটি Pod অনুপলব্ধ থাকতে পারে। বেশি হলে দ্রুত, কিন্তু আপডেটের সময় ক্ষমতা কমে।

দুটোই সংখ্যা বা শতাংশ হতে পারে। Default হলো দুটোই 25%. শতাংশ হলে maxSurge উপরে আর maxUnavailable নিচে round হয়, যাতে সেবা নিরাপদ দিকে থাকে। দুটোই ০ দেওয়া যায় না। আসল API বলে: may not be 0 when `maxSurge` is 0. নিচের ব্লকটি চালিয়ে ছোট আর বড় Deployment-এ হিসাবটি দেখুন।

// How maxSurge and maxUnavailable percentages become Pod counts.
// maxSurge rounds UP, maxUnavailable rounds DOWN.
const surgePct = 25, unavailPct = 25;   // the Kubernetes defaults; try 10 and 10

function resolve(R) {
  let surge = Math.ceil(R * surgePct / 100);
  let unavail = Math.floor(R * unavailPct / 100);
  if (surge === 0 && unavail === 0) unavail = 1;   // both zero could never move
  return { R, surge, unavail, maxTotal: R + surge, minAvail: R - unavail };
}

console.log("replicas surge unavail maxTotal minAvailable");
for (const R of [1, 2, 3, 4, 5, 8, 10, 20, 100]) {
  const r = resolve(R);
  console.log([r.R, r.surge, r.unavail, r.maxTotal, r.minAvail]
    .map(v => String(v).padStart(6)).join(" "));
}

৪ replica-তে surge 1, unavail 1, তাই সীমা ৫ আর ৩। ৩ replica-তে 25% মানে surge ⌈০.৭৫⌉ = ১ আর unavail ⌊০.৭৫⌋ = ০: Pod একটিও কমানো যাবে না। ১ আর ২ replica-তেও unavail ০. ১০০ replica-তে সীমা ১২৫ আর ৭৫, মানে একসাথে ৫০টি Pod বদলানো যায়। Worked উদাহরণ: replicas ১০, surge ৩, unavail ২ হলে সর্বোচ্চ ১৩ আর সর্বনিম্ন available ৮.

এবার maxSurge: 1 আর maxUnavailable: 1 নিয়ে ৪টি Pod বদলানোর আসল ক্রম দেখুন। “পরে” চাপলে প্রতিটি ধাপে controller-এর সিদ্ধান্ত দেখা যাবে। সংখ্যাগুলো আসল run-এর kubectl get rs -w থেকে নেওয়া।

সীমা: মোট ≤ ৫, available ≥ ৩
পুরোনো Pod ● (1.27)
নতুন Pod ◇ তৈরি হচ্ছে, ◆ Ready
মোট Pod
Available
  1. শুরু। ৪টি পুরোনো Pod Ready. আপনি kubectl set image চালালেন, Pod template বদলাল। Controller-এর হাতে দুই সীমা: মোট ৫, available ৩.
  2. আগে বাড়তি জায়গা ব্যবহার। মোট ৪, সীমা ৫, তাই নতুন ReplicaSet বানিয়ে ১টি Pod. মোট ৫, কিন্তু available এখনো ৪, কারণ নতুন Pod Ready নয়।
  3. maxUnavailable খরচ। Available ৪, সীমা ৩, তাই ১টি পুরোনো Pod সরানো যায়। মোট ৪, available ৩: নিচের দেয়াল ছুঁলাম।
  4. আরেকটি নতুন Pod. মোট আবার ৪, তাই আরও ১টি নতুন Pod. মোট ৫, available ৩: এবার দুই দেয়ালই ছোঁয়া। Controller এখন অপেক্ষায়, কিছুই করার অনুমতি নেই।
  5. প্রথম নতুন Pod Ready. Available ৪ হলো, তাই ১টি পুরোনো Pod সরল, available আবার ৩, মোট ৪। Ready হওয়াই পরের ধাপের টিকিট।
  6. দ্বিতীয় নতুন Pod Ready. Available ৪, মোট ৪, তাই তৃতীয় নতুন Pod এল। মোট ৫.
  7. আরেকটি পুরোনো Pod গেল। Available ৪ থেকে ৩, মোট ৪। পুরোনো Pod বাকি ১টি।
  8. চতুর্থ নতুন Pod. নতুন ReplicaSet-এর desired ৪ হলো। মোট ৫, available ৩। আবার দুই দেয়ালের মাঝে অপেক্ষা।
  9. তৃতীয় নতুন Pod Ready. শেষ পুরোনো Pod সরল। মোট ৪, available ৩, সব Pod এখন নতুন version.
  10. শেষ। চতুর্থ Pod Ready, available ৪. পুরোনো ReplicaSet মোছেনি, desired ০ নিয়ে আছে। এটিই rollback-এর পুঁজি। পুরো সময়ে মোট কখনো ৫ পেরোয়নি, available কখনো ৩-এর নিচে নামেনি।

আসল transcript-টিও দেখুন। এই run-এ পুরো rollout-এ প্রায় ৭.৬ সেকেন্ড লেগেছে:

$ kubectl set image deploy/web nginx=nginx:1.28-alpine
deployment.apps/web image updated

$ kubectl rollout status deploy/web
Waiting for deployment "web" rollout to finish: 2 out of 4 new replicas have been updated...
…
Waiting for deployment "web" rollout to finish: 3 out of 4 new replicas have been updated...
…
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
…
Waiting for deployment "web" rollout to finish: 3 of 4 updated replicas are available...
deployment "web" successfully rolled out

$ kubectl get rs
NAME             DESIRED   CURRENT   READY   AGE
web-84c4bfd5dd   4         4         4       8s
web-df7bd6557    0         0         0       32s

# kubectl describe deploy web-এর event থেকে:
Scaled up replica set web-84c4bfd5dd from 0 to 1
Scaled down replica set web-df7bd6557 from 4 to 3
Scaled up replica set web-84c4bfd5dd from 1 to 2
Scaled down replica set web-df7bd6557 from 3 to 2
Scaled up replica set web-84c4bfd5dd from 2 to 3
Scaled down replica set web-df7bd6557 from 2 to 1
…

Event-এর ক্রম ঠিক উপরের স্টেপারের মতো। get rs-এ দুটি ReplicaSet, পুরোনোটির DESIRED ০. Service-এ ২০টি অনুরোধ পাঠিয়ে উত্তরে Server: nginx/1.28.3 পাওয়া গেছে, মানে নতুন version সত্যিই চলছে। Event তালিকা kubectl ছোট করে, তাই শেষ দিকের কয়েকটি একসাথে মিশে যেতে পারে।

ছোট বদলগুলো নিজে দেখার জন্য নিচের সিমুলেটরটি controller-এর দুই নিয়মে চলে। উপরের ৪টি লাইন বদলে আবার চালান। যেমন R = 10, surge = 3, unavail = 2 দিন, অথবা surge = 0, unavail = 1.

// Rolling update, following the Deployment controller's two rules.
// Change these lines and press Run again.
const R = 4;              // spec.replicas
const surge = 1;          // maxSurge, as a Pod count
const unavail = 1;        // maxUnavailable, as a Pod count
const readyAfter = 3;     // seconds from Pod creation to Ready

const maxTotal = R + surge, minAvail = R - unavail;
let old = R, clock = 0, newPods = [];    // newPods = creation times
const ready = () => newPods.filter(t => clock - t >= readyAfter).length;
const show = what => console.log(
  `t=${String(clock).padStart(2)} old=${old} new=${newPods.length}` +
  ` ready=${ready()} total=${old + newPods.length}` +
  ` avail=${old + ready()} ${what}`);

console.log(`maxTotal=${maxTotal} minAvailable=${minAvail}`);
show("start");
while (old > 0 || ready() < R) {
  // Rule 1: create new Pods while total < maxTotal.
  const up = Math.min(maxTotal - old - newPods.length, R - newPods.length);
  if (up > 0) {
    for (let i = 0; i < up; i++) newPods.push(clock);
    show(`+${up} new`);
  }
  // Rule 2: remove old Pods only if available stays >= minAvail.
  const notReady = newPods.length - ready();
  const down = Math.min(old, old + newPods.length - minAvail - notReady);
  if (down > 0) { old -= down; show(`-${down} old`); }
  if (up <= 0 && down <= 0) clock++;     // nothing allowed: wait
}
show("done");

প্রতিটি লাইনে t সেকেন্ড, পুরোনো আর নতুন Pod, নতুনের মধ্যে Ready, মোট আর available. avail কখনো ৩-এর নিচে নামেনি, total কখনো ৫ পেরোয়নি। সিমুলেটরে একসাথে বানানো Pod একই মুহূর্তে Ready হয়, তাই t= 3-এ একবারে -2 old. আসল run-এ Pod কয়েক মিলিসেকেন্ড আগে-পরে Ready হয়, তাই একটি একটি করে হয়েছিল। মোট সময় একই ধারার: সিমুলেটরে ৬ সেকেন্ড, আসল run-এ প্রায় ৭.৬ সেকেন্ড।

৪ · kubectl rollout — status, history, undo, pause, resume

কমান্ডযা করেযা মনে রাখবেন
statusRollout শেষ বা ব্যর্থ হওয়া পর্যন্ত অপেক্ষা করেব্যর্থ হলে non-zero exit, তাই CI/CD pipeline-এ কাজে লাগে
historyRevision-এর তালিকা দেখায়--revision=2 দিলে সেই template
undoআগের revision-এর template ফিরিয়ে আনেএটিও একটি নতুন rollout, নতুন revision নম্বর পায়
pauseTemplate বদলালেও rollout শুরু হতে দেয় নাকয়েকটি বদল একসাথে করার সুযোগ
resumeথামানো rollout আবার চালায়জমা বদল মিলে একটিই নতুন ReplicaSet
restartTemplate-এ restartedAt annotation বসিয়ে সব Pod বদলায়একই image, তবু নতুন ReplicaSet

১.২৮ থেকে ১.২৭-এ ফিরে যাওয়ার আসল run:

$ kubectl rollout undo deploy/web
deployment.apps/web rolled back

$ kubectl annotate deploy/web kubernetes.io/change-cause="back to 1.27" --overwrite
deployment.apps/web annotated

$ kubectl rollout status deploy/web
…
deployment "web" successfully rolled out

$ kubectl get rs
NAME             DESIRED   CURRENT   READY   AGE
web-84c4bfd5dd   0         0         0       18s
web-df7bd6557    4         4         4       42s

$ kubectl rollout history deploy/web
deployment.apps/web
REVISION  CHANGE-CAUSE
2         image 1.28
3         back to 1.27

তিনটি জিনিস লক্ষ করুন। (১) নতুন ReplicaSet হয়নি: পুরোনো web-df7bd6557-ই আবার ৪ হয়েছে, কারণ একই template মানে একই hash. (২) Revision ১ তালিকা থেকে উধাও, সেটির নম্বর এখন ৩। Undo আগের revision-কে সামনে তুলে আনে। (৩) CHANGE-CAUSE আসে kubernetes.io/change-cause annotation থেকে। এটি নিজে থেকে বদলায় না, ভুলে গেলে আগের লেখা পরের revision-এ চলে আসে। পরে আমাদের তালিকায় পাঁচটি ভিন্ন revision-এর পাশে একই লেখা “back to 1.27” দেখা গেছে। পুরোনো --record flag deprecated, তাই annotation-ই ভরসা।

দুবার undo করলে কী হয়? আসল পরীক্ষায় দ্বিতীয় undo ঠিক সেই ভাঙা version-এ ফিরিয়ে নিয়ে গেছে, যেখান থেকে পালিয়েছিলেন। কারণ undo মানে “আগের নম্বর”, “শেষ ভালোটা” নয়। নিশ্চিত হতে --to-revision=N দিন, আর আগে rollout history --revision=N দিয়ে image মিলিয়ে নিন।

Pause আর resume

$ kubectl rollout pause deploy/web
deployment.apps/web paused

$ kubectl set image deploy/web nginx=nginx:1.28-alpine
deployment.apps/web image updated

$ kubectl set resources deploy/web -c=nginx --limits=memory=80Mi
deployment.apps/web resource requirements updated

$ kubectl get rs
NAME             DESIRED   CURRENT   READY   AGE
web-6f799dff6    0         0         0       15s
web-6f8459f7c8   0         0         0       75s
web-84c4bfd5dd   0         0         0       105s
web-df7bd6557    4         4         4       2m9s

$ kubectl rollout status deploy/web --timeout=4s
Waiting for deployment "web" rollout to finish: 0 out of 4 new replicas have been updated...
error: timed out waiting for the condition

$ kubectl rollout undo deploy/web
error: you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume' and try again

$ kubectl rollout resume deploy/web
deployment.apps/web resumed

$ kubectl get rs
NAME             DESIRED   CURRENT   READY   AGE
web-6f799dff6    0         0         0       26s
web-6f8459f7c8   0         0         0       86s
web-74f8dbbc47   4         4         4       7s
web-84c4bfd5dd   0         0         0       116s
web-df7bd6557    0         0         0       2m20s

Pause-এর সময় দুটি বদল করা হলো, তবু পুরোনো Pod-ই ৪টি চলেছে। Resume করতেই দুটি বদল মিলে একটিই নতুন ReplicaSet web-74f8dbbc47 এল, মানে Pod দুবার বদলাতে হলো না। Pause অবস্থায় undo নিষেধ, আর rollout status আটকে থাকে। Pause করা Deployment-এ deadline-এর হিসাবও চলে না, তাই ভুলে থেমে থাকলে কোনো alert পাবেন না।

revisionHistoryLimit: কত পুরোনো ReplicaSet রাখবেন

উপরের তালিকায় DESIRED 0-এর সারিগুলো দেখুন। এগুলোই rollback-এর ইতিহাস। revisionHistoryLimit বলে কতটি পুরোনো ReplicaSet থাকবে, default ১০. আসল run-এ এটি ১ করা হলো:

$ kubectl patch deploy web -p '{"spec":{"revisionHistoryLimit":1}}'
deployment.apps/web patched

$ kubectl get rs
NAME             DESIRED   CURRENT   READY   AGE
web-59d9464878   0         0         0       75s
web-74f8dbbc47   4         4         4       109s

$ kubectl rollout undo deploy/web --to-revision=1
error: unable to find specified revision 1 in history

ছয়টি ReplicaSet থেকে দুটি থাকল: চলমানটি আর ১টি পুরোনো। মুছে যাওয়া revision-এ আর ফেরা যায় না। 0 দিলে rollback-ই অসম্ভব। অনেক বেশি রাখলে অকারণে ফাঁকা object জমে। সাধারণত default যথেষ্ট, আর Git-এ manifest থাকলে (পর্ব ৬০) পুরোনো version সেখান থেকেও ফিরিয়ে আনা যায়।

৫ · যে tag নেই: আটকে যাওয়া rollout

এবার ইচ্ছে করে ভুল করি। nginx 1.27 চলছে, আর আমরা এমন একটি tag দিলাম যা registry-তে নেই। ধরুন, AI বা কোনো সহকর্মী একটি image tag-এর বানান ভুল করেছেন। Deployment কি সব ভেঙে ফেলবে?

$ kubectl set image deploy/web nginx=nginx:1.27-no-such-tag
deployment.apps/web image updated

$ kubectl get rs                    # ৯ সেকেন্ড পরে
NAME             DESIRED   CURRENT   READY   AGE
web-6f8459f7c8   2         2         0       9s
web-84c4bfd5dd   0         0         0       39s
web-df7bd6557    3         3         3       63s

$ kubectl get pods -l app=web
NAME                   READY   STATUS         RESTARTS   AGE
web-6f8459f7c8-w984k   0/1     ErrImagePull   0          9s
web-6f8459f7c8-wzvgs   0/1     ErrImagePull   0          9s
web-df7bd6557-fx6mv    1/1     Running        0          29s
web-df7bd6557-gwn44    1/1     Running        0          29s
web-df7bd6557-hcknr    1/1     Running        0          26s

$ kubectl get deploy web
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/4     2            3           63s

# Service-এ ২০টি অনুরোধ পাঠানো হলো, ফল গুনে:
     20   HTTP/1.1 200 OK  Server: nginx/1.27.5

$ kubectl rollout status deploy/web
Waiting for deployment "web" rollout to finish: 2 out of 4 new replicas have been updated...
error: deployment "web" exceeded its progress deadline

# kubectl get deploy web -o jsonpath দিয়ে condition:
Available=True MinimumReplicasAvailable: Deployment has minimum availability.
Progressing=False ProgressDeadlineExceeded: ReplicaSet "web-6f8459f7c8" has timed out progressing.

# kubectl describe pod web-6f8459f7c8-w984k (ছাঁটা)
Warning  Failed   …  kubelet  Failed to pull image "nginx:1.27-no-such-tag": … not found
Warning  Failed   …  kubelet  Error: ErrImagePull
Normal   BackOff  …  kubelet  Back-off pulling image "nginx:1.27-no-such-tag"
Warning  Failed   …  kubelet  Error: ImagePullBackOff

সেবা ভাঙেনি। ২০টি অনুরোধের ২০টিই পুরোনো version থেকে সফল। কিন্তু ক্ষমতা কমেছে: READY 3/4. কারণ অঙ্ক: controller ১টি পুরোনো Pod সরানোর অনুমতি আগেই খরচ করে ফেলেছে, আর নতুন ২টি কখনো Ready হবে না। তাই চতুর্থ পুরোনো Pod আর ফেরে না। Pod-এর অবস্থা ErrImagePull আর ImagePullBackOff-এর মধ্যে ঘুরছে। প্রায় ৪২ সেকেন্ড পরে rollout status ব্যর্থ হলো, কারণ আমরা deadline ৪০ সেকেন্ড দিয়েছিলাম।

মূল ধারণা

progressDeadlineSeconds (default ৬০০) মানে “শেষ অগ্রগতির পর এত সেকেন্ড কিছু না ঘটলে আটকে গেছে বলে ধরুন।” এটি মোট সময়ের সীমা নয়। প্রতিটি নতুন Pod Ready হলে ঘড়ি নতুন করে শুরু হয়। আর এটি শুধু একটি condition বদলায়: Progressing=False. Kubernetes নিজে rollback করে না, Pod-ও সরায় না। সিদ্ধান্ত আপনার বা আপনার pipeline-এর।

Deployment-এর অবস্থা
সময় (প্রায়)
Revision
RS 1.27: desired / ready
RS bad tag: desired / ready
READY
Progressing
  1. সুস্থ অবস্থা। nginx 1.27 চলছে, ৪টি Pod Ready, এটি revision ৩.
  2. ভুল tag দেওয়া হলো। Template বদলাল, তাই revision ৪ আর নতুন ReplicaSet. Controller নিয়ম মেনে নতুন Pod ২টি বানাল, পুরোনো ১টি সরাল। ঠিক যেমন ভালো image-এ করত, কারণ image আছে কি না সে আগে থেকে জানে না।
  3. Image নামানো ব্যর্থ। kubelet tag পায় না, Pod ErrImagePull. নতুন Pod Ready নয়, তাই controller আর এগোতে পারে না। পুরোনো ৩টি Pod সব অনুরোধ সামলাচ্ছে।
  4. Deadline পেরোল। ৪০ সেকেন্ডে কোনো অগ্রগতি নেই। Progressing=False, কারণ ProgressDeadlineExceeded. কিন্তু Available=True এখনো, আর কিছুই নিজে থেকে বদলায়নি।
  5. Rollback. undo revision ৩-এর template ফিরিয়ে আনল, নতুন নম্বর ৫. RS 1.27-এর desired ৪, ভাঙা RS ০. আটকে থাকা ২টি Pod সরল।
  6. আবার সুস্থ। ৪/৪ Ready, undo-এর পর কয়েক সেকেন্ডেই শেষ। History-তে এখন ২, ৪ আর ৫ আছে। সেবা কোনো মুহূর্তে বন্ধ হয়নি, শুধু কিছুক্ষণ ক্ষমতা ৭৫% ছিল।

আটকে থাকলে কত ক্ষমতা হারাবেন, সেটি আগে থেকেই হিসাব করা যায়: নতুন Pod হবে min(surge + unavail, R)টি, আর available থাকবে R − unavail. নিচের মডেলটি কয়েকটি নকশা তুলনা করে, আর দেখায় যে ধীর কিন্তু এগোতে থাকা rollout-কে deadline ধরে না।

// A rollout whose new Pods may never become Ready.
// progressDeadlineSeconds counts time since the LAST progress.
function run(name, R, s, u, deadline, readyAfter) {
  let old = R, pods = [], clock = 0, last = 0, ready = 0, stuck = false;
  while (clock <= 200 && !stuck && (old > 0 || ready < R)) {
    const nowReady = pods.filter(t => clock - t >= readyAfter).length;
    const up = Math.min(R + s - old - pods.length, R - pods.length);
    const fresh = nowReady > ready;
    for (let i = 0; i < up; i++) pods.push(clock);
    ready = nowReady;
    const down = Math.min(old, old + pods.length - (R - u) - (pods.length - ready));
    if (down > 0) old -= down;
    if (up > 0 || down > 0 || fresh) last = clock;
    if (clock - last >= deadline) stuck = true;
    else if (up <= 0 && down <= 0) clock++;
  }
  const avail = old + ready;
  console.log(`${name}: t=${clock}s old=${old} new=${pods.length} ` +
    `ready=${ready} avail=${avail}/${R} ` +
    (stuck ? "ProgressDeadlineExceeded" : "complete"));
}
const never = Infinity;
run("bad image, 1+1   ", 4, 1, 1, 40, never);
run("bad image, 1+0   ", 4, 1, 0, 40, never);
run("bad image, 0+1   ", 4, 0, 1, 40, never);
run("bad image, 3+2   ", 10, 3, 2, 40, never);
run("slow Pods (30s)  ", 4, 1, 0, 40, 30);
run("slower Pods (45s)", 4, 1, 0, 40, 45);

লাইন ১-এ আমাদের আসল ফল: old 3, new 2, avail 3/4. লাইন ২-এ maxUnavailable: 0: আটকে গেলেও ৪/৪ সেবা, দাম হলো বাড়তি একটি Pod-এর জায়গা। তাই ক্ষমতা যেখানে টানাটানি, সেখানে maxUnavailable: 0 নিরাপদ। শেষ দুই লাইন দেখুন। প্রতিটি Pod ৩০ সেকেন্ডে Ready হলে মোট ১২০ সেকেন্ড লাগলেও ৪০ সেকেন্ডের deadline ভাঙে না, কারণ প্রতিবার ঘড়ি শূন্য হয়। ৪৫ সেকেন্ড লাগলে ৪০ সেকেন্ডেই “আটকে গেছে” ধরা পড়ে। এই মডেল সেখানেই থামে, কিন্তু আসল controller চেষ্টা চালিয়ে যায়, আর পরে Pod Ready হলে rollout এগোতে পারে। Pod যত ধীরে ওঠে, deadline তত বড় দিন।

ঘটনায় কী করবেন: প্রথমে kubectl rollout status, তারপর get rs আর get pods, তারপর describe pod-এর Events. সেবা পুরোনো version-এ চলছে, তাই তাড়াহুড়ো নেই, তবে অবস্থা অসম্পূর্ণ: undo করুন, অথবা সঠিক tag দিয়ে নতুন set image দিন। CI/CD-তে kubectl rollout status --timeout=… চালালে ব্যর্থতায় pipeline নিজেই থামবে।

চাইলে নিজে চালিয়ে দেখতে পারেন, তবে এটি ঐচ্ছিক। minikube, kind বা Docker Desktop-এর local cluster-এ খরচ লাগে না। Cloud-এর managed cluster-এ খরচ হতে পারে। শেষে kubectl delete deploy web দিয়ে পরিষ্কার করুন, আর নিশ্চিত হয়ে নিন যে আপনি নিজের পরীক্ষার cluster-এই আছেন।

৬ · Readiness: rollout-এর দরজা

Image ঠিক থাকলেও নতুন version ভাঙা হতে পারে। Controller কীভাবে বুঝবে? সে শুধু একটি জিনিস দেখে: Pod Ready কি না। Readiness probe ছাড়া container শুরু হলেই Pod Ready ধরা হয়, এমনকি ভেতরের app তখনো ৩০ সেকেন্ড ধরে তৈরি হচ্ছে, অথবা ভাঙা config-এ ভুল উত্তর দিচ্ছে। পর্ব ৪৩-এ probe দেখেছেন, এখানে দেখুন সেটি কীভাবে rollout থামায়।

ব্যাংকের নতুন কর্মী চেয়ারে বসলেই তাঁর কাছে গ্রাহক পাঠানো হয় না। আগে কম্পিউটারে লগইন, ক্যাশ বুঝে নেওয়া। Readiness probe ঠিক এই “কাজের জন্য তৈরি” চিহ্ন। তৈরি না হওয়া পর্যন্ত পুরোনো কর্মীও ওঠেন না।

এবার image ঠিক রেখে probe-এর path এমন করা হলো, যা 404 দেয়। এটি “নতুন version ভাঙা” অবস্থার সহজ অনুকরণ:

$ kubectl patch deploy web --type=json -p='[{"op":"replace",
  "path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path",
  "value":"/no-such-page"}]'
deployment.apps/web patched

$ kubectl get pods -l app=web
NAME                  READY   STATUS    RESTARTS   AGE
web-6f799dff6-d8b87   0/1     Running   0          10s
web-6f799dff6-wnf6d   0/1     Running   0          10s
web-df7bd6557-fx6mv   1/1     Running   0          90s
web-df7bd6557-gwn44   1/1     Running   0          90s
web-df7bd6557-hcknr   1/1     Running   0          87s

$ kubectl get deploy web
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/4     2            3           2m4s

# kubectl describe pod থেকে (ছাঁটা):
Warning  Unhealthy  …  kubelet  Readiness probe failed: HTTP probe failed with statuscode: 404

# Service-এ ২০টি অনুরোধ:
     20   HTTP/1.1 200 OK  Server: nginx/1.27.5

এবার Pod-এর STATUS Running, কিন্তু READY 0/1. আগের অংশের মতো একই অঙ্ক: নতুন ২টি, পুরোনো ৩টি, rollout থেমে আছে। পার্থক্য হলো process চলছে, শুধু সে “তৈরি” বলছে না। তাই Running দেখে নিশ্চিন্ত হওয়া চলে না, READY কলামটিই আসল। Service-ও এই Pod-এ অনুরোধ পাঠায়নি: ২০টির ২০টি পুরোনো version থেকে এসেছে। ঠিক করতে আবার rollout undo.

minReadySeconds: Ready হওয়ার পরেও একটু দেখা

কিছু app শুরুতে ঠিক থাকে, ১০ সেকেন্ড পরে crash করে। minReadySeconds বলে, Pod Ready হওয়ার পর এত সেকেন্ড না ভেঙে থাকলে তবেই তাকে Available ধরা হবে। Default ০. একে প্রায় একটি ছোট পর্যবেক্ষণের সময় ভাবতে পারেন, যেমন নতুন রাঁধুনির প্রথম হাঁড়ি চেখে তবেই বড় ডেকচির দায়িত্ব দেওয়া। minReadySeconds: 10 দিয়ে একই ৪ replica-র rollout:

$ kubectl patch deploy web -p '{"spec":{"minReadySeconds":10,"progressDeadlineSeconds":120}}'
deployment.apps/web patched

$ kubectl set image deploy/web nginx=nginx:1.27-alpine
deployment.apps/web image updated

$ kubectl get deploy web          # ৫ সেকেন্ড পরে
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    5/4     2            3           2m53s

$ kubectl get rs
NAME             DESIRED   CURRENT   READY   AGE
web-59d9464878   2         2         2       6s
…
web-74f8dbbc47   3         3         3       40s
…

$ kubectl get deploy web          # আরও ৫ সেকেন্ড পরে
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    5/4     2            3           2m57s

READY 5/4, কিন্তু AVAILABLE 3. নতুন ২টি Pod Ready হয়েছে, তবু গোনা হচ্ছে না, তাই পুরোনো Pod সরছে না। পুরো rollout-এ এবার প্রায় ২৫ সেকেন্ড লেগেছে, যেখানে minReadySeconds ছাড়া লেগেছিল প্রায় ৭.৬ সেকেন্ড। মনে রাখুন progressDeadlineSeconds-কে minReadySeconds-এর চেয়ে বড় হতেই হবে। না হলে API বলে: must be greater than minReadySeconds. এটি রোগ ধরার জাল, প্রতিষেধক নয়: ১০ সেকেন্ডের মধ্যে যে ভুল ধরা পড়ে না, তা এটি ধরবে না।

কত সময় লাগবে: একটি হিসাব

Rollout অনেকটা ব্যাচে চলে। প্রতি ব্যাচে surge + unavailটি Pod বদলায়, আর একটি ব্যাচে সময় লাগে Ready হওয়ার সময় যোগ minReadySeconds:

T ≈ ⌈R ÷ (surge + unavail)⌉ × (tready + minReadySeconds)

Worked উদাহরণ: R = ৪, surge ১, unavail ১, tready ≈ ৩ সেকেন্ড, minReady ১০ সেকেন্ড হলে ব্যাচ ⌈৪ ÷ ২⌉ = ২, আর T ≈ ২ × ১৩ = ২৬ সেকেন্ড। আসল run-এ প্রায় ২৫ সেকেন্ড লেগেছে। নিচের ব্লকে সূত্রটি ছোট সিমুলেশনের সাথে মেলানো হয়েছে। সূত্রে আপনার কয়েকটি সংখ্যা বসিয়ে দেখুন।

// Rollout time: the quick formula against a small simulation.
function simulate(R, s, u, ready, minReady) {
  const wait = ready + minReady;
  let old = R, pods = [], clock = 0;
  const av = () => pods.filter(t => clock - t >= wait).length;
  while (old > 0 || av() < R) {
    const up = Math.min(R + s - old - pods.length, R - pods.length);
    for (let i = 0; i < up; i++) pods.push(clock);
    const down = Math.min(old, old + pods.length - (R - u) - (pods.length - av()));
    if (down > 0) old -= down;
    if (up <= 0 && down <= 0) clock++;
  }
  return clock;
}
const estimate = (R, s, u, ready, minReady) =>
  Math.ceil(R / (s + u)) * (ready + minReady);

console.log("R  surge unav ready minReady | formula sim");
for (const [R, s, u, ready, minReady] of [
  [4, 1, 1, 3, 0], [4, 1, 1, 3, 10], [4, 1, 0, 3, 0], [10, 3, 2, 20, 0],
  [10, 1, 0, 20, 10], [100, 25, 25, 30, 0], [100, 1, 0, 30, 0],
]) {
  console.log(`${R} ${s} ${u} ${ready} ${minReady} | ` +
    `${estimate(R, s, u, ready, minReady)}s ` +
    `${simulate(R, s, u, ready, minReady)}s`);
}

প্রতি সারিতে সূত্র আর সিমুলেশন মিলেছে। তুলনাটি দেখুন: ১০০ replica, প্রতিটি ৩০ সেকেন্ডে Ready, 25%/25% দিলে ৬০ সেকেন্ড, কিন্তু surge 1, unavail 0 দিলে ৩০০০ সেকেন্ড, মানে ৫০ মিনিট। ধীরে করলে নিরাপদ, তবে একটি সীমা ভুলবেন না: ধীর rollout-এ একটি ভুল ধরতে যত সময় লাগে, পুরোনো আর নতুন version-ও তত বেশি সময় একসাথে থাকে। এই হিসাব মোটামুটি আন্দাজ: image নামানোর সময় আর Pod বসানোর দেরি এতে নেই। আর terminating Pod মোট-এর হিসাবে সাধারণত ধরা হয় না, তাই কিছুক্ষণ আসল Pod সংখ্যা সীমার চেয়ে বেশি দেখা যেতে পারে।

৭ · Recreate আর zero-downtime-এর আসল শর্ত

Recreate: আগে সব সরান, তারপর নতুন

Recreate strategy-তে সব পুরোনো Pod আগে সরে, তারপর নতুন Pod আসে। ঠিক দোকানের শাটার নামিয়ে সাজ বদলানোর মতো। সেবা মাঝে বন্ধ থাকে। তাহলে কেন? যখন দুই version একসাথে চলতে পারে না: যেমন একটি মাত্র volume একজনই লিখতে পারে, অথবা নতুন version পুরোনো data format ভেঙে দেয়।

$ kubectl patch deploy web -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'
deployment.apps/web patched

$ kubectl set image deploy/web nginx=nginx:1.28-alpine
deployment.apps/web image updated

$ kubectl get deploy web          # ১ সেকেন্ড পরে
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    0/4     0            0           3m19s

$ kubectl get pods -l app=web
NAME                   READY   STATUS      RESTARTS   AGE
web-59d9464878-n2d77   0/1     Completed   0          32s
web-59d9464878-pfhr4   0/1     Completed   0          19s
web-59d9464878-sf7b2   0/1     Completed   0          32s
web-59d9464878-svt7m   0/1     Completed   0          19s

# rollout-এর সময় চলা ২০০টি অনুরোধের ফল:
recreate ok=188 bad=12

READY 0/4: এক মুহূর্তের জন্য কোনো Pod নেই। ২০০ অনুরোধের ১২টি ব্যর্থ। nginx খুব দ্রুত ওঠে বলে ফাঁকটি এখানে কয়েক সেকেন্ডের। যে app উঠতে এক মিনিট নেয়, তার ফাঁকও ততটাই। Strategy বদলাতে rollingUpdate: null লাগে, কারণ Recreate-এর সাথে ওই অংশ রাখা যায় না।

Rolling update মানেই কি শূন্য ব্যর্থতা?

না। উপরের একই run-এ RollingUpdate-এ ফিরে গিয়ে আবার মাপলে ২০০টির ৬টি ব্যর্থ হয়েছিল। Zero-downtime দুই দিকের কাজ: নতুন Pod-এ অনুরোধ যাবে শুধু সে তৈরি হলে, আর পুরোনো Pod থেকে অনুরোধ সরবে সে থামার আগে। দ্বিতীয়টিই অনেকে ভুলে যান। Pod মোছার সময় দুটি কাজ প্রায় একসাথে শুরু হয়:

  1. kubelet Pod থামানো শুরু করে: preStop hook থাকলে সেটি চালায়, তারপর container-কে SIGTERM বা image-এর stop signal পাঠায়।
  2. অন্যদিকে Service-এর তালিকা থেকে Pod বাদ পড়ে, আর সেই খবর প্রতিটি node-এ ছড়ায়।

দ্বিতীয় কাজটি একটু দেরিতে পৌঁছালে অনুরোধ এমন Pod-এ যায়, যা আর শুনছে না। preStop-এ কয়েক সেকেন্ড অপেক্ষা করলে আগে তালিকা হালনাগাদ হয়, তারপর app থামে। terminationGracePeriodSeconds (default ৩০) এই পুরো সময়ের সীমা, preStop-সহ। সময় ফুরালে SIGKILL. একই ভাবনা পর্ব ১৮-এর connection draining আর পর্ব ১১-এর graceful stop-এ দেখেছেন।

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 4
  selector:
    matchLabels:
      app: api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          command: ["sh", "-c", "sleep 4; exec nginx -g 'daemon off;'"]
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            periodSeconds: 1
            failureThreshold: 1
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "5"]
          resources:
            requests:
              cpu: 20m
              memory: 16Mi
            limits:
              cpu: 200m
              memory: 64Mi

আমরা একটি ছোট পরীক্ষা চালিয়েছি। Container ইচ্ছে করে ৪ সেকেন্ড দেরিতে শুরু হয় (sleep 4), যাতে ধীরে ওঠা app-এর মতো লাগে। ৪ replica, maxSurge: 1, maxUnavailable: 0. একটি client Pod rollout-এর সময় Service-এ ৫০০টি অনুরোধ পাঠায়, প্রতিটি নতুন connection-এ, ১ সেকেন্ড timeout সহ। প্রতিটি নকশা দুবার চালানো:

নকশাব্যর্থ, run ১ (৫০০-এর মধ্যে)ব্যর্থ, run ২
ক. Readiness নেই, preStop নেই৩৮৬১
খ. Readiness আছে, preStop নেই৫৫
গ. Readiness + preStop: sleep 5১০

ক-তে Pod শুরু হতেই Ready, অথচ nginx তখনো শুনছে না, তাই অনেক অনুরোধ ব্যর্থ। খ-তে এই ফাঁক নেই, কিন্তু থামার সময়ের ফাঁক রয়ে গেছে। গ-তে প্রায় শূন্য। গ-এর ১টি ব্যর্থতার কারণ আমরা প্রমাণ করিনি, হতে পারে timeout. সংখ্যা machine-ভেদে আলাদা হবে, ধারাটিই শিক্ষা। Kubernetes ১.৩৪-এর এই cluster lifecycle.preStop.sleep.seconds নামের সরাসরি লেখার পথও গ্রহণ করেছে (server dry-run পাস), যাতে image-এ sleep কমান্ড লাগে না। পুরোনো version-এ এটি নাও থাকতে পারে।

Zero-downtime-এর শর্ত

(১) সৎ readiness probe. (২) ভদ্রভাবে থামা: SIGTERM ধরে হাতের অনুরোধ শেষ করা, অথবা এমন stop signal, যা nginx-এর মতো সেটি করে। (৩) preStop-এ ছোট অপেক্ষা। (৪) Grace period যথেষ্ট বড়। (৫) একাধিক replica, আর maxUnavailable এমন যে বাকি Pod ভার সামলাতে পারে। (৬) দুই version একসাথে চলতে পারে: API আর database schema পিছনের দিকে মানানসই।

শেষ শর্তটি Kubernetes-এর বাইরের। Database-এর জন্য expand-contract কৌশল System Design · Deployment ও release-এ আছে। আর শুধু অল্প কিছু ব্যবহারকারীকে আগে নতুন version দিতে হলে Deployment-এর rolling update যথেষ্ট নয়, কারণ সে শুধু Pod গোনে, traffic-এর ভাগ বা error rate দেখে না। সেজন্য canary release, যা পর্ব ৫৯-এ আছে।

৮ · PodDisruptionBudget: স্বেচ্ছায় সরানোর বাজেট

একটি হাসপাতালের ঈদের ছুটির রোস্টার ভাবুন: “যা-ই হোক, ওয়ার্ডে কমপক্ষে ৩ জন নার্স থাকবেন।” কেউ ছুটির আবেদন করলে এই নিয়মে যাচাই হয়। কিন্তু কেউ হঠাৎ অসুস্থ হলে রোস্টার তাঁকে আটকাতে পারে না। PodDisruptionBudget ঠিক এই রোস্টার।

PDB বলে দেয় স্বেচ্ছাকৃত সরানোর সময় কমপক্ষে কতটি Pod থাকবে (minAvailable), অথবা সর্বোচ্চ কতটি যেতে পারে (maxUnavailable). স্বেচ্ছাকৃত মানে node drain, cluster upgrade, cluster autoscaler-এর node সরানো। এই পথগুলো Eviction API ব্যবহার করে, আর সেখানেই PDB যাচাই হয়। Node হঠাৎ নষ্ট হলে, বা কেউ kubectl delete pod চালালে PDB কিছুই করে না।

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 3
  selector:
    matchLabels:
      app: web

এই cluster-এর সীমা: আমাদের cluster-এ একটিই node, আর সেখানে kubectl drain চালানো হয়নি। এক node-এ drain চালালে Pod-এর যাওয়ার আর কোনো জায়গা থাকে না, তাই আসল রূপটি বোঝা যায় না। নিচে আমরা Eviction API হাতে ডেকেছি, যা drain ভেতরে করে। সরানো Pod-এর বদলি একই node-এ এসেছে। বহু-node cluster-এ সেটি অন্য node-এ যেত। এই অংশের drain-সংক্রান্ত কথা তাই নথিভিত্তিক, আসল run-এ দেখা নয়।

$ kubectl get pdb
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   3               N/A               1                     3s

# প্রথম Pod সরানোর অনুরোধ (Eviction API, কমান্ড ছাঁটা):
{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success","code":201}

# সাথে সাথে দ্বিতীয় Pod:
Error from server (TooManyRequests): Cannot evict pod as it would violate the pod's disruption budget.

$ kubectl get pdb
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   3               N/A               0                     3s

$ kubectl get pods -l app=web
NAME                   READY   STATUS              RESTARTS   AGE
web-59d9464878-24jrj   1/1     Terminating         0          7s
web-59d9464878-45qbz   1/1     Running             0          7s
web-59d9464878-d6sb9   1/1     Running             0          10s
web-59d9464878-qk4kn   1/1     Running             0          10s
web-59d9464878-r67t5   0/1     ContainerCreating   0          0s

$ kubectl set image deploy/web nginx=nginx:1.28-alpine     # বদলি Ready হওয়ার পরে
deployment.apps/web image updated

$ kubectl get pdb
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   3               N/A               0                     11s

৪টি Pod, minAvailable: 3, তাই বাজেট ১. প্রথম eviction সফল, বাজেট ০, দ্বিতীয়টি HTTP 429-এ ফিরল (TooManyRequests). Drain এই উত্তর পেলে অপেক্ষা করে আবার চেষ্টা করে। শেষ দুটি কমান্ড সবচেয়ে শিক্ষণীয়: বাজেট ০ হলেও rolling update ঠিকই শেষ হয়েছে। কারণ Deployment-এর rollout PDB মানে না, সে নিজের maxUnavailable মানে। দুটি আলাদা পথের আলাদা নিয়ম।

ফাঁদ: replicas ৩ আর minAvailable: 3 হলে বাজেট সবসময় ০, মানে কোনো eviction সফল হবে না, আর node drain চিরকাল আটকে থাকতে পারে। একটি replica-র Deployment-এও একই সমস্যা। শতাংশ বা maxUnavailable: 1 ব্যবহার করলে এই ফাঁদ কম।

৯ · Deployment ছাড়া অন্য workload

Deployment বানানো হয়েছে এমন Pod-এর জন্য, যারা বিনিময়যোগ্য আর চিরকাল চলতে থাকে। বাকি কাজের জন্য আলাদা object আছে।

Workloadকখনবাস্তব উদাহরণদৈনন্দিন উপমা
DeploymentStateless সেবা, Pod বিনিময়যোগ্যweb, APIযেকোনো কাউন্টারের কর্মী
DaemonSetপ্রতিটি node-এ একটিlog agent, monitoring agentপ্রতি ভবনে একজন দারোয়ান
Jobশেষ হওয়ার মতো কাজmigration, বড় হিসাবএকবারের রাজমিস্ত্রি
CronJobসময়সূচিতে Jobরাতের রিপোর্ট, backupমাসের ১ তারিখে বিদ্যুৎ বিল
StatefulSetস্থায়ী নাম আর নিজস্ব storagedatabase, queueনির্দিষ্ট আসনের যাত্রী

StatefulSet-এর জন্য storage বোঝা লাগে, তাই সেটি পর্ব ৪৭-এ।

DaemonSet

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-agent
spec:
  selector:
    matchLabels:
      app: log-agent
  template:
    metadata:
      labels:
        app: log-agent
    spec:
      containers:
        - name: agent
          image: alpine:3.20
          command: ["sh", "-c", "sleep 3000"]
$ kubectl get ds
NAME        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
log-agent   1         1         1       1            1           <none>          6s

এখানে replicas নেই। DESIRED হলো node-এর সংখ্যা, এই cluster-এ ১. ৫০ node-এর cluster-এ একই YAML ৫০টি Pod বানাত, আর নতুন node এলে সেখানেও নিজে থেকে একটি বসাত। সেটি আমরা এখানে পরীক্ষা করতে পারিনি, কারণ node একটিই।

Job আর CronJob

apiVersion: batch/v1
kind: Job
metadata:
  name: always-fails
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: task
          image: alpine:3.20
          command: ["sh", "-c", "echo processing; exit 1"]
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report
spec:
  schedule: "0 2 * * *"
  timeZone: "Asia/Dhaka"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: report
              image: alpine:3.20
              command: ["sh", "-c", "echo report done"]

restartPolicy এখানে Never বা OnFailure হতে হয়, default Always Job-এ চলে না। always-fails ইচ্ছে করে সবসময় exit 1 করে। backoffLimit: 2 মানে প্রথম চেষ্টার পরে আরও ২ বার। নিচের সময়গুলো আসল Pod-এর start আর finish সময় থেকে হিসাব করা।

Job: always-fails · restartPolicy Never
সময়
Pod
মোট ব্যর্থ
পরের অপেক্ষা
Job-এর অবস্থা
  1. প্রথম চেষ্টা। Job একটি Pod বানাল। Container চলছে, এখনো ব্যর্থ হয়নি।
  2. প্রথম ব্যর্থতা। Exit code ১, Pod Error. Never মানে এই Pod আর restart হবে না। Job ১০ সেকেন্ড অপেক্ষা করবে।
  3. দ্বিতীয় চেষ্টা। ১০ সেকেন্ড পরে একটি নতুন Pod. আগের Pod মোছেনি, তাই তার log পড়া যায়।
  4. দ্বিতীয় ব্যর্থতা। অপেক্ষা দ্বিগুণ হয়ে ২০ সেকেন্ড। এটিই exponential backoff. Kubernetes-এ এই অপেক্ষার সীমা ৬ মিনিট।
  5. তৃতীয় চেষ্টা। শেষ সুযোগ: প্রথমটি + backoffLimit-এর ২টি।
  6. Job ব্যর্থ। তৃতীয় ব্যর্থতায় সীমা শেষ। Job Failed, কারণ BackoffLimitExceeded. ৩টি Error Pod রয়ে গেছে পরে দেখার জন্য।

এবার restartPolicy: OnFailure সহ আরেকটি Job, যেটি তৃতীয় চেষ্টায় সফল হয়। এটি একটি emptyDir-এ চেষ্টার সংখ্যা জমায়:

$ kubectl get jobs
NAME                      STATUS     COMPLETIONS   DURATION   AGE
always-fails              Failed     0/1           51s        51s
nightly-report-29889810   Complete   1/1           5s         34s
third-try                 Complete   1/1           7s         51s

$ kubectl get pods -l job-name=always-fails
NAME                 READY   STATUS   RESTARTS   AGE
always-fails-jn25s   0/1     Error    0          51s
always-fails-lghzs   0/1     Error    0          18s
always-fails-q8dzx   0/1     Error    0          39s

$ kubectl get pods -l job-name=third-try
NAME              READY   STATUS      RESTARTS   AGE
third-try-zpczm   0/1     Completed   2          51s

$ kubectl logs -l job-name=third-try
try 3

পার্থক্যটি ধরুন। Never: ৩টি Pod, প্রতিটির RESTARTS ০. OnFailure: ১টি Pod, RESTARTS ২, কারণ একই Pod-এর container আবার চলেছে। nightly-report-29889810 এসেছে CronJob থেকে, যেটি পরীক্ষার জন্য প্রতি মিনিটে চলেছিল। পাতার YAML-এ সময়সূচি রাতের, 0 2 * * *, মানে ঢাকার সময় রাত ২টা। নামের সংখ্যাটি নির্ধারিত সময় থেকে তৈরি হয়। একই Job দুবার চলতে পারে, তাই কাজটি idempotent রাখুন। concurrencyPolicy: Forbid আগের কাজ চলতে থাকলে নতুনটি বাদ দেয়। timeZone না দিলে সময় controller-এর সময় অঞ্চল ধরে চলে, যা প্রায়ই UTC. Cron-এর ধারণা পর্ব ১০-এ দেখেছেন।

আরও গভীরে: Deployment controller প্রতিবার দুটি হিসাব করে: maxTotal − বর্তমান মোট দিয়ে নতুন Pod, আর মোট − minAvailable − নতুনের অনুপলব্ধ দিয়ে পুরোনো Pod সরানো। আগে পুরোনোদের মধ্যে অসুস্থ Pod সরে। Rollout-এর মাঝে replicas বদলালে সে proportional scaling করে: বাড়তি Pod সব ReplicaSet-এ অনুপাতে ভাগ করে। ReplicaSet-এর deployment.kubernetes.io/max-replicas annotation এই হিসাবের সূত্র। ধারণাটি Deployment controller-এর উৎস কোডের rolling.go অংশে আর Kubernetes-এর Deployment ডকুমেন্টেশনে পাবেন, তবে version-ভেদে খুঁটিনাটি বদলাতে পারে।

পরীক্ষা ও ইন্টারভিউতে:

  • Deployment আর ReplicaSet-এর পার্থক্য? ReplicaSet সংখ্যা ঠিক রাখে, Deployment version বদল আর rollback সামলায়।
  • replicas ১০, surge ৩, unavailable ২ হলে সীমা? সর্বোচ্চ ১৩, সর্বনিম্ন available ৮.
  • Rollout আটকে গেলে নিজে rollback হয়? না, শুধু ProgressDeadlineExceeded condition হয়।
  • Readiness আর liveness-এর পার্থক্য? Readiness traffic আটকায়, liveness container restart করে (পর্ব ৪৩)।
  • PDB কি rolling update থামায়? না, এটি শুধু eviction-এর জন্য।

১০ · AI-এর যুগে Deployment লেখা

AI কয়েক সেকেন্ডে Deployment YAML লিখে দেয়, আর সেটি প্রায়ই চলেও। সমস্যা হলো “চলে” আর “নিরাপদে বদলানো যায়” এক কথা নয়। অধিকাংশ ভুল প্রথম rollout-এ ধরা পড়ে না, পড়ে পরের আপডেটে।

AI-এর সাধারণ ভুলফলযাচাই
image: myapp:latestTemplate বদলায় না, তাই rollout-ই হয় না; rollback অর্থহীননির্দিষ্ট tag বা digest (পর্ব ৪১)
Readiness probe নেইRollout ভাঙা version-ও “সফল” দেখায়Probe আছে কি না, সেটি সত্যিই app দেখে কি না
extensions/v1beta1 বা পুরোনো APICluster প্রত্যাখ্যান করেkubectl api-resources, --dry-run=server
selector বদলে দেওয়াfield is immutableDiff দেখুন: kubectl diff -f
kubectl delete deployment দিয়ে “ঠিক” করাসব Pod চলে যায়, সেবা বন্ধআগে rollout undo ভাবুন, context আর namespace মিলিয়ে নিন
Resource limit ছাড়া, বা maxUnavailable: 100%Node চাপে পড়ে, বা rollout-এ সব Pod একসাথে যায়অঙ্ক করুন: R − unavail ভার নিতে পারে?
অস্তিত্বহীন flag বা fieldError, অথবা পুরোনো tool-এ field চুপচাপ উপেক্ষাServer dry-run, kubectl explain deployment.spec

নিচের prompt AI-কে শুধু YAML লিখতে নয়, নিজের সিদ্ধান্ত হিসাব করে দেখাতে বাধ্য করে। ফলে ভুল ধরা সহজ হয়। কোনো secret বা আসল account তথ্য prompt-এ দেবেন না।

তুমি একজন সতর্ক Kubernetes reviewer. আমার cluster-এর version ১.৩৪। নিচে আমার Deployment YAML আছে। [এখানে YAML পেস্ট করুন, secret মুছে] ১. apps/v1 ছাড়া অন্য কোনো পুরোনো API আছে কি না বলো। ২. image-এ latest বা ট্যাগহীন কিছু থাকলে দেখাও। ৩. replicas, maxSurge আর maxUnavailable থেকে সর্বোচ্চ Pod আর সর্বনিম্ন available হিসাব করে দেখাও। ৪. readiness probe, preStop আর terminationGracePeriodSeconds কি একসাথে যুক্তিসঙ্গত? কেন? ৫. image না পাওয়া গেলে কতটি Pod চালু থাকবে, হিসাব করে বলো। ৬. কোনো পুরোনো YAML পুরোটা নতুন করে লিখো না। শুধু যে লাইন বদলাতে হবে, কারণসহ দাও। ৭. যা নিশ্চিত নও, সেখানে "নিশ্চিত নই" লেখো। কোনো flag বানিয়ে লিখো না।

Production-এ পাঠানোর আগে চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Deployment সরাসরি Pod না বানিয়ে মাঝে ReplicaSet রাখে কেন? একটি স্তর কম হলে তো সহজ হতো।

ইতিহাসটি উত্তর দেয়। Kubernetes-এর শুরুর দিকে ছিল ReplicationController: সে Pod-এর সংখ্যা ঠিক রাখত। Version বদলাতে ব্যবহারকারীর ল্যাপটপে চলত kubectl rolling-update, যা একটি নতুন controller বানিয়ে দুটির সংখ্যা ধীরে ধীরে ওঠানামা করাত। সমস্যা হলো পুরো কাজটি client-এর ভেতরে চলত। ল্যাপটপ বন্ধ হলে, বা network ছিঁড়লে, আপডেট অর্ধেক অবস্থায় পড়ে থাকত, আর কোনো object-এ লেখা থাকত না যে “এটি ৪ নম্বর version-এ যাচ্ছে”।

Deployment এই কাজটি server-এ নিয়ে এলো। আপনি শুধু কাঙ্ক্ষিত অবস্থা লেখেন, controller বাকিটা করে, আর সেই অবস্থা cluster-এ সংরক্ষিত থাকে। ReplicaSet এল set-ভিত্তিক selector নিয়ে, আর Deployment আর ReplicaSet apps/v1-এ স্থিত হয় Kubernetes ১.৯-এ (২০১৭ সালের শেষে)। পুরোনো kubectl rolling-update পরে সরিয়ে দেওয়া হয়।

মাঝের স্তরের সুবিধাগুলো দেখুন:

  • এক দায়িত্ব: ReplicaSet শুধু “এই ছাঁচের N টি Pod” জানে। Version কী, সে জানে না। Deployment শুধু দুই ReplicaSet-এর সংখ্যা ঠেলে। প্রতিটি ছোট, পরীক্ষাযোগ্য reconcile loop.
  • Rollback-এর বিনামূল্যে ইতিহাস: প্রতিটি পুরোনো version একটি ফাঁকা ReplicaSet, যার মধ্যে পুরো template আছে। আলাদা database লাগেনি।
  • পুনঃব্যবহার: Undo করলে একই hash-এর ReplicaSet আবার বড় হয়, যা আমরা আসল run-এ দেখেছি।

সাধারণত ReplicaSet হাতে বানানোর দরকার হয় না। কিন্তু সমস্যার সময় kubectl get rs পড়তে পারাই আসল শক্তি, কারণ rollout-এর পুরো গল্প সেখানে লেখা। মালিকানা স্পষ্ট লেখা থাকে বলেই --cascade=orphan দিয়ে Deployment মুছেও তার Pod চালু রাখা যায়।

মূল উপলব্ধি: বড় controller-এর বদলে ছোট controller-এর শিকল বানালে প্রতিটি স্তর আলাদা বোঝা, পরীক্ষা আর debug করা যায়। আর কাজ client থেকে server-এ এলে আধা-সম্পন্ন অবস্থার ভয় কমে।

প্র ০২Kubernetes নিজে কেন zero-downtime নিশ্চিত করে না? Pod সরানোর আগে Service আপডেট হওয়া পর্যন্ত অপেক্ষা করলেই তো হতো।

কারণ Kubernetes একটি বিতরণ করা ব্যবস্থা, যেখানে কোনো একটি কেন্দ্র সবার কাজ একসাথে নিশ্চিত করে না। Pod মুছতে গেলে API server একটি object বদলায়। তারপর আলাদা আলাদা প্রোগ্রাম সেই খবর দেখে নিজের কাজ করে: kubelet Pod থামায়, EndpointSlice controller তালিকা হালনাগাদ করে, প্রতিটি node-এর kube-proxy তার নিয়ম বদলায়, আর Ingress বা cloud load balancer নিজের তালিকা বদলায়। কারও জন্য কেউ অপেক্ষা করে না। এটিই নকশা: কোনো একটি node ধীর হলে সবাই আটকে যাবে না।

“সবাই হালনাগাদ করেছে” নিশ্চিত হতে হলে প্রতিটি node আর প্রতিটি বাইরের load balancer-এর কাছ থেকে স্বীকৃতি নিতে হতো। হাজার node-এর cluster-এ একটি node অসুস্থ হলেই সব Pod-এর মুছে যাওয়া আটকে যেত। আর cloud load balancer Kubernetes-এর নিয়ন্ত্রণেই নেই। Distributed systems-এর পরিচিত trade-off: শক্ত সমন্বয় কিনলে availability দিতে হয়।

তাই সমাধানটি বাস্তববাদী এবং একটু “অসুন্দর”: preStop-এ কয়েক সেকেন্ড sleep. এটি কোনো নিশ্চয়তা নয়, শুধু “এতক্ষণে খবর পৌঁছে গেছে” বলে একটি অনুমান। আমাদের পরীক্ষায় এই অপেক্ষায় ব্যর্থ অনুরোধ ৫ থেকে ০-১ এ নেমেছে। এত সাধারণ একটি কৌশল এত ব্যবহার হয়েছে যে Kubernetes পরে সরাসরি sleep action যোগ করেছে, যা image-এ sleep ছাড়াই চলে।

App-এর দিকেও কাজ আছে। HTTP keep-alive connection খোলা থাকলে client পুরোনো Pod-কেই পাঠাতে থাকে, তাই app-এর উচিত থামার সময় Connection: close পাঠানো বা server-কে ভদ্রভাবে বন্ধ করা। Client-এর দিকে অল্প retry রাখা, আর idempotent অনুরোধের জন্য সেটি নিরাপদ রাখা, শেষ ফাঁকটুকু ঢাকে।

আরও বড় কথা, “zero-downtime” শব্দটিই সাবধানে বলতে হয়। সত্যিকার লক্ষ্য একটি পরিমাপযোগ্য SLO (পর্ব ৬৬): যেমন ৯৯.৯% অনুরোধ সফল। Rollout তার error budget-এর সামান্য খরচ।

মূল উপলব্ধি: বিতরণ করা ব্যবস্থায় “সবাই একসাথে জানল” বলে কিছু নেই। ভালো নকশা এই দেরি মেনে নিয়ে ফাঁকটি ঢাকে: readiness, preStop, graceful shutdown আর client retry মিলে।

প্র ০৩Rolling update-এর সময় দুই version একসাথে চলে। এটি কত বড় ঝুঁকি, আর ইতিহাস থেকে কী শেখার আছে?

ছবি ৪৪.১-এ দেখেছেন, rollout-এর পুরো সময় পুরোনো আর নতুন Pod একই Service-এর পেছনে। একই ব্যবহারকারীর দুটি পরপর অনুরোধ দুই version-এ যেতে পারে। Version 2 যদি একটি নতুন JSON field লেখে আর version 1 তা পড়তে না পারে, অথবা নতুন column ছাড়া database-এ version 2 ভেঙে পড়ে, তাহলে rollout “সফল” হলেও ব্যবহারকারী ভুল দেখবেন।

অসম্পূর্ণ deployment-এর সবচেয়ে আলোচিত উদাহরণগুলোর একটি Knight Capital-এর ঘটনা, ২০১২ সালের আগস্টে। মার্কিন SEC-এর ২০১৩ সালের প্রকাশিত প্রতিবেদন অনুযায়ী, নতুন trading code ৮টি server-এ পৌঁছানোর কথা ছিল, কিন্তু একটিতে পৌঁছায়নি। সেই server-এ পুরোনো, বহু বছর অব্যবহৃত code একটি পুনর্ব্যবহৃত flag-এর কারণে চালু হয়ে যায়। প্রায় ৪৫ মিনিটে বিপুল অনাকাঙ্ক্ষিত অর্ডার তৈরি হয়, আর প্রতিষ্ঠানটির ক্ষতি ৪৬০ মিলিয়ন ডলারের মতো হয়েছিল বলে প্রতিবেদনে বলা হয়। ঘটনাটি Kubernetes-এর নয়, তবে শিক্ষা একই: কোন machine-এ কোন version চলছে, তা অনুমান নয়, যাচাইয়ের বিষয়।

Deployment এখানে একটি বড় উন্নতি দেয়: get rs এক নজরে দেখায় কোন version-এ কতটি Pod. কিন্তু সামঞ্জস্য আপনাকেই নিশ্চিত করতে হয়:

  • API: নতুন field ঐচ্ছিক রাখা, পুরোনো field সরাতে দুই release অপেক্ষা।
  • Database: expand-contract: আগে column যোগ, তারপর code, শেষে পুরোনো column সরানো। এতে rollback-ও নিরাপদ।
  • Feature flag: Code পৌঁছানো আর feature চালু হওয়া আলাদা করা (পর্ব ৫৯)। আবার Knight-এর ঘটনা মনে করায় যে পুরোনো flag পরিষ্কার করতে হবে।

আর যদি সামঞ্জস্য সত্যিই অসম্ভব হয়? তখন Recreate, অথবা আলাদা environment-এ blue-green. ছোট বন্ধের সময় অনেক ক্ষেত্রে ভুল ডেটার চেয়ে সস্তা।

মূল উপলব্ধি: Rolling update Pod বদলানো সমাধান করে, version-এর সহাবস্থান নয়। প্রতিটি release-কে ধরে নিন আগের release-এর পাশে চলবে, আর rollback-এর পরেও।

প্র ০৪Deployment আটকে গেলে নিজে rollback করে না কেন? আর rollout দ্রুত করা ভালো, না ধীরে?

নিজে rollback করলে Kubernetes-এর মূল নীতি ভাঙত। আপনি spec-এ যা লিখেছেন, সেটিই সত্য। Controller যদি নিজে থেকে spec.template ফিরিয়ে দিত, তাহলে Git-এর manifest আর cluster আলাদা হয়ে যেত। GitOps tool (পর্ব ৬০) আবার সেটি ঠিক করত, আর শুরু হতো টানাটানি। তা ছাড়া সে জানে না আপনার পরিস্থিতি। আটকে যাওয়ার কারণ হতে পারে node-এ জায়গা নেই, registry সাময়িক বন্ধ, অথবা quota ফুরিয়েছে। সেগুলো ঠিক হলে rollout নিজেই এগোতে পারে। আর পুরোনো version-এ ফিরলে যদি database আগেই বদলে গিয়ে থাকে, rollback নিজেই নতুন ক্ষতি করে।

তাই নকশাটি হলো: Kubernetes তথ্য দেয় (condition, event, exit code), সিদ্ধান্ত নেয় উপরের স্তর। Pipeline rollout status ব্যর্থ হলে undo চালাতে পারে। Argo Rollouts বা Flagger-এর মতো tool মেট্রিক দেখে এগোয় বা ফেরে।

গতির প্রশ্নে ইতিহাসের একটি উদাহরণ হলো Cloudflare-এর ২০১৯ সালের জুলাইয়ের ঘটনা। তাদের প্রকাশিত postmortem অনুযায়ী, একটি WAF নিয়মের regular expression বিশ্বজুড়ে প্রায় একসাথে পৌঁছে CPU শেষ করে ফেলে, আর প্রায় আধা ঘণ্টা ব্যাপক সমস্যা হয়। নিয়মগুলো দ্রুত ছড়ানোই ছিল ব্যবস্থার গুণ, কিন্তু ভুলটিও ততটাই দ্রুত ছড়িয়েছে।

এখানে trade-off স্পষ্ট:

  • ধীর rollout: ভুল ধরার সময় বেশি, ক্ষতির ব্যাসার্ধ ছোট। কিন্তু দুই version-এর সহাবস্থান লম্বা, আর জরুরি নিরাপত্তা patch-ও ধীর।
  • দ্রুত rollout: সহাবস্থান ছোট, কিন্তু ভাঙা জিনিস সবাইকে একসাথে ছোঁয়।

কৌশলগতভাবে maxUnavailable আর minReadySeconds শুধু “কতক্ষণ দেখব” ঠিক করে, “কী দেখব” নয়। ভুল যদি error rate-এ ধরা পড়ে, Pod-এর Ready চিহ্নে নয়, তাহলে তাড়াতাড়ি বা ধীরে কোনোটিই বাঁচাবে না। তখন লাগবে metric-ভিত্তিক canary (পর্ব ৫৯)।

মূল উপলব্ধি: Kubernetes-এর কাজ হলো ঘোষণা করা অবস্থায় পৌঁছানোর চেষ্টা আর সৎভাবে জানানো। কখন হার মানতে হবে, সেটি ব্যবসার সিদ্ধান্ত, তাই উপরের স্তরে থাকে।

প্র ০৫Deployment-এর maxUnavailable আছে, তাহলে PodDisruptionBudget-এর দরকার কী? আর PDB ভুল হলে cluster upgrade কীভাবে আটকে যায়?

দুটি আলাদা কর্তৃপক্ষের আলাদা কাজ। maxUnavailable হলো app owner-এর নিয়ম, নিজের version বদলানোর সময়। PDB হলো cluster operator-এর সাথে চুক্তি: “আপনি node বদলাতে চাইলে আমার Pod-কে এই সীমার নিচে নামাবেন না।” Platform দল একসাথে শত শত Deployment-এর Pod-এর node সারায়, তারা প্রতিটি app চেনে না। PDB সেই app-এর চাহিদা একটি যন্ত্রে-পড়ার রূপে জানায়।

ধরুন ৩টি node, ৩টি Pod, প্রতিটি আলাদা node-এ। Node upgrade-এ kubectl drain প্রতিটি Pod-কে eviction পাঠায়। PDB না থাকলে দ্রুত দুটি node খালি হলে সেবা হঠাৎ ১টি Pod-এ চলে আসতে পারে। maxUnavailable: 1-এর PDB থাকলে দ্বিতীয় eviction ফেরত আসে, drain অপেক্ষা করে, বদলি Pod Ready হলে আবার চেষ্টা করে।

ভুল PDB-তে উল্টো ঘটনা ঘটে। replicas: 3 আর minAvailable: 3 হলে Allowed disruptions সবসময় ০. Drain চিরকাল অপেক্ষা করে। Managed Kubernetes-এর upgrade-এ এই সমস্যা এত পরিচিত যে provider-গুলো আলাদা ব্যবস্থা রাখে: কেউ একটি সময়ের পরে PDB উপেক্ষা করে জোর করে সরায়, কেউ অপেক্ষা করে upgrade ব্যর্থ দেখায়। আচরণ provider-ভেদে আলাদা, তাই তাদের নথি পড়ে নিন। replicas: 1-এর Deployment-এ PDB একই ফাঁদ।

আরেকটি সূক্ষ্মতা: PDB অসুস্থ Pod-কে কীভাবে গোনে। সব Pod যদি আগে থেকেই CrashLoop-এ, তাহলে বাজেট ০, আর সেই ভাঙা Pod-গুলো node থেকে সরানোও যায় না। এজন্য নতুন version-এ unhealthyPodEvictionPolicy নামের একটি সেটিং এসেছে, যা নথিতে দেখে নেবেন।

আর PDB কখনোই একটি জিনিস দেয় না: node crash, kernel panic বা zone বিভ্রাট থেকে সুরক্ষা। সেখানে লাগে একাধিক replica আর topology spread (পর্ব ৪৮)। PDB শুধু ভদ্র অনুরোধের নিয়ন্ত্রণ।

মূল উপলব্ধি: নির্ভরযোগ্যতা অনেক সময় দুই দলের মধ্যে চুক্তি। ভালো চুক্তিতে দুই পক্ষই নড়তে পারে: খুব কড়া চুক্তি নিরাপত্তা দেয় না, পুরো ব্যবস্থাকে আটকে রাখে।

অনুশীলন

খাতা-কলমে হিসাব করুন। Cluster না থাকলেও সবগুলো করা যায়।

  1. হিসাব: একটি Deployment-এ replicas ২০, maxSurge: 10%, maxUnavailable: 10%. প্রতিটি Pod ৩০ সেকেন্ডে Ready হয়, minReadySeconds: 10. (ক) সর্বোচ্চ মোট Pod আর সর্বনিম্ন available কত? (খ) rollout-এর আনুমানিক সময় কত? (গ) image না পাওয়া গেলে আটকে থাকা অবস্থায় পুরোনো আর নতুন Pod কয়টি, available কত শতাংশ?

    (ক) surge = ⌈২০ × ০.১⌉ = ২, unavail = ⌊২০ × ০.১⌋ = ২. সর্বোচ্চ মোট ২২, সর্বনিম্ন available ১৮.

    (খ) ব্যাচ ⌈২০ ÷ ৪⌉ = ৫. প্রতি ব্যাচে ৩০ + ১০ = ৪০ সেকেন্ড। T ≈ ৫ × ৪০ = ২০০ সেকেন্ড, প্রায় ৩ মিনিট ২০ সেকেন্ড।

    (গ) নতুন Pod = surge + unavail = ৪, পুরোনো ১৮, মোট ২২. Available ১৮, মানে ১৮ ÷ ২০ = ৯০%.

  2. ফল অনুমান করুন: নতুন Deployment-এ এই ক্রম চলল: (১) nginx:1.27 দিয়ে apply, (২) set image 1.28, (৩) set image 1.29, (৪) rollout undo, (৫) আবার rollout undo. প্রতিটি rollout সফল। শেষে কোন image চলবে, আর rollout history-তে কোন কোন revision থাকবে?

    Revision ১ = 1.27, ২ = 1.28, ৩ = 1.29. প্রথম undo আগের revision ২-এর template আনে, নম্বর হয় ৪ (image 1.28), আর ২ তালিকা থেকে সরে। এখন “আগের” হলো ৩. দ্বিতীয় undo তাই 1.29 ফিরিয়ে আনে, নম্বর ৫, আর ৩ সরে। শেষে image 1.29, history: ১, ৪, ৫. ভাঙা version থেকে পালাতে দুবার undo করলে ঠিক সেখানেই ফেরা যায়। এই আচরণ আমরা আসল cluster-এ একই রকম ক্রমে দেখেছি। সমাধান: --to-revision.

  3. নকশা সমালোচনা: একজন সহকর্মী AI-এর লেখা এই সেটিং এনেছেন: replicas: 2, image: shop:latest, maxUnavailable: 100%, readiness probe নেই, terminationGracePeriodSeconds: 0, আর PDB minAvailable: 2. কমপক্ষে পাঁচটি সমস্যা আর ঠিক করার উপায় বলুন।
    • latest: একই tag-এ template বদলায় না, তাই rollout হয় না, আর কোন image চলছে বোঝা যায় না। নির্দিষ্ট tag বা digest দিন।
    • maxUnavailable: 100%: দুটি Pod-ই একসাথে যেতে পারে, ভুল image-এ পুরো সেবা বন্ধ। 1 বা 0 দিন, আর surge রাখুন।
    • Readiness নেই: না-উঠা Pod-এ traffic যায়, rollout ভাঙা version-কেও সফল বলে।
    • Grace ০: Pod হঠাৎ মরে, চলমান অনুরোধ হারায়। Default ৩০ রাখুন, preStop যোগ করুন।
    • minAvailable: 2 আর replicas: 2: বাজেট ০, node drain আটকে থাকবে। maxUnavailable: 1 দিন বা replicas বাড়ান।
    • ২ replica অল্প: একটি গেলে অন্যটির ওপর পুরো ভার।
  4. ঘটনা পড়ুন: রাত ১১টায় alert এসেছে। আপনি দেখলেন: kubectl get deploy api দেখায় api 7/8 2 7 (READY, UP-TO-DATE, AVAILABLE). Deployment-এর surge আর unavailable দুটোই ১। কী ঘটেছে, আর পরপর কোন তিনটি কমান্ড চালাবেন?

    ৮ replica-র মধ্যে ৭টি available, আর মাত্র ২টি Pod নতুন version-এ। এটি আটকে যাওয়া rollout-এর চেনা ছবি: নতুন ২টি Ready হচ্ছে না, পুরোনো ৭টি সেবা দিচ্ছে। সেবা চলছে, তবে এক Pod কম।

    1. kubectl rollout status deploy/api --timeout=10s: আটকে আছে কি না।
    2. kubectl get rs আর kubectl get pods: নতুন Pod-এর অবস্থা। ImagePullBackOff, CrashLoopBackOff, না Running 0/1?
    3. kubectl describe pod <নতুন-pod>: Events-এ আসল কারণ।

    এরপর kubectl rollout history দেখে rollout undo --to-revision=N, অথবা কারণ ঠিক করে নতুন rollout.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমRolling update-এর ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. kubectl set image Deployment-এর Pod template বদলায়
  2. Deployment controller নতুন ReplicaSet বানায়
  3. নতুন ReplicaSet-এর Pod তৈরি হয়ে readiness probe পাস করে
  4. Available যথেষ্ট থাকায় পুরোনো ReplicaSet-এর desired কমে
  5. সব নতুন Pod available, পুরোনো ReplicaSet desired ০ নিয়ে থাকে

একটি Deployment-এ replicas ১০, maxSurge: 25%, maxUnavailable: 25%. Rolling update-এ সর্বোচ্চ মোট Pod আর সর্বনিম্ন available কত?

  • ১৩ আর ৭
  • ১৩ আর ৮
  • ১২ আর ৮
  • ১২ আর ৭

kubectl get deploy web-এ 3/4 2 3 দেখা গেল। Replicas ৪, surge ১, unavailable ১, আর নতুন tag registry-তে নেই। সেবার অবস্থা কী?

  • সব Pod মুছে গেছে, সেবা বন্ধ
  • Kubernetes নিজে আগের version-এ ফিরে গেছে
  • পুরোনো ৩টি Pod সেবা দিচ্ছে, rollout থেমে আছে
  • নতুন ২টি Pod সেবা দিচ্ছে

নিচের কোন বদল Deployment-এ নতুন rollout (নতুন ReplicaSet) শুরু করে?

  • Container-এর একটি environment variable বদলানো
  • replicas ৪ থেকে ৬ করা
  • revisionHistoryLimit ১০ থেকে ৫ করা
  • minReadySeconds ০ থেকে ১০ করা

progressDeadlineSeconds: 600 পেরিয়ে গেলে Deployment-এ কী হয়?

  • সব নতুন Pod মুছে যায়
  • স্বয়ংক্রিয়ভাবে আগের revision-এ ফিরে যায়
  • মোট ৬০০ সেকেন্ড পার হলেই Deployment মুছে যায়
  • Progressing condition False হয়, কারণ ProgressDeadlineExceeded

৪টি Pod, PDB minAvailable: 3. একটি Pod eviction-এ সরানো হলো, বদলি এখনো Ready নয়। এই মুহূর্তে কোনটি ঠিক?

  • দ্বিতীয় eviction-ও সফল হবে
  • দ্বিতীয় eviction প্রত্যাখ্যাত হবে, তবে rolling update তবু চলতে পারবে
  • দ্বিতীয় eviction আর rolling update দুটোই আটকে যাবে
  • Node crash করলেও PDB Pod-গুলোকে রক্ষা করবে

প্রতি রাতে একবার চলে শেষ হওয়া একটি রিপোর্ট আর প্রতিটি node-এ একটি করে log agent চালাতে কোন object দুটি মানানসই?

  • দুটোই Deployment
  • StatefulSet আর Deployment
  • CronJob আর DaemonSet
  • Job আর ReplicaSet

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে একসাথে:

মনে রাখুন

  1. Deployment → ReplicaSet → Pod: মালিকানা ownerReferences-এ লেখা, আর শুধু spec.template বদলালে rollout হয়।
  2. সর্বোচ্চ মোট = R + maxSurge, সর্বনিম্ন available = R − maxUnavailable; শতাংশে surge উপরে, unavailable নিচে round.
  3. ভুল image সেবা ভাঙে না, rollout আটকে দেয়। progressDeadlineSeconds শুধু condition বদলায়, rollback আপনার কাজ: rollout undo --to-revision=N.
  4. Zero-downtime = সৎ readiness + graceful shutdown + preStop + যথেষ্ট grace + দুই version-এর সামঞ্জস্য।
  5. PDB শুধু eviction-এর জন্য, rollout-এর জন্য নয়। শেষ হওয়ার কাজে Job/CronJob, প্রতি node-এ DaemonSet, স্থায়ী পরিচয়ে StatefulSet.

পরের পর্বে: Pod আসে, যায়, IP বদলায়। তাহলে অন্য Pod আর ব্যবহারকারী তাদের খুঁজে পাবে কীভাবে? উত্তর Service আর cluster DNS.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান