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

Helm, Kustomize আর operator — package, overlay আর CRD

Helm, Kustomize and Operators — Packaging, Overlays and CRDs

Taught in Bangla, this lesson tackles repeated YAML across environments, writing a Helm chart with templates and values, install, upgrade and rollback through stored releases, template traps and what each dry-run checks, trusting third-party charts, Kustomize base, overlay and patch, choosing between Helm and Kustomize, CRDs that extend the API, and when an operator is not worth writing.

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

  • নিজের ছোট chart লেখা, render করা, install, upgrade আর rollback করা
  • Helm-এর release কোথায় জমা থাকে, আর সেখানে কী কী ঝুঁকি
  • Template-এর চুপচাপ ভুলগুলো চেনা, আর কোন --dry-run আসলে কী যাচাই করে
  • Kustomize-এর base, overlay আর patch দিয়ে dev ও prod বানানো
  • CRD তৈরি করে নিজের resource চালানো, আর operator কখন লিখবেন না তা ঠিক করা

১ · একই YAML তিন জায়গায় — সমস্যাটি কোথায়

আগের পর্বগুলোতে আপনি Deployment, Service, ConfigMap লিখেছেন। এবার ধরুন একটি কাল্পনিক বইয়ের দোকান “বইঘর”-এর ওয়েবসাইট তিন জায়গায় চলবে: dev, staging আর prod. প্রতিটি জায়গায় YAML প্রায় একই, পার্থক্য সামান্য:

ঘরdevstagingprod
replicas123
Image tag1.27-alpine1.27.5-alpine1.27.5-alpine
Memory limit48Mi48Mi64Mi
পাতার লেখাBoighor devBoighor stageBoighor live

সহজ পথ হলো ফোল্ডার তিনটি, YAML তিন কপি। প্রথম সপ্তাহে এটি চলে। তারপর কেউ prod-এর probe ঠিক করলেন, কিন্তু staging-এ করলেন না। ছয় মাস পরে তিনটি ফাইল আলাদা হয়ে গেছে, আর কোন পার্থক্য ইচ্ছাকৃত আর কোনটি ভুল, তা কেউ জানে না। পর্ব ৩৪-এর misconfiguration-এর বড় উৎস এটিই।

বিয়েবাড়ির ক্যাটারিং ভাবুন। রেসিপি খাতা একটিই, কিন্তু আজ ৫০ জন, কাল ৫০০ জন। প্রতি অনুষ্ঠানের জন্য কেউ পুরো খাতা নতুন করে লেখে না। সমাধান দুই রকম। এক, রেসিপিতে ফাঁকা ঘর রাখা: “চাল ___ কেজি”, অনুষ্ঠানের দিন ঘর পূরণ। এটিই Helm. দুই, মূল রেসিপি যেমন আছে তেমনই রাখা, আর আলাদা চিরকুটে লেখা: “আজ ঝাল কম, লোক দ্বিগুণ”। এটিই Kustomize.

অনেকে প্রথমে sed বা একটি ছোট script দিয়ে লেখা বদলানোর কথা ভাবেন। ছোট কাজে এটি চলে। কিন্তু তখন নিয়মগুলো script-এর ভেতরে লুকিয়ে যায়, আর কোনো ভুল বদল ধরার কেউ থাকে না। এই পর্বের টুলগুলো সেই একই কাজ করে, তবে নিয়মগুলো পরিষ্কার, পরীক্ষাযোগ্য আর দলের সবার চেনা ধরনে।

আউটপুট সম্পর্কে: এই পর্বের প্রতিটি transcript একটি local single-node cluster (Kubernetes ১.৩৪), Helm ৩.১৯ আর kubectl-এর ভেতরের Kustomize ৫.৭-এ আসলে চালানো। Namespace ছিল abcl-kz. আপনার run-এ নাম, বয়স, IP আর সময় আলাদা হবে। Browser-এ cluster চালানো যায় না, তাই transcript-গুলো পড়ার জন্য, আর simulator-গুলো নিজে বদলে চালানোর জন্য।

২ · Helm-এর chart — template আর values

Helm হলো Kubernetes-এর package manager. apt যেমন একটি package নামায় আর বসায়, Helm তেমনি একটি chart বসায়। Chart হলো একটি ফোল্ডার, যেখানে template, default মান আর পরিচয়পত্র থাকে। একটি chart একই cluster-এ একাধিকবার বসানো যায়। প্রতিটি বসানোকে বলে release. apt-এর মতো “একটি মেশিনে একটি কপি” নয়, এই পার্থক্যটি মনে রাখুন।

আমরা নিজেই একটি ছোট chart লিখেছি। ফাইলগুলো:

boighor/
├── Chart.yaml            # name, chart version, app version
├── values.yaml           # default values
└── templates/
    ├── _helpers.tpl      # reusable snippets, renders nothing itself
    ├── configmap.yaml
    ├── deployment.yaml
    ├── service.yaml
    └── NOTES.txt         # message printed after install
replicaCount: 1
image:
  repository: nginx
  tag: 1.27-alpine
greeting: Boighor dev
resources:
  requests: {cpu: 10m, memory: 16Mi}
  limits: {memory: 48Mi}
# templates/configmap.yaml
data:
  index.html: {{ .Values.greeting | quote }}

# templates/deployment.yaml
metadata:
  name: {{ .Release.Name }}-web
spec:
  replicas: {{ .Values.replicaCount }}
  ...
      containers:
        - name: web
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

{{ }}-এর ভেতরের লেখা Go template ভাষা। .Values মানে values, .Release.Name মানে release-এর নাম। | quote হলো pipe, যা আগের মানকে quote-এর ভেতরে রাখে। nindent 12 নতুন লাইনে ১২ space ভেতরে লেখে। Values কয়েক স্তর থেকে আসে, আর পরেরটি জেতে:

chart-এর values.yaml → -f prod.yaml → --set key=value
ডানেরটি বামেরটির ওপরে বসে

Chart + values → manifest

Helm cluster-এ পাঠায় সাধারণ YAML, template নয়

Chart আর values থেকে manifest তৈরি templates/ফাঁকা ঘরসহ YAML values.yamldefault মান -f, --setenv-এর মান Template enginehelm template এখানেই থামে Manifestসাধারণ Kubernetes YAML API serverDeployment, Service… Release Secretrevision-এর কপি
ছবি ৫০.১ — Template engine শুধু লেখা বানায়। Kubernetes template কী, তা জানেই না।

Helm-এর শুরু ২০১৫ সালে, Deis নামের একটি স্টার্টআপে। Helm ৩ বেরোয় ২০১৯ সালে, আর ২০২০ সালে এটি CNCF-এর graduated project হয়। Kustomize Google-এ শুরু হয় ২০১৮ সালে, আর ২০১৯ সালে Kubernetes ১.১৪ থেকে kubectl-এর -k flag-এ ঢোকে। Helm ৪ বেরিয়েছে ২০২৫ সালের শেষে, আর এই পর্বের সব আউটপুট Helm ৩.১৯-এর। ঘোষিত সূচি অনুযায়ী Helm ৩-এর security fix ১১ নভেম্বর ২০২৬ পর্যন্ত। Helm ৪-এ নতুন release ডিফল্টে server-side apply ব্যবহার করে (বদল মেলানোর কাজ করে API server নিজে), আর কয়েকটি flag-এর নাম বদলেছে। তাই নিজের version-এর docs দেখে নিন।

একটি খুঁটিনাটি কাজে লাগবে। _helpers.tpl-এ আমরা label-এর একটি টুকরো define করেছি, আর প্রতিটি template সেটি include করে। ফলে label একবারই লিখতে হয়। ফাইলের নাম _ দিয়ে শুরু হলে Helm সেটিকে আলাদা manifest হিসেবে ছাপে না। Deployment-এ আরেকটি ছোট কৌশল আছে: ConfigMap-এর hash একটি annotation-এ রাখা। ConfigMap বদলালে চলমান pod নিজে থেকে restart হয় না। কিন্তু annotation বদলালে pod template বদলায়, তাই rolling update শুরু হয়। আমাদের run-এ greeting বদলানোর পরে ঠিক তাই হয়েছিল, pod-এর নাম boi-web-564657b5-… থেকে boi-web-77966c88f8-… হয়েছিল।

      annotations:
        checksum/page: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum | trunc 12 }}

helm template cluster-এ কিছু না পাঠিয়ে শুধু ফল দেখায়। তাই কোনো chart হাতে পেলে এটিই প্রথম কমান্ড। Prod-এর মান দিয়ে দেখি:

$ cat values-prod.yaml
replicaCount: 3
greeting: Boighor live

$ helm lint boighor
==> Linting boighor
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed

$ helm template boi ./boighor -f values-prod.yaml \
    --set image.tag=1.27.5-alpine | grep -E 'replicas:|image:|index.html'
  index.html: "Boighor live"
  replicas: 3
          image: "nginx:1.27.5-alpine"

replicas আর লেখা এসেছে ফাইল থেকে, image.tag এসেছে --set থেকে, আর repository রয়ে গেছে chart-এর default. নিচের simulator ঠিক এই merge আর render করে।

নিজে বদলে দেখুন: ছোট template renderer

// A tiny Helm-style renderer. Change the values, --set or template.
const chartValues = { replicaCount: 1, image: { tag: "1.27-alpine" }, greeting: "Boighor dev" };
const envFiles = { dev: {}, prod: { replicaCount: 3, greeting: "Boighor live" } };
const setFlags = { dev: {}, prod: { "image.tag": "1.27.5-alpine" } };
const template = [
  "replicas: {{ .Values.replicaCount }}",
  'image: "nginx:{{ .Values.image.tag }}"',
  "page: {{ .Values.greeting | quote }}",
  "memory: {{ .Values.memoryLimit }}",
];

const isObj = (x) => x !== null && typeof x === "object";
function merge(low, high) {            // the later source wins, key by key
  const out = { ...low };
  for (const k in high) out[k] = isObj(out[k]) && isObj(high[k]) ? merge(out[k], high[k]) : high[k];
  return out;
}
function setPath(obj, path, v) {       // "a.b" = 1 becomes { a: { b: 1 } }
  return merge(obj, path.split(".").reverse().reduce((acc, k) => ({ [k]: acc }), v));
}
const lookup = (obj, path) => path.split(".").reduce((o, k) => (o == null ? undefined : o[k]), obj);
function render(line, values) {
  return line.replace(/\{\{\s*\.Values\.([\w.]+)\s*(\|\s*quote\s*)?\}\}/g, (_, path, quote) => {
    const v = lookup(values, path);
    if (v === undefined) console.log("  (note: " + path + " is missing, rendered as empty, no error)");
    return quote ? JSON.stringify(String(v)) : (v ?? "");
  });
}
for (const env of ["dev", "prod"]) {
  let values = merge(chartValues, envFiles[env]);
  for (const [path, v] of Object.entries(setFlags[env])) values = setPath(values, path, v);
  console.log("--- " + env + " ---");
  for (const line of template) console.log(render(line, values));
}

আউটপুটে prod-এর অংশ: replicas: 3, image: "nginx:1.27.5-alpine", page: "Boighor live". শেষ লাইনটি খেয়াল করুন: memoryLimit কোথাও নেই, তবু কোনো error নেই, শুধু memory: খালি। আসল Helm-ও এই কাজই করে, যা পরের অংশগুলোতে দেখবেন। এবার envFiles.prod-এ memoryLimit: "64Mi" যোগ করে, তারপর setFlags.prod-এ "replicaCount": 5 দিয়ে চালান। স্তরগুলো কে কাকে ঢাকছে, পরিষ্কার হবে।

৩ · Install থেকে rollback — release একটি জমা রাখা state

kubectl apply কিছু মনে রাখে না। Helm মনে রাখে। প্রতিটি install বা upgrade একটি নতুন revision তৈরি করে, আর তার পুরো manifest একটি Secret-এ জমা রাখে। ডিফল্ট storage driver এটিই, আর Secret থাকে release-এর namespace-এ।

$ helm install boi ./boighor -n abcl-kz --wait --timeout 60s
NAME: boi
NAMESPACE: abcl-kz
STATUS: deployed
REVISION: 1
NOTES:
boi is up with 1 replica(s).

$ kubectl get secrets -n abcl-kz
NAME                        TYPE                 DATA   AGE
sh.helm.release.v1.boi.v1   helm.sh/release.v1   1      32s

$ kubectl get secret sh.helm.release.v1.boi.v1 -n abcl-kz \
    -o jsonpath='{.metadata.labels}'
{"modifiedAt":"1793387662","name":"boi","owner":"helm","status":"deployed","version":"1"}

Secret-এর ভেতরের লেখা base64 দুইবার আর gzip একবার খুললে একটি JSON পাওয়া গেল, যার key: name, info, chart, manifest, version, namespace. অর্থাৎ পুরো chart আর render করা manifest সেখানে আছে। --set দিলে তার মানও থাকে। Encryption নয়, শুধু encoding. তাই values-এ password দিলে, ওই namespace-এর Secret পড়ার অনুমতি যাঁর আছে, তিনি তা পড়তে পারবেন। ব্যবস্থা পর্ব ৩৩-এর secret manager আর পর্ব ৪৯-এর RBAC-এ।

এবার নিচের stepper-এ পুরো জীবনচক্র দেখুন। আমরা ইচ্ছা করে একটি ভুল image tag দিয়ে upgrade ব্যর্থ করেছি।

Release boi-এর অবস্থা
Revision
Status
Image tag
Pod
পাতার লেখা
  1. Install. Revision ১ তৈরি হলো। --wait থাকায় Helm pod ready হওয়া পর্যন্ত অপেক্ষা করল। এটি না দিলে Helm manifest পাঠিয়েই deployed বলে দেয়, app চলছে কি না তা না দেখেই।
  2. Upgrade. নতুন মান দুটি নিয়ে revision ২. Revision ১ এখন superseded. পাতার লেখা বদলেছে, আর image একই।
  3. ভুল tag. Revision ৩ তৈরি হলো, কিন্তু ২৫ সেকেন্ডে pod ready হয়নি। Helm failed লিখল আর exit code 1 দিল। লক্ষ করুন, ব্যর্থ revision-ও একটি সংখ্যা খরচ করে। Helm নিজে থেকে পিছিয়ে যায়নি, কারণ আমরা তা বলিনি।
  4. সাইট কিন্তু চলছে। পর্ব ৪৪-এর rolling update নতুন pod ready না হওয়া পর্যন্ত পুরোনো দুটি pod রেখে দিয়েছে। ব্যবহারকারী কিছুই টের পাননি। Helm-এর failed মানে সাইট বন্ধ নয়। আবার deployed মানেও সব ঠিক নয়। দুটি আলাদা তথ্য।
  5. Rollback. সংখ্যা পিছিয়ে ২-এ গেল না, এগিয়ে ৪ হলো। ইতিহাস মোছা হয় না, revision ২-এর কপিটি নতুন revision হিসেবে বসে। Pod-এর নাম দেখুন: d528q আর tfxt7 একই রয়েছে, কারণ template আগের অবস্থায় ফিরতেই পুরোনো ReplicaSet-ই আবার ঠিক হয়ে গেল।
  6. হাতে বদলালে? kubectl scale দিয়ে 3 করা হলো, chart-এ আছে 2. পরের upgrade-এ 2-এ ফিরে এলো। Helm ৩ পুরোনো manifest, নতুন manifest আর live অবস্থা তিনটিই মেলায়। একে বলে three-way merge. তাই হাতের বদল পরের upgrade-এ হারিয়ে যেতে পারে।
$ helm history boi -n abcl-kz
REVISION  STATUS      CHART          DESCRIPTION
1         superseded  boighor-0.1.0  Install complete
2         superseded  boighor-0.1.0  Upgrade complete
3         failed      boighor-0.1.0  Upgrade "boi" failed: context deadline exceeded
4         deployed    boighor-0.1.0  Rollback to 2

$ helm get values boi -n abcl-kz
USER-SUPPLIED VALUES:
greeting: Boighor v2
replicaCount: 2

$ helm uninstall boi -n abcl-kz
release "boi" uninstalled

$ helm history boi -n abcl-kz
Error: release: not found

history-এর UPDATED আর APP VERSION কলাম ছোট রাখার জন্য বাদ দেওয়া হয়েছে। এই তালিকা rollback-এর পরের অবস্থা দেখায়, তাই ধাপ ৬-এর revision ৫ এখানে নেই। uninstall সব object-এর সাথে সব revision-ও মুছে দেয়, তাই এরপর আর rollback নেই। --keep-history দিলে শেষ revision uninstalled অবস্থায় থেকে যায়। Upgrade-এর ইতিহাসও সীমিত: --history-max-এর default ১০। আর --atomic দিলে ব্যর্থতায় Helm নিজেই পিছিয়ে যায়, CI-তে যা সুবিধার। Helm ৪-এ এই flag-এর নতুন নাম --rollback-on-failure.

--reuse-values নিয়ে একটু সাবধান থাকুন। এটি আগের revision-এর values রেখে দেয়, তাই নতুন chart version-এ নতুন default এলে তা হয়তো পৌঁছাবে না। সবচেয়ে পরিষ্কার পথ হলো, প্রতিটি environment-এর values একটি ফাইলে Git-এ রাখা, আর প্রতিবার -f দিয়ে সেটি দেওয়া। তাহলে --set-এ দেওয়া একবারের বদল কারও স্মৃতিতে আটকে থাকে না, আর cluster-এর অবস্থা Git থেকেই বোঝা যায়।

৪ · Template-এর ফাঁদ আর কোন dry-run কী দেখে

Go template YAML বোঝে না। সে শুধু লেখা বসায়। তাই ভুল ধরা পড়ে অনেক পরে, কখনো ধরাই পড়ে না। নিচের প্রতিটি সারি আসলে চালিয়ে পাওয়া ফল:

যা লিখলেনযা হলোকে ধরল
বানান ভুল: .Values.replicacountreplicas: খালি, আর Kubernetes default 1 ধরেকেউ না: lint, lint --strict, dry-run সব পার
values-এ greeting: noপাতায় লেখা "false"কেউ না, YAML-এ no মানে boolean
values-এ tag: 1.10nginx:1.1, কারণ এটি সংখ্যা হয়ে গেছেকেউ না, শুধু image pull ব্যর্থ হবে
values-এ tag: "1.10"nginx:1.10এটিই সঠিক রূপ
nindent ছাড়া toYamlYAML parse error … line 34helm template আর lint
--set replicaCount=threereplicas: threeশুধু API server, install-এর সময়

শেষ সারিটি সবচেয়ে শিক্ষণীয়। Helm-এর তিনটি যাচাই পার হলো, তবু object-টি ভুল:

$ helm install boi2 ./boighor --set replicaCount=three --dry-run=client | grep -E 'STATUS|replicas:'
STATUS: pending-install
  replicas: three

$ helm install boi2 ./boighor --set replicaCount=three --dry-run=server | grep -E 'STATUS|replicas:'
STATUS: pending-install
  replicas: three

$ helm template boi ./boighor --set replicaCount=three > three.yaml
$ kubectl apply --dry-run=server -f three.yaml -n abcl-kz
configmap/boi-page created (server dry run)
service/boi-web created (server dry run)
Error from server (BadRequest): error when creating "three.yaml": Deployment in
version "v1" cannot be handled as a Deployment: json: cannot unmarshal string into
Go struct field DeploymentSpec.spec.replicas of type int32

নামের মিল দেখে ভুল করবেন না। Helm-এর dry-run-এ server মানে মূলত template-এর lookup cluster থেকে তথ্য আনতে পারবে। এই run-এ Helm object-গুলো API server-এর schema দিয়ে যাচাই করায়নি, তাই ভুল ধরা পড়েনি। helm template --validate-ও এখানে exit 0 দিয়েছিল। kubectl apply --dry-run=server সত্যিই API server-কে দিয়ে যাচাই করায় এবং ভুলটি ধরেছে। Helm-এর version-ভেদে আচরণ বদলাতে পারে, তাই নিজের version-এ পরীক্ষা করে নিন।

আমাদের নিরাপদ অভ্যাস তাই চার ধাপের:

  • helm lint আর helm template … | less দিয়ে চোখে পড়া।
  • helm template … | kubectl apply --dry-run=server -f - দিয়ে API-কে যাচাই করানো।
  • required function দিয়ে জরুরি মান বাধ্যতামূলক করা। মান না থাকলে render-ই ব্যর্থ হয়, আসল run-এ এসেছিল execution error at … greeting is needed.
  • values.schema.json দিয়ে values-এর type বেঁধে দেওয়া। তাহলে three Helm-এই আটকাবে, আর ভুল নামের key-ও ধরা যাবে।

Chart যে key পড়ে না, values-এ তা দিলে Helm কিছুই বলে না। --set replicas=5 লিখলেন, chart পড়ে replicaCount: কোনো error ছাড়াই 1টি replica চলবে। Upgrade-এর পরে helm get manifest বা kubectl get দিয়ে ফল মিলিয়ে দেখুন।

৫ · Chart কোথা থেকে নেবেন, আর কতটা বিশ্বাস করবেন

বেশিরভাগ সময় আপনি chart লিখবেন না, অন্যের chart বসাবেন। উৎস দুই রকম: পুরোনো ধরনের HTTP chart repository (helm repo add), আর container registry-তে রাখা OCI artifact (oci://…). Chart খুঁজতে Artifact Hub একটি জনপ্রিয় তালিকা, তবে সেটি নিজে কোনো chart-এর নিরাপত্তার নিশ্চয়তা দেয় না।

Chart হলো এমন কোড, যা আপনার cluster-এ আপনার অনুমতি নিয়ে চলে। এতে ClusterRole থাকতে পারে, hook হিসেবে Job চলতে পারে, CRD বসতে পারে। পর্ব ৪১-এর supply chain-এর সব প্রশ্ন এখানেও প্রযোজ্য। বসানোর আগে:

# example-repo and example-chart are placeholders
helm repo add example-repo https://charts.example.com
helm search repo example-repo/example-chart --versions

# Read before you install; always pin an exact version
helm show values example-repo/example-chart --version 1.2.3 > values.yaml
helm template demo example-repo/example-chart --version 1.2.3 -f values.yaml \
  | grep -E '^kind:' | sort | uniq -c

# Charts stored in a registry can be pulled by exact version
helm pull oci://registry.example.com/charts/example-chart --version 1.2.3

grep '^kind:' এক লাইনে বলে দেয় chart কী কী বানাবে। ClusterRoleBinding, DaemonSet বা অপরিচিত webhook দেখলে থেমে পড়ুন। এই ব্লকের কমান্ডগুলো placeholder নাম দিয়ে লেখা, আমরা চালাইনি। Chart signing-এর জন্য Helm-এর --verify (provenance ফাইল) আছে, আর OCI chart-এ cosign-ও ব্যবহার করা হয়।

প্রশ্নযা দেখবেন
কে বানিয়েছেন?Software-এর নিজের দল, নাকি অচেনা কেউ
রক্ষণাবেক্ষণ চলছে?শেষ release কবে, নিরাপত্তা-সমস্যার উত্তর মেলে কি না
কী অনুমতি চায়?ClusterRole, wildcard, host access, privileged pod
কোন image টানে?পর্ব ৪১-এর মতো tag নয়, digest; নিজের registry-তে কপি রাখা ভালো
Install-এর সময় কী চলে?Hook-এর Job, আর সেগুলো কী করে

Chart-এর crds/ ফোল্ডারের CRD প্রথম install-এ বসে, কিন্তু Helm ৩ upgrade-এ সেগুলো বদলায় না, uninstall-এও মোছে না। এটি ইচ্ছাকৃত, কারণ CRD মুছলে সব custom resource মুছে যায় (অংশ ৮-এ আসল run). নতুন chart version-এ CRD বদলালে তা আলাদা করে দেখতে হবে।

৬ · Kustomize — template ছাড়া base আর overlay

Kustomize উল্টো পথে হাঁটে। কোনো {{ }} নেই। Base ফোল্ডারে থাকে পুরোপুরি বৈধ Kubernetes YAML, যা নিজে থেকেই apply করা যায়। প্রতিটি environment-এর overlay শুধু বলে কী বদলাবে। সব জায়গার কেন্দ্রে একটি করে kustomization.yaml. এটি kubectl-এর ভেতরেই আছে, তাই আলাদা কিছু বসাতে হয় না।

একটি base, দুটি overlay, দুটি ফল

Base বদলায় না; প্রতিটি overlay নিজের পার্থক্য যোগ করে

Kustomize-এর base আর overlay base/deployment.yaml, service.yamlreplicas: 1, সাধারণ YAML overlays/devdev- prefix, env=devপাতার লেখা বদল overlays/prodprod- prefix, env=prod3 replica, নতুন tag, 64Mi dev-web1 replicanginx:1.27-alpine prod-web3 replicanginx:1.27.5-alpine
ছবি ৫০.২ — kubectl kustomize overlays/prod তিনটি স্তর মিলিয়ে সাধারণ YAML ছাপে।
# overlays/prod/kustomization.yaml
namespace: abcl-kz
namePrefix: prod-
labels:
  - pairs:
      env: prod
    includeSelectors: true
resources:
  - ../../base
images:
  - name: nginx
    newTag: 1.27.5-alpine
patches:
  - path: more-replicas.yaml
  - target:
      kind: Deployment
      name: web
    patch: |-
      - op: add
        path: /spec/template/metadata/annotations
        value:
          tier: prod
configMapGenerator:
  - name: page
    behavior: replace
    literals:
      - index.html=Boighor live
# overlays/prod/more-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: web
          resources:
            limits:
              memory: 64Mi

দুই রকম patch এখানে আছে। more-replicas.yaml হলো strategic merge patch: সাধারণ YAML-এর একটি টুকরো, যেখানে শুধু বদলানো ঘর থাকে। লক্ষ করুন, containers list পুরোটা লিখতে হয়নি, name: web মিলিয়ে সে ঠিক container-টি খুঁজে নেয়। দ্বিতীয়টি JSON patch: path ধরে নির্দিষ্ট কাজ (add, replace, remove). নিচের stepper-এ web Deployment-টি কীভাবে prod-web হলো, দেখুন।

Basebase/deployment.yaml থেকে
metadata:
  name: web
spec:
  replicas: 1
  ...
      - image: nginx:1.27-alpine
        resources:
          limits:
            memory: 48Mi
      volumes:
      - configMap:
          name: page
নাম আর labelnamespace, namePrefix, labels
metadata:
  labels:
    app: boighor
    env: prod
  name: prod-web
  namespace: abcl-kz
spec:
  replicas: 1
  selector:
    matchLabels:
      app: boighor
      env: prod
Patchmore-replicas.yaml মিলল
spec:
  replicas: 3
  ...
      - image: nginx:1.27-alpine
        name: web
        resources:
          limits:
            memory: 64Mi
          requests:
            cpu: 10m       # base থেকে অক্ষত
            memory: 16Mi   # base থেকে অক্ষত
Image আর JSON patchimages, op: add
  template:
    metadata:
      annotations:
        tier: prod
    ...
      - image: nginx:1.27.5-alpine
ConfigMap generatorনামের শেষে content hash
kind: ConfigMap
metadata:
  name: prod-page-5m9hf2tk56
data:
  index.html: Boighor live
# Deployment-এর reference-ও বদলেছে:
      volumes:
      - configMap:
          name: prod-page-5m9hf2tk56
Cluster-এapply করার পরে
$ kubectl apply -k overlays/dev
$ kubectl apply -k overlays/prod
$ kubectl get deploy -n abcl-kz
NAME       READY   UP-TO-DATE   AVAILABLE
dev-web    1/1     1            1
prod-web   3/3     3            3
$ kubectl exec deploy/prod-web -n abcl-kz -- wget -qO- localhost
Boighor live
  1. শুরু। Base-এ নাম web, এক replica, 48Mi. এটি নিজে থেকেই একটি পূর্ণ, বৈধ বর্ণনা।
  2. নাম আর label. namePrefix নাম বদলাল, namespace ঘর বসাল। includeSelectors: true থাকায় env: prod শুধু label-এ নয়, selector-এও ঢুকল।
  3. Strategic merge patch. replicas 3 হলো আর limit 64Mi. Patch-এ requests ছিল না, তাই base-এর মান অক্ষত।
  4. Image আর JSON patch. images ট্রান্সফর্মার nginx নামের image-এর tag বদলাল, YAML ঘাঁটতে হলো না। JSON patch একটি annotation যোগ করল।
  5. Generator. লেখা থেকে ConfigMap তৈরি হলো, নামের শেষে লেখার hash. Deployment-এর reference-ও নিজে থেকে বদলাল। লেখা বদলালে নাম বদলাবে, তাই pod নতুন করে উঠবে।
  6. আসল ফল। দুটি environment একসাথে চলছে, যা পাতার লেখা দিয়ে মিলিয়ে দেখা হলো। Base একবারও বদলায়নি। কিন্তু প্রথম চেষ্টা এত মসৃণ ছিল না, পরের অংশে দেখুন।

আমাদের নিজের ভুল। প্রথম run-এ overlay-তে env label ছিল না। দুটি Deployment-ই ready, অথচ prod-web-এ ঢুকে পাতায় পেলাম Boighor dev! কারণ namePrefix নাম বদলায়, selector নয়। একই namespace-এ দুই environment-এর selector ছিল একটিই, app=boighor.

# Before the fix: both Services select all 4 pods
$ kubectl get endpointslices -n abcl-kz \
    -o custom-columns='NAME:.metadata.name,ENDPOINTS:.endpoints[*].addresses[0]'
NAME             ENDPOINTS
dev-web-pbjnj    10.1.0.75,10.1.0.74,10.1.0.76,10.1.0.73
prod-web-5qvxm   10.1.0.75,10.1.0.74,10.1.0.76,10.1.0.73

# After env label with includeSelectors: true
NAME             ENDPOINTS
dev-web-rvxv9    10.1.0.77
prod-web-psnd7   10.1.0.79,10.1.0.80,10.1.0.78

কোনো কমান্ড error দেয়নি, অথচ dev-এর traffic prod-এ যাচ্ছিল। বাস্তবে environment আলাদা namespace বা আলাদা cluster-এ রাখলে এই সংঘর্ষ হয় না। তবে একটি কথা মনে রাখুন: Deployment-এর spec.selector তৈরির পরে বদলানো যায় না। তাই আমাদের পুরোনো Deployment মুছে নতুন করে apply করতে হয়েছিল।

Generator-এর আরেকটি দিক আসল run-এ দেখা গেল। লেখা বদলে আবার apply করতেই prod-page-mcd77bgdht তৈরি হলো, rolling update চলল, কিন্তু পুরোনো prod-page-5m9hf2tk56 মুছল না। Rollback-এর জন্য এটি ভালো, তবে সময়ের সাথে পুরোনো ConfigMap জমে। পরে পরিষ্কার করতে হবে। বদল আগে দেখতে চাইলে kubectl diff -k overlays/prod চালান।

নিজে বদলে দেখুন: overlay merge

// A Kustomize-style overlay: the base stays untouched. Try name: "wbe".
const base = { kind: "Deployment", metadata: { name: "web" },
  spec: { replicas: 1, containers: [
    { name: "web", image: "nginx:1.27-alpine", limits: { memory: "48Mi" } },
    { name: "log", image: "alpine:3.20", debug: true } ] } };
const overlay = { namePrefix: "prod-", newTag: "1.27.5-alpine",
  patch: { metadata: { name: "web" }, spec: { replicas: 3, containers: [
    { name: "web", limits: { memory: "64Mi" } }, { name: "log", debug: null } ] } } };
const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
function merge(a, b) {
  if (Array.isArray(a) && Array.isArray(b)) {       // lists of named items: match by name
    const out = a.map((item) => { const p = b.find((x) => x.name === item.name); return p ? merge(item, p) : item; });
    return out.concat(b.filter((x) => !a.some((item) => item.name === x.name)));
  }
  if (!isObj(a) || !isObj(b)) return b;             // a plain value: the patch wins
  const out = { ...a };
  for (const k in b) { if (b[k] === null) delete out[k]; else out[k] = k in a ? merge(a[k], b[k]) : b[k]; }
  return out;
}
function build(res, ov) {
  if (ov.patch.metadata.name !== res.metadata.name)
    return console.log("error: no resource matches patch for " + ov.patch.metadata.name);
  const out = merge(res, ov.patch);
  out.metadata = { ...out.metadata, name: ov.namePrefix + res.metadata.name };
  out.spec.containers = out.spec.containers.map((c) =>
    c.image.startsWith("nginx:") ? { ...c, image: "nginx:" + ov.newTag } : c);
  return out;
}
const show = (o) => { console.log(o.metadata.name, "replicas=" + o.spec.replicas);
  for (const c of o.spec.containers) console.log("  ", c.name, c.image, JSON.stringify({ limits: c.limits, debug: c.debug })); };
show(base);
const final = build(base, overlay);
if (final) show(final);
console.log("base replicas after build:", base.spec.replicas);

আউটপুটে প্রথমে web replicas=1, তারপর prod-web replicas=3, যেখানে web container পেয়েছে nginx:1.27.5-alpine আর 64Mi. log container অক্ষত, শুধু debug: null সেই key মুছেছে। Strategic merge patch-ও null দিয়ে key মোছে। শেষ লাইন base replicas after build: 1 দেখায়, base বদলায়নি। এবার patch-এর নাম "wbe" করে চালান। তখন error: no resource matches patch for wbe আসবে। আসল Kustomize-ও আমাদের run-এ বলেছিল no resource matches strategic merge patch. Helm-এর চুপচাপ ফাঁকা ঘরের সাথে পার্থক্যটি দেখুন।

৭ · Helm নাকি Kustomize — আর দুটো একসাথে

প্রশ্নHelmKustomize
পদ্ধতিTemplate + valuesBase + patch
Source ফাইলTemplate, একা বৈধ YAML নয়সাধারণ YAML, একা apply করা যায়
ইতিহাস ও rollbackআছে, release হিসেবেনেই; Git আর Deployment-এর rollback ভরসা
বিতরণVersion-সহ chart, repositoryGit-এ ফোল্ডার
শর্ত আর loopআছে, তাই জটিলতাও বাড়েইচ্ছা করেই নেই
ভালো কোথায়অন্যকে দেওয়া software, অনেক optionনিজের app, অল্প পার্থক্য
অসুবিধাTemplate পড়া কঠিন, চুপচাপ ভুলগভীর overlay-র শিকল বোঝা কঠিন

একটি সহজ নিয়ম: অন্যকে দেওয়ার জন্য বানালে, বা ব্যবহারকারীরা অনেক ধরনে সাজাবেন, Helm ভালো। নিজের দলের একটি app, আর দুই-তিনটি environment-এ সামান্য পার্থক্য হলে Kustomize-ই যথেষ্ট, আর সেখানে YAML-ও পরিষ্কার পড়া যায়। যে দলে chart-এর template শর্ত আর loop-এ ভরে পড়াই যায় না, সেখানে template একটি দুর্বল প্রোগ্রামিং ভাষায় পরিণত হয়েছে। সেটি থামার সংকেত।

এ দুটি প্রতিদ্বন্দ্বী নয়। প্রচলিত ধরন হলো, অন্যের software নিন Helm chart হিসেবে, আর নিজের প্রতিষ্ঠানের ছোট বদল করুন Kustomize দিয়ে, যাতে chart-এর ভেতরে হাত দিতে না হয়। ধরুন chart-এ securityContext-এর কোনো value নেই:

$ helm template boi ./boighor > combo/rendered.yaml
$ cat combo/kustomization.yaml
namespace: abcl-kz
resources:
  - rendered.yaml
patches:
  - target:
      kind: Deployment
      name: boi-web
    patch: |-
      - op: replace
        path: /spec/replicas
        value: 2
      - op: add
        path: /spec/template/spec/containers/0/securityContext
        value:
          allowPrivilegeEscalation: false

$ kubectl kustomize combo | grep -E '^kind|replicas|allowPriv'
kind: ConfigMap
kind: Service
kind: Deployment
  replicas: 2
          allowPrivilegeEscalation: false

$ kubectl apply -k combo --dry-run=server
configmap/boi-page created (server dry run)
service/boi-web created (server dry run)
deployment.apps/boi-web created (server dry run)

এখানে Helm শুধু render করেছে, তাই release আর revision নেই। ইতিহাস রাখতে চাইলে Helm-এর post-renderer ব্যবহার করা যায়: render-এর পরে, পাঠানোর আগে, manifest একটি প্রোগ্রামের ভেতর দিয়ে যায় (Helm ৪-এ এটি plugin হিসেবে দিতে হয়)। Kustomize-এরও helmCharts নামে একটি ব্যবস্থা আছে, যা --enable-helm flag চায়। Argo CD আর Flux-এর মতো GitOps টুল দুটোকেই বোঝে, যা পর্ব ৬০-এ আসবে।

৮ · CRD — Kubernetes API-কে নতুন শব্দ শেখানো

এতক্ষণ আমরা Kubernetes-এর জানা জিনিসই সাজিয়েছি: Deployment, Service, ConfigMap. কিন্তু ধরুন আপনার দল বলতে চায়, “orders database-এর প্রতি রাতে backup চাই, শেষ ৭টি রাখবে।” Kubernetes-এ Backup বলে কিছু নেই। CustomResourceDefinition (CRD) দিয়ে শব্দটি যোগ করা যায়। তারপর সে অন্য যেকোনো object-এর মতোই kubectl, RBAC, watch আর etcd-এর সুবিধা পায়।

পৌরসভার ফরম ভাবুন। জন্মনিবন্ধন আর ট্রেড লাইসেন্সের ফরম আগে থেকেই আছে। নতুন একটি সেবা চালু হলে আগে নতুন ফরমের নকশা ছাপাতে হয়: কোন ঘর আছে, কোনটি বাধ্যতামূলক। এটিই CRD. তারপর মানুষ ফরম পূরণ করে জমা দেন। এটিই custom resource. কিন্তু ফরম জমা পড়লেই কাজ হয়ে যায় না, টেবিলে একজন কর্মচারী লাগে। সেই কর্মচারীই operator.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: backups.abcl-kz.example.com   # must be plural.group
spec:
  group: abcl-kz.example.com
  scope: Namespaced
  names:
    kind: Backup
    plural: backups
    singular: backup
    shortNames: [bk]
  versions:
    - name: v1alpha1
      served: true
      storage: true
      subresources:
        status: {}
      additionalPrinterColumns:
        - {name: Database, type: string, jsonPath: .spec.database}
        - {name: Schedule, type: string, jsonPath: .spec.schedule}
        - {name: Keep, type: integer, jsonPath: .spec.keep}
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: [database, schedule]
              properties:
                database: {type: string}
                schedule: {type: string}
                keep:
                  type: integer
                  minimum: 1
                  default: 7
            status:
              type: object
              properties:
                lastBackup: {type: string}
$ kubectl get backups -n abcl-kz
error: the server doesn't have a resource type "backups"

$ kubectl apply -f backup-crd.yaml
customresourcedefinition.apiextensions.k8s.io/backups.abcl-kz.example.com created

$ kubectl api-resources --api-group=abcl-kz.example.com
NAME      SHORTNAMES   APIVERSION                     NAMESPACED   KIND
backups   bk           abcl-kz.example.com/v1alpha1   true         Backup

$ cat daily.yaml
apiVersion: abcl-kz.example.com/v1alpha1
kind: Backup
metadata:
  name: orders-daily
spec:
  database: orders
  schedule: "0 2 * * *"

$ kubectl apply -f daily.yaml -n abcl-kz
backup.abcl-kz.example.com/orders-daily created

$ kubectl get backups -n abcl-kz
NAME           DATABASE   SCHEDULE    KEEP
orders-daily   orders     0 2 * * *   7

$ kubectl get all -n abcl-kz
No resources found in abcl-kz namespace.

Resource তৈরি হলো, keep না দিলেও schema-র default 7 বসে গেল, আর kubectl get-এর কলাম এলো আমাদের দেওয়া সংজ্ঞা থেকে। কিন্তু শেষ লাইন দেখুন: আর কিছুই তৈরি হয়নি। কোনো Pod নেই, কোনো Job নেই। CRD শুধু একটি তথ্যের ছক। একটি ছোট বাস্তব অসুবিধাও পেলাম: CRD Established হওয়ার সাথে সাথে পাঠানো Backup resource mapping not found error দিয়েছিল, প্রায় এক সেকেন্ড পরে ঠিক হয়। Script-এ এই ফাঁকটুকুর জন্য অপেক্ষা বা retry রাখুন।

Kubernetes-এ শুরুতে এই সুবিধার নাম ছিল ThirdPartyResource. ২০১৭ সালে ১.৭ version-এ CRD তার জায়গা নেয়। apiextensions.k8s.io/v1 GA হয় ২০১৯ সালে ১.১৬-তে, আর পুরোনো v1beta1 সরে যায় ১.২২-এ। তাই AI বা পুরোনো ব্লগে v1beta1-এর CRD দেখলে সেটি আজকের cluster-এ চলবে না।

Version-এর নামেও অর্থ আছে। v1alpha1 বলে, এই API এখনো বদলাতে পারে। প্রতিটি CRD-তে ঠিক একটি version-এ storage: true থাকে, অর্থাৎ etcd-তে সেই রূপে জমা হয়। আর subresources.status চালু থাকায় status আলাদা পথে লেখা হয়। আমাদের run-এ kubectl patch backup orders-daily --subresource=status দিয়ে হাতে lastBackup বসানো গেল, যে কাজটি আসলে controller-এর।

Schema-র কাজ এবার দেখি। ভুল ফরম API server-ই ফিরিয়ে দেয়, কারও লেখা কোড ছাড়াই। Owner সম্পর্ক আর CRD মোছার ফলও দেখি:

# keep: 0, and schedule is missing
$ kubectl apply -f bad.yaml -n abcl-kz
The Backup "orders-bad" is invalid:
* spec.keep: Invalid value: 0: spec.keep in body should be greater than or equal to 1
* spec.schedule: Required value

# A CronJob with ownerReferences pointing at the Backup
$ kubectl get cronjob orders-daily-job -n abcl-kz \
    -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}'
Backup/orders-daily

$ kubectl delete cronjob orders-daily-job -n abcl-kz
cronjob.batch "orders-daily-job" deleted from abcl-kz namespace
$ kubectl get cronjob -n abcl-kz
No resources found in abcl-kz namespace.

# Created again, then the owner is deleted
$ kubectl delete backup orders-daily -n abcl-kz
$ kubectl get cronjob -n abcl-kz     # about 16 s later
No resources found in abcl-kz namespace.

# With one Backup still present, delete the CRD itself
$ kubectl delete crd backups.abcl-kz.example.com
customresourcedefinition.apiextensions.k8s.io "backups.abcl-kz.example.com" deleted
$ kubectl get backups -n abcl-kz
Error from server (NotFound): Unable to list "abcl-kz.example.com/v1alpha1, Resource=backups":
the server could not find the requested resource (get backups.abcl-kz.example.com)

তিনটি শিক্ষা। এক, CronJob হাতে মুছলে কেউ ফিরিয়ে আনেনি, কারণ controller নেই। দুই, ownerReference থাকায় মালিক মুছতেই Kubernetes-এর garbage collector সন্তানকে মুছল। এই run-এ প্রায় ১৬ সেকেন্ড লেগেছিল, তাই তৎক্ষণাৎ হবে ধরে নেবেন না। তিন, CRD মুছলে তার সব custom resource মুছে যায়। Production-এ এই একটি kubectl delete crd সব certificate বা database-এর সংজ্ঞা মুছে দিতে পারে।

৯ · Operator — নিজের type-এর reconcile loop

পর্ব ৪২-এ দেখেছেন, Kubernetes-এর মূল ধারণা হলো reconciliation loop: কাঙ্ক্ষিত অবস্থা পড়া, আসল অবস্থা দেখা, পার্থক্য দূর করা, তারপর আবার দেখা। Deployment controller এটিই করে। Operator হলো একই ধারণা, আপনার নিজের type-এর জন্য: একটি CRD আর একটি controller, যা সেই CRD দেখে। CoreOS ২০১৬ সালে “operator” নামটি জনপ্রিয় করে। ভাবনাটি ছিল, একজন অভিজ্ঞ operations প্রকৌশলীর জ্ঞান কোডে লিখে রাখা।

আমাদের Backup-এর কোনো আসল controller নেই। নিচের stepper আর simulator একটি কাল্পনিক controller-এর সিমুলেশন, আসল run নয়।

Backup orders-daily (কাল্পনিক controller)
Tick
কাঙ্ক্ষিত schedule
আসল CronJob
কাজের সারি
status
  1. নতুন Backup. Controller খবর পেল, আসল অবস্থা দেখল, CronJob নেই। সে একটি বানাল আর তাতে ownerReference বসাল।
  2. মিলে গেছে। কাঙ্ক্ষিত আর আসল এক। কোনো কাজ নেই। একই reconcile বারবার চললেও কিছু বদলায় না, একে বলে idempotent. Status-এ Ready লেখা হলো।
  3. কেউ CronJob মুছলেন। Controller নিজের সন্তানদেরও watch করে, তাই খবর এলো, আর মালিক orders-daily-এর নাম কাজের সারিতে উঠল। লক্ষ করুন, সারিতে শুধু নাম ওঠে, “কী ঘটেছে” তা নয়।
  4. মেরামত। Reconcile আবার আসল অবস্থা দেখল, CronJob নেই, তাই আবার বানাল। হাতে মুছলে আগের অংশে কেউ ফেরায়নি, এখানে ফেরাল। পার্থক্য শুধু controller.
  5. Spec বদল। কেউ Backup-এ সময় 0 3 করলেন। Controller CronJob বদলে দিল। মানুষ শুধু কাঙ্ক্ষিত অবস্থা বলেন, পথ বলে না।
  6. মোছা। Backup মুছলে ownerReference-এর কারণে সন্তানও গেল। Cluster-এর বাইরের জিনিস, যেমন object storage-এর ফাইল, মুছতে হলে লাগে finalizer, যা কাজ শেষ না হওয়া পর্যন্ত মোছা আটকে রাখে।

আসল controller-এর ভেতরের কাঠামো খুবই ছোট। Go-র controller-runtime-এ একটি Reconcile function থাকে, যা কাজের সারি থেকে একটি নাম পায়। কাঠামোটি মোটামুটি এরকম, এটি চালানোর কোড নয়:

# Pseudocode, not a real program
reconcile(name):
    backup = get Backup name
    if backup is gone: return                   # owner GC handles children
    want = cronJobFrom(backup.spec)             # with ownerReference
    have = get CronJob for this backup
    if have is missing: create want
    else if have differs from want: update have
    set backup.status                           # never touch spec here
    on any error: return it, so the queue retries with backoff

লক্ষ করুন, function-টি জানে না কেন ডাকা হলো। সে শুধু তুলনা করে। Error হলে সারি একটু পরে আবার চেষ্টা করে, আর চেষ্টার মাঝের সময় ধীরে ধীরে বাড়ে।

নিচের simulator-এ আরেকটি কঠিন পরিস্থিতি আছে: controller কয়েক ঘণ্টা বন্ধ, আর সেই সময়ে দুটি ঘটনা ঘটে গেল।

// A reconcile loop for a fictional Backup resource, over simulated hours.
const spec = { everyHours: 3, keep: 2 };           // desired state, as in the Backup
const cluster = { cronJob: null, copies: [] };     // actual state in this toy world
const script = {                                   // what happens at which hour
  4: () => { cluster.cronJob = null; return "someone deletes the CronJob"; },
  7: () => { spec.keep = 1; return "Backup spec changes: keep=1"; },
  9: () => { cluster.cronJob = null; return "CronJob deleted while controller is down"; },
};
const controllerDown = new Set([7, 8, 9]);         // no reconcile in these hours

function reconcile() {
  const actions = [];
  if (!cluster.cronJob) {
    cluster.cronJob = { every: spec.everyHours };
    actions.push("create CronJob");
  } else if (cluster.cronJob.every !== spec.everyHours) {
    cluster.cronJob.every = spec.everyHours;
    actions.push("update CronJob");
  }
  while (cluster.copies.length > spec.keep) actions.push("prune " + cluster.copies.shift());
  return actions;
}
for (let hour = 0; hour <= 12; hour++) {
  const event = script[hour] ? script[hour]() : "";
  const down = controllerDown.has(hour);
  const actions = down ? [] : reconcile();
  if (cluster.cronJob && hour > 0 && hour % cluster.cronJob.every === 0)
    cluster.copies.push("copy-" + String(hour).padStart(2, "0"));
  console.log("h" + String(hour).padStart(2, "0"),
    down ? "[down]" : "[up]  ",
    "cron=" + (cluster.cronJob ? "yes" : "no "),
    "copies=[" + cluster.copies.join(",") + "]",
    event && "| " + event,
    actions.length ? "| " + actions.join("; ") : "");
}

আউটপুটের মূল লাইন h04 … someone deletes the CronJob | create CronJob, অর্থাৎ controller চালু থাকলে একই ঘণ্টায় মেরামত। h07 থেকে h09 controller বন্ধ: spec বদলেছে, CronJob-ও মুছেছে, কিন্তু কেউ সাড়া দেয়নি। h10 [up] … create CronJob; prune copy-03: ফিরে এসে সে ঘটনাগুলো খোঁজেনি, শুধু আসল অবস্থা দেখে দুটি কাজই করেছে। এটিই level-triggered নকশা। শেষে h12-এ [copy-06,copy-12]: copy দুটি, keep 1, কারণ নতুন copy reconcile-এর পরে তৈরি হয়েছে। পরের tick-এ ঠিক হবে। একে eventual consistency বলে। controllerDown-এ বড় পরিসর দিয়ে চালিয়ে দেখুন, h09-এর পরে কী হয়।

বাস্তবের operator আর তাদের পরিপক্বতা

ProjectCustom resource (উদাহরণ)যা করে
cert-managerCertificate, IssuerTLS certificate আনে, Secret-এ রাখে, মেয়াদের আগে নবায়ন করে
Prometheus OperatorPrometheus, ServiceMonitorMonitoring stack চালায়, পর্ব ৬৩-এর বিষয়
CloudNativePGClusterPostgreSQL replica, failover আর backup
StrimziKafkaKubernetes-এ Kafka cluster চালায়

Resource-এর নাম version-ভেদে বদলাতে পারে, তাই প্রতিটি project-এর নিজের docs দেখুন। সব operator সমান পরিণত নয়। Operator Framework পাঁচটি capability level বলে: ১) সাধারণ install, ২) নিরবচ্ছিন্ন upgrade, ৩) পূর্ণ জীবনচক্র, যেমন backup আর recovery, ৪) গভীর পর্যবেক্ষণ, ৫) স্বয়ংক্রিয় চালনা, যেমন autoscaling আর auto-tuning নিজে থেকে করা। “Operator” লেখা থাকলেই backup আর failover আছে, এমন নয়। শুধু install করা operator আসলে একটি chart-এর চেয়ে বেশি কিছু নয়।

ঝুঁকি। Operator প্রায়ই cluster-জোড়া অনুমতি নিয়ে চলে, তাই এর একটি bug বা চুরি যাওয়া token বহু namespace ছোঁয়। একই bug সব instance-এ একসাথে লাগে। Operator থেমে গেলে চলমান app সাধারণত চলতে থাকে, কিন্তু মেরামত, নবায়ন আর failover থেমে যায়, যা চোখে পড়ে দেরিতে। Finalizer আটকে গেলে namespace মোছাও আটকে থাকতে পারে। ভরসা না থাকলে finalizer জোর করে খালি করবেন না, এতে বাইরের জিনিস অনাথ হয়ে থাকে।

কখন operator লিখবেন না

Kubebuilder আর Operator SDK দিয়ে শুরু করা সহজ, তাই লোভ হয়। কিন্তু একবার লিখলে আপনি একটি distributed system-এর controller-এর মালিক: upgrade, CRD version, test, on-call সব আপনার। প্রশ্ন করুন:

  • একটি CronJob, Job বা Helm chart দিয়ে হয়ে যায়? তাহলে সেটিই ব্যবহার করুন। Stateless web app-এর জন্য প্রায় কখনোই operator লাগে না।
  • কাজটি চলমানভাবে দেখাশোনার, নাকি একবার বসানোর? শুধু বসানো হলে chart-ই যথেষ্ট।
  • ভালো একটি operator আগে থেকেই আছে? তার level, রক্ষণাবেক্ষণ আর অনুমতি দেখুন। Cloud-এর managed সেবাও একটি বিকল্প (পর্ব ২২).
  • দলে কে এটি দুই বছর পরে সামলাবেন?

আরও গভীরে: Controller সাধারণত controller-runtime-এর informer দিয়ে API server-কে watch করে, আর কাজের সারিতে নাম জমায়, তাই একই object-এর অনেক ঘটনা একটি reconcile-এ মিশে যায়। CRD-র schema-র ওপরে CEL rule (x-kubernetes-validations) দিয়ে ঘরগুলোর পারস্পরিক নিয়ম, আর conversion webhook দিয়ে একাধিক API version চালানো যায়। Status আলাদা subresource হওয়ায় ব্যবহারকারী spec, আর controller status লেখে, আর metadata.generation আর status.observedGeneration মিলিয়ে বোঝা যায় controller সর্বশেষ spec দেখেছে কি না। CRD-তে না কুলালে নিজের API server জোড়ার পথ API Aggregation-ও আছে।

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

  • Helm chart আর release-এর পার্থক্য? Chart হলো package; release হলো cluster-এ তার একটি বসানো instance, নিজের revision ইতিহাসসহ।
  • Helm release কোথায় থাকে? ডিফল্টভাবে release-এর namespace-এর Secret-এ, প্রতিটি revision আলাদা Secret.
  • Rollback করলে revision কত হয়? নতুন একটি সংখ্যা। ইতিহাস পিছিয়ে যায় না।
  • Helm বনাম Kustomize? Template ও packaging বনাম সাধারণ YAML-এর ওপরে patch. একসাথেও চলে।
  • CRD আর operator-এর পার্থক্য? CRD শুধু API-তে নতুন type যোগ করে; operator সেই type দেখে কাজ করে।

১০ · AI-এর যুগে chart, overlay আর operator

AI চোখের পলকে একটি Helm chart লিখে দিতে পারে, আর সেটি দেখতেও সুন্দর হয়। কিন্তু অংশ ৪ দেখিয়েছে, এখানে অনেক ভুল চুপচাপ পার হয়ে যায়। “চলছে” দেখে ভরসা করা তাই বিশেষ করে বিপজ্জনক।

AI-এর সাধারণ ভুলকীভাবে ধরবেন
Chart-এ নেই এমন values key বা বানানো flaghelm show values, helm <cmd> --help, render করা ফল
image.tag: latest, version ছাড়া chartনির্দিষ্ট tag বা digest, --version বাঁধা
Values-এ বা template-এ passwordSecret manager; Git-এ secret scan
ClusterRole-এ *, বা cluster-admingrep '^kind:' আর RBAC পড়া, least privilege
সরানো API version, যেমন CRD-র v1beta1kubectl api-resources, server dry-run
Service LoadBalancer বা 0.0.0.0/0 খুলে দেওয়াRendered manifest-এ Service আর Ingress পড়া
“আটকে গেলে” delete crd বা finalizer খালি করাআগে কী মুছবে তা জানা, backup, মানুষের অনুমোদন
ছোট কাজে operator লিখে দেওয়াআগে CronJob বা chart দিয়ে হয় কি না প্রশ্ন করা

AI-কে chart যাচাই করতে দিন, লেখার সাথে সাথে বসাতে নয়। Prompt-এ rendered আউটপুট দিন, আর secret সরিয়ে নিন।

আমি Kubernetes 1.34-এ নিচের Helm chart-এর rendered manifest বসাতে যাচ্ছি (helm template দিয়ে পাওয়া, secret সরানো): [এখানে manifest পেস্ট করুন] ১. প্রতিটি kind কী করছে আর কেন দরকার, সহজ বাংলায় বলো। ২. সরানো API version, latest tag, resource limit না থাকা, cluster-wide অনুমতি বা খোলা network আছে কি না দেখাও। ৩. কোনো flag বা values key বানিয়ে বলো না; নিশ্চিত না হলে "docs-এ যাচাই করুন" লেখো। ৪. শেষে আমাকে চালানোর জন্য শুধু read-only বা dry-run কমান্ড দাও, delete বা apply নয়।

AI-এর দেওয়া chart বা manifest হাতে পেলে

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

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

প্র ০১Helm ২-এর Tiller কেন বাদ দিতে হলো? একটি সার্ভার কম হওয়ায় আসলে কী বদলাল?

Helm শুরু হয় ২০১৫ সালে Deis-এ, পরে Google-এর একটি প্রকল্পের সাথে মিশে Helm ২ হয়। তখন cluster-এ Tiller নামে একটি সার্ভার চলত। Client chart পাঠাত, আর Tiller সেটি বসাত ও release-এর হিসাব রাখত। তখনকার Kubernetes-এ RBAC পরিণত ছিল না, তাই সার্ভারের এই নকশা স্বাভাবিক ছিল।

RBAC চালু হওয়ার পরে সমস্যা ধরা পড়ল। Tiller-কে প্রায়ই বড় অনুমতি দেওয়া হতো, আর Helm client-এর পরিচয় সে আলাদা করে দেখত না। ফলে ছোট অনুমতির একজন ব্যবহারকারীও Tiller-এর মাধ্যমে বড় কাজ করিয়ে নিতে পারতেন। একে বলে confused deputy সমস্যা, পর্ব ৪৯-এর least privilege-এর উল্টো ছবি।

২০১৯ সালের Helm ৩ Tiller বাদ দিল। এখন Helm নিছক একটি client, যা আপনার নিজের kubeconfig-এর পরিচয়ে API server-এর সাথে কথা বলে। অনুমতি তাই সরাসরি RBAC-ই ঠিক করে। Release-এর হিসাব গেল release-এর namespace-এর Secret-এ। আর দুই-দিকের বদলে three-way merge এলো, যা হাতের বদল দেখতে পায়। আমাদের run-এ kubectl scale-এর বদল পরের upgrade-এ ঠিক হয়ে গিয়েছিল।

দাম কী? Helm-এর কোনো চলমান প্রক্রিয়া নেই, তাই সে পরে আর দেখে না। Drift ধরতে হলে আপনাকে আবার upgrade চালাতে হবে, অথবা Argo CD বা Flux-এর মতো টুল লাগবে। Helm ৪-ও একই রকম client, তবে নতুন release-এ তার apply করার পদ্ধতি আলাদা (server-side apply), তাই নিজের version-এর আচরণ docs-এ দেখে নিন।

মূল উপলব্ধি: নিরাপত্তায় মধ্যস্থতাকারী সার্ভার প্রায়ই পরিচয়কে ঝাপসা করে। পরিচয় যত সরাসরি, অনুমতির হিসাব তত সহজ।

প্র ০২YAML-কে লেখা হিসেবে template করা কি মূলগতভাবে ভুল নকশা? বিকল্পগুলো কী?

অনেকের সমালোচনা ঠিক এখানেই। Go template জানে না সে YAML লিখছে। তাই indentation হাতে সামলাতে হয় (nindent 12), আর গঠনের ভুল ধরা পড়ে পরে। আমাদের run-এ greeting: no হয়ে গেল "false", কারণ YAML ১.১-এর পুরোনো নিয়মে no মানে মিথ্যা (boolean). দেশের কোড NO থেকে এটি “Norway problem” নামে পরিচিত। tag: 1.10 হলো সংখ্যা 1.1, আর একই লেখা --set-এ দিলে আমাদের run-এ 1.10-ই থাকল। অর্থাৎ একই মান দুই পথে দুই রকম!

বিকল্প কয়েক দিকে গেছে:

  • Template নয়, গঠনের ওপরে কাজ: Kustomize YAML-কে object হিসেবে পড়ে, তাই indentation ভাঙে না। বিনিময়ে শর্ত বা loop নেই।
  • Configuration ভাষা: Jsonnet, CUE আর KCL-এর মতো ভাষায় type আর function আছে। শক্তিশালী, তবে একটি নতুন ভাষা শিখতে হয়।
  • সাধারণ প্রোগ্রামিং ভাষা: cdk8s-এর মতো টুলে TypeScript বা Python থেকে manifest তৈরি হয়। পরীক্ষা লেখা সহজ, কিন্তু যেকোনো কোড ঘটনা ঘটাতে পারে, তাই ফল আগে থেকে বোঝা কঠিন।

তবু Helm টিকে আছে, কারণ বাস্তবে নকশার সৌন্দর্যের চেয়ে বিতরণ বড়। Version-সহ package, হাজার chart আর চেনা কমান্ড: এই ব্যবস্থা ভাঙা কঠিন। values.schema.json, required আর CI-তে server dry-run অনেক ফাঁদ বন্ধ করে।

মূল উপলব্ধি: নকশার ত্রুটি জানা মানে টুল ফেলে দেওয়া নয়। ত্রুটি কোথায় জানলে আপনি ঠিক সেখানেই পাহারা বসাতে পারবেন।

প্র ০৩Helm release cluster-এর ভেতরে, Terraform state বাইরে। State কোথায় রাখা ভালো?

দুটি টুলকেই মনে রাখতে হয় “আমি শেষবার কী বসিয়েছিলাম”। না হলে পুরোনো জিনিস মোছা বা পার্থক্য বের করা যায় না। পর্ব ৫৩-এ Terraform state আসবে, যা সাধারণত object storage-এর মতো বাইরের backend-এ থাকে। Helm রাখে যে cluster-এ বসাচ্ছে, সেখানেই।

Cluster-এ রাখার সুবিধা: আলাদা storage লাগে না। Release-এর সাথে state একই জায়গায়, আর Kubernetes-এর RBAC-ই পাহারা দেয়। Namespace মুছলে হিসাবও মুছে যায়, যা ছোট পরীক্ষায় পরিষ্কার।

অসুবিধা: Cluster হারালে ইতিহাসও গেল। তখন rollback নেই, Git থেকে আবার বসাতে হবে। Secret-এ পুরো manifest আর --set-এর মান থাকে, আর সেটি শুধু base64 আর gzip. আমরা নিজে তা খুলে দেখেছি। তাই Secret পড়ার অনুমতি মানে values পড়ার অনুমতি। আবার Secret-এর আকারের সীমা আছে, etcd-তে সাধারণত এক MiB-র আশেপাশে, তাই বিশাল chart সমস্যা করতে পারে। এজন্য Helm-এর SQL storage driver-ও আছে। --history-max না থাকলে অনেক revision etcd ভরাত।

GitOps-এর দৃষ্টিভঙ্গি: পর্ব ৬০-এ দেখবেন, সেখানে সত্যের উৎস Git, cluster নয়। Argo CD chart render করে নিজে apply করে, আর Helm release তৈরিই নাও হতে পারে; Flux-এর ধরন আলাদা। তখন “rollback” মানে Git-এ commit ফেরানো।

মূল উপলব্ধি: প্রতিটি declarative টুলের একটি state আছে। প্রশ্ন হলো, সেটি কোথায়, কে পড়তে পারে, হারালে কী হবে, আর সত্যের আসল উৎস কোনটি।

প্র ০৪Controller ঘটনায় সাড়া দেয়, নাকি অবস্থা দেখে? Edge আর level-triggered-এর পার্থক্য কেন এত গুরুত্বপূর্ণ?

ধরুন একটি সিস্টেম প্রতিটি ঘটনার জন্য একটি কাজ করে: “CronJob মুছেছে, তাই একটি বানাতে হবে।” একে বলে edge-triggered. ইলেকট্রনিক্স থেকে আসা শব্দ: সংকেতের বদলের মুহূর্তে সাড়া দেওয়া। এর দুর্বলতা হলো, বার্তা হারালে কাজও হারাল। Controller restart হলো, network ছিঁড়ল, বা একই ঘটনা দুবার এলো, তাহলে হিসাব গোলমাল।

Level-triggered সিস্টেম বলে: “এখনকার অবস্থা কী, আর কী হওয়া উচিত?” ঘটনা শুধু একটি ইঙ্গিত যে আবার দেখা দরকার। আমাদের simulator-এ controller তিন ঘণ্টা বন্ধ ছিল, আর সে দুটি ঘটনা দেখেনি। ফিরে এসে এক reconcile-এই দুটি ঠিক হলো। ঘটনার তালিকা মিলিয়ে এমনটি পেতে হলে অনেক জটিলতা লাগত।

Kubernetes-এর নকশায় এই চিন্তা গভীর। Burns, Grant, Oppenheimer, Brewer আর Wilkes-এর “Borg, Omega, and Kubernetes” (২০১৬) নিবন্ধে এই শিক্ষার আলোচনা আছে: ছোট, স্বাধীন controller, যারা শেয়ার করা অবস্থা দেখে নিজের কাজ করে। কোনো কেন্দ্রীয় অর্কেস্ট্রা নেই। একজন ব্যর্থ হলেও বাকিরা চলে, আর সে ফিরে এসে গুছিয়ে নেয়।

এর দাবিও আছে। Reconcile idempotent হতে হবে, কারণ সে বারবার চলবে। বাইরের API ডাকলে দুবার ডাকাও নিরাপদ হতে হবে, যেমন একটি নির্দিষ্ট নামে resource তৈরি করে আগে আছে কি না দেখা। “Backup নিন” ধরনের একবারের আদেশ এই ছাঁচে অস্বস্তিকর, তাই সাধারণত প্রতি কাজের জন্য আলাদা নামের একটি resource বানানো হয়।

মূল উপলব্ধি: ঘটনা হারাতে পারে, অবস্থা হারায় না। যে সিস্টেম অবস্থা থেকে নিজেকে গুছিয়ে নিতে পারে, সে ব্যর্থতার পরে নিজে নিজে সেরে ওঠে।

প্র ০৫Database চালাতে operator, নাকি cloud-এর managed database?

পর্ব ২২-এর managed database-এ provider failover, backup, patch সামলায়, আর আপনি ঘণ্টা ধরে বিল দেন। PostgreSQL-এর জন্য CloudNativePG-এর মতো operator-ও replica, failover আর backup করে, তবে তা চলে আপনার cluster-এ। Spec দেখতে দুটোই সহজ, তাই ভুল ধারণা হয় যে দায়িত্বও সমান।

Operator-এর পক্ষে: যেখানে managed সেবা নেই, যেমন নিজের data center বা কঠোর data residency-র জায়গায়, সেখানে এটি বড় সমাধান। সব environment-এ একই ধরন পাওয়া যায়, খরচও অনেক সময় কম দেখায়, আর একটি provider-এ বাঁধা পড়ার ভয় কমে।

Managed-এর পক্ষে: রাত তিনটায় disk ভরে গেলে কে উঠবেন? Operator-এর সিদ্ধান্ত ভুল হলে, যেমন ভুল node-কে primary বানালে, উদ্ধার করবে কে? Operator নিজেই upgrade লাগে, আর তার সাথে Kubernetes-এরও upgrade আছে। Storage-এর আচরণ, node drain আর backup থেকে ফেরার মহড়া সবই আপনার। মোট খরচের হিসাবে মানুষের সময় ধরতে হবে।

বিশ্বাসের প্রশ্ন: অংশ ৫-এর মতোই operator-ও অন্যের কোড, আর এটি পুরো database সামলায়। ২০২৫ সালে একটি জনপ্রিয় chart ও image catalog-এর (Bitnami) বিনামূল্যের বিতরণ-নিয়ম বদলে যাওয়ায় অনেক দলকে দ্রুত পথ বদলাতে হয়েছিল বলে জানা যায়। শিক্ষাটি একটি প্রতিষ্ঠানকে নিয়ে নয়: যার ওপর দাঁড়িয়ে আছেন, সে বদলে গেলে আপনার পরিকল্পনা কী? নির্দিষ্ট version-এর কপি নিজের registry-তে রাখা তাই বুদ্ধিমানের কাজ।

একটি যুক্তিসঙ্গত সূচনা: cloud-এ থাকলে আর দলে গভীর database দক্ষতা না থাকলে managed. আর operator বেছে নিলে আগে backup থেকে সত্যিকারের restore আর failover-এর মহড়া দিন।

মূল উপলব্ধি: Operator কাজ স্বয়ংক্রিয় করে, দায়িত্ব নয়। ব্যর্থতার সময় কে জবাব দেবেন, সেই উত্তরই সিদ্ধান্ত ঠিক করে।

অনুশীলন

Cluster ছাড়াও এগুলো করা যায়। নিজের কম্পিউটারে চালালে আলাদা namespace ব্যবহার করুন আর শেষে তা মুছে দিন। Cloud cluster-এ খরচ হতে পারে।

  1. Values আর revision-এর হিসাব: Chart-এ replicaCount: 1, a.yaml-এ 2, b.yaml-এ 3. (ক) helm template x . -f a.yaml -f b.yaml --set replicaCount=5-এ replicas কত? (খ) --set বাদ দিলে? (গ) -f b.yaml -f a.yaml করলে? (ঘ) একটি release-এ install, upgrade, ব্যর্থ upgrade, rollback, upgrade পরপর চললে শেষ revision কত?

    (ক) ৫, --set সবার ওপরে। (খ) ৩, একাধিক -f-এ পরেরটি জেতে। (গ) ২, ক্রম উল্টে গেছে। (ঘ) ৫: install ১, upgrade ২, ব্যর্থ upgrade ৩, rollback ৪, upgrade ৫. ব্যর্থ upgrade আর rollback দুটোই একটি করে সংখ্যা নেয়।

  2. আউটপুট অনুমান করুন: Template-এ page: {{ .Values.greeting | quote }} আর image: "nginx:{{ .Values.tag }}". values.yaml-এ আছে greeting: no আর tag: 1.10. helm template কী ছাপবে? কোনো error আসবে? কীভাবে ঠিক করবেন?

    page: "false" আর image: "nginx:1.1". কোনো error নেই। আমাদের আসল run-এ ঠিক এই ফলই এসেছে। YAML পড়ার সময়েই no boolean আর 1.10 সংখ্যা হয়ে যায়, template পরে যা পায় তা-ই ছাপে। ঠিক করতে values-এ quote দিন: greeting: "no", tag: "1.10". স্থায়ী সমাধান হিসেবে values.schema.json-এ দুটিকেই string বলুন।

  3. একটি chart সমালোচনা করুন: একজন সহকর্মী এই values.yaml Git-এ দিলেন: image: {tag: latest}, dbPassword: "Secret123", rbac: {clusterAdmin: true}, service: {type: LoadBalancer}. Install-এ --wait নেই, আর chart-এর উৎস একটি অচেনা ব্যক্তিগত repository. কী কী বদলাতে বলবেন?
    • latest-এর বদলে নির্দিষ্ট tag বা digest, যাতে rollback অর্থবহ হয়।
    • Password Git-এ নয়, আর release Secret-এও নয়: secret manager থেকে আনা। ফাঁস হওয়া মানটি বদলে ফেলা।
    • clusterAdmin নয়: শুধু দরকারি verb আর resource-এর Role.
    • LoadBalancer সত্যিই দরকার কি না দেখা: এটি internet-মুখী হতে পারে আর খরচও আনে।
    • --wait --timeout যোগ, যাতে ব্যর্থতা CI-তে ধরা পড়ে।
    • Chart-এর উৎস যাচাই, --version বাঁধা, render করে পড়া, সম্ভব হলে নিজের registry-তে কপি।
  4. একটি overlay লিখুন: অংশ ৬-এর base থেকে staging বানান: নামের আগে stage-, label env=stage, 2টি replica, image tag 1.27.5-alpine. দুটি environment-এর selector যাতে না মেশে, তা নিশ্চিত করুন। Base ছুঁতে পারবেন না।
    namespace: abcl-kz
    namePrefix: stage-
    labels:
      - pairs:
          env: stage
        includeSelectors: true
    resources:
      - ../../base
    images:
      - name: nginx
        newTag: 1.27.5-alpine
    replicas:
      - name: web
        count: 2

    replicas ঘর দিয়ে কোনো patch ফাইল ছাড়াই সংখ্যা বদলানো যায়। kubectl kustomize overlays/stage চালিয়ে দেখুন, Deployment আর Service-এর selector-এ env: stage আছে কি না। আমরা এটি চালিয়ে ও server dry-run করে যাচাই করেছি।

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

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

ক্রমএকটি নতুন chart নিরাপদে বসানোর ধাপ সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. helm show values দিয়ে option পড়া, version বাঁধা
  2. helm template দিয়ে render করে manifest পড়া
  3. kubectl apply --dry-run=server দিয়ে API-কে যাচাই করানো
  4. helm install --wait --timeout চালানো
  5. ব্যর্থ হলে helm history দেখে helm rollback

Helm ৩-এ একটি release-এর revision ৩ ব্যর্থ হওয়ার পরে helm rollback app 2 সফল হলো। helm list-এ এখন revision কত দেখাবে?

  • ২
  • ৪
  • ৩
  • ১

Helm ৩ ডিফল্টভাবে release-এর ইতিহাস কোথায় রাখে?

  • Release-এর namespace-এর Secret-এ
  • ব্যবহারকারীর কম্পিউটারের একটি ফাইলে
  • Tiller-এর ডেটাবেসে
  • Chart repository-তে

একটি Helm template-এ replicas: {{ .Values.replicacount }} লেখা, কিন্তু values-এর key হলো replicaCount. Render করলে কী হবে?

  • helm template error দিয়ে থেমে যাবে
  • Helm নিজে থেকে replicaCount-এর মান বসাবে
  • replicas: ঘরটি ফাঁকা থাকবে, কোনো error ছাড়া
  • replicas: 0 লেখা হবে, আর সব pod বন্ধ হবে

Kustomize-এ dev আর prod overlay একই namespace-এ, দুটির নাম আলাদা (namePrefix), কিন্তু label একই app=shop. দুটি Service-এর কী হবে?

  • Kustomize নিজে থেকে selector আলাদা করবে
  • kubectl apply error দিয়ে থেমে যাবে
  • প্রতিটি Service শুধু নিজের Deployment-এর pod পাবে
  • দুটি Service-ই দুই environment-এর সব pod-এ traffic পাঠাবে

একটি CRD apply করা হলো, আর তার একটি custom resource-ও তৈরি হলো, কিন্তু cluster-এ কোনো controller নেই। কী ঘটবে?

  • Kubernetes নিজে থেকে Pod চালিয়ে কাজটি করবে
  • Object etcd-তে জমা থাকবে, কিন্তু কোনো কাজ হবে না
  • API server resource-টি প্রত্যাখ্যান করবে
  • Garbage collector resource-টি মুছে দেবে

একটি controller তিন ঘণ্টা বন্ধ ছিল, আর সেই সময়ে দুটি watch event হারিয়ে গেছে। ভালোভাবে লেখা level-triggered controller ফিরে এসে কী করবে?

  • কাঙ্ক্ষিত আর আসল অবস্থা মিলিয়ে পার্থক্য দূর করবে
  • API server-এর কাছে হারানো event আবার চাইবে, না পেলে থেমে যাবে
  • শুধু নতুন event-এর জন্য অপেক্ষা করবে
  • সব child object মুছে নতুন করে বানাবে

শব্দকোষ

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

মনে রাখুন

  1. Helm = template + values + release. Rollback সংখ্যা পিছিয়ে দেয় না, নতুন revision বানায়।
  2. Render করে পড়ুন। Template চুপচাপ ভুল করে; kubectl apply --dry-run=server-ই API-র আসল যাচাই।
  3. Kustomize = সাধারণ YAML-এর base + পার্থক্যের overlay. namePrefix selector বদলায় না।
  4. CRD শুধু নতুন type; কাজ করে controller. CRD মুছলে সব custom resource মোছে।
  5. Operator মানে নিজের reconcile loop. আগে প্রশ্ন করুন, CronJob বা chart-এ হয় কি না, আর দুই বছর পরে এটি কে সামলাবেন।

পরের পর্বে: মডিউল ৮ এখানেই শেষ। আপনি এখন cluster-এর ভেতরের সবকিছু কোড হিসেবে বলতে পারেন। কিন্তু cluster, VPC আর database নিজেই কে বানাবে? পর্ব ৫১-এ শুরু হবে Infrastructure as Code: declarative, idempotent আর drift.

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

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