এই পর্বে যা শিখবেন
- নিজের ছোট 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 প্রায় একই, পার্থক্য সামান্য:
| ঘর | dev | staging | prod |
|---|---|---|---|
| replicas | 1 | 2 | 3 |
| Image tag | 1.27-alpine | 1.27.5-alpine | 1.27.5-alpine |
| Memory limit | 48Mi | 48Mi | 64Mi |
| পাতার লেখা | Boighor dev | Boighor stage | Boighor 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 installreplicaCount: 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 কয়েক স্তর থেকে আসে, আর পরেরটি জেতে:
values.yaml → -f prod.yaml → --set key=valueডানেরটি বামেরটির ওপরে বসে
Chart + values → manifest
Helm cluster-এ পাঠায় সাধারণ YAML, 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 ব্যর্থ করেছি।
| Revision | |
| Status | |
| Image tag | |
| Pod | |
| পাতার লেখা |
- Install. Revision ১ তৈরি হলো।
--waitথাকায় Helm pod ready হওয়া পর্যন্ত অপেক্ষা করল। এটি না দিলে Helm manifest পাঠিয়েইdeployedবলে দেয়, app চলছে কি না তা না দেখেই। - Upgrade. নতুন মান দুটি নিয়ে revision ২. Revision ১ এখন
superseded. পাতার লেখা বদলেছে, আর image একই। - ভুল tag. Revision ৩ তৈরি হলো, কিন্তু ২৫ সেকেন্ডে pod ready হয়নি। Helm
failedলিখল আর exit code 1 দিল। লক্ষ করুন, ব্যর্থ revision-ও একটি সংখ্যা খরচ করে। Helm নিজে থেকে পিছিয়ে যায়নি, কারণ আমরা তা বলিনি। - সাইট কিন্তু চলছে। পর্ব ৪৪-এর rolling update নতুন pod ready না হওয়া পর্যন্ত পুরোনো দুটি pod রেখে দিয়েছে। ব্যবহারকারী কিছুই টের পাননি। Helm-এর
failedমানে সাইট বন্ধ নয়। আবারdeployedমানেও সব ঠিক নয়। দুটি আলাদা তথ্য। - Rollback. সংখ্যা পিছিয়ে ২-এ গেল না, এগিয়ে ৪ হলো। ইতিহাস মোছা হয় না, revision ২-এর কপিটি নতুন revision হিসেবে বসে। Pod-এর নাম দেখুন:
d528qআরtfxt7একই রয়েছে, কারণ template আগের অবস্থায় ফিরতেই পুরোনো ReplicaSet-ই আবার ঠিক হয়ে গেল। - হাতে বদলালে?
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 foundhistory-এর 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.replicacount | replicas: খালি, আর Kubernetes default 1 ধরে | কেউ না: lint, lint --strict, dry-run সব পার |
values-এ greeting: no | পাতায় লেখা "false" | কেউ না, YAML-এ no মানে boolean |
values-এ tag: 1.10 | nginx:1.1, কারণ এটি সংখ্যা হয়ে গেছে | কেউ না, শুধু image pull ব্যর্থ হবে |
values-এ tag: "1.10" | nginx:1.10 | এটিই সঠিক রূপ |
nindent ছাড়া toYaml | YAML parse error … line 34 | helm template আর lint |
--set replicaCount=three | replicas: 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-কে যাচাই করানো।requiredfunction দিয়ে জরুরি মান বাধ্যতামূলক করা। মান না থাকলে render-ই ব্যর্থ হয়, আসল run-এ এসেছিলexecution error at … greeting is needed.values.schema.jsonদিয়ে values-এর type বেঁধে দেওয়া। তাহলেthreeHelm-এই আটকাবে, আর ভুল নামের 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.3grep '^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 নিজের পার্থক্য যোগ করে
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 হলো, দেখুন।
metadata:
name: web
spec:
replicas: 1
...
- image: nginx:1.27-alpine
resources:
limits:
memory: 48Mi
volumes:
- configMap:
name: page
metadata:
labels:
app: boighor
env: prod
name: prod-web
namespace: abcl-kz
spec:
replicas: 1
selector:
matchLabels:
app: boighor
env: prod
spec: replicas: 3 ... - image: nginx:1.27-alpine name: web resources: limits: memory: 64Mi requests: cpu: 10m # base থেকে অক্ষত memory: 16Mi # base থেকে অক্ষত
template:
metadata:
annotations:
tier: prod
...
- image: nginx:1.27.5-alpine
kind: ConfigMap metadata: name: prod-page-5m9hf2tk56 data: index.html: Boighor live # Deployment-এর reference-ও বদলেছে: volumes: - configMap: name: prod-page-5m9hf2tk56
$ 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
- শুরু। Base-এ নাম
web, এক replica, 48Mi. এটি নিজে থেকেই একটি পূর্ণ, বৈধ বর্ণনা। - নাম আর label.
namePrefixনাম বদলাল,namespaceঘর বসাল।includeSelectors: trueথাকায়env: prodশুধু label-এ নয়, selector-এও ঢুকল। - Strategic merge patch.
replicas3 হলো আর limit 64Mi. Patch-এrequestsছিল না, তাই base-এর মান অক্ষত। - Image আর JSON patch.
imagesট্রান্সফর্মারnginxনামের image-এর tag বদলাল, YAML ঘাঁটতে হলো না। JSON patch একটি annotation যোগ করল। - Generator. লেখা থেকে ConfigMap তৈরি হলো, নামের শেষে লেখার hash. Deployment-এর reference-ও নিজে থেকে বদলাল। লেখা বদলালে নাম বদলাবে, তাই pod নতুন করে উঠবে।
- আসল ফল। দুটি 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 — আর দুটো একসাথে
| প্রশ্ন | Helm | Kustomize |
|---|---|---|
| পদ্ধতি | Template + values | Base + patch |
| Source ফাইল | Template, একা বৈধ YAML নয় | সাধারণ YAML, একা apply করা যায় |
| ইতিহাস ও rollback | আছে, release হিসেবে | নেই; Git আর Deployment-এর rollback ভরসা |
| বিতরণ | Version-সহ chart, repository | Git-এ ফোল্ডার |
| শর্ত আর 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 নয়।
| Tick | |
| কাঙ্ক্ষিত schedule | |
| আসল CronJob | |
| কাজের সারি | |
| status |
- নতুন Backup. Controller খবর পেল, আসল অবস্থা দেখল, CronJob নেই। সে একটি বানাল আর তাতে ownerReference বসাল।
- মিলে গেছে। কাঙ্ক্ষিত আর আসল এক। কোনো কাজ নেই। একই reconcile বারবার চললেও কিছু বদলায় না, একে বলে idempotent. Status-এ
Readyলেখা হলো। - কেউ CronJob মুছলেন। Controller নিজের সন্তানদেরও watch করে, তাই খবর এলো, আর মালিক
orders-daily-এর নাম কাজের সারিতে উঠল। লক্ষ করুন, সারিতে শুধু নাম ওঠে, “কী ঘটেছে” তা নয়। - মেরামত। Reconcile আবার আসল অবস্থা দেখল, CronJob নেই, তাই আবার বানাল। হাতে মুছলে আগের অংশে কেউ ফেরায়নি, এখানে ফেরাল। পার্থক্য শুধু controller.
- Spec বদল। কেউ
Backup-এ সময় 0 3 করলেন। Controller CronJob বদলে দিল। মানুষ শুধু কাঙ্ক্ষিত অবস্থা বলেন, পথ বলে না। - মোছা। 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 আর তাদের পরিপক্বতা
| Project | Custom resource (উদাহরণ) | যা করে |
|---|---|---|
| cert-manager | Certificate, Issuer | TLS certificate আনে, Secret-এ রাখে, মেয়াদের আগে নবায়ন করে |
| Prometheus Operator | Prometheus, ServiceMonitor | Monitoring stack চালায়, পর্ব ৬৩-এর বিষয় |
| CloudNativePG | Cluster | PostgreSQL replica, failover আর backup |
| Strimzi | Kafka | Kubernetes-এ 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 বা বানানো flag | helm show values, helm <cmd> --help, render করা ফল |
image.tag: latest, version ছাড়া chart | নির্দিষ্ট tag বা digest, --version বাঁধা |
| Values-এ বা template-এ password | Secret manager; Git-এ secret scan |
ClusterRole-এ *, বা cluster-admin | grep '^kind:' আর RBAC পড়া, least privilege |
সরানো API version, যেমন CRD-র v1beta1 | kubectl 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 সরিয়ে নিন।
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-এ খরচ হতে পারে।
-
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 দুটোই একটি করে সংখ্যা নেয়। -
আউটপুট অনুমান করুন: 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 পড়ার সময়েইnoboolean আর1.10সংখ্যা হয়ে যায়, template পরে যা পায় তা-ই ছাপে। ঠিক করতে values-এ quote দিন:greeting: "no",tag: "1.10". স্থায়ী সমাধান হিসেবেvalues.schema.json-এ দুটিকেইstringবলুন। -
একটি chart সমালোচনা করুন: একজন সহকর্মী এই
values.yamlGit-এ দিলেন: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-তে কপি।
-
একটি overlay লিখুন: অংশ ৬-এর base থেকে staging বানান: নামের আগে
stage-, labelenv=stage, 2টি replica, image tag1.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: 2replicasঘর দিয়ে কোনো patch ফাইল ছাড়াই সংখ্যা বদলানো যায়।kubectl kustomize overlays/stageচালিয়ে দেখুন, Deployment আর Service-এর selector-এenv: stageআছে কি না। আমরা এটি চালিয়ে ও server dry-run করে যাচাই করেছি।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নতুন chart নিরাপদে বসানোর ধাপ সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
helm show valuesদিয়ে option পড়া, version বাঁধাhelm templateদিয়ে render করে manifest পড়াkubectl apply --dry-run=serverদিয়ে API-কে যাচাই করানোhelm install --wait --timeoutচালানো- ব্যর্থ হলে
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 templateerror দিয়ে থেমে যাবে- Helm নিজে থেকে
replicaCount-এর মান বসাবে replicas:ঘরটি ফাঁকা থাকবে, কোনো error ছাড়াreplicas: 0লেখা হবে, আর সব pod বন্ধ হবে
Kustomize-এ dev আর prod overlay একই namespace-এ, দুটির নাম আলাদা (namePrefix), কিন্তু label একই app=shop. দুটি Service-এর কী হবে?
- Kustomize নিজে থেকে selector আলাদা করবে
kubectl applyerror দিয়ে থেমে যাবে- প্রতিটি 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 মুছে নতুন করে বানাবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Helm = template + values + release. Rollback সংখ্যা পিছিয়ে দেয় না, নতুন revision বানায়।
- Render করে পড়ুন। Template চুপচাপ ভুল করে;
kubectl apply --dry-run=server-ই API-র আসল যাচাই। - Kustomize = সাধারণ YAML-এর base + পার্থক্যের overlay.
namePrefixselector বদলায় না। - CRD শুধু নতুন type; কাজ করে controller. CRD মুছলে সব custom resource মোছে।
- Operator মানে নিজের reconcile loop. আগে প্রশ্ন করুন, CronJob বা chart-এ হয় কি না, আর দুই বছর পরে এটি কে সামলাবেন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫১ · Infrastructure as Code — declarative, idempotent আর driftপরবর্তী পর্বমডিউল ৯-এর শুরু: cloud-এর পরিকাঠামো কোড হিসেবে।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ৪১ · Registry আর supply chainআগের পর্বTag, digest আর signing, যা chart-এর ক্ষেত্রেও খাটে।
- Software Engineering · Continuous Delivery ও DevOpsপাশের ট্র্যাকRelease-এর ধারণা আর pipeline-এর ভিত্তি।
- System Design · Deployment ও Releaseগভীরে যানRolling, canary আর rollback-এর নকশা।