এই পর্বে যা শিখবেন
- ConfigMap-কে env আর ফাইল হিসেবে দেওয়া, আর কোন পথে পরিবর্তন পৌঁছায়
env,envFromআর mounted ফাইলের মধ্যে কোন মান জেতে- Secret-এর আসল ঝুঁকি, etcd encryption, RBAC আর external secret-এর পথ
- emptyDir, PV, PVC, StorageClass, dynamic provisioning, access mode আর reclaim policy
- StatefulSet-এর ক্রম, স্থায়ী নাম, headless Service আর replica-প্রতি PVC
- Kubernetes-এ database চালানো বনাম managed service, snapshot, Velero আর zone-বাঁধা disk
এই পর্বের আউটপুট কোথা থেকে: সব transcript একটি local single-node cluster-এ (Kubernetes ১.৩৪) আসল run থেকে নেওয়া। নাম, age, IP আর সময় আপনার ক্ষেত্রে আলাদা হবে। জায়গা বাঁচাতে লম্বা লাইন ছেঁটে … দেওয়া হয়েছে। এই cluster-এর default storage একটি local hostpath provisioner, তাই আসল cloud disk নেই। Cloud-এর আচরণ আলাদা হলে তা স্পষ্ট করে বলা আছে।
১ · Config কেন image-এর বাইরে
ধরুন একটি কাল্পনিক অনলাইন বাজার অ্যাপ। Staging-এ log হবে বিস্তারিত, production-এ সংক্ষিপ্ত। Database-এর ঠিকানাও দুই জায়গায় আলাদা। এই মানগুলো image-এর ভেতরে লিখলে প্রতিটি পরিবেশের জন্য আলাদা image বানাতে হবে। তখন যে image পরীক্ষা হলো, production-এ সেটি আর যাচ্ছে না।
The Twelve-Factor App-এর পরামর্শ তাই স্পষ্ট: যা পরিবেশভেদে বদলায়, তা কোডের বাইরে থাকবে। Kubernetes-এ এই কাজের জন্য আছে দুটি object: ConfigMap আর Secret. দুটিই namespace-এর ভেতরে থাকে, আর একটি object-এর আকার সর্বোচ্চ প্রায় ১ MiB. ফলে এগুলো config-এর জন্য, বড় ফাইল বা ডেটার জন্য নয়।
রেস্তোরাঁর কথা ভাবুন। রান্নাঘর আর রেসিপি হলো image, প্রতিটি শাখায় একই। আজকের দাম আর “আজ ইলিশ নেই” লেখা থাকে মেনুবোর্ডে। মেনুবোর্ড বদলাতে রান্নাঘর ভাঙতে হয় না। ConfigMap হলো সেই মেনুবোর্ড। আর ক্যাশবাক্সের চাবি বোর্ডে ঝোলানো হয় না, সেটি আলাদা জায়গায় থাকে। সেটিই Secret.
নিচের manifest-এ একটি ConfigMap আর একটি Pod আছে। Pod একই ConfigMap তিন পথে পাচ্ছে: environment variable হিসেবে, পুরো volume হিসেবে, আর subPath দিয়ে একটি ফাইল হিসেবে।
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: info
app.conf: |
greeting=salam
max_items=20
---
apiVersion: v1
kind: Pod
metadata:
name: cfg-demo
spec:
containers:
- name: app
image: alpine:3.20
command: ["sleep", "3600"]
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
volumeMounts:
- name: conf
mountPath: /etc/app
- name: conf
mountPath: /etc/one/app.conf
subPath: app.conf
resources:
requests: { cpu: 10m, memory: 16Mi }
limits: { cpu: 100m, memory: 32Mi }
volumes:
- name: conf
configMap:
name: app-config$ kubectl exec cfg-demo -- printenv LOG_LEVEL
info
$ kubectl exec cfg-demo -- cat /etc/app/LOG_LEVEL
info
$ kubectl exec cfg-demo -- ls -la /etc/app
…
drwxr-xr-x 2 root root 4096 Oct 30 19:15 ..2026_10_30_19_15_08.3481835902
lrwxrwxrwx 1 root root 32 Oct 30 19:15 ..data -> ..2026_10_30_19_15_08.3481835902
lrwxrwxrwx 1 root root 16 Oct 30 19:15 LOG_LEVEL -> ..data/LOG_LEVEL
lrwxrwxrwx 1 root root 15 Oct 30 19:15 app.conf -> ..data/app.confConfigMap-এর প্রতিটি key একটি ফাইল হয়েছে। ফাইলগুলো আসলে symlink: LOG_LEVEL যায় ..data-য়, আর ..data যায় একটি সময়-চিহ্নিত folder-এ। এই নকশাই পরে কাজে লাগবে। নতুন মান এলে kubelet নতুন folder বানিয়ে শুধু ..data ঘুরিয়ে দেয়, তাই সব ফাইল একসাথে বদলায়।
ConfigMap না থাকলে (আর optional: true না দিলে) container চালু হয় না। আসল run-এ অনুপস্থিত ConfigMap-এর জন্য status এলো CreateContainerConfigError, আর event-এ configmap "no-such-config" not found. এই শব্দটি দেখলে আগে নাম আর namespace মেলান।
২ · ConfigMap বদলালে কে দেখে — env বনাম ফাইল
এবার মান বদলানো যাক। LOG_LEVEL হবে debug, max_items হবে 50. Pod restart করা হবে না।
$ kubectl patch configmap app-config --type merge -p \
'{"data":{"LOG_LEVEL":"debug","app.conf":"greeting=salam\nmax_items=50\n"}}'
configmap/app-config patched
# after the kubelet sync, same Pod, no restart
$ kubectl exec cfg-demo -- printenv LOG_LEVEL
info
$ kubectl exec cfg-demo -- cat /etc/app/LOG_LEVEL
debug
$ kubectl exec cfg-demo -- cat /etc/app/app.conf
greeting=salam
max_items=50
$ kubectl exec cfg-demo -- cat /etc/one/app.conf
greeting=salam
max_items=20একই ConfigMap, একই Pod, তিন রকম ফল। ফাইল নতুন মান পেয়েছে। printenv এখনো info, কারণ environment variable process চালু হওয়ার সময় একবারই বসে। subPath ফাইলে এখনো 20, কারণ subPath mount সেই symlink-বদলের সুবিধা পায় না।
| ConfigMap LOG_LEVEL | |
| চলমান Pod-এর env | |
| /etc/app/LOG_LEVEL | |
| /etc/app/app.conf | |
| subPath ফাইল | |
| Restart-এর পর নতুন Pod-এর env |
- শুরুর অবস্থা। ConfigMap-এ
infoআরmax_items=20. তিনটি পথেই একই মান দেখা যাচ্ছে। - API-তে বদল।
kubectl patchশুধু etcd-তে থাকা object বদলেছে। Node-এ এখনো কিছু বদলায়নি, তাই container আগের মানই দেখছে। - kubelet মিলিয়ে নিল। kubelet নতুন মান নিয়ে নতুন folder বানিয়ে
..dataঘুরিয়ে দিল। দুটি ফাইলই একসাথে বদলাল। আসল run-এ এই দেরি মাপা হয়েছে ৬৩, ১৬ আর ১৮ সেকেন্ড, আর Pod চালুর ঠিক পরের বদলে প্রায় সাথে সাথে। সময়টি নির্দিষ্ট নয়, kubelet-এর sync আর cache-এর ওপর নির্ভর করে। - যা বদলায়নি। env আর subPath ফাইল আগের মানে আছে। কোনো error নেই, কোনো সতর্কবার্তাও নেই। তাই এই ভুল চুপচাপ টিকে থাকে।
- নতুন Pod. একটি Deployment-এ
kubectl rollout restartচালানো হলো। নতুন Pod শুরুতে ConfigMap পড়ল, তাই তার env-এdebug. পুরোনো Pod-এ ৭৫ সেকেন্ড পরেওinfoছিল।
ফাইল বদলালেই app নতুন মান নেবে, এমন নয়। App ফাইলটি আবার পড়লে তবেই নেবে। Nginx-এর মতো অনেক program-কে reload করতে হয়। তাই ফাইলে বদল এলেও app-এর আচরণ যাচাই করুন। উল্টো দিকে, একটি ভুল config সব Pod-এ একসাথে পৌঁছাতে পারে। ফলে অনেক দল নতুন নামের ConfigMap বানিয়ে rolling update চালায়। এতে পুরোনো মানে ফিরে যাওয়াও সহজ হয়।
আরেকটি উপায় immutable: true. আসল run-এ এমন ConfigMap বদলাতে গেলে API server বলল: data: Forbidden: field is immutable when `immutable` is set. এতে ভুল বদল আটকায়, আর kubelet-এর নজরদারির চাপও কমে।
env, envFrom আর ফাইল — কোন মান জেতে
নিচের Pod-এ envFrom-এ দুটি ConfigMap আছে, env-এ নিজের মান আছে, আর base ফাইল হিসেবেও mount করা। Pod-এর log-এ আসল ফল এলো COLOR=green SIZE=large MODE=fast, আর ফাইলে blue.
| নিয়ম | আসল run-এ প্রমাণ |
|---|---|
envFrom-এ একই key থাকলে পরের উৎস জেতে | base-এ blue, extra-তে green. ফল green |
env-এর মান সব envFrom-কে হারায় | base-এ small, env-এ large. ফল large |
| Mounted ফাইল এই হিসাবের বাইরে | env-এ green থাকলেও ফাইলে blue |
| ফাইল আর env-এর মধ্যে কে জিতবে, তা Kubernetes ঠিক করে না | এটি app-এর নিজের নিয়ম |
নিচের simulator-এ POD আর CHANGE বদলে দেখুন। উৎসের ক্রম উল্টে দিলে বা env থেকে একটি মান সরালে ফল কীভাবে বদলায়, তা মিলিয়ে নিন।
// Which value does the container see? Edit POD and CHANGE, then run.
const cm = {
base: { COLOR: "blue", SIZE: "small" },
extra: { COLOR: "green" },
};
const POD = {
envFrom: ["base", "extra"], // later source wins
env: { MODE: "fast", SIZE: "large" },// env beats every envFrom
file: "base", // mounted at /etc/app
};
const CHANGE = { map: "base", key: "SIZE", value: "medium" };
function envAtStart(p, maps) {
const out = {};
for (const name of p.envFrom) Object.assign(out, maps[name]);
return Object.assign(out, p.env);
}
const show = (o) => Object.keys(o).sort().map(k => k + "=" + o[k]).join(" ");
const running = envAtStart(POD, cm); // fixed when the container starts
console.log("at start env : " + show(running));
console.log("at start file: " + show(cm[POD.file]));
const after = JSON.parse(JSON.stringify(cm));
after[CHANGE.map][CHANGE.key] = CHANGE.value;
console.log(`-- ConfigMap ${CHANGE.map}: ${CHANGE.key}=${CHANGE.value}`);
console.log("same Pod env : " + show(running) + " (frozen)");
console.log("same Pod file: " + show(after[POD.file]) + " (after kubelet sync)");
console.log("new Pod env : " + show(envAtStart(POD, after)));
// The app, not Kubernetes, decides between a file and a variable.
const appReads = (key) => after[POD.file][key] ?? running[key] ?? "default";
console.log("app rule file > env > default: SIZE=" + appReads("SIZE") +
" MODE=" + appReads("MODE") + " PORT=" + appReads("PORT"));প্রথম লাইনে আসে at start env : COLOR=green MODE=fast SIZE=large, যা আসল Pod-এর সাথে মেলে। ConfigMap-এ SIZE=medium করার পর ফাইল হয় COLOR=blue SIZE=medium, কিন্তু env থাকে আগের মতো। new Pod env-এও SIZE=large, কারণ env-এর মান ConfigMap-কে হারায়। শেষ লাইন SIZE=medium MODE=fast PORT=default দেখায়, app নিজে ফাইলকে আগে পড়লে ফল কেমন হয়।
৩ · Secret — base64 কেন encryption নয়
Secret-এর গঠন ConfigMap-এর মতোই, তবে মান থাকে base64-এ। নামটি শুনে অনেকে ভাবেন মানটি লুকানো আছে। একটি নকল মান দিয়ে দেখা যাক।
$ kubectl create secret generic db-cred \
--from-literal=username=demo_user \
--from-literal=password='fake-Passw0rd-123'
secret/db-cred created
$ kubectl get secret db-cred -o yaml
apiVersion: v1
data:
password: ZmFrZS1QYXNzdzByZC0xMjM=
username: ZGVtb191c2Vy
kind: Secret
…
type: Opaque
$ kubectl get secret db-cred -o jsonpath='{.data.password}' | base64 -d
fake-Passw0rd-123
$ kubectl describe secret db-cred
…
Data
====
password: 17 bytes
username: 9 bytesকোনো চাবি ছাড়াই মানটি ফিরে এলো। Encoding মানে লেখার ধরন বদলানো, যেমন বাংলা কথাকে রোমান হরফে লেখা। Encryption মানে চাবি ছাড়া পড়া যায় না। Base64 আছে যেকোনো byte, যেমন certificate, YAML-এ রাখার সুবিধার জন্য। describe মান দেখায় না, শুধু আকার দেখায়। এটি ভুলে চোখে পড়া আটকায়, কিন্তু get-এর অনুমতি থাকলে কোনো বাধা নেই। এই কারণেই manifest-এর Secret git-এ রাখা বিপজ্জনক।
Secret Pod-এ দুই পথে যায়, ConfigMap-এর মতোই। আসল run-এ ফাইল পথে defaultMode: 0400 দিয়ে দেখা গেল:
$ kubectl exec sec-demo -- ls -lL /run/cred
total 8
-r-------- 1 root root 17 Oct 30 19:20 password
-r-------- 1 root root 9 Oct 30 19:20 username
# which filesystem holds the mount
tmpfs /run/cred tmpfs
$ kubectl auth can-i get secrets \
--as=system:serviceaccount:abcl-kc:default
noSecret volume বসেছে tmpfs-এ, মানে node-এর memory-তে, disk-এ নয়। ফাইলের অনুমতি -r--------, শুধু owner পড়তে পারে। আর namespace-এর default service account-এর Secret পড়ার অনুমতি নেই। তবে যিনি namespace-এ Pod বানাতে পারেন, তিনি সেই Pod-এ Secret mount করেও পড়তে পারেন। তাই create pods-এর অনুমতিও আসলে Secret-এর অনুমতির কাছাকাছি।
Env-এ Secret দেওয়া সহজ, কিন্তু ফাইলই সাধারণত নিরাপদ। Env প্রায়ই crash report, debug endpoint আর child process-এ ছড়িয়ে পড়ে। আর ফাইল পথে rotation-এর মান চলমান Pod-এ পৌঁছায়। শর্ত একটাই: app-কে ফাইলটি আবার পড়তে হবে।
৪ · Secret আসলে যেভাবে রক্ষা পায়
পর্ব ৩৩ Secret আর KMS-এ envelope encryption আর secret manager দেখেছেন। Kubernetes-এ সেগুলো তিন স্তরে বসে।
স্তর ১: etcd-তে encryption at rest
Kubernetes-এর default-এ Secret etcd-তে encrypted না-ও থাকতে পারে। Upstream Kubernetes-এ encryption at rest নিজে চালু করতে হয়। API server একটি config ফাইল পড়ে আর etcd-তে লেখার আগে encrypt করে। নিচের ফাইলটি API server-এর flag দিয়ে দেওয়া হয়, kubectl apply দিয়ে নয়।
# Passed to kube-apiserver with --encryption-provider-config
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
apiVersion: v2
name: example-kms
endpoint: unix:///var/run/kmsplugin/socket.sock
- identity: {}প্রথম provider নতুন লেখা encrypt করে। পরের provider-গুলো শুধু পুরোনো ডেটা পড়ার জন্য। identity মানে encryption নেই। এটি চালু করলে আগের Secret নিজে থেকে encrypted হয় না, প্রতিটি আবার লিখতে হয়। KMS provider-এ মূল চাবি cluster-এর বাইরে থাকে। Managed Kubernetes-এ এই অংশ provider সামলায়, তবে সুবিধার নাম আর default provider-ভেদে আলাদা। নিজের cluster-এর docs দেখে নিন। এই ফাইল এই পর্বের cluster-এ চালানো হয়নি, কারণ এতে API server বদলাতে হয়।
স্তর ২: RBAC — কে পড়তে পারে
etcd encryption চুরি যাওয়া disk বা backup থেকে বাঁচায়। কিন্তু API দিয়ে get secret করলে মান খোলাই আসে। তাই RBAC-এ Secret-এর get আর list অল্প কয়েকজনকে দিন। list-ও মান ফেরত দেয়, শুধু নাম নয়। Audit log চালু রাখুন, যাতে কে পড়ল তা জানা যায়। পর্ব ৪৯-এ RBAC আরও বিস্তারে আসবে।
স্তর ৩: মান cluster-এর বাইরে রাখা
| পথ | কীভাবে কাজ করে | মান কোথায় থাকে | মনে রাখুন |
|---|---|---|---|
| External Secrets Operator | Controller বাইরের manager থেকে মান এনে Secret বানায় | Manager-এ, আর একটি কপি cluster-এর Secret-এ | App বদলাতে হয় না, তবে etcd সুরক্ষা লাগবেই |
| Secrets Store CSI Driver | Pod চালুর সময় মান ফাইল হিসেবে mount হয় | Manager-এ, আর Pod-এর tmpfs-এ | etcd-তে কপি না-ও থাকতে পারে, env চাইলে অতিরিক্ত ব্যবস্থা লাগে |
| App নিজে manager-কে ডাকে | SDK দিয়ে চলার সময় মান আনে | Manager-এ, আর process-এর memory-তে | সবচেয়ে কম ছড়ায়, তবে কোড জড়িয়ে যায় |
Provider-এর উদাহরণ: AWS Secrets Manager, Azure Key Vault, Google Secret Manager, আর নিজে চালানোর জন্য HashiCorp Vault. Pod কীভাবে manager-এর কাছে পরিচয় দেবে, সেটিও প্রশ্ন। স্থায়ী চাবি না রেখে workload identity দিয়ে সাময়িক পরিচয় নেওয়াই ভালো। Secret rotation-এর নিয়ম পর্ব ৩৩-এই আছে।
৫ · Pod মরে, ডেটা কোথায় থাকে
Container-এর নিজের filesystem তার সাথেই মুছে যায়, পর্ব ৩৯-এ Docker-এ যা দেখেছেন। Kubernetes-এ এর সমাধান volume. প্রশ্ন হলো, volume-এর আয়ু কতটা।
| কী ঘটল | Container-এর filesystem | emptyDir | PVC |
|---|---|---|---|
| Container crash করে restart | মুছে যায় | থাকে | থাকে |
| Pod মুছে নতুন Pod | মুছে যায় | মুছে যায় | থাকে |
| PVC মুছে দিলেন | — | — | Reclaim policy ঠিক করে |
emptyDir হলো Pod-এর সাথে জন্মানো ফাঁকা ফোল্ডার। এটি cache, সাময়িক ফাইল বা দুই container-এর মধ্যে ফাইল দেওয়া-নেওয়ার জন্য ভালো। medium: Memory দিলে এটি tmpfs হয়, আর ব্যবহৃত অংশ container-এর memory limit-এ গোনা হয়।
গুদামঘরের কথা ভাবুন। PersistentVolume হলো গুদামের একটি আসল ঘর। PersistentVolumeClaim হলো দরখাস্ত: “১০০ বর্গফুট শুকনো ঘর চাই।” ব্যবসায়ী ঘরের নম্বর জানেন না, রসিদ দেখালেই মাল রাখতে পারেন। আর StorageClass হলো ঘরের ধরন: সাধারণ ঘর, না হিমঘর। ব্যবসায়ী বদলালেও রসিদ থাকলে মাল ঘরেই থাকে।
Pod থেকে আসল disk পর্যন্ত
Pod → PVC → PV → cloud disk
নিচের প্রমাণটি দেখুন। Pod notes-1 দুটি ফাইল লিখল: একটি PVC-তে, একটি emptyDir-এ। তারপর Pod মুছে একই PVC দিয়ে নতুন Pod চালানো হলো।
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: notes-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 16Mi
---
apiVersion: v1
kind: Pod
metadata:
name: notes-1
spec:
containers:
- name: app
image: alpine:3.20
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
- name: scratch
mountPath: /scratch
resources:
requests: { cpu: 10m, memory: 16Mi }
limits: { cpu: 100m, memory: 64Mi }
volumes:
- name: data
persistentVolumeClaim:
claimName: notes-data
- name: scratch
emptyDir: {}$ kubectl exec notes-1 -- sh -c \
'echo "order-1001 paid" > /data/orders.txt; echo "temp cache" > /scratch/tmp.txt'
$ kubectl delete pod notes-1
pod "notes-1" deleted from abcl-kc namespace
$ kubectl get pvc notes-data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS …
notes-data Bound pvc-75471c50-… 16Mi RWO hostpath …
# a new Pod notes-2 uses the same claim
$ kubectl exec notes-2 -- cat /data/orders.txt
order-1001 paid
$ kubectl exec notes-2 -- sh -c 'ls -la /scratch; cat /scratch/tmp.txt'
total 8
drwxrwxrwx 2 root root 4096 Oct 30 19:19 .
drwxr-xr-x 1 root root 4096 Oct 30 19:19 ..
cat: can't open '/scratch/tmp.txt': No such file or directory
command terminated with exit code 1PVC-র ফাইল নতুন Pod পেল, emptyDir-এর ফাইল হারাল। Pod মোছার পরও PVC Bound ছিল। আলাদা run-এ container-কে ইচ্ছা করে থামানো হলো: RESTARTS 1 হলো, আর emptyDir-এর ফাইল তখনও ছিল। তাই emptyDir-এর আয়ু container নয়, Pod.
৬ · StorageClass, dynamic provisioning আর access mode
আগে admin হাতে disk আর PV বানিয়ে রাখতেন। একে বলে static provisioning. এখন সাধারণ পথ dynamic provisioning: PVC এলেই provisioner disk আর PV বানায়। উপরের PVC-তে StorageClass লেখা ছিল না, তাই cluster-এর default class hostpath কাজে লেগেছে।
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION …
hostpath (default) docker.io/hostpath Delete Immediate false …- PVC
notes-dataদরখাস্ত - StorageClass
hostpathধরন - Provisioner
docker.io/hostpathdisk বানায় - PV
pvc-75471c50-…আসল জায়গা
| PVC-র status | |
| PV | |
| Event | |
| Pod notes-1 |
- দরখাস্ত এলো।
kubectl applyPVC বানাল: ১৬ MiB,ReadWriteOnce. এখনো কোনো জায়গা নেই। - Controller ধরন খুঁজল। PV controller দেখল মানানসই PV নেই। Default StorageClass
hostpathপেল। Event এলো “Waiting for a volume to be created”. - Provisioner কাজ ধরল। Event
Provisioning. এখানে এটি node-এ একটি folder বানায়। Cloud-এ এই ধাপে CSI driver provider-এর API ডেকে আসল disk বানায়, আর খরচ শুরু হয়। - PV তৈরি। Event
ProvisioningSucceeded. PV-র নামpvc-দিয়ে শুরু, পরে PVC-র UID. Reclaim policy এসেছে StorageClass থেকে:Delete. - Bind. PVC আর PV একে অপরের সাথে জোড়া লাগল। এই জোড়া এক-এক: একটি PV একই সময়ে একটিই PVC-র।
- Pod লাগাল। kubelet volume-টি
/data-য় mount করল। Cloud-এ এর আগে disk-কে node-এ attach করতে হয়, তাই Pod শুরুতে কিছুটা দেরি হতে পারে।
volumeBindingMode ঠিক করে disk কখন বানানো হবে। Immediate মানে PVC এলেই। WaitForFirstConsumer মানে Pod কোথায় বসবে তা জানার পরে। নিজের একটি class বানিয়ে আসল run-এ দেখা গেল:
$ kubectl get storageclass abcl-kc-late
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE …
abcl-kc-late docker.io/hostpath Retain WaitForFirstConsumer …
$ kubectl get pvc late-data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS …
late-data Pending abcl-kc-late …
# event on the claim
WaitForFirstConsumer waiting for first consumer to be created before binding
# after a Pod that uses the claim is created
$ kubectl get pvc late-data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS …
late-data Bound pvc-9065f351-… 16Mi RWO abcl-kc-late …Pod না আসা পর্যন্ত PVC Pending ছিল, আর এটি কোনো ভুল নয়। Pod আসার পরই disk তৈরি হলো। অনেক cloud class-এ এই মোড ব্যবহার হয়, যার কারণ অংশ ৯-এ দেখবেন। তাই Pending দেখে ঘাবড়ানোর আগে event পড়ুন।
Access mode — কে কীভাবে লাগাবে
| Mode | মানে | সাধারণ উৎস |
|---|---|---|
ReadWriteOnce (RWO) | একটি node পড়া-লেখা করতে পারে | Block storage: EBS, Azure Disk, Persistent Disk |
ReadWriteOncePod (RWOP) | একটিমাত্র Pod | Docs-এ মূলত CSI volume-এর জন্য বলা; এই run-এ hostpath-এও মানা হয়েছে |
ReadOnlyMany (ROX) | অনেক node, শুধু পড়া | Driver-ভেদে আলাদা |
ReadWriteMany (RWX) | অনেক node, পড়া-লেখা | File storage: EFS, Azure Files, Filestore, NFS |
এটি সবচেয়ে ভুল বোঝা বিষয়গুলোর একটি। RWO-র “Once” মানে এক Pod নয়, এক node. আসল run-এ এক node-এ দুটি Pod একই RWO PVC লাগিয়ে দুজনেই order-1001 paid পড়তে পেরেছে। RWOP-তে দ্বিতীয় Pod Pending থেকে গেছে, event-এ: PersistentVolumeClaim with ReadWriteOncePod access mode already in-use by another pod.
এই cluster-এর একটি সীমা: ১৬ MiB-র PVC-তে ২৪ MiB-র ফাইল লেখা গেছে। Hostpath provisioner আকারের সীমা মানে না। Cloud disk-এ PVC-র আকারই disk-এর আকার। তাই local-এ চলা মানেই production-এ জায়গা কুলোবে, এমন নয়।
৭ · PVC মুছলে disk-এর কী হয় — reclaim policy
Pod মুছলে ডেটা থাকে। কিন্তু PVC মুছলে কী হবে, তা ঠিক করে reclaim policy. দুটি আসল মান আছে। পুরোনো Recycle deprecated, নতুন কাজে ব্যবহার করবেন না। Dynamic PV সাধারণত StorageClass থেকে policy পায়, আর StorageClass-এ না লিখলে default হয় Delete.
# Policy Delete (class hostpath)
$ kubectl delete pvc notes-data
$ kubectl get pv pvc-75471c50-…
Error from server (NotFound): persistentvolumes "pvc-75471c50-…" not found
# Policy Retain (class abcl-kc-late)
$ kubectl delete pvc late-data
$ kubectl get pv
NAME CAPACITY … RECLAIM POLICY STATUS CLAIM …
pvc-8c4331f9-… 16Mi … Retain Released abcl-kc/late-data …
# A new claim naming that PV does not bind yet
FailedBinding volume "pvc-9065f351-…" already bound to a different claim.
# Remove the old claim reference, then bind again
$ kubectl patch pv pvc-8c4331f9-… --type json \
-p '[{"op":"remove","path":"/spec/claimRef"}]'
$ kubectl get pv
NAME CAPACITY … RECLAIM POLICY STATUS …
pvc-8c4331f9-… 16Mi … Retain Available …
$ kubectl get pvc late-new
NAME STATUS VOLUME CAPACITY …
late-new Bound pvc-8c4331f9-… 16Mi …
$ kubectl exec late-pod2 -- cat /data/orders.txt
order-1001 paidDelete-এ PV সাথে সাথে চলে গেছে, আর এই provisioner-এ ডেটাও। Retain-এ PV থেকে গেছে, তবে Released অবস্থায়, পুরোনো PVC-র নাম নিয়ে। Released মানে আপনা থেকে আবার ব্যবহার হবে না। পুরোনো মালিকের তথ্য নতুন কেউ যেন না পায়, এটিই এর উদ্দেশ্য। claimRef সরালে PV হলো Available, নতুন PVC জুড়ল, আর পুরোনো ফাইল ফিরে এলো। তাই PV আবার খোলার আগে ভেবে নিন, এই ডেটা কার হাতে যাচ্ছে। ওপরের FailedBinding লাইনটি আগের একটি run-এর, তাই PV-র নাম আলাদা।
Retain-এ PV-র জীবনচক্র
Phases of a retained PersistentVolume
- BoundPVC-র সাথে যুক্ত
- ReleasedPVC মোছা, ডেটা আছে
- AvailableclaimRef সরানো
- Boundনতুন PVC
Retain ডেটা বাঁচায়, কিন্তু বিলও বাঁচিয়ে রাখে। PV মুছলেও আসল cloud disk মোছে না, সেটি আলাদা করে মুছতে হয়। নিচের simulator-এ policy, আকার আর admin-এর পদক্ষেপ বদলে দেখুন। দাম এখানে হিসাবের জন্য ধরে নেওয়া মান, আসল দাম provider-এর pricing পাতায় দেখুন।
// What happens to the disk after a PVC is deleted. Edit CASES and run.
// RATE is an assumed round number for the arithmetic, not a real price.
const RATE = 0.10; // assumed $ per GiB per month
const CASES = [
{ name: "dev-cache", policy: "Delete", gib: 20, pvcDeleted: true, admin: "" },
{ name: "orders-db", policy: "Retain", gib: 100, pvcDeleted: true, admin: "" },
{ name: "old-logs", policy: "Retain", gib: 200, pvcDeleted: true, admin: "pv" },
{ name: "old-report", policy: "Retain", gib: 50, pvcDeleted: true, admin: "pv+disk" },
{ name: "reports", policy: "Delete", gib: 50, pvcDeleted: false, admin: "" },
];
function outcome(c) {
if (!c.pvcDeleted) return { pv: "Bound", disk: "in use", data: "safe" };
if (c.policy === "Delete") return { pv: "gone", disk: "deleted", data: "LOST" };
if (c.admin === "pv+disk") return { pv: "gone", disk: "deleted", data: "LOST" };
if (c.admin === "pv") return { pv: "gone", disk: "orphan", data: "on disk, no PV" };
return { pv: "Released", disk: "kept", data: "on disk, not reusable yet" };
}
let bill = 0, orphan = 0;
console.log("name policy PV disk data");
for (const c of CASES) {
const o = outcome(c);
const cost = o.disk === "deleted" ? 0 : c.gib * RATE;
bill += cost;
if (c.pvcDeleted && o.disk !== "deleted") orphan += cost;
console.log(c.name.padEnd(12) + c.policy.padEnd(8) + o.pv.padEnd(10) +
o.disk.padEnd(9) + o.data + " ($" + cost.toFixed(2) + "/month)");
}
console.log("monthly disk bill: $" + bill.toFixed(2));
console.log("paid for disks no PVC uses: $" + orphan.toFixed(2));orders-db Released অবস্থায় ডেটা রেখেছে, খরচ ধরা হয়েছে $10.00/month. old-logs-এ PV মোছা হলেও disk orphan, তাই ২০০ × ০.১০ = $20.00 চলছে, অথচ cluster থেকে তাকে আর দেখা যায় না। মোট বিল $35.00, যার $30.00 এমন disk-এর, যা কোনো PVC ব্যবহার করে না। মিলিয়ে দেখুন: ০ + ১০ + ২০ + ০ + ৫ যোগ করলে ৩৫ ডলারই হয়।
৮ · StatefulSet — নাম, ক্রম আর নিজের disk
Deployment-এর Pod-গুলো একে অপরের বদলি। নাম এলোমেলো, কাজ একই, আর কোনো Pod-এর আলাদা গুরুত্ব নেই। কিন্তু database replica এমন নয়। প্রতিটির নিজের ডেটা, নিজের ভূমিকা। Primary কে, replica কে, কে কার সাথে কথা বলবে, সব আগে থেকে জানা দরকার। এই stateful কাজের জন্য StatefulSet.
Deployment হলো হোটেল: যে ঘর খালি, সেখানে থাকুন, চলে গেলে ঘর অন্য কারও। StatefulSet হলো মেসের নির্দিষ্ট সিট: সিট ১-এর বাসিন্দা বাড়ি থেকে ফিরলে আবার সিট ১-এই ওঠেন, আর খাটের নিচে তাঁর নিজের ট্রাঙ্কটিই পান।
apiVersion: v1
kind: Service
metadata:
name: web
spec:
clusterIP: None # headless: no virtual IP
selector:
app: web
ports:
- port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web # must name the headless Service
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
readinessProbe:
tcpSocket:
port: 80
periodSeconds: 2
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
resources:
requests: { cpu: 20m, memory: 16Mi }
limits: { cpu: 100m, memory: 48Mi }
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 16Mi$ kubectl get pods -l app=web -w # trimmed to one line per change
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 2s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 2s
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY …
data-web-0 Bound pvc-045ec649-… 16Mi …
data-web-1 Bound pvc-23fc99da-… 16Mi …
data-web-2 Bound pvc-2f530807-… 16Mi …
# from another Pod; each replica wrote its own name to its own disk
$ kubectl exec client -- nslookup -type=a web.abcl-kc.svc.cluster.local
…
Name: web.abcl-kc.svc.cluster.local
Address: 10.1.0.241
Name: web.abcl-kc.svc.cluster.local
Address: 10.1.0.242
Name: web.abcl-kc.svc.cluster.local
Address: 10.1.0.245
$ kubectl exec client -- nslookup -type=a web-1.web.abcl-kc.svc.cluster.local
…
Address: 10.1.0.242
$ kubectl exec client -- wget -qO- http://web-1.web
I am web-1web-1 শুরু হয়েছে web-0 Ready হওয়ার পরে, web-2 তার পরে। প্রতিটি replica পেয়েছে নিজের PVC: নামের নিয়ম <template>-<pod>. পর্ব ৪৫-এর সাধারণ Service একটি virtual IP দেয়। Headless Service-এ DNS সরাসরি তিনটি Pod-এর IP ফেরত দিল, আর প্রতিটি Pod নিজের নামেও DNS record পেল। StatefulSet এই Service নিজে বানায় না, তাই আগেই বানাতে হয়। volumeClaimTemplates অংশটি replica-প্রতি PVC বানায়।
ক্রম সত্যিই বাধ্যতামূলক। একটি run-এ readiness probe ইচ্ছা করে ভাঙা হয়েছিল। replicas: 2 থাকলেও ১৫ সেকেন্ড পরে শুধু gate-0 ছিল 0/1 Running, আর gate-1 তৈরিই হয়নি। StatefulSet আটকে থাকলে তাই সবচেয়ে ছোট ordinal-এর যে Pod Ready নয়, তাকে আগে দেখুন।
| Pod-এর অবস্থা | |
| Pod-এর নাম | |
| Pod UID | |
| Pod IP | |
| PVC | |
| Disk-এর ফাইল |
- স্বাভাবিক অবস্থা।
web-1চলছে, IP 10.1.0.242, নিজের PVCdata-web-1-এ একটি ফাইল আছে। - Pod মোছা হলো।
kubectl delete pod web-1. PodTerminating. PVC-তে কোনো বদল নেই, disk আগের মতোই আছে। - Controller ফাঁক দেখল। Ordinal ১ অনুপস্থিত। পুরোনো Pod পুরো চলে যাওয়ার পরেই সে একই নামে নতুন Pod বানাল। UID নতুন: নামে একই, আসলে আরেকটি Pod.
- নিজের PVC. নতুন Pod যেকোনো PVC নয়, ঠিক
data-web-1পেল। নামের নিয়মই এই জোড়া ঠিক করে দেয়। - আবার Ready. IP বদলে হলো 10.1.0.252. তাই IP মনে রাখা চলে না, নাম মনে রাখতে হয়।
- যাচাই।
wget http://web-1.webফেরত দিলI am web-1. পরিচয় আর ডেটা টিকে আছে, Pod আর IP বদলে গেছে।
কমানো, মোছা আর রেখে যাওয়া PVC
$ kubectl scale sts web --replicas=1
# watch: web-2 Terminating first, then web-1
$ kubectl get pvc --no-headers
data-web-0 Bound pvc-045ec649-… 16Mi …
data-web-1 Bound pvc-23fc99da-… 16Mi …
data-web-2 Bound pvc-2f530807-… 16Mi …
$ kubectl scale sts web --replicas=3
$ kubectl exec client -- wget -qO- http://web-2.web
I am web-2
$ kubectl delete sts web
statefulset.apps "web" deleted from abcl-kc namespace
$ kubectl get pods -l app=web
No resources found in abcl-kc namespace.
$ kubectl get pvc --no-headers
data-web-0 Bound pvc-045ec649-… 16Mi …
data-web-1 Bound pvc-23fc99da-… 16Mi …
data-web-2 Bound pvc-2f530807-… 16Mi …কমানোর সময় ক্রম উল্টো: বড় ordinal আগে যায়। PVC কোনোটিই মোছেনি, তাই বাড়ানোর পর web-2 নিজের ডেটা ফেরত পেল। StatefulSet মুছলেও PVC-গুলো থেকে গেছে। এটি ইচ্ছাকৃত নিরাপত্তা, কিন্তু cloud-এ এই disk-গুলোর বিল চলতেই থাকে। একই StatefulSet আবার বানালে web-1 পুরোনো ফাইলই পড়েছে: I am web-1.
Kubernetes-এর নতুন version-এ persistentVolumeClaim দিয়ে এই আচরণ বদলানো যায়। আসল run-এ whenDeleted: Delete বসিয়ে StatefulSet মোছার পর PVC-ও চলে গেছে: No resources found. Default হলো দুটিই Retain. Production database-এ default-ই নিরাপদ, আর পরীক্ষার পরিবেশে Delete খরচ বাঁচায়।
Rolling update — উল্টো ক্রমে
আসল run-এ template বদলানোর পর Pod গেছে web-2 → web-1 → web-0 ক্রমে, প্রতিটি Ready হওয়ার পরে পরেরটি। partition: N দিলে শুধু N বা তার বড় ordinal বদলায়, বাকিরা পুরোনো version-এ থাকে। এটি canary-র মতো সতর্ক পরীক্ষায় কাজে লাগে। podManagementPolicy: Parallel দিলে তৈরি আর মোছা একসাথে হয়, তবে rolling update-এর ক্রম তাতে বদলায় না। নিচের simulator-এ এগুলো বদলে দেখুন।
// StatefulSet create and rolling update on a simulated clock (seconds).
// Edit SET and run. broken = ordinal that never becomes Ready, -1 = none.
const SET = { replicas: 4, ready: 5, stop: 2, policy: "OrderedReady",
partition: 0, broken: -1 };
function create(s) {
let t = 0;
console.log("-- create, policy " + s.policy);
for (let i = 0; i < s.replicas; i++) {
const at = s.policy === "Parallel" ? 0 : t;
if (i === s.broken) {
console.log(`t=${at}s web-${i} created, never Ready`);
if (s.policy !== "Parallel") {
console.log(`STOP: web-${i + 1} and later are not created`); return false;
}
continue;
}
console.log(`t=${at}s web-${i} created, Ready at t=${at + s.ready}s`);
t = at + s.ready;
}
return s.broken < 0;
}
function update(s) {
let t = 0;
console.log(`-- rolling update, partition ${s.partition}`);
for (let i = s.replicas - 1; i >= 0; i--) {
if (i < s.partition) { console.log(` web-${i} keeps the old revision`); continue; }
const up = t + s.stop, done = up + s.ready;
console.log(`t=${t}s web-${i} stops, new one at t=${up}s, Ready at t=${done}s`);
t = done;
}
console.log(`rollout time: ${t}s`);
}
if (create(SET)) update(SET); else console.log("rolling update not started");Default মানে চারটি Pod ক্রমে Ready হয়: t=15s web-3 created, Ready at t=20s. তাই শুরু করতে লাগে ৪ × ৫ = ২০ সেকেন্ড। Update-এ web-3 আগে যায়, আর শেষ লাইন rollout time: 28s, মানে ৪ × (২ + ৫) = ২৮. partition: 2 দিলে শুধু web-3 আর web-2 বদলাবে। broken: 1 দিলে web-2 তৈরিই হবে না। সময়গুলো ধরে নেওয়া মান, আসল সময় নয়।
৯ · Database, snapshot আর zone-বাঁধা disk
Kubernetes-এ database, নাকি managed service
StatefulSet নাম আর disk দেয়। সে replication, failover, backup বা version upgrade জানে না। সেই জ্ঞান আসে database নিজে থেকে, অথবা একটি operator থেকে, যা পর্ব ৫০-এ দেখবেন।
| প্রশ্ন | Managed service (পর্ব ২২) | Kubernetes-এ নিজে |
|---|---|---|
| Failover, backup, patch | Provider সামলায় | আপনি বা operator সামলান |
| নিয়ন্ত্রণ, extension, version | Provider যা দেয় | পুরোটা আপনার |
| খরচের ধরন | সেবার দাম বেশি, মানুষের সময় কম | সেবার দাম কম, মানুষের সময় বেশি |
| একই পরিবেশ সব জায়গায় | Provider-ভেদে আলাদা | On-premises আর cloud-এ একই নিয়ম |
| কার জন্য ভালো | বেশিরভাগ দল | Database চালানোর দক্ষতা আছে এমন দল |
সাধারণ নিয়ম: দলে অভিজ্ঞ database engineer না থাকলে production-এর মূল database managed service-এ রাখুন। Kubernetes-এ রাখলে বুঝেশুনে operator বাছুন, আর restore নিয়মিত পরীক্ষা করুন।
Snapshot আর backup
PVC ডেটা টিকিয়ে রাখে, backup নয়। ভুল করে DROP TABLE চালালে সেটি disk-এ আর replica-তে ছড়িয়ে যায়। VolumeSnapshot API (snapshot.storage.k8s.io/v1, Kubernetes ১.২০-এ GA) দিয়ে PVC-র snapshot নেওয়া যায়, আর সেখান থেকে নতুন PVC বানানো যায়। এর জন্য snapshot-সমর্থিত CSI driver আর snapshot controller লাগে। এই পর্বের cluster-এ এই API নেই, kubectl api-resources-এ দেখা যায়নি। তাই এখানে চালিয়ে দেখানো হয়নি।
Velero একধাপ ওপরের হাতিয়ার। এটি Kubernetes object-গুলো object storage-এ রাখে, আর volume-এর জন্য snapshot বা ফাইল-স্তরের কপি নেয়। ধারণাটি হলো পুরো namespace-কে অন্য cluster-এ ফিরিয়ে আনা। Disk snapshot সাধারণত crash-consistent, database-এর জন্য তাই নিজের backup হাতিয়ারও রাখুন। RPO, RTO আর restore পরীক্ষার হিসাব পর্ব ২৪-এ আছে।
Cloud CSI driver আর zone-এর বাঁধন
CSI হলো storage জোড়ার মানসম্মত interface. প্রতিটি provider নিজের driver দেয়, যেমন AWS EBS CSI, Azure Disk CSI, Google Compute Engine Persistent Disk CSI. এই পর্বের cluster-এ কোনো CSI driver নেই, kubectl get csidrivers বলেছে No resources found. একটি node-এ zone-এর বাঁধন দেখানোও সম্ভব নয়, তাই নিচের অংশ ধারণা হিসেবে পড়ুন।
Block disk সাধারণত একটি availability zone-এ থাকে। অন্য zone-এর node তাকে লাগাতে পারে না। Immediate মোডে disk আগে বানানো হলে সেটি এমন zone-এ পড়তে পারে, যেখানে Pod-এর জায়গা নেই। তখন Pod Pending থাকে। WaitForFirstConsumer এই সমস্যা কাটায়: scheduler আগে node বাছে, disk সেই zone-এ বানানো হয়।
Disk যে zone-এ, Pod-কেও সেখানেই থাকতে হয়
Zonal block volume and node affinity
web-0 zone B-তে চালু হতে পারবে না, যদি না zone A-তে অন্য node থাকে।এজন্য StatefulSet-এর replica-গুলো আলাদা zone-এ ছড়িয়ে রাখা হয়, আর প্রতি zone-এ ফাঁকা জায়গা রাখা হয়। Provider-ভেদে zone-redundant disk-এর ব্যবস্থাও আছে, তবে তার সীমা আর দাম আলাদা। Replica দিয়ে ডেটার কপি অন্য zone-এ রাখলে disk বিকল হলেও সেবা চলে। পর্ব ২০-এ block আর file storage-এর পার্থক্য আছে, আর System Design-এর replication পর্বে আছে replica-র তত্ত্ব।
আরও গভীরে: StatefulSet “at most one” আচরণ রক্ষা করতে চায়: একই নামের দুটি Pod একসাথে থাকবে না। Node যোগাযোগহীন হলে Pod Terminating বা Unknown থেকে যেতে পারে, আর controller নতুনটি বানায় না। --force দিয়ে মুছলে পুরোনো process হয়তো তখনও disk-এ লিখছে, ফলে split-brain-এর ঝুঁকি আসে। তাই force delete-এর আগে node সত্যিই বন্ধ কি না নিশ্চিত হোন। Kubernetes এখানে ইচ্ছা করেই availability-র বদলে safety বেছে নিয়েছে।
পরীক্ষা ও ইন্টারভিউতে:
- ConfigMap বনাম Secret? দুটি প্রায় একই গঠনের; Secret-এ base64, tmpfs mount আর আলাদা RBAC-এর সুবিধা, কিন্তু base64 encryption নয়।
- PV বনাম PVC? PV হলো cluster-এর আসল storage, PVC হলো namespace-এর দরখাস্ত।
- RWO মানে কী? এক node-এ পড়া-লেখা। এক Pod চাইলে RWOP.
- Deployment বনাম StatefulSet? StatefulSet স্থায়ী নাম, ক্রম আর replica-প্রতি PVC দেয়।
- PVC কেন
Pending? মানানসই class নেই, provisioner সমস্যা, অথবাWaitForFirstConsumer-এ Pod-এর অপেক্ষা।
১০ · AI-এর যুগে config আর storage
AI মিনিটে YAML লিখে দেয়। Config আর storage-এর ভুল কিন্তু চালানোর সময় ধরা পড়ে না। ডেটা হারালে বা গোপন মান ফাঁস হলে তবেই বোঝা যায়। তাই এখানে যাচাই সবচেয়ে জরুরি।
| AI যা প্রায়ই করে | কেন বিপজ্জনক | কীভাবে ধরবেন |
|---|---|---|
| Secret-এর YAML-এ আসল মান base64 করে দেয়, আর বলে “encoded, তাই নিরাপদ” | Git-এ গেলে মান প্রকাশ্য | base64 -d চালিয়ে দেখুন, secret scanner রাখুন |
| Database Deployment-এ চালায়, একটি RWO PVC সব replica-য় দেয় | একই ডেটায় একাধিক লেখক, বা Pod আটকে থাকা | Stateful কাজে StatefulSet বা managed service |
storageClassName মনগড়া লেখে | PVC চিরকাল Pending | kubectl get sc-এর সাথে মেলান |
| RWX চায় block storage-এ | Provisioning ব্যর্থ | Driver-এর docs-এ access mode দেখুন |
Config-এ subPath দেয়, আর বলে মান আপনা থেকে বদলাবে | পুরোনো মান চুপচাপ চলে | মান বদলে Pod-এর ভেতরে পড়ে দেখুন |
“জায়গা খালি করতে” kubectl delete pvc --all লিখে দেয় | Delete policy-তে ডেটা শেষ | আগে kubectl get pv-এ policy দেখুন, backup নিশ্চিত করুন |
latest tag, পুরোনো API version, অচেনা field দেয় | পরে ভাঙে, বা field উপেক্ষিত হয় | --dry-run=server, kubectl explain |
প্রশ্নে পরিবেশ আর ঝুঁকি দুটোই বলে দিন, আর আসল secret কখনো prompt-এ দেবেন না:
AI-এর লেখা manifest চালানোর আগে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Secret যদি শুধু base64 হয়, Kubernetes তাহলে Secret নামে আলাদা object রাখল কেন?
কারণ Secret-এর মূল্য encoding-এ নয়, আলাদা ব্যবহারে। Base64 এসেছে একটি সহজ প্রয়োজন থেকে: Kubernetes-এর API JSON-এ কথা বলে, আর certificate বা key-র মতো মান যেকোনো byte হতে পারে। JSON string-এ এমন byte সরাসরি রাখা যায় না, তাই base64. এটি লুকানোর জন্য ছিল না।
আলাদা object হওয়ায় অনেক কিছু সম্ভব হয়েছে। RBAC-এ ConfigMap পড়ার অনুমতি দিয়েও Secret আটকানো যায়। etcd encryption শুধু Secret-এর জন্য চালু করা যায়, যা উপরের config ফাইলেই দেখেছেন। Secret volume tmpfs-এ বসে, তাই node-এর disk-এ লেখা হয় না। kubelet শুধু সেই Pod-এর Secret আনে, যে Pod সেই node-এ চলছে। আর describe মান না দেখিয়ে শুধু আকার দেখায়।
এই স্তরগুলো একবারে আসেনি। etcd encryption-এর সুবিধা এসেছে Kubernetes-এর ইতিহাসে পরে, আর KMS-ভিত্তিক envelope encryption তার পরে পরিণত হয়েছে। KMS v2 ১.২৯ version-এ stable হয়। মানে নামটি আগে এসেছে, সুরক্ষা ধাপে ধাপে যোগ হয়েছে। সমালোচনাও ন্যায্য: “Secret” নামটি অনেককে নিরাপত্তার ভুল ধারণা দেয়। Kubernetes-এর নিজের docs-ও সতর্ক করে যে default-এ মান encrypted না-ও থাকতে পারে।
ফলে একজন সচেতন প্রকৌশলী প্রশ্ন বদলে নেন। তিনি জিজ্ঞেস করেন না “মান কি লুকানো?” তিনি জিজ্ঞেস করেন: disk চুরি হলে কী হবে, backup ফাঁস হলে কী হবে, কোন মানুষ আর কোন service account পড়তে পারে, আর ফাঁস হলে আমি জানব কীভাবে। প্রতিটি প্রশ্নের উত্তর আলাদা স্তর।
মূল উপলব্ধি: নিরাপত্তা একটি object-এর নামে থাকে না। এটি থাকে স্তরে: encryption, অনুমতি, audit আর বাইরের চাবি। Secret সেই স্তরগুলো জোড়ার জায়গা মাত্র।
প্র ০২Twelve-Factor বলে config env-এ রাখতে। তাহলে Kubernetes-এ ফাইলকে অনেকে ভালো বলেন কেন?
The Twelve-Factor App লেখা হয়েছিল ২০১১ সালের দিকে, Heroku-র Adam Wiggins-এর নেতৃত্বে। সেই পরিবেশে env ছিল সবচেয়ে সরল আর ভাষা-নিরপেক্ষ পথ। কোডে বসে থাকা config-এর তুলনায় এটি বিশাল উন্নতি, আর আজও সাধারণ config-এর জন্য চমৎকার। App শুরুতে একবার পড়ে, মান সারাজীবন একই থাকে, ফলে আচরণ অনুমান করা সহজ।
কিন্তু বিশেষ করে secret-এ env-এর দুর্বলতা আছে। Process-এর সম্পূর্ণ env অনেক সময় crash report বা debug পাতায় চলে যায়। Child process-ও পুরোটা উত্তরাধিকার পায়। Container inspect আর Pod-এর manifest-এও কখনো মান দেখা যায়, বিশেষত যদি মান সরাসরি YAML-এ লেখা হয়। আর এই পর্বের আসল run-এই দেখেছেন, চলমান Pod-এ env বদলায় না। তাই rotation-এ restart লাগে।
ফাইলের সুবিধা হলো অনুমতি দেওয়া যায় (0400), tmpfs-এ থাকে, আর নতুন মান চলমান Pod-এ পৌঁছায়। বড় বা গঠনযুক্ত config, যেমন একটি TLS certificate বা YAML ফাইল, env-এ রাখা এমনিতেই অসুবিধার। কিন্তু দাম আছে: app-কে ফাইল আবার পড়তে শিখতে হবে। অর্ধেক Pod নতুন মান আর অর্ধেক পুরোনো মান নিয়ে কিছুক্ষণ চলতে পারে। আর ভুল config restart ছাড়াই সব জায়গায় পৌঁছাতে পারে।
বাস্তব সমাধান তাই মিশ্র। সাধারণ, অ-গোপন মান env-এ। গোপন মান আর বড় config ফাইলে। আর দুটি ক্ষেত্রেই বদলের সময় সচেতন rolling update, যাতে পরিবর্তন ধাপে ধাপে যায় আর ফেরার পথ থাকে। অনেক দল ConfigMap-এর hash Pod template-এ বসায়, যাতে config বদলালে নিজে থেকেই নতুন rollout হয়। Kustomize এবং Helm-এর পরিচিত নকশায় এই কৌশল দেখা যায়, যা পর্ব ৫০-এর বিষয়।
মূল উপলব্ধি: “env না file” প্রশ্নের চেয়ে বড় প্রশ্ন হলো: config বদল কীভাবে নিয়ন্ত্রিত, ধাপে ধাপে আর ফেরতযোগ্যভাবে চলমান সিস্টেমে পৌঁছাবে।
প্র ০৩Dynamic PV-র default reclaim policy কেন Delete? এটি কি বিপজ্জনক নয়?
Default-টি একটি ছোট দর্শন প্রকাশ করে: যা স্বয়ংক্রিয়ভাবে তৈরি হলো, তা স্বয়ংক্রিয়ভাবে পরিষ্কারও হোক। Dynamic provisioning-এ মানুষ disk হাতে বানায় না। PVC-ই disk-এর জীবনের একমাত্র কারণ। Retain default হলে পরীক্ষার cluster-এ, CI-তে আর প্রতিদিনের কাজে হাজার হাজার disk পড়ে থাকত, আর কেউ তাদের মালিক খুঁজে পেত না। Cloud-এ সেই পরিত্যক্ত disk-গুলো প্রতি মাসে বিল করে যেত। হাতে বানানো PV-র default কিন্তু Retain, কারণ সেখানে মানুষ ইচ্ছা করে disk বানিয়েছেন।
তবে ঝুঁকি বাস্তব। একটি ভুল kubectl delete namespace বা delete pvc Delete policy-তে আসল disk মুছে দিতে পারে। Kubernetes কিছু সুরক্ষা দেয়: Pod ব্যবহার করছে এমন PVC একটি finalizer-এর কারণে তখনই মোছে না, Pod যাওয়া পর্যন্ত অপেক্ষা করে। কিন্তু এটি ভুল থেকে সম্পূর্ণ রক্ষা করে না, শুধু চলমান Pod-এর নিচ থেকে disk সরানো আটকায়।
মানুষের ভুল থেকে বড় দুর্ঘটনার একটি পরিচিত উদাহরণ GitLab-এর ২০১৭ সালের database ঘটনা। প্রকাশিত postmortem অনুযায়ী, একজন প্রকৌশলী ভুল server-এ ডেটা মুছে ফেলেন, আর তারপর দেখা যায় কয়েকটি backup ব্যবস্থা ঠিকমতো কাজ করছিল না। এটি Kubernetes-এর ঘটনা নয়, কিন্তু শিক্ষা একই: মোছা সহজ, আর ফেরার পথ না পরীক্ষা করলে সেটি নেই।
তাই production-এ একটি যুক্তিসঙ্গত নকশা হলো মূল ডেটার জন্য Retain policy-র আলাদা StorageClass, cloud-এর disk snapshot, আর কে Delete চালাতে পারে তার RBAC সীমা। সাথে একটি নিয়মিত হিসাব, যা Released অবস্থার PV আর মালিকহীন disk খুঁজে বের করে। Retain শুধু দরজা খোলা রাখে, ডেটার backup-এর জায়গা নেয় না।
মূল উপলব্ধি: Default-গুলো সাধারণ ক্ষেত্রের জন্য বানানো। আপনার সবচেয়ে দামি ডেটা সাধারণ ক্ষেত্র নয়, তাই তার policy নিজে হাতে ঠিক করুন।
প্র ০৪StatefulSet থাকতেও production database Kubernetes-এ রাখা নিয়ে এত তর্ক কেন?
শুরুতে Kubernetes-এর নকশা ছিল মূলত stateless কাজের জন্য, যেখানে Pod যেকোনো সময় বদলানো যায়। Stateful কাজের জন্য ২০১৬ সালে PetSet নামে একটি alpha সুবিধা আসে। এই নাম “pets vs cattle” উপমা থেকে: গবাদি পশু বদলানো যায়, পোষা প্রাণীকে আলাদা যত্ন লাগে। Kubernetes ১.৫-এ এটি StatefulSet নাম পায়, আর ১.৯-এ, ২০১৭ সালের শেষ দিকে, stable হয়। তাই “Kubernetes-এ database চলে না” কথাটি এক সময় অনেকটা সত্য ছিল, এখন তা আর ঠিক নয়।
তর্ক এখন অন্য জায়গায়। পক্ষের যুক্তি: একই deployment পদ্ধতি সব পরিবেশে, CloudNativePG-র মতো operator দিয়ে failover আর backup স্বয়ংক্রিয়, on-premises-এ চলার স্বাধীনতা, আর provider-এ বাঁধা না থাকা। বিপক্ষের যুক্তি: Pod সরে গেলে disk attach হতে সময় লাগে, node upgrade মানে ডেটাবেসেও ঝাঁকুনি, memory limit-এ OOM kill মানে হঠাৎ database বন্ধ, আর সমস্যার সময় Kubernetes আর database দুটিই একসাথে বুঝতে হয়।
Kubernetes শুধু “নাম আর disk” দেয়, এটি মনে রাখলে ছবিটি পরিষ্কার হয়। প্রকৃত database-এর কাজগুলো, যেমন কোন replica সবচেয়ে এগিয়ে, কীভাবে নতুন primary বাছা হবে, quorum আছে কি না, এসব StatefulSet জানে না। Operator সেই জ্ঞান কোডে লেখার চেষ্টা। তবে operator-ও একটি জটিল software, যাকে পরীক্ষা করতে আর upgrade করতে হয়।
সিদ্ধান্তটি তাই প্রযুক্তির চেয়ে দলের বিষয়। প্রশ্নগুলো হলো: রাত ৩টায় database failover ব্যর্থ হলে কে ঠিক করবে? শেষ কবে restore পরীক্ষা হয়েছে? Managed service-এর বাড়তি খরচ কি একজন প্রকৌশলীর সময়ের চেয়ে কম? ছোট দলের জন্য উত্তর প্রায়ই managed service. আর যাঁদের ডেটা আইনি কারণে নির্দিষ্ট জায়গায় রাখতে হয়, বা অনেক ছোট database চালাতে হয়, তাঁদের কাছে Kubernetes-এর আকর্ষণ বেশি।
মূল উপলব্ধি: “চালানো যায়” আর “চালানো উচিত” দুটি আলাদা প্রশ্ন। প্রথমটির উত্তর প্রযুক্তি দেয়, দ্বিতীয়টির উত্তর দেয় আপনার দলের দক্ষতা, খরচ আর ঝুঁকি সহ্য করার ক্ষমতা।
প্র ০৫StatefulSet-এ ক্রম আর স্থায়ী নাম কি সবসময় দরকার? Parallel মোড কখন ঠিক?
ক্রমের মূল্য বোঝা যায় একটি পুরোনো সমস্যা থেকে: cluster গঠন। ধরুন তিনটি node-এর একটি consensus-ভিত্তিক সিস্টেম, যেমন etcd. প্রথম সদস্য চালু হওয়ার পর অন্যরা তার ঠিকানা জেনে যোগ দেয়। সব একসাথে চালু হলে কে কার সাথে যোগ দেবে, তা অস্পষ্ট হয়ে যায়। সবচেয়ে খারাপ ক্ষেত্রে দুটি আলাদা cluster তৈরি হতে পারে। ক্রমিক শুরু এই সমস্যা সরল করে: web-0 সবসময় আগে আসে, তাই “প্রথম সদস্য কে” প্রশ্নের উত্তর থাকে।
স্থায়ী নামের কারণও একই ধরনের। Replication config-এ লেখা থাকে “db-0-এর কাছ থেকে নকল করুন।” IP বদলালে এই config ভাঙত, যা আপনি আসল run-এ দেখেছেন: 10.1.0.242 হয়ে গেছে 10.1.0.252, কিন্তু নামটি রয়ে গেছে। আর ডেটা ও নামের জোড়া নিশ্চিত করে যে replica নিজের পুরোনো log থেকে দ্রুত ধরে নিতে পারে, পুরো ডেটা নতুন করে নকল করতে হয় না।
উল্টো ক্রমের rolling update-এরও যুক্তি আছে। অনেক নকশায় db-0 primary. শেষে তাকে বদলালে failover-এর সংখ্যা কমে। আর partition দিয়ে আগে একটি replica-তে নতুন version পরীক্ষা করা যায়।
কিন্তু সব কাজে ক্রম লাগে না। Cassandra-র মতো কিছু সিস্টেমে সদস্যরা নিজেরাই একে অপরকে খুঁজে নেয়, আর ক্রম শুধু সময় নষ্ট করে। ৫০টি replica ক্রমে চালু হলে, প্রতিটিতে ৩০ সেকেন্ড ধরলে, ৫০ × ৩০ = ১,৫০০ সেকেন্ড, মানে ২৫ মিনিট লাগবে। তখন podManagementPolicy: Parallel দরকারি। আবার, ক্রম ঝুঁকিও তৈরি করে: একটি Pod আটকে গেলে পরের সবাই আটকে যায়, যা এই পর্বের gate উদাহরণে দেখেছেন। ভুল version-এ আটকে গেলে অনেক ক্ষেত্রে হাতে হস্তক্ষেপ লাগে। আর নাম স্থায়ী হলেও Pod আসলে নতুন, তাই app-কে ধরে নিতে হবে যে memory-র সব কিছু হারিয়েছে।
মূল উপলব্ধি: StatefulSet একটি যন্ত্র, পরিচয় আর ক্রমের দাম আছে। আপনার সিস্টেম ঠিক কোন নিশ্চয়তা চায়, তা জেনেই বেছে নিন। কোনো পরিচয় বা নিজস্ব disk না লাগলে Deployment-ই সরল আর ভালো।
অনুশীলন
প্রথম তিনটি কাগজ-কলমেই করা যায়। শেষটির জন্য নিজের local cluster থাকলে ভালো, না থাকলে কমান্ডগুলো লিখে যুক্তি মেলান।
-
হিসাব করুন: ৫ replica-র একটি StatefulSet, প্রতিটি PVC ৫০ GiB, policy Retain. ২ replica-তে নামানো হলো, পরে পুরো StatefulSet মুছে ফেলা হলো, কিন্তু PVC কেউ মোছেনি। ধরে নিন disk-এর দাম মাসে GiB-প্রতি $০.১০। (ক) কমানোর পরে কতটি PVC অব্যবহৃত? (খ) মাসিক অপচয় কত? (গ) StatefulSet মোছার পরে মাসিক অপচয় কত? (ঘ) ৬ মাসে কত?
(ক) ৫ − ২ = ৩টি PVC:
data-…-2,-3,-4. StatefulSet কমালেও PVC মোছে না।(খ) ৩ × ৫০ × ০.১০ = $১৫ প্রতি মাসে।
(গ) StatefulSet মোছার পর ৫টি PVC-ই অব্যবহৃত: ৫ × ৫০ × ০.১০ = $২৫ প্রতি মাসে।
(ঘ) ৬ × ২৫ = $১৫০. দাম এখানে ধরে নেওয়া মান। মূল কথা হলো এই বিল চুপচাপ চলে।
-
Manifest সমালোচনা করুন: একজন লিখেছেন: Deployment,
replicas: 3, imagepostgres:latest, সব Pod একটি RWO PVC শেয়ার করে, আর ConfigMap-এPOSTGRES_PASSWORD: admin123. অন্তত পাঁচটি সমস্যা বের করুন, আর ভালো নকশা দিন।- Password ConfigMap-এ: Secret বা বাইরের secret manager লাগবে, আর মান git-এ থাকবে না।
- দুর্বল password: নকল উদাহরণ হলেও এমন মান production-এ চলবে না।
- Deployment: Database-এর স্থায়ী পরিচয় লাগে, তাই StatefulSet.
- এক PVC তিন Pod: এক node-এ থাকলে তিনটি Postgres একই ডেটা ফোল্ডার ধরতে পারে, বা অন্য node-এ Pod আটকে যাবে। RWO এক node-এর কথা বলে, এক লেখকের নিশ্চয়তা নয়।
latest: অজানা major version আসতে পারে। নির্দিষ্ট version দরকার।- Replication নেই: তিনটি কপি মানেই high availability নয়, Postgres-এর replication লাগবে।
- ভালো নকশা: Managed database, অথবা StatefulSet বা operator, replica-প্রতি PVC, Retain policy, নিয়মিত backup আর restore পরীক্ষা।
-
ফল অনুমান করুন: ConfigMap
c-তেX=1. Podp-তেenvFrom-এc,env-এY=2, আরc/cfg-এ পুরো volume হিসেবে,/one/X-এ subPath দিয়ে mount করা। কমান্ড:kubectl patchকরেX=9, দুই মিনিট অপেক্ষা, তারপরprintenv X,cat /cfg/X,cat /one/X,printenv Y. এরপর Pod মুছে একই manifest আবার চালিয়ে একই চারটি কমান্ড। কী দেখবেন?একই Pod-এ:
printenv Xদেখায় 1,/cfg/Xদেখায় 9,/one/Xদেখায় 1,printenv Yদেখায় 2.নতুন Pod-এ: 9, 9, 9, 2. নতুন container শুরুতেই ConfigMap-এর এখনকার মান পায়।
-
কমান্ড লিখুন: Retain policy-র একটি PV
Releasedহয়ে আছে। ঐচ্ছিক local cluster-এ ক্রম লিখুন: (ক) অবস্থা দেখা (খ) পুরোনো PVC-র বন্ধন সরানো (গ) সেই PV-র নাম দিয়ে নতুন PVC বানানো (ঘ) ফাইল পড়ে যাচাই (ঙ) শেষে পরিষ্কার করা। Cloud-এ করলে খরচ হতে পারে, তাই পরিষ্কার করতে ভুলবেন না।kubectl get pv # (a) STATUS Released kubectl patch pv PV_NAME --type json \ -p '[{"op":"remove","path":"/spec/claimRef"}]' # (b) becomes Available kubectl apply -f new-claim.yaml # (c) spec.volumeName: PV_NAME kubectl get pvc # STATUS Bound kubectl exec some-pod -- cat /data/orders.txt # (d) read it kubectl delete pod some-pod # (e) clean up kubectl delete pvc new-claim kubectl delete pv PV_NAMEনতুন PVC-তে StorageClass, access mode আর আকার PV-র সাথে মিলতে হবে। Retain policy-র PV মুছলে cloud-এর disk মোছে না, সেটি provider-এর console বা CLI থেকে আলাদা করে মুছতে হবে। এই পর্বের আসল run-এ পরিষ্কারের জন্য policy Delete-এ বদলে নেওয়া হয়েছিল, তখন PV আর local ডেটা দুটিই চলে যায়।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমImmediate binding-এ dynamic provisioning-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📝 ব্যবহারকারী PVC বানালেন, status Pending
- 🗂️ Controller StorageClass দেখে provisioner-কে কাজ দিল
- 💽 Provisioner আসল storage আর PV বানাল
- 🔗 PV আর PVC Bound হলো
- 📦 Pod চালু হলো, kubelet volume mount করল
একটি Kubernetes Secret-এর মান ZGVtbw==. এটি থেকে কোনটি সত্য?
- মানটি AES দিয়ে encrypted, চাবি ছাড়া পড়া যাবে না
- মানটি একমুখী hash, আসল মান ফেরানো যাবে না
- মানটি base64 encoding, চাবি ছাড়াই আসল মান ফেরানো যায়
- এই মান দেখলেই বোঝা যায় etcd encryption চালু আছে
একটি ConfigMap-এর মান বদলানো হলো। চলমান Pod-এ একই মান env হিসেবে, পুরো volume হিসেবে আর subPath দিয়ে আছে। কোনো restart হয়নি। কিছু সময় পরে নতুন মান কোথায় দেখা যাবে?
- তিনটিতেই
- শুধু পুরো volume হিসেবে mount করা ফাইলে
- শুধু env-এ
- কোথাও নয়, Pod restart ছাড়া কিছুই বদলায় না
ReadWriteOnce PVC সম্পর্কে কোনটি ঠিক?
- একটি PVC একসাথে একটিমাত্র Pod ব্যবহার করতে পারে
- PVC-তে জীবনে একবারই লেখা যায়
- অনেক node একসাথে পড়া-লেখা করতে পারে
- একই সময়ে একটি node পড়া-লেখা করতে পারে, সেই node-এ একাধিক Pod থাকতে পারে
Retain policy-র একটি PV-র PVC মুছে ফেলা হলো। ঠিক পরে কী অবস্থা হবে?
- PV
Releasedহবে, ডেটা থাকবে, নতুন PVC আপনা থেকে জুড়বে না - PV আর disk দুটিই মুছে যাবে
- PV নিজে থেকে
Availableহয়ে পরের PVC-কে দেবে - Disk ফাঁকা করে PV আবার ব্যবহারের জন্য তৈরি হবে
৩ replica-র একটি StatefulSet web-এ Pod web-1 মুছে দেওয়া হলো। কী ঘটবে?
- নতুন এলোমেলো নামের Pod আসবে, আর নতুন PVC পাবে
- একই নাম, কিন্তু ফাঁকা নতুন PVC
- একই নামে নতুন Pod আসবে, আগের PVC লাগবে, তবে IP বদলাতে পারে
- সব Pod ক্রমে restart হবে
Multi-zone cluster-এ block disk-এর StorageClass-এ সাধারণত WaitForFirstConsumer কেন ব্যবহার হয়?
- এতে disk দ্রুততর হয়
- Pod কোন zone-এ বসবে তা জানার পরে disk বানানোর জন্য
- PVC মোছার পর ডেটা রেখে দেওয়ার জন্য
- একটি disk অনেক zone-এর node-এ লাগানোর জন্য
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Config image-এর বাইরে: env শুরুতে একবার বসে, mounted ফাইল বদলায়, subPath বদলায় না।
envজেতেenvFrom-এর ওপর, আরenvFrom-এ পরের উৎস জেতে। ফাইল বনাম env ঠিক করে app.- Base64 encryption নয়। Secret রক্ষা পায় etcd encryption, RBAC আর বাইরের secret manager দিয়ে।
- Pod → PVC → PV → disk. Pod মুছলে ডেটা থাকে; PVC মুছলে reclaim policy ঠিক করে। Retain মানে ডেটাও থাকে, বিলও থাকে।
- StatefulSet দেয় স্থায়ী নাম, ক্রম আর replica-প্রতি PVC, কিন্তু failover বা backup দেয় না। PVC পেছনে থেকে যায়, আর block disk zone-এ বাঁধা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৮ · Scheduling আর resource — request, limit, QoS আর autoscalingপরবর্তী পর্বPod কোথায় বসবে আর কত resource পাবে।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ৩৩ · Secret আর KMSআগের ভিত্তিEnvelope encryption, rotation আর secret manager.
- পর্ব ২৪ · Backup আর disaster recoveryপাশের ট্র্যাকRPO, RTO, snapshot আর restore পরীক্ষা।
- DBMS & SQL · Backup আর replicationগভীরে যানDatabase-এর ভেতর থেকে backup আর replication.