এই পর্বে যা শিখবেন
- Immutable artifact, container image-এর layer, tag বনাম digest আর registry — কেন “একবার build” নিয়ম
- CI/CD pipeline-এর ধাপ, trunk-based development, ছোট ব্যাচ আর DORA-র চারটি metric
- Kubernetes-এর Pod, ReplicaSet, Deployment, Service; maxSurge আর maxUnavailable একটি সিমুলেটরে মেপে
- Recreate, rolling, blue-green, canary আর shadow — কোনটি কখন; canary-র পরিসংখ্যান আর blast radius
- Graceful shutdown, connection draining আর zero-downtime database migration-এর expand–contract
১ · কোড থেকে production — build একবার, artifact অপরিবর্তনীয়
আপনার ল্যাপটপে কোড চলছে। এবার সেটি লাখো মানুষের কাছে পৌঁছাতে হবে — কোনো অনুরোধ না ভেঙে, আর ভুল হলে কয়েক মিনিটে ফিরে আসার পথ রেখে। এই পুরো যাত্রার নাম deployment. এই পর্বের প্রশ্ন: সিস্টেমটি কীভাবে প্রতিদিন নিরাপদে বদলানো যায়। অনেক বড় outage নকশার ত্রুটিতে নয়, একটি পরিবর্তনের মুহূর্তে শুরু হয় — নতুন কোড, নতুন config বা নতুন schema.
একটি ওষুধ কারখানার কথা ভাবুন। একটি ব্যাচ একবারই তৈরি হয়, পরীক্ষাগারে যাচাই হয়, তারপর সিল করা বাক্সে একটি ব্যাচ নম্বর বসে। ঢাকা, রাজশাহী, সিলেটের প্রতিটি ফার্মেসিতে সেই একই সিল করা বাক্স যায় — কেউ নিজের দোকানে নতুন করে বড়ি বানায় না। কোনো সমস্যা হলে ব্যাচ নম্বর ধরে ঠিক ওই বাক্সগুলো ফেরত আনা যায়। Software-এও তাই: একবার build, পরীক্ষিত জিনিসটিই সবখানে, আর একটি অপরিবর্তনীয় নম্বর দিয়ে চেনা।
Build once: একটি commit থেকে একবারই একটি immutable artifact তৈরি হয় — একটি container image, একটি jar ফাইল বা একটি VM image. Test, staging আর production-এ সেই একই artifact “উন্নীত” (promote) হয়। প্রতিটি পরিবেশের জন্য আলাদা করে build করলে staging-এ যা পরীক্ষা করেছেন আর production-এ যা চলছে, সেগুলো আলাদা জিনিস — হয়তো মাঝখানে একটি library-র নতুন সংস্করণ ঢুকে পড়েছে। পরিবেশের পার্থক্য থাকবে শুধু config-এ: database-এর ঠিকানা, কতগুলো কপি, কোন feature চালু। Heroku-র প্রকৌশলীদের ২০১১ সালের দিকে লেখা Twelve-Factor App নীতিমালা এই বিভাজনকে জনপ্রিয় করে: config আসে পরিবেশ থেকে, কোডের ভেতর থেকে নয়।
আজকের সবচেয়ে প্রচলিত artifact হলো container image. Container নিজে কী — namespace আর cgroup দিয়ে ঘেরা একটি সাধারণ process — সেটি অপারেটিং সিস্টেম কোর্সের container পর্বে বিস্তারিত আছে; VM-এর সাথে তুলনা virtual machine পর্বে। এখানে আমাদের দরকার তিনটি ধারণা:
- Layer: একটি image কয়েকটি layer-এর স্তূপ — নিচে base OS-এর ফাইল, তার ওপর runtime, তারপর dependency, সবশেষে আপনার কোড। প্রতিটি layer তার বিষয়বস্তুর hash দিয়ে চেনা, তাই যে layer বদলায়নি সেটি আবার build বা আবার download করতে হয় না। কোড বদলালে শুধু ওপরের পাতলা layer নতুন।
- Tag বনাম digest: tag (যেমন
shop:1.4.0বাshop:latest) একটি নাম, যা যেকোনো সময় অন্য image-এর দিকে সরানো যায়। digest (যেমনshop@sha256:9f2c…) image-এর বিষয়বস্তুর SHA-256 hash — বিষয়বস্তু এক বিট বদলালেও digest বদলায়। তাই digest একটি প্রতিশ্রুতি, tag একটি লেবেল। - Registry: container registry হলো image-এর গুদাম। CI image বানিয়ে সেখানে push করে, আর cluster-এর প্রতিটি যন্ত্র সেখান থেকে pull করে।
| বিষয় | Tag (:1.4.0, :latest) | Digest (@sha256:…) |
|---|---|---|
| বদলানো যায়? | হ্যাঁ — একই নাম নতুন image-এ সরানো যায় | না — বিষয়বস্তু থেকেই হিসাব |
| দুই যন্ত্রে একই কোড নিশ্চিত? | না — আগে আর পরে pull করলে আলাদা হতে পারে | হ্যাঁ — একই digest মানে একই bytes |
| মানুষের পড়ার সুবিধা | ভালো | খারাপ, লম্বা hash |
| কোথায় মানায় | মানুষের আলোচনা, release-এর নাম | Deploy-এর manifest, rollback, নিরীক্ষা |
একটি Dockerfile-এ layer-এর ক্রম গুরুত্বপূর্ণ — যা কম বদলায় তা আগে, যাতে cache কাজে লাগে:
# base image pinned by digest, not by a moving tag
FROM node:20-slim@sha256:<digest>
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci # installs the exact versions in the lockfile
COPY . . # app code last: changes most often
USER node # do not run as root
CMD ["node", "server.js"]এই চিন্তার বড় রূপ immutable infrastructure: চলমান সার্ভারে ssh করে হাতে সংশোধন নয়; বদল দরকার হলে নতুন image বানিয়ে পুরোনোটি বদলে দেওয়া। হাতে সংশোধনের ফল configuration drift — দশটি “একই” সার্ভার চুপচাপ দশ রকম হয়ে যায়। Stateless সার্ভিস (পর্ব ১৩) এই নিয়মের পূর্বশর্ত: কপিটির ভেতরে মূল্যবান কিছু না থাকলেই তাকে নির্ভয়ে ফেলে দিয়ে নতুন কপি বসানো যায়।
Deploy-এর manifest-এ digest লিখুন, tag নয়। তখন “production-এ ঠিক কী চলছে” প্রশ্নের উত্তর একটি নির্দিষ্ট hash, আর rollback মানে আগের hash-এ ফেরা — যা গতকাল ঠিক যে bytes চলছিল, আজও সেই bytes.
২ · CI/CD pipeline — build, test, scan, deploy, verify
Continuous integration (CI) মানে প্রতিটি পরিবর্তন দিনে কয়েকবার মূল শাখায় মেলানো, আর প্রতিবার স্বয়ংক্রিয়ভাবে build আর test চালানো। Continuous delivery (CD) এক ধাপ এগিয়ে: প্রতিটি সফল build যেকোনো সময় production-এ পাঠানোর মতো তৈরি থাকে, শেষ সিদ্ধান্ত একজন মানুষের এক ক্লিক। Continuous deployment-এ সেই ক্লিকও নেই — সব যাচাই পাস করলে pipeline নিজেই production-এ নিয়ে যায়। Jez Humble আর David Farley-র ২০১০ সালের বই Continuous Delivery এই শব্দগুলোকে প্রতিষ্ঠিত করে।
একটি সাধারণ pipeline-এর পাঁচটি ধাপ, প্রতিটি আগেরটি ব্যর্থ হলে থেমে যায় (fail fast):
- Build: কোড compile, dependency lockfile থেকে, image তৈরি আর digest লেখা — একবারই।
- Test: দ্রুত unit test আগে, ধীর integration আর contract test পরে।
- Scan আর sign: dependency-র জানা দুর্বলতা খোঁজা, উপাদানের তালিকা (SBOM) তৈরি, image-এ স্বাক্ষর — অংশ ৮-এ।
- Deploy: image registry-তে push, তারপর cluster-কে নতুন digest জানানো — প্রথমে canary-তে।
- Verify: deploy-এর পরে smoke test আর metric-এর তুলনা — error rate, latency, SLO (পর্ব ৫০)। “Deploy শেষ” মানে “ঠিকভাবে চলছে প্রমাণিত”, শুধু “কোড পৌঁছেছে” নয়।
Commit থেকে ব্যবহারকারী — pipeline, registry আর cluster
CI/CD pipeline pushes one image to a registry; the cluster runs stable and canary behind a load balancer
ছোট ব্যাচ কেন নিরাপদ: ডাকঘরে মাসের শেষে পাঁচশো চিঠির একটি বস্তা পাঠালে একটি ভুল ঠিকানার চিঠি খুঁজে পেতে পুরো বস্তা ঘাঁটতে হয়; প্রতিদিন বিশটি পাঠালে ভুলটি কোন দিনের, তা সঙ্গে সঙ্গে বোঝা যায়। Deploy-এও তাই: তিনটি পরিবর্তনে কারণ খোঁজা সহজ, ফেরা সস্তা; তিনশোটিতে প্রতিটি deploy ভয়ের ঘটনা। Trunk-based development এই চক্র ভাঙে: সবাই দিনে অন্তত একবার মূল শাখায় (trunk) ছোট পরিবর্তন মেলান, দীর্ঘজীবী শাখা নেই; অসম্পূর্ণ feature লুকানো থাকে feature flag-এর পেছনে (অংশ ৪)।
Deploy-প্রক্রিয়া কতটা ভালো, মাপবেন কীভাবে? DevOps Research and Assessment (DORA) দলের বহু বছরের জরিপ-গবেষণা — যার সারাংশ Forsgren, Humble আর Kim-এর ২০১৮ সালের বই Accelerate — চারটি DORA metric প্রস্তাব করে। দুটি গতির, দুটি স্থিতিশীলতার:
| Metric | কী মাপে | ধরন |
|---|---|---|
| Deployment frequency | কত ঘন ঘন production-এ deploy হয় | গতি |
| Lead time for changes | commit থেকে production-এ চলা পর্যন্ত সময় | গতি |
| Change failure rate | কত শতাংশ deploy-এর পরে সংশোধন, rollback বা hotfix লাগে | স্থিতিশীলতা |
| Time to restore service | ব্যর্থ deploy-এর পর সেবা ফিরতে কত সময় (MTTR-এর কাছাকাছি ধারণা) | স্থিতিশীলতা |
গবেষণাটির সবচেয়ে আলোচিত দাবি: ভালো দলগুলো গতি আর স্থিতিশীলতা দুটিতেই ভালো — দুটির মধ্যে “একটি বাড়ালে অন্যটি কমে” এমন দর-কষাকষি দেখা যায়নি। তবে সাবধান: এটি জরিপভিত্তিক সম্পর্ক, আর পরের বছরের রিপোর্টগুলোতে metric-এর নাম ও সংজ্ঞা কিছুটা বদলেছে (সংস্করণভেদে মিলিয়ে নিন)। আর metric-কে লক্ষ্য বানালে মানুষ সংখ্যাটাকেই খেলায় — এটি দলের নিজের উন্নতির আয়না, দলগুলোর তুলনার মাপকাঠি নয়।
৩ · Orchestration — Kubernetes-এর মূল ধারণা আর rolling update
দশটি সার্ভারে হাতে image চালানো সম্ভব; পাঁচশোটিতে নয়। Container orchestration এই কাজ যন্ত্রের হাতে দেয়: কোন যন্ত্রে কোন container বসবে, কোনটি মরে গেলে নতুনটি কোথায়, traffic কাকে যাবে। সবচেয়ে প্রচলিত open-source orchestrator Kubernetes, যা Google ২০১৪ সালে open source করে আর যার নকশায় Google-এর ভেতরের Borg ব্যবস্থার অভিজ্ঞতা কাজে লেগেছে বলে তাঁদের প্রকাশিত paper-এ বলা আছে। এখানে ধারণার স্তরে চারটি জিনিস — নেটওয়ার্কের দিক (CNI, kube-proxy, Ingress) আছে Computer Networks কোর্সের cloud আর container নেটওয়ার্কিং পর্বে।
📦 Pod
Pod সবচেয়ে ছোট চালানোর একক — এক বা কয়েকটি container, যারা একই network ঠিকানা আর storage ভাগ করে। মরলে নতুন Pod নতুন ঠিকানায় জন্মায়।
একটি কপি🧮 ReplicaSet
ReplicaSet নিশ্চিত করে একটি নির্দিষ্ট template-এর ঠিক N-টি Pod চলছে — কম হলে বানায়, বেশি হলে মোছে।
“সবসময় ১০টি”🔁 Deployment
Deployment ReplicaSet-এর ওপরের স্তর: নতুন image এলে নতুন ReplicaSet বানিয়ে ধাপে ধাপে বড় করে আর পুরোনোটি ছোট করে — rolling update.
সংস্করণ বদল🚪 Service
Service একটি স্থির নাম আর ঠিকানা, যার পেছনে শুধু প্রস্তুত Pod-গুলোর তালিকা। Pod আসে-যায়, client-এর ঠিকানা বদলায় না।
স্থির দরজাসবকিছুর পেছনে একটি দর্শন: আপনি “কী করতে হবে” লেখেন না, লেখেন “কেমন থাকা উচিত” — declarative নকশা। “shop-এর ১০টি কপি, এই digest-এর image” — এই কাঙ্ক্ষিত অবস্থা cluster-এর etcd-এ জমা থাকে, আর controller-গুলো একটি control loop-এ বারবার দেখে আসল অবস্থা কাঙ্ক্ষিত অবস্থার সাথে মেলে কি না; না মিললে মেলানোর পরের ছোট পদক্ষেপটি নেয়।
Rolling update-এর দুটি লাগাম: rolling deployment-এ নতুন image এলে Deployment দুটি সংখ্যা মেনে চলে। maxSurge — কাঙ্ক্ষিত সংখ্যার চেয়ে সর্বোচ্চ কতগুলো বাড়তি Pod একসাথে থাকতে পারে। maxUnavailable — কাঙ্ক্ষিত সংখ্যার চেয়ে সর্বোচ্চ কতগুলো কম প্রস্তুত Pod থাকতে পারে। লেখার সময়ের Kubernetes documentation অনুযায়ী দুটির ডিফল্ট ২৫%; শতাংশ থেকে সংখ্যা বানাতে maxSurge ওপরের দিকে আর maxUnavailable নিচের দিকে round হয়। ১০টি কপিতে তাই ৩টি বাড়তি পর্যন্ত, আর অন্তত ৮টি প্রস্তুত।
“প্রস্তুত” কে ঠিক করে? readiness probe — Pod-এর একটি ঠিকানায় নিয়মিত জিজ্ঞাসা, “এখন কি traffic নেওয়া যাবে?” নতুন Pod যতক্ষণ এই পরীক্ষায় পাস না করে, Service তাকে traffic দেয় না, আর Deployment তাকে “উপলব্ধ” ধরে না, তাই পরের পুরোনো Pod-ও সরায় না। ভাঙা image-এর rollout এভাবে নিজেই আটকে যায় — নিরাপত্তার জাল। Readiness আর liveness-এর পার্থক্য, আর probe কতটা গভীর হবে, তা পর্ব ৪৮-এর বিষয়; সাধারণ health check দেখেছেন পর্ব ১৪-এ। একটি জরুরি সতর্কতা: আটকে যাওয়া rollout লেখার সময়ের Kubernetes-এ নিজে থেকে পেছনে ফেরে না — একটি সময়সীমা পেরোলে “ব্যর্থ” চিহ্ন পড়ে, ফেরানোর কাজ আপনার বা আপনার deploy-টুলের।
apiVersion: apps/v1
kind: Deployment
metadata: { name: shop }
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # rounds up -> at most 3 extra pods
maxUnavailable: 25% # rounds down -> at least 8 ready
selector: { matchLabels: { app: shop } }
template:
metadata: { labels: { app: shop } }
spec:
terminationGracePeriodSeconds: 30
containers:
- name: web
image: registry.example.com/shop@sha256:9f2c... # digest, not :latest
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
lifecycle:
preStop: { exec: { command: ["sleep", "5"] } }সংখ্যা দুটি কীভাবে capacity আর সময় বদলায়, নিজে মেপে দেখুন। নিচের সিমুলেটরে ১০টি কপি; প্রতি ৫ সেকেন্ডে controller একবার দেখে — আগে নতুন Pod বানায় (maxSurge মেনে), তারপর যতগুলো পুরোনো সরানো যায় সরায় (maxUnavailable মেনে)। প্রতিটি নতুন Pod প্রস্তুত হতে seed দিয়ে বাছা ২০ থেকে ৪০ সেকেন্ড লাগে (image pull, চালু হওয়া, warm-up).
// Rolling update of 10 replicas: how maxSurge / maxUnavailable shape capacity and time.
function mulberry32(a) { return () => { a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
const N = 10, TICK = 5; // controller looks every 5 s
function rollout(surge, unavail, show) {
const rand = mulberry32(53); // same pod start-up times for every setting
let old = N, t = 0, minCap = N, peak = N; const fresh = []; // fresh = ready-at times
while (true) {
const ready = fresh.filter(r => r <= t).length;
const want = Math.min(N, N + surge - old) - fresh.length; // scale up (surge cap)
for (let i = 0; i < want; i++) fresh.push(t + 20 + 5 * Math.floor(rand() * 5)); // 20..40 s
const kill = Math.min(old, old + ready - (N - unavail)); // scale down (floor)
if (kill > 0) old -= kill;
const cap = old + ready; minCap = Math.min(minCap, cap);
peak = Math.max(peak, old + fresh.length);
if (show) console.log(`t=${String(t).padStart(3)}s old ${String(old).padStart(2)} ` +
`new ${ready}/${fresh.length}`.padEnd(10) + ` serving ${String(cap).padStart(2)} ` +
"#".repeat(cap));
if (old === 0 && ready === N) return { t, minCap, peak };
t += TICK;
}
}
console.log("Timeline, maxSurge=3, maxUnavailable=2 (25% of 10, rounded up / down):");
rollout(3, 2, true);
console.log("\nsurge unavail done after lowest serving most pods at once");
for (const [s, u] of [[1, 0], [0, 1], [3, 2], [10, 0], [0, 10]]) {
const r = rollout(s, u, false);
console.log(`${String(s).padStart(4)} ${String(u).padStart(6)} ${String(r.t).padStart(9)} s` +
` ${String(r.minCap).padStart(10)}/10 ${String(r.peak).padStart(12)}`);
}আউটপুট:
Timeline, maxSurge=3, maxUnavailable=2 (25% of 10, rounded up / down):
t= 0s old 8 new 0/3 serving 8 ########
t= 5s old 8 new 0/5 serving 8 ########
t= 10s old 8 new 0/5 serving 8 ########
t= 15s old 8 new 0/5 serving 8 ########
t= 20s old 6 new 2/5 serving 8 ########
t= 25s old 5 new 3/7 serving 8 ########
t= 30s old 5 new 3/8 serving 8 ########
t= 35s old 5 new 3/8 serving 8 ########
t= 40s old 4 new 4/8 serving 8 ########
t= 45s old 2 new 6/9 serving 8 ########
t= 50s old 2 new 6/10 serving 8 ########
t= 55s old 1 new 7/10 serving 8 ########
t= 60s old 1 new 7/10 serving 8 ########
t= 65s old 0 new 8/10 serving 8 ########
t= 70s old 0 new 8/10 serving 8 ########
t= 75s old 0 new 9/10 serving 9 #########
t= 80s old 0 new 9/10 serving 9 #########
t= 85s old 0 new 9/10 serving 9 #########
t= 90s old 0 new 10/10 serving 10 ##########
surge unavail done after lowest serving most pods at once
1 0 340 s 10/10 11
0 1 345 s 9/10 10
3 2 90 s 8/10 13
10 0 40 s 10/10 20
0 10 45 s 0/10 10
ডিফল্ট ২৫%-এ (৩ বাড়তি, ২ কম) শুরুতেই ২টি পুরোনো Pod সরে যায় আর ৩টি নতুন বানানো শুরু হয়; প্রায় পুরো সময় traffic নিচ্ছে 8টি — ২০% capacity কম। ৯০ সেকেন্ডে শেষ, একসাথে সর্বোচ্চ 13টি Pod. সারসংক্ষেপের টেবিলে পুরো দর-কষাকষি: 1/0 কখনো capacity হারায় না, কিন্তু একটি একটি করে চলায় 340 s লাগে। 0/1-এ বাড়তি যন্ত্র লাগে না, কিন্তু একটি কপি কম থাকে, আর সময় প্রায় একই (345 s). 10/0 আসলে blue-green-এর মতো — 40 s-এ শেষ, capacity পূর্ণ, কিন্তু মুহূর্তের জন্য 20টি Pod, অর্থাৎ দ্বিগুণ যন্ত্র। আর 0/10 হলো recreate — সব পুরোনো একসাথে বন্ধ, প্রায় ২৫ সেকেন্ড 0/10 serving: পুরো downtime. ভিড়ের সময় পুরো capacity লাগলে maxUnavailable শূন্য রাখুন, নইলে deploy নিজেই overload তৈরি করে (পর্ব ১৮)।
Infrastructure as code আর GitOps: Pod-এর বাইরের জিনিস — network, load balancer, database-এর instance, DNS — একইভাবে ফাইলে লেখা যায়: infrastructure as code (IaC). Terraform-এর মতো টুল (HashiCorp, ২০১৪) কাঙ্ক্ষিত অবস্থা পড়ে আসল অবস্থার সাথে তুলনা করে একটি “plan” দেখায় — কী তৈরি, কী বদল, কী মোছা হবে — আর অনুমোদনের পর প্রয়োগ করে। কেউ console-এ হাতে কিছু বদলালে পরের তুলনায় সেটি ধরা পড়ে: drift. GitOps (শব্দটি Weaveworks ২০১৭ সালের দিকে জনপ্রিয় করে) আরও এক ধাপ: কাঙ্ক্ষিত অবস্থার একমাত্র উৎস একটি git repository; cluster-এর ভেতরের একটি agent (Argo CD বা Flux-এর মতো টুল) repository দেখে নিজেই মেলায়। লাভ: প্রতিটি বদল একটি review করা commit, আর ফেরা মানে git revert. সতর্কতা: password বা key কখনো সরল লেখায় git-এ নয় — secret-এর ব্যবস্থাপনা পর্ব ৫২-এ।
৪ · Deploy আর release আলাদা — feature flag আর পাঁচটি কৌশল
দুটি শব্দ প্রায়ই এক অর্থে বলা হয়, কিন্তু নকশায় এরা আলাদা। Deploy মানে নতুন কোড সার্ভারে পৌঁছে চলতে শুরু করা। Release মানে ব্যবহারকারীরা নতুন আচরণ দেখতে পাওয়া। Deploy আর release আলাদা রাখলে কোড আজ production-এ চলে যায়, কিন্তু নতুন “কিস্তিতে পেমেন্ট” বোতামটি খোলে আগামী সপ্তাহে — প্রথমে কর্মীদের জন্য, তারপর ১% গ্রাহকের জন্য, তারপর সবার জন্য। মাঝখানে কোনো নতুন deploy লাগে না।
যন্ত্রটির নাম feature flag (বা feature toggle): কোডের ভেতরে একটি শর্ত, যার মান চলমান অবস্থায় একটি config সেবা থেকে আসে — “এই ব্যবহারকারীর জন্য নতুন পথ চালু কি না”। সবচেয়ে মূল্যবান রূপটি kill switch: নতুন feature গোলমাল করলে কয়েক সেকেন্ডে বন্ধ, কোনো rollback বা নতুন build ছাড়াই। Flag-এর দাম আছে — flag debt. প্রতিটি flag কোডে দুটি পথ রাখে; দশটি flag মানে তাত্ত্বিকভাবে ১,০২৪টি সমন্বয়। তাই প্রতিটি release flag-এর একটি মালিক আর মেয়াদ থাকা দরকার: rollout শেষ হলে flag আর পুরোনো পথ দুটিই কোড থেকে মুছে ফেলা। পুরোনো flag-এর নাম নতুন কাজে আবার ব্যবহার করা বিশেষ বিপজ্জনক — ভাবনার প্রশ্ন ১-এ একটি বিখ্যাত ঘটনা।
কোড কীভাবে পুরোনো সংস্করণের জায়গা নেবে, তার পাঁচটি প্রধান কৌশল:
| কৌশল | কীভাবে | Downtime | বাড়তি capacity | ফেরা কত দ্রুত | দুই সংস্করণ একসাথে? |
|---|---|---|---|---|---|
| Recreate | সব পুরোনো বন্ধ, তারপর সব নতুন চালু | হ্যাঁ | না | আবার পুরো deploy | না |
| Rolling | কয়েকটি করে বদল | না | সামান্য (maxSurge) | আবার rolling, মিনিট | হ্যাঁ, কিছুক্ষণ |
| Blue-green | পুরো নতুন দল পাশে, তারপর router এক ধাপে বদল | না | দ্বিগুণ, switch পর্যন্ত | সেকেন্ড — router ফেরানো | না (database ছাড়া) |
| Canary | অল্প traffic নতুনে, মেপে ধাপে ধাপে বাড়ানো | না | সামান্য | সেকেন্ড — canary-র ভাগ শূন্য | হ্যাঁ, দীর্ঘক্ষণ |
| Shadow | আসল traffic-এর কপি নতুনে, উত্তর ফেলে দেওয়া | না | নতুনের পুরো ভার | প্রযোজ্য নয় — ব্যবহারকারী দেখেন না | হ্যাঁ |
Blue-green-এর শক্তি তার সরলতায়: দুটি সমান দল, “blue” (এখনকার) আর “green” (নতুন)। Green পুরো প্রস্তুত আর যাচাই হলে router একটি config বদলে সব traffic green-এ পাঠায়। গোলমাল হলে একই বদল উল্টো। ধাপে ধাপে দেখুন:
| Blue (v1.4) | |
| Green (v1.5) | |
| Router-এর দিক | |
| মোট instance | |
| Database |
- শুরু — সবাই blue-তে। v1.4-এর ১০টি instance সব traffic নিচ্ছে। Green দল এখনো নেই।
- Green তৈরি, কিন্তু অন্ধকারে। v1.5-এর ১০টি instance চালু হলো, ভেতরের একটি ঠিকানায় ৪২টি smoke test পাস করল। কোনো ব্যবহারকারী এখনো green দেখেননি। এই মুহূর্তে ২০টি instance — দ্বিগুণ খরচ।
- এক ধাপে switch. Router-এর একটি config বদলে ১০০% traffic green-এ। কোনো মিশ্র অবস্থা নেই — একজন ব্যবহারকারী একই মুহূর্তে দুই সংস্করণ দেখবেন না।
- সমস্যা ধরা পড়ল। তিন মিনিটে error rate ২.৪% — SLO-র অনুমতি ০.১%. ব্যবহারকারীদের সবাই প্রভাবিত, কারণ blue-green-এ প্রথম ধাপটিই ১০০%.
- এক ধাপে ফেরা। Router আবার blue-তে। Blue-র instance-গুলো কখনো বন্ধ হয়নি, cache-ও গরম, তাই কয়েক সেকেন্ডে error স্বাভাবিক। শর্ত একটিই: v1.5 database-এ যা লিখেছে, v1.4 তা পড়তে পারে — অংশ ৭-এর বিষয়।
- পরে। Green কিছুক্ষণ রাখা হলো log আর তদন্তের জন্য, তারপর বন্ধ — instance আবার ১০। সংশোধিত v1.5.1 এলে green-এ আবার একই চক্র।
ধাপ ৪ blue-green-এর দুর্বলতাও দেখায়: switch মানে সবার জন্য একসাথে। তাই অনেক দল দুটি মেশায় — green-এ প্রথমে অল্প traffic (canary), তারপর পুরো switch. আর shadow traffic-এ (traffic mirroring) আসল অনুরোধের একটি কপি নতুন সংস্করণে যায়, কিন্তু উত্তর ব্যবহারকারীর কাছে যায় না — আসল ভারে latency আর উত্তরের মিল নিরাপদে মাপা যায়। সাবধান: shadow অনুরোধ যেন টাকা না কাটে, SMS না পাঠায়, database-এ না লেখে — side effect-ওয়ালা কাজগুলো সেখানে বন্ধ বা নকল করে রাখতে হয়। কাছাকাছি ধারণা dark launch: নতুন feature-এর backend অংশ আসল ব্যবহারকারীর জন্য চালু, কিন্তু পর্দায় কিছু দেখা যায় না। Data migration-এ এর আত্মীয় shadow read.
৫ · Canary — অল্প দিয়ে শুরু, মেপে এগোনো, নিজে থেকে ফেরা
নামটি এসেছে কয়লাখনির পুরোনো প্রথা থেকে: বিষাক্ত গ্যাস মানুষের আগে ছোট ক্যানারি পাখির ওপর প্রভাব ফেলত, তাই সেটি ছিল আগাম সতর্কসংকেত। Canary release-এ নতুন সংস্করণ প্রথমে ছোট এক ভাগ traffic পায় — ধরা যাক ১%. কিছুক্ষণ (bake time) metric দেখা হয়; ঠিক থাকলে ৫%, তারপর ২৫%, তারপর সবাই। এই ধাপে ধাপে ছড়ানোর সাধারণ নাম progressive delivery.
প্রতিটি ধাপে প্রশ্ন একটি: canary কি stable-এর চেয়ে খারাপ? তাই automated canary analysis — একটি যন্ত্র কয়েকটি metric (error rate, p99 latency, CPU, ব্যবসার সংখ্যা যেমন সফল পেমেন্ট) canary আর তুলনার দলে মিলিয়ে দেখে, আর SLO-র সীমা (পর্ব ৫০) ভাঙলে বা পার্থক্য পরিসংখ্যানে অর্থবহ হলে নিজেই rollback করে। “পরিসংখ্যানে অর্থবহ” কথাটা এখানে আসল — কারণ ১% traffic মানে খুব কম অনুরোধ, আর কম অনুরোধে কয়েকটি error ভাগ্যক্রমেও আসে।
সবচেয়ে সরল পরীক্ষা two-proportion z-test. Canary-তে ncটি অনুরোধে ecটি error (হার pc), stable-এ nbটিতে ebটি (হার pb). দুটি দলের মোট হার p = (ec + eb) ÷ (nc + nb) ধরে নিলে, “দুটি আসলে সমান” হলে পার্থক্যের স্বাভাবিক ছড়ানো (standard error) √(p(1 − p)(1/nc + 1/nb)). তখন
z বড় মানে পার্থক্যটি ভাগ্যের চেয়ে বড়। এখানে নিয়ম ধরা হলো z > 2.33 হলে rollback — এক দিকের পরীক্ষায় প্রায় ১% ভুল সতর্কসংকেতের সম্ভাবনা। অনুমানগুলো স্পষ্ট রাখা দরকার: অনুরোধগুলো একে অপরের থেকে স্বাধীন (একজন ব্যবহারকারীর দশটি retry স্বাধীন নয়), আর error এত কম নয় যে সাধারণ বণ্টনের আসন্ন মান ভেঙে পড়ে। বারবার পরীক্ষায় ভুল সতর্কসংকেত সবমিলিয়ে বাড়ে। নিচের সিমুলেটরে প্রতি ধাপে মোট ৪০,০০০টি অনুরোধ (ধরা যাক, ১০ মিনিটে), stable-এর আসল error ০.৫%; তিনটি release-এর আসল error ০.৫%, ১% আর ২%.
// Canary analysis: 1% -> 5% -> 25% of traffic, two-proportion z-test at each step.
function mulberry32(a) { return () => { a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
const PER_STEP = 40000; // requests in one 10-minute analysis window (assumed)
const BASE_ERR = 0.005; // stable version: 0.5% of requests fail
const Z_LIMIT = 2.33; // one-sided, about 1% false-alarm chance per step
const errorsIn = (n, p, rand) => { let e = 0; for (let i = 0; i < n; i++) if (rand() < p) e++; return e; };
function analyse(label, canaryErr, seed) {
const rand = mulberry32(seed);
console.log(`\n${label}: canary true error rate ${(canaryErr * 100).toFixed(1)}%`);
console.log("traffic canary n errs rate baseline n errs rate z decision");
for (const share of [0.01, 0.05, 0.25]) {
const nc = Math.round(PER_STEP * share), nb = PER_STEP - nc;
const ec = errorsIn(nc, canaryErr, rand), eb = errorsIn(nb, BASE_ERR, rand);
const pc = ec / nc, pb = eb / nb, pool = (ec + eb) / (nc + nb);
const z = (pc - pb) / Math.sqrt(pool * (1 - pool) * (1 / nc + 1 / nb));
const bad = z > Z_LIMIT;
console.log(`${String(share * 100).padStart(5)}% ${String(nc).padStart(8)} ${String(ec).padStart(4)} ` +
`${(pc * 100).toFixed(2).padStart(5)}% ${String(nb).padStart(10)} ${String(eb).padStart(4)} ` +
`${(pb * 100).toFixed(2).padStart(5)}% ${z.toFixed(2).padStart(5)} ` +
(bad ? "ROLLBACK" : share < 0.25 ? "continue" : "promote to 100%"));
if (bad) return;
}
}
analyse("Release A (healthy)", 0.005, 7);
analyse("Release B (bug doubles errors)", 0.010, 7);
analyse("Release C (bug: 4x errors)", 0.020, 7);আউটপুট:
Release A (healthy): canary true error rate 0.5%
traffic canary n errs rate baseline n errs rate z decision
1% 400 2 0.50% 39600 231 0.58% -0.22 continue
5% 2000 13 0.65% 38000 176 0.46% 1.19 continue
25% 10000 50 0.50% 30000 158 0.53% -0.32 promote to 100%
Release B (bug doubles errors): canary true error rate 1.0%
traffic canary n errs rate baseline n errs rate z decision
1% 400 5 1.25% 39600 231 0.58% 1.73 continue
5% 2000 27 1.35% 38000 176 0.46% 5.44 ROLLBACK
Release C (bug: 4x errors): canary true error rate 2.0%
traffic canary n errs rate baseline n errs rate z decision
1% 400 7 1.75% 39600 231 0.58% 3.02 ROLLBACK
Release A সুস্থ: ৫% ধাপে canary-র হার 0.65% বনাম 0.46% — দেখতে খারাপ, কিন্তু z 1.19, ভাগ্যের সীমার ভেতরে; তিন ধাপ পেরিয়ে promote. Release B-তে error আসলেই দ্বিগুণ, অথচ ১% ধাপে ৪০০ অনুরোধে মাত্র 5টি error, z 1.73 — যন্ত্র “continue” বলল। এটি ভুল নয়, এটি তথ্যের অভাব: এত কম নমুনায় দ্বিগুণ হওয়াও প্রমাণ করা যায় না (পরীক্ষার statistical power কম)। ৫% ধাপে ২,০০০ অনুরোধে z 5.44 — পরিষ্কার, ROLLBACK. Release C-র bug বড় (৪ গুণ), তাই ১% ধাপেই z 3.02, সেখানেই ফেরা। শিক্ষা: ১% ধাপ বড় বিপর্যয় ধরে, সূক্ষ্ম অবনতি নয়। প্রথম ধাপে যথেষ্ট অনুরোধ জমা না হওয়া পর্যন্ত অপেক্ষা করুন, নইলে “পাস” শব্দটির কোনো অর্থ নেই।
Blast radius: একটি খারাপ release কতজনকে কতক্ষণ ছোঁয়, সেটিই তার blast radius. মাপার একটি সহজ একক “user-minute” — প্রতি মিনিটে খারাপ সংস্করণে থাকা ব্যবহারকারীর সংখ্যার যোগফল। নিচের সিমুলেটরে প্রতি মিনিটে ৮০ হাজার থেকে ১ লাখ ২০ হাজার ব্যবহারকারী (seed দিয়ে বাছা); big-bang-এ মিনিট ০ থেকেই ১০০%, canary-তে ১০ মিনিট করে ১% → ৫% → ২৫% → ১০০%. Bug ধরা পড়ে N মিনিট পরে, আর ফেরাতে আরও ৫ মিনিট লাগে, যার মধ্যে গড়ে অর্ধেক ব্যবহারকারী তখনো খারাপ সংস্করণে।
// Blast radius of a bad release: user-minutes of impact, big-bang vs canary steps.
function mulberry32(a) { return () => { a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
const rand = mulberry32(2026);
const active = Array.from({ length: 120 }, () => Math.round(80000 + rand() * 40000)); // users online each minute
const ROLLBACK_MIN = 5; // minutes from "detected" until no user is on the bad version
const bigBang = () => 1.0; // share of users on the new version at minute m
const canary = m => m < 10 ? 0.01 : m < 20 ? 0.05 : m < 30 ? 0.25 : 1.0;
function impact(share, detectAfter) { // sum over minutes of (users online x share on bad version)
let total = 0;
for (let m = 0; m < detectAfter + ROLLBACK_MIN; m++) {
const s = m < detectAfter ? share(m) : share(detectAfter - 1) * 0.5; // rollback: about half left on average
total += active[m] * s;
}
return Math.round(total);
}
console.log("users online per minute: min " + Math.min(...active) + ", max " + Math.max(...active));
console.log("detected after big-bang user-min canary user-min canary / big-bang");
for (const n of [5, 15, 25, 45]) {
const b = impact(bigBang, n), c = impact(canary, n);
console.log(`${String(n).padStart(8)} min ${b.toLocaleString("en-US").padStart(15)} ` +
`${c.toLocaleString("en-US").padStart(15)} ${(c / b * 100).toFixed(1).padStart(14)}%`);
}আউটপুট:
users online per minute: min 80038, max 119718
detected after big-bang user-min canary user-min canary / big-bang
5 min 728,677 7,287 1.0%
15 min 1,668,789 44,659 2.7%
25 min 2,679,809 248,512 9.3%
45 min 4,695,604 2,084,030 44.4%
৫ মিনিটে ধরা পড়লে big-bang-এর ক্ষতি 728,677 user-minute, canary-র 7,287 — ঠিক ১%, কারণ তখনো শুধু ১% canary-তে। ১৫ মিনিটে অনুপাত 2.7%, ২৫ মিনিটে 9.3%. কিন্তু ৪৫ মিনিটে 44.4% — canary তখন ৩০ মিনিটেই ১০০%-এ পৌঁছে গেছে, আর বাকি ক্ষতি big-bang-এর মতোই। মূল কথা: canary তখনই রক্ষা করে, যখন bug ধরার সময় rollout শেষ হওয়ার সময়ের চেয়ে কম। ধীরে প্রকাশ পায় এমন সমস্যা (memory leak, মাসের শেষের হিসাব) ধরতে দীর্ঘ bake time লাগে।
Rollback নাকি roll-forward? Rollback মানে আগের পরিচিত-ভালো artifact-এ ফেরা; roll-forward মানে দ্রুত একটি সংশোধন বানিয়ে সামনে এগোনো। সাধারণ নিয়ম: আগুন লাগলে আগে rollback — পরীক্ষিত পথ, মিনিটে শেষ; তারপর শান্তভাবে কারণ খোঁজা। Roll-forward তখনই, যখন ফেরা অসম্ভব বা বেশি ঝুঁকির: নতুন সংস্করণ database-এ এমন ফরম্যাটে লিখেছে যা পুরোনোটি পড়তে পারে না, queue-তে নতুন ধরনের বার্তা জমেছে, বা ঘটে যাওয়া কাজ (পাঠানো SMS, কাটা টাকা) ফেরানো যায় না। তাই release-এর আগেই জিজ্ঞেস করুন: “এখান থেকে আগেরটিতে নিরাপদে ফেরা যায়?” উত্তর “না” হলে release-টিকে ফেরানো যায় এমন ছোট ধাপে ভাঙুন — অংশ ৭-এর মতো। Backup আর বড় দুর্যোগ থেকে ফেরার পরিকল্পনা আলাদা বিষয় — পর্ব ৫১.
Canary-র তিনটি নীরব ফাঁদ। (১) একই ব্যবহারকারী দুই সংস্করণে: প্রতিটি অনুরোধ এলোমেলো ভাগ হলে একজন একবার নতুন, একবার পুরোনো পাতা দেখেন; ভাগটি user ID-র hash দিয়ে করুন, যাতে প্রত্যেকে একটিতেই থাকেন। (২) ঠান্ডা canary: নতুন Pod-এর cache খালি, তাই প্রথম মিনিটে latency খারাপ দেখায় — দীর্ঘদিন চলা stable-এর সাথে নয়, একই সময়ে নতুন চালু হওয়া একটি পুরোনো-সংস্করণের তুলনা-দলের (baseline) সাথে মেলালে তুলনা ন্যায্য হয়। (৩) রাত তিনটার canary: traffic কম, নমুনা ছোট।
৬ · Zero-downtime — graceful shutdown, draining আর মানানসই API
Rolling, blue-green বা canary — সবখানে কোনো না কোনো সময় পুরোনো একটি instance বন্ধ হয়। বন্ধের মুহূর্তে যদি সে হাতের অনুরোধগুলো ফেলে দেয়, তাহলে “zero-downtime deploy” আসলে প্রতিবার কয়েকশো ভাঙা অনুরোধ। রেস্তোরাঁর একজন ওয়েটারের শিফট শেষ হলে তিনি হাতের প্লেট মাটিতে রেখে চলে যান না: আগে ম্যানেজারকে বলেন “নতুন টেবিল আর আমাকে দেবেন না”, তারপর যে টেবিলগুলোর খাবার চলছে সেগুলো শেষ করেন, তারপর বিদায়। এটিই graceful shutdown, আর load balancer-এর দিক থেকে connection draining (পর্ব ১৪)।
Kubernetes-এ একটি Pod সরানোর সময় দুটি কাজ একসাথে শুরু হয়: Pod-টিকে Service-এর তালিকা থেকে সরানো, আর Pod-কে বন্ধের সংকেত দেওয়া। প্রথমটি সব যন্ত্র আর load balancer-এ পৌঁছাতে কয়েক সেকেন্ড লাগে, তাই সংকেত পেয়েই listener বন্ধ করলে সেই কয়েক সেকেন্ডে আসা অনুরোধ ভাঙে। প্রচলিত সমাধান একটি preStop hook-এ ছোট অপেক্ষা। ধাপে ধাপে (সময় Pod মোছার আদেশ থেকে; ডিফল্ট সময়সীমা লেখার সময়ের documentation অনুযায়ী ৩০ সেকেন্ড):
| Pod-এর অবস্থা | |
| Service-এর তালিকায়? | |
| নতুন অনুরোধ | |
| চলমান (in-flight) | |
| সময় |
- স্বাভাবিক অবস্থা। Pod প্রস্তুত, Service-এর তালিকায় আছে, ১২টি অনুরোধ চলছে।
- সরানোর আদেশ — কিন্তু এখনই থামা নয়। তালিকা থেকে সরানোর খবর ছড়াচ্ছে; কিছু load balancer এখনো পুরোনো তালিকা দেখে অনুরোধ পাঠাচ্ছে। preStop-এর ৫ সেকেন্ডের অপেক্ষায় Pod সেগুলোও নিচ্ছে — কোনোটি ভাঙে না।
- SIGTERM. ৫ সেকেন্ড পরে SIGTERM সংকেত পৌঁছায়। অ্যাপ নতুন সংযোগ নেওয়া বন্ধ করে; পুরোনো keep-alive সংযোগে জানিয়ে দেয় এটিই শেষ (HTTP/1.1-এ
Connection: close, HTTP/2-এ GOAWAY), যাতে client নতুন সংযোগ অন্য Pod-এ খোলে। - হাতের কাজ শেষ। চলমান ৯টি অনুরোধ একে একে শেষ। Background worker হলে অর্ধেক করা job-টি ack না করে queue-তে ফেরত দেয়, যাতে অন্য worker নেয় — job-টি idempotent হওয়া তাই জরুরি (পর্ব ৩৭)।
- নিজে থেকে বিদায়। ১৮ সেকেন্ডে exit 0, সময়সীমার আগেই। সময়সীমা পেরোলে SIGKILL আসত — তখন যা চলছিল সব হারাত। দীর্ঘ অনুরোধ বা WebSocket থাকলে সময়সীমা বাড়ানো দরকার।
let shuttingDown = false;
app.get("/ready", (req, res) => res.status(shuttingDown ? 503 : 200).end());
process.on("SIGTERM", () => {
shuttingDown = true; // readiness fails: no new traffic
server.close(() => process.exit(0)); // stop accepting; exit when in-flight are done
server.closeIdleConnections?.(); // idle keep-alive sockets (newer Node versions)
setTimeout(() => process.exit(1), 20000); // hard stop: 5 s preStop + 20 s < 30 s grace
});চালু হওয়ার দিকেও একই যত্ন: readiness তখনই “হ্যাঁ”, যখন database-সংযোগ খোলা আর দরকারি cache ভরা — cold start-এর Pod-এ পুরো ভার এলে latency লাফায়। অনেক load balancer নতুন instance-এ ধীরে ভার বাড়ায় (slow start).
দুই সংস্করণ একসাথে — মানানসই থাকতেই হবে। Rolling আর canary-তে কয়েক মিনিট থেকে কয়েক ঘণ্টা v1 আর v2 পাশাপাশি চলে: v2-এর পাঠানো বার্তা v1 পড়ে, v1-এর লেখা সারি v2 পড়ে, আর একজন ব্যবহারকারীর পরপর দুটি অনুরোধ দুই সংস্করণে যেতে পারে। তাই প্রতিটি পরিবর্তনকে দুই দিকে মানানসই হতে হয়: নতুন কোড পুরোনো ডেটা আর পুরোনো client বোঝে (backward compatibility), পুরোনো কোড নতুন ডেটা দেখে ভেঙে পড়ে না, অচেনা field ডিঙিয়ে যায় (forward compatibility). Protobuf-Avro-তে এর নিয়ম পর্ব ১২-এ, API-র সংস্করণ পর্ব ৮-এ। Mobile অ্যাপে কথাটা আরও কঠিন: পুরোনো অ্যাপ মাসের পর মাস ফোনে থেকে যায়, আর তাকে rollback করার কোনো উপায় নেই। আরেকটি সাধারণ ফাঁদ: session যদি instance-এর memory-তে থাকে, প্রতিটি deploy সবাইকে logout করায় — session বাইরে রাখার কারণ পর্ব ১৫-এ। Microservices-এ নিয়মটি আরও জরুরি (পর্ব ৪৭)।
৭ · Database migration, downtime ছাড়া — expand/contract
কোড ফেরানো যায় এক মিনিটে; ডেটা নয়। আর পুরোনো আর নতুন — দুই সংস্করণের কোডই একই database-এ কথা বলে। তাই schema বদল deploy-এর সবচেয়ে ঝুঁকির অংশ। দুটি আলাদা বিপদ আছে।
বিপদ ১ — lock. অনেক ALTER TABLE কাজ চলার সময় টেবিলে একচেটিয়া lock নেয়, আর কিছু কাজ (যেমন কলামের type বদল) পুরো টেবিল নতুন করে লেখে — কোটি সারির টেবিলে মিনিট বা ঘণ্টা, এর মধ্যে সব লেখা আটকে। সূক্ষ্মতর ফাঁদ lock queue: lock পেতে ALTER যদি একটি দীর্ঘ চলমান query-র পেছনে অপেক্ষা করে, তাহলে PostgreSQL-এ তার পেছনে আসা সাধারণ SELECT-গুলোও সারিতে দাঁড়ায় — একটি “তাৎক্ষণিক” কলাম যোগও পুরো সেবা থামিয়ে দিতে পারে। প্রতিকার: lock_timeout-এর মতো ছোট সময়সীমা, ব্যর্থ হলে পরে আবার; index বানাতে PostgreSQL-এর CREATE INDEX CONCURRENTLY; আর MySQL-এ বড় টেবিলের জন্য gh-ost বা pt-online-schema-change-এর মতো online schema change টুল, যারা একটি ছায়া-টেবিলে কপি করে শেষে নাম বদলে নেয়। কোন বদল “তাৎক্ষণিক” (শুধু metadata) আর কোনটি টেবিল নতুন করে লেখে, তা database আর তার সংস্করণভেদে আলাদা — নিজের সংস্করণের documentation-এ মিলিয়ে নিন। Lock-এর মৌলিক ধারণা পর্ব ২৪-এ।
SET lock_timeout = '2s'; -- no lock within 2 s? give up, retry later
ALTER TABLE users ADD COLUMN mobile text; -- nullable, no default: usually metadata-only
CREATE INDEX CONCURRENTLY idx_users_mobile ON users (mobile); -- avoids a long write lockবিপদ ২ — মানানসইতা। ধরা যাক users টেবিলের phone কলামের নাম mobile করতে হবে। এক ধাপে RENAME করলে যে মুহূর্তে বদল হলো, তখনো চলমান সব v1 instance phone খুঁজে error পায়; আগে কোড deploy করলে নতুন কোড mobile খুঁজে পায় না। কোনো ক্রমেই এক ধাপে নিরাপদ নয়। সমাধান expand/contract (একে parallel change-ও বলা হয়): আগে প্রসারিত করুন, দুটোই কিছুদিন চালান, সব পুরোনো কোড বিদায় নিলে তবেই সংকোচন। কলাম যোগ থেকে মুছে ফেলা পর্যন্ত প্রতিটি অবস্থায় চলমান সব সংস্করণ কাজ করে:
| Schema-র কলাম | |
| চলমান অ্যাপ | |
| লেখা যায় | |
| পড়া হয় | |
| mobile ভরা সারি | |
| ফেরার পথ |
- শুরু। শুধু
phone, সব instance-এ v1. - Expand — খালি কলাম যোগ।
mobilenullable, default ছাড়া — অনেক database-এ এটি শুধু metadata বদল। v1 নতুন কলামের কথা জানেই না, তাই কিছুই ভাঙে না। - দুই জায়গায় লেখা। v2 প্রতিটি লেখা দুটি কলামেই করে (dual write), কিন্তু পড়ে এখনো
phoneথেকে। Rolling-এর সময় v1 আর v2 একসাথে — দুজনেই ঠিক চলে। - Backfill. পুরোনো সারিগুলোর
mobileভরা হয় হাজার সারির ছোট ব্যাচে, replication lag আর database-এর ভার দেখে গতি নিয়ন্ত্রণ করে (backfill).mobile IS NULLশর্তটি কাজটিকে idempotent রাখে — মাঝপথে থামলে আবার চালানো যায়, আর v2-এর নতুন লেখা চাপা পড়ে না। - পড়া বদল। সব সারি ভরা নিশ্চিত হওয়ার পরেই v3 পড়ে
mobileথেকে, তবে এখনো দুটিতেই লেখে — গোলমাল হলে v2-তে ফেরা নিরাপদ, কারণphoneএখনো হালনাগাদ। - পুরোনো লেখা বন্ধ। v4 শুধু
mobile-এ লেখে। এখন থেকে v2-তে ফেরা আর নিরাপদ নয় — তাই এই ধাপ নেওয়া হয় v3 যথেষ্ট দিন ভালো চলার পরে। - Contract — পুরোনো কলাম মোছা। কয়েক দিন বা সপ্তাহ পরে, কোথাও
phone-এর কোনো ব্যবহার নেই নিশ্চিত হয়ে (query log, report, অন্য সেবা)। এটিই একমাত্র ধাপ, যা আর ফেরানো যায় না — তাই এর আগে backup.
ক্রম ভুল হলে কী হয়, সিমুলেটরে দেখুন। চারটি অ্যাপ-সংস্করণ, প্রতিটি অবস্থায় ছয়টি অনুরোধ — প্রতিটি অনুরোধ চলমান সংস্করণগুলোর একটিতে যায়, একটি সারি লেখে আর একটি এলোমেলো সারি পড়ে। FAIL মানে কলামই নেই (error), EMPTY মানে কলাম আছে কিন্তু মান খালি — নীরব ভুল:
// Rename column phone -> mobile while old and new app versions run side by side.
function mulberry32(a) { return () => { a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
const APP = { v1: { w: ["phone"], r: "phone" }, v2: { w: ["phone", "mobile"], r: "phone" },
v3: { w: ["phone", "mobile"], r: "mobile" }, v4: { w: ["mobile"], r: "mobile" } };
const STEP = { add: s => s.cols.add("mobile"), drop: s => s.cols.delete("phone"),
rename: s => { s.cols.delete("phone"); s.cols.add("mobile");
s.rows.forEach(r => { r.mobile = r.phone; delete r.phone; }); },
backfill: s => s.rows.forEach(r => { if (r.mobile == null) r.mobile = r.phone; }) };
function run(name, plan) {
const rand = mulberry32(53), s = { cols: new Set(["phone"]), rows: [] };
for (let i = 0; i < 4; i++) s.rows.push({ phone: "01" + Math.floor(rand() * 1e9) });
console.log(`\n== ${name}`);
for (const [step, versions] of plan) {
if (STEP[step]) STEP[step](s);
let wFail = 0, rOk = 0, rEmpty = 0, rFail = 0;
for (let k = 0; k < 6; k++) { // 6 requests, random version each
const app = APP[versions[Math.floor(rand() * versions.length)]];
if (app.w.every(c => s.cols.has(c))) {
const num = "01" + Math.floor(rand() * 1e9), row = {};
app.w.forEach(c => row[c] = num); s.rows.push(row);
} else wFail++;
if (!s.cols.has(app.r)) rFail++;
else { const row = s.rows[Math.floor(rand() * s.rows.length)];
row[app.r] == null ? rEmpty++ : rOk++; }
}
console.log(`${(step + " [" + versions + "]").padEnd(24)} write-fail ${wFail} read ok ${rOk} read EMPTY ${rEmpty} read FAIL ${rFail}`);
}
}
run("Big-bang rename", [["rename", ["v1"]], ["deploy", ["v1", "v4"]], ["deploy", ["v4"]]]);
run("Expand/contract, backfill skipped", [["add", ["v1"]], ["deploy", ["v1", "v2"]],
["deploy", ["v2", "v3"]], ["deploy", ["v3"]]]);
run("Expand/contract", [["add", ["v1"]], ["deploy", ["v1", "v2"]], ["backfill", ["v2"]],
["deploy", ["v2", "v3"]], ["deploy", ["v3", "v4"]], ["drop", ["v4"]]]);আউটপুট:
== Big-bang rename
rename [v1] write-fail 6 read ok 0 read EMPTY 0 read FAIL 6
deploy [v1,v4] write-fail 2 read ok 4 read EMPTY 0 read FAIL 2
deploy [v4] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
== Expand/contract, backfill skipped
add [v1] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
deploy [v1,v2] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
deploy [v2,v3] write-fail 0 read ok 4 read EMPTY 2 read FAIL 0
deploy [v3] write-fail 0 read ok 2 read EMPTY 4 read FAIL 0
== Expand/contract
add [v1] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
deploy [v1,v2] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
backfill [v2] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
deploy [v2,v3] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
deploy [v3,v4] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
drop [v4] write-fail 0 read ok 6 read EMPTY 0 read FAIL 0
Big-bang rename: নাম বদলের মুহূর্তে সব instance এখনো v1 — ছয়টি লেখা আর ছয়টি পড়াই ব্যর্থ, অর্থাৎ পুরো outage. v4 ছড়ানোর সময়ও যতক্ষণ v1 আছে, error চলে (write-fail 2, read FAIL 2). Backfill বাদ: কোনো error নেই — আর এটিই ভয়ের। v3 পুরোনো সারির খালি mobile পড়ছে (read EMPTY 2, তারপর 4) — গ্রাহকের ফোন নম্বর “নেই” দেখাচ্ছে, OTP যাচ্ছে না, অথচ কোনো alert বাজছে না। পূর্ণ expand/contract-এ ছয়টি অবস্থার প্রতিটিতে read ok 6, কোনো error বা খালি পড়া নেই। ক্রমটিই নিরাপত্তা।
তিনটি ব্যবহারিক নিয়ম। প্রথমত, schema বদল আর তার ওপর নির্ভরশীল কোড কখনো একই deploy-এ নয়। দ্বিতীয়ত, migration অ্যাপ চালুর সময় নয় — পঞ্চাশটি Pod একসাথে একই ALTER চালাতে দৌড়ায়; এটি একবার চলা আলাদা job. তৃতীয়ত, বড় backfill থামানো-আবার-শুরু-করা যায় এমন background job (পর্ব ৩৭); একই ধারা (dual write → backfill → পড়া বদল) এক database থেকে আরেকটিতে বা নতুন shard-এ ডেটা সরাতেও কাজে লাগে (পর্ব ২৩)। ORM-এর migration টুলের দিক DBMS কোর্সের ORM আর migration পর্বে।
৮ · Config আর software supply chain
Config-ও কোড: timeout, pool-এর আকার, flag-এর মান, rate limit (পর্ব ৪৯) — এগুলো বদলানো কোড বদলানোর মতোই বিপজ্জনক, আর প্রকাশিত বহু postmortem-এ বড় outage-এর শুরু একটি config বদল থেকে বলে বর্ণনা আছে। তাই config-এর জন্যও একই নিয়ম: version control-এ রাখা, review, schema দিয়ে যাচাই (“timeout ঋণাত্মক হতে পারে না”), আর একসাথে সবখানে নয় — canary-র মতো ধাপে ধাপে ছড়ানো। চলমান অবস্থায় বদলানো dynamic config-এ একটি ভুল মান সেকেন্ডে সবখানে পৌঁছায়, তাই এক ক্লিকে আগের মানে ফেরার ব্যবস্থা জরুরি। Password, API key আর certificate config নয়, secret — image-এ বা git-এ নয়, আলাদা secret store থেকে চালুর সময় (পর্ব ৫২)।
Supply chain — যা আপনি লেখেননি: একটি সাধারণ সেবার image-এ আপনার নিজের কোডের চেয়ে অন্যের লেখা library অনেক বেশি। প্রতিরক্ষার মৌলিক অভ্যাসগুলো:
- Pin করা: lockfile-এ প্রতিটি dependency-র নির্দিষ্ট সংস্করণ আর তার hash (dependency pinning); base image-ও digest দিয়ে।
- উপাদানের তালিকা: প্রতিটি image-এর SBOM (software bill of materials) — কোন library, কোন সংস্করণ। SPDX আর CycloneDX প্রচলিত ফরম্যাট। নতুন দুর্বলতা প্রকাশ পেলে “কোন সেবায় এটি আছে?” — উত্তর মিনিটে।
- Scan: pipeline-এ জানা দুর্বলতার (CVE) তালিকার সাথে মেলানো, আর যতটা সম্ভব ছোট base image রাখা।
- স্বাক্ষর আর যাচাই: CI image-এ একটি digital signature দেয় (artifact signing; Sigstore-এর cosign-এর মতো টুল আছে), আর cluster শুধু বিশ্বস্ত pipeline-এর স্বাক্ষরওয়ালা image চালাতে দেয়। SLSA-র মতো কাঠামো build-এর উৎস-প্রমাণের (provenance) স্তর বর্ণনা করে — বিস্তারিত সংস্করণভেদে বদলায়।
আরও গভীরে: Burns, Grant, Oppenheimer, Brewer ও Wilkes-এর “Borg, Omega, and Kubernetes” (ACM Queue, ২০১৬) Google-এর তিন প্রজন্মের cluster ব্যবস্থার শিক্ষা বর্ণনা করে — declarative অবস্থা আর reconciliation loop কেন। Netflix আর Google ২০১৮ সালে open-source automated canary analysis টুল Kayenta প্রকাশ করে বলে তাদের engineering blog-এ জানানো হয়; সেখানে canary-কে দীর্ঘদিন চলা stable-এর সাথে নয়, একই আকারের নতুন চালু baseline-এর সাথে তুলনার পরামর্শ আছে। Google-এর Site Reliability Workbook (২০১৮)-এ canary নিয়ে একটি অধ্যায় আছে, আর Danilo Sato-র “ParallelChange” (martinfowler.com, ২০১৪) expand/contract-এর পরিচিত বর্ণনা।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা system design ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন আসতে পারে:
- Blue-green আর canary-র পার্থক্য? — Blue-green এক ধাপে সবাইকে সরায় আর মুহূর্তে ফেরে, দাম দ্বিগুণ capacity; canary অল্প ভাগ দিয়ে মেপে ধাপে ধাপে এগোয়, ক্ষতি ছোট রাখে।
- ১০টি replica, maxSurge 25%, maxUnavailable 25% — সর্বোচ্চ কত Pod আর কমপক্ষে কতটি প্রস্তুত? — ১৩টি Pod (৩টি বাড়তি, ওপরে round), কমপক্ষে ৮টি প্রস্তুত (২টি কম, নিচে round).
- Downtime ছাড়া কলামের নাম বদলাবেন কীভাবে? — Expand/contract: নতুন কলাম যোগ, দুই জায়গায় লেখা, backfill, পড়া বদল, পুরোনো লেখা বন্ধ, শেষে পুরোনো কলাম মোছা।
- Deploy আর release-এর পার্থক্য? — Deploy কোড পৌঁছানো, release ব্যবহারকারীর জন্য খোলা; feature flag দুটিকে আলাদা করে।
- Image tag-এর বদলে digest কেন? — Tag সরানো যায়, digest বিষয়বস্তুর hash; digest-এ সব যন্ত্রে আর rollback-এ ঠিক একই bytes.
৯ · AI-এর যুগে deployment-নকশা যাচাই
“আমার সেবার জন্য zero-downtime deploy-এর নকশা দাও” লিখলে AI মুহূর্তে পরিপাটি উত্তর দেয় — প্রায়ই “Kubernetes, Argo CD, Istio আর canary দিয়ে।” কিন্তু কয়েকটি ভুল প্রায় নিয়ম করে আসে:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| “Canary দিন, তাহলে নিরাপদ” — অনুরোধের সংখ্যা ছাড়া | ৪০০ অনুরোধে error দ্বিগুণ হওয়াও প্রমাণ হয় না | অংশ ৫-এর সিমুলেটরে নিজের traffic বসিয়ে z মেপে দেখুন |
| Database migration-কে একটি সাধারণ deploy-ধাপ ধরা | RENAME বা lock-ভারী ALTER পুরোনো কোড ভাঙে বা টেবিল আটকায় | প্রতিটি অবস্থায় কোন সংস্করণ চলছে আর কী পড়ে-লেখে, অংশ ৭-এর মতো ছক বানান |
| “Rollback একটি কমান্ড” — সবসময় | নতুন ফরম্যাটের ডেটা, পাঠানো বার্তা, মোবাইল অ্যাপ ফেরানো যায় না | প্রতিটি release-এ লিখে নিন: কোথা থেকে কোথায় ফেরা নিরাপদ |
:latest tag আর readiness ছাড়া manifest | দুই যন্ত্রে দুই কোড; প্রস্তুত হওয়ার আগেই traffic | Manifest-এ digest আর readiness probe আছে কি না দেখুন |
| টুলের ডিফল্ট, সীমা বা দামের বানানো সংখ্যা | Kubernetes, CI বা cloud-এর ডিফল্ট সংস্করণভেদে বদলায় | অফিসিয়াল documentation, আর একটি staging cluster-এ ছোট পরীক্ষা |
| ছোট দলের জন্য service mesh, GitOps আর পাঁচটি টুল | Over-engineering | সবচেয়ে ছোট যথেষ্ট সমাধান খুঁজুন |
AI-কে আপনার বাস্তব সংখ্যা আর সীমাবদ্ধতা দিন, আর প্রতিটি সিদ্ধান্তের failure-mode চান:
AI-এর দেওয়া release-পরিকল্পনা হাতে পেলে — যাচাইয়ের চেকলিস্ট
প্রশ্ন একটাই: কতজনকে, কতক্ষণ, আর ফেরার পথ আছে কি?
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। সূত্র মুখস্থ নয়, কোন নকশা কেন বেছে নেওয়া হয় সেটাই আসল।
প্র ০১২০১২ সালে একটি মার্কিন শেয়ার-ব্যবসার প্রতিষ্ঠান প্রায় ৪৫ মিনিটে কয়েকশো মিলিয়ন ডলার হারায় — একটি deploy আর একটি পুরোনো flag-এর কারণে। ঘটনাটি থেকে deploy-নকশার কী শিক্ষা?
প্রতিষ্ঠানটি Knight Capital, আর ঘটনার বিবরণ মার্কিন নিয়ন্ত্রক SEC-এর ২০১৩ সালের প্রকাশিত আদেশে আছে। সেই বিবরণ অনুযায়ী, একটি নতুন trading সুবিধার জন্য কোড আটটি সার্ভারে হাতে deploy করা হচ্ছিল; একটি সার্ভারে নতুন কোড পৌঁছায়নি। নতুন সুবিধা চালু করতে একটি পুরোনো flag-কে নতুন অর্থে ব্যবহার করা হয়েছিল — অথচ সেই flag বহু বছর আগে বন্ধ হওয়া একটি পুরোনো কাজের কোডও চালু করত, যা তখনো সেই এক সার্ভারে রয়ে গিয়েছিল। Flag চালু হতেই সেই সার্ভার পুরোনো যুক্তিতে অগণিত order পাঠাতে শুরু করে। প্রায় ৪৫ মিনিট পরে থামানো যায়, আর ক্ষতি দাঁড়ায় কয়েকশো মিলিয়ন ডলারে — প্রতিষ্ঠানটি স্বাধীনভাবে টিকে থাকতে পারেনি; জরুরি বিনিয়োগ নিতে হয়, পরে অন্য প্রতিষ্ঠানের সাথে মিশে যায়।
এই পর্বের প্রায় প্রতিটি নীতি এখানে ভেঙেছে। (১) Deploy যাচাই হয়নি: “আটটিতেই একই artifact চলছে কি না”, কেউ স্বয়ংক্রিয়ভাবে দেখেনি; digest-ভিত্তিক deploy আর verify ধাপ এটি সঙ্গে সঙ্গে ধরত। (২) Flag debt: মৃত কোড মোছা হয়নি, আর পুরোনো flag-এর নাম নতুন অর্থে আবার ব্যবহার হয়েছে — একটি flag-এর একটিই অর্থ, একটিই মালিক, আর rollout শেষে মুছে ফেলার নিয়ম থাকলে এটি হতো না। (৩) Kill switch আর blast radius: পুরো ব্যবস্থা একসাথে চালু হয়েছিল; ছোট অংশে শুরু আর অস্বাভাবিক order-এর হারে স্বয়ংক্রিয় থামা থাকলে ক্ষতি অনেক ছোট থাকত। (৪) ভয়ের মুহূর্তে ভুল rollback: বিবরণ অনুযায়ী নতুন কোড সরানোর চেষ্টায় সমস্যা আরও বেড়েছিল, কারণ flag তখনো চালু — ফেরার পথ আগে থেকে পরীক্ষা করা ছিল না।
Mobile wallet-এ একই ধরনের ভুল মানে দ্বিগুণ টাকা কাটা — deploy-এর যন্ত্রায়ন তাই আরাম নয়, নিরাপত্তার শর্ত।
মূল উপলব্ধি: বড় বিপর্যয় প্রায়ই একটি ছোট, পরিচিত অবহেলার যোগফল — অসম্পূর্ণ deploy, পুরোনো flag, আর ফেরার অপরীক্ষিত পথ। প্রতিটির প্রতিকার সাধারণ, কিন্তু নিয়ম করে প্রতিদিন মানতে হয়।
প্র ০২অনেক প্রতিষ্ঠান ভাবে “কম deploy মানে কম ঝুঁকি” — তাই মাসে একবার বড় release, আর বৃহস্পতিবার বিকেলের পর deploy নিষেধ। এই ধারণা কতটা ঠিক?
একটি deploy-এর ঝুঁকি মোটামুটি তার ভেতরের পরিবর্তনের পরিমাণের সাথে বাড়ে। মাসে একবার deploy করলে প্রতিটিতে এক মাসের পরিবর্তন — শত শত commit, কয়েকটি দলের কাজ। কিছু ভাঙলে কোন পরিবর্তনটি দায়ী, খুঁজতে ঘণ্টা লাগে; ফেরালে সব দলের সব কাজ একসাথে ফেরে; আর পরের release-এর জন্য চাপ আরও বাড়ে। ফলে বড় release সত্যিই ভয়ের হয়, আর সেই ভয় deploy আরও কমায় — একটি দুষ্টচক্র। DORA গবেষণা আর Accelerate (২০১৮) বইয়ের জরিপে ঘন ঘন deploy করা দলগুলোর change failure rate আর ফিরে আসার সময় বরং ভালো পাওয়া গেছে। তবে এটি সম্পর্ক, কারণ-ফল নয়: ঘন ঘন deploy করতে পারে তারাই, যাদের test, যন্ত্রায়ন আর ছোট ব্যাচের অভ্যাস আছে। শুধু deploy-সংখ্যা বাড়ালে নিরাপত্তা আসে না; ভিত্তিটা আগে লাগে।
“শুক্রবার deploy নয়” নিয়মের পেছনে একটি সত্যিকারের যুক্তি আছে — সপ্তাহান্তে মানুষ কম, সমস্যা ধরতে আর সারাতে দেরি। কিন্তু এটি উপসর্গের চিকিৎসা। ভালো প্রশ্ন: আমাদের deploy কেন এমন যে তার পরে মানুষকে পাহারা দিতে হয়? স্বয়ংক্রিয় canary analysis আর rollback থাকলে শুক্রবারের deploy-ও ছোট আর নিরাপদ। তবে কিছু সময়ে সংযম যুক্তিসংগত — ঈদের আগের সপ্তাহে কেনাকাটার অ্যাপ বা মাসের বেতনের দিনে একটি পেমেন্ট সেবা, যখন ভিড় সর্বোচ্চ আর ভুলের দাম সবচেয়ে বেশি। সেখানে “change freeze” একটি ব্যবসায়িক সিদ্ধান্ত, প্রকৌশলের ভয় নয়; আর freeze-এর আগে-পরে জমে থাকা পরিবর্তন যেন একসাথে একটি বিশাল release না হয়, সেটিও পরিকল্পনায় রাখতে হয়।
মূল উপলব্ধি: ঝুঁকি deploy-এর সংখ্যায় নয়, প্রতিটি deploy-এর আকারে আর ফেরার গতিতে। ছোট, ঘন ঘন, স্বয়ংক্রিয়ভাবে যাচাই করা deploy সাধারণত কম, বড় আর ভয়ের deploy-এর চেয়ে নিরাপদ।
প্র ০৩Blue-green-এ “মুহূর্তে ফেরা” যায় — তাহলে সবাই canary-র ঝামেলায় যায় কেন? আর blue-green-এর database নিয়ে আসল সমস্যাটা কী?
Blue-green-এর তিনটি বড় সুবিধা: কোনো মুহূর্তে দুই সংস্করণ মিশে চলে না, নতুন দলকে switch-এর আগে পুরোপুরি পরীক্ষা করা যায়, আর ফেরা একটি router-এর বদল। Humble ও Farley-র ২০১০ সালের Continuous Delivery বই আর Martin Fowler-এর লেখায় ধারণাটি জনপ্রিয় হয়। কিন্তু অংশ ৪-এর stepper-এ দেখেছেন: switch-এর মুহূর্তে ১০০% ব্যবহারকারী নতুন সংস্করণে। কোনো bug যদি শুধু আসল ভারে বা আসল ডেটায় দেখা দেয়, smoke test তা ধরবে না, আর ধরা পড়ার আগ পর্যন্ত সবাই প্রভাবিত। Canary এখানে মৌলিকভাবে আলাদা — প্রথম ক্ষতি ১% মানুষের। তাছাড়া blue-green পুরো দ্বিগুণ capacity চায়, যা বড় সেবায় বড় খরচ, যদিও cloud-এ শুধু switch-এর সময়টুকু ভাড়া নিলে খরচ কমে।
Database-এর সমস্যা আরও গভীর। Blue আর green দুটি আলাদা দল, কিন্তু database সাধারণত একটিই — কারণ দুটি database আলাদা রাখলে switch-এর মুহূর্তে ডেটা সিঙ্ক করা প্রায় অসম্ভব। ফলে green যদি schema বদলায়, blue-কেও সেই schema-তে চলতে হবে, নইলে ফেরা ভাঙে। আর green চলার সময় যে ডেটা লিখেছে, ফেরার পরে blue-কে তা পড়তে পারতে হবে। মানে “blue-green-এ দুই সংস্করণ মেশে না” কথাটি কোডের জন্য সত্য, ডেটার জন্য নয় — ডেটার স্তরে দুই সংস্করণকে মানানসই রাখার দায় পুরোপুরি থেকে যায়, আর সেটিই অংশ ৭-এর expand/contract.
বাস্তবে অনেক দল দুটি মেশায়: green দল বানানো, তার পর router দিয়ে green-এ ১%, ৫%, ২৫% traffic — canary — আর শেষে পূর্ণ switch, blue কিছুক্ষণ গরম রেখে। যে ব্যবস্থায় মিশ্র সংস্করণ একেবারেই চলে না (যেমন কিছু পুরোনো stateful ব্যবস্থা), সেখানে খাঁটি blue-green যুক্তিসংগত।
মূল উপলব্ধি: Blue-green ফেরার গতি দেয়, canary ক্ষতির আকার ছোট রাখে — আর দুটির কোনোটিই database-এর মানানসইতার দায় থেকে মুক্তি দেয় না।
প্র ০৪Kubernetes-এর মতো declarative ব্যবস্থায় “কী চাই” লিখলেই যন্ত্র মিলিয়ে নেয়। তাহলে এখনো এত deploy-সম্পর্কিত outage হয় কেন?
Declarative নকশা ঠিক যা প্রতিশ্রুতি দেয়, তা-ই করে: আসল অবস্থাকে লেখা অবস্থার দিকে টানে। কিন্তু লেখা অবস্থাটাই ভুল হলে যন্ত্র নিষ্ঠার সাথে ভুলটিই সবখানে ছড়ায়, আর দ্রুত। Borg থেকে Kubernetes পর্যন্ত অভিজ্ঞতার বর্ণনায় (“Borg, Omega, and Kubernetes”, ২০১৬) reconciliation loop-কে শক্তি হিসেবে দেখানো হয়েছে — কিন্তু loop জানে না আপনার উদ্দেশ্য কী ছিল।
সাধারণ কয়েকটি ফাঁদ। (১) অগভীর readiness: probe শুধু “process চলছে” দেখে, তাই database-এ সংযোগ না পাওয়া Pod-ও “প্রস্তুত” — rollout নির্বিঘ্নে শেষ হয়, অথচ সব অনুরোধ ব্যর্থ। আবার খুব গভীর liveness probe database ধীর হলে সব Pod একসাথে restart করায় (পর্ব ৪৮)। (২) Rollout সফল, release ব্যর্থ: Kubernetes-এর কাছে “সফল” মানে Pod-গুলো প্রস্তুত, ব্যবহারকারীর অভিজ্ঞতা নয়; error rate আর latency দেখার verify ধাপ আলাদা লাগে। (৩) Config-এর ভুল একসাথে: একটি ConfigMap বা global setting সব cluster-এ একসাথে বদলালে canary-র সব সুরক্ষা পাশ কাটানো হয়। (৪) ধারণক্ষমতা: maxSurge-এর বাড়তি Pod-এর জন্য cluster-এ জায়গা না থাকলে rollout আটকে থাকে, আর maxUnavailable ভিড়ের সময় capacity কমায় (অংশ ৩-এর সিমুলেটর)। (৫) Database: orchestrator কোড বদলায়, schema-র মানানসইতা দেখে না।
GitOps এই সমস্যার কিছুটা সামলায় — প্রতিটি বদল review হয়, ইতিহাস থাকে, ফেরা সহজ। কিন্তু review-কারী মানুষ, আর একটি ছোট YAML বদলের প্রভাব প্রায়ই বড় — তাই config-এর বদলও কোডের মতোই canary-তে, ছোট অংশে আগে, মেপে মেপে ছড়ানো দরকার।
মূল উপলব্ধি: Declarative যন্ত্র “কীভাবে” প্রশ্নটি সমাধান করে, “কী” আর “ঠিক হলো কি না” নয়। ভালো probe, deploy-এর পরের যাচাই, আর config-কেও ধাপে ধাপে ছড়ানোর অভ্যাস ছাড়া যন্ত্র শুধু ভুলকে দক্ষ করে।
প্র ০৫২০২০ সালের SolarWinds ঘটনা আর ২০২১-এর Log4Shell — software supply chain নিয়ে এত আলোচনা কেন শুরু হলো, আর deploy-প্রক্রিয়া কীভাবে প্রতিরক্ষায় সাহায্য করে?
দুটি ঘটনা দুই রকম শিক্ষা দেয়। ২০২০ সালের শেষে প্রকাশ পায়, SolarWinds নামের একটি প্রতিষ্ঠানের network-পর্যবেক্ষণ সফটওয়্যারের build ব্যবস্থায় আক্রমণকারীরা ঢুকে পড়েছিল; ফলে প্রতিষ্ঠানের নিজের স্বাক্ষর করা, সাধারণ update-এর ভেতর দিয়েই ক্ষতিকর কোড বহু গ্রাহকের কাছে পৌঁছায়। শিক্ষা: “স্বাক্ষর আছে” মানে শুধু “এই build ব্যবস্থা থেকে এসেছে” — build ব্যবস্থাটাই বিশ্বস্ত না হলে স্বাক্ষর কিছু প্রমাণ করে না। ২০২১ সালের ডিসেম্বরে Java-র বহুল ব্যবহৃত logging library Log4j-তে একটি গুরুতর দুর্বলতা (Log4Shell নামে পরিচিত) প্রকাশ পায়। তখন বহু প্রতিষ্ঠানের প্রথম, কঠিন প্রশ্ন ছিল: “আমাদের কোন কোন সেবায় Log4j আছে, কোন সংস্করণে?” — কারণ অনেক জায়গায় এটি সরাসরি নয়, অন্য library-র ভেতর দিয়ে এসেছিল।
এই পর্বের অভ্যাসগুলো প্রতিরক্ষার স্তর। SBOM থাকলে দ্বিতীয় প্রশ্নের উত্তর একটি খোঁজ, দিনের পর দিন হাতে খোঁজাখুঁজি নয়; মার্কিন সরকারের ২০২১ সালের একটি নির্বাহী আদেশও সরকারি ক্রয়ে SBOM-এর ওপর জোর দেয়। Pin আর lockfile নিশ্চিত করে নতুন, অপরীক্ষিত কোড নিঃশব্দে build-এ ঢোকে না; আবার দ্রুত সংশোধন ছড়াতে হলে pipeline দ্রুত আর স্বয়ংক্রিয় হওয়াও দরকার — যে দল দিনে কয়েকবার deploy করে, সে সংশোধিত library ঘণ্টার মধ্যে সব সেবায় পৌঁছাতে পারে। Build একবার, স্বাক্ষর, আর deploy-এর সময় যাচাই নিশ্চিত করে যা চলছে তা বিশ্বস্ত pipeline-এরই তৈরি। SLSA-র মতো কাঠামো আর reproducible build — একই উৎস থেকে যে কেউ build করলে একই bytes — build ব্যবস্থাকেও যাচাইয়ের আওতায় আনার চেষ্টা। ২০২৪ সালে xz নামের একটি compression library-তে সুপরিকল্পিতভাবে লুকানো backdoor ধরা পড়া দেখায়, ঝুঁকিটা কাল্পনিক নয়।
মূল উপলব্ধি: আপনার সেবার বেশিরভাগ কোড অন্যের লেখা। কী আছে তা জানা (SBOM), কী ঢুকছে তা নিয়ন্ত্রণ (pin), কোথা থেকে এল তা প্রমাণ (স্বাক্ষর), আর দ্রুত বদলাতে পারা (স্বয়ংক্রিয় pipeline) — চারটিই একসাথে প্রতিরক্ষা।
অনুশীলন
খাতা-কলম আর উপরের সিমুলেটর পাশে রাখুন। আগে নিজে করুন, তারপর উত্তর মিলিয়ে নিন।
-
হিসাব — canary, error budget আর blue-green-এর খরচ: (ক) একটি canary ধাপে canary ২,০০০টি অনুরোধে ৩০টি error, stable ৩৮,০০০টিতে ১৯০টি। Pooled two-proportion z কত? নিয়ম z > 2.33 হলে rollback — কী হবে? (খ) সেবার SLO মাসে ৯৯.৯% সফল অনুরোধ, মাসে মোট ১ কোটি অনুরোধ। একটি খারাপ release-এ error হার ০.৫% থেকে ২% হলো, আর ভিড়ের সময় (৩০০ অনুরোধ/সেকেন্ড) ২০ মিনিট পরে ধরা পড়ল। Big-bang হলে বাড়তি কতগুলো ব্যর্থ অনুরোধ, আর error budget-এর কত শতাংশ? Canary ৫%-এ থাকলে? (গ) ২০টি instance সারা মাস (৭২০ ঘণ্টা) চলে। মাসে ১০টি blue-green release, প্রতিটিতে ১ ঘণ্টা দুটি দল একসাথে। বাড়তি instance-ঘণ্টা কত, আর মোটের কত শতাংশ?
(ক) pc = ৩০ ÷ ২,০০০ = ০.০১৫; pb = ১৯০ ÷ ৩৮,০০০ = ০.০০৫; pooled p = ২২০ ÷ ৪০,০০০ = ০.০০৫৫। Standard error = √(০.০০৫৫ × ০.৯৯৪৫ × (১/২,০০০ + ১/৩৮,০০০)) = √(০.০০৫৪৭০ × ০.০০০৫২৬৩) ≈ √(২.৮৭৯ × ১০−৬) ≈ ০.০০১৬৯৭। z = ০.০১০ ÷ ০.০০১৬৯৭ ≈ ৫.৮৯ > ২.৩৩ → rollback.
(খ) Error budget = ১ কোটির ০.১% = ১০,০০০টি ব্যর্থ অনুরোধ। ২০ মিনিটে অনুরোধ = ৩০০ × ১,২০০ = ৩,৬০,০০০; বাড়তি error হার ২% − ০.৫% = ১.৫%. Big-bang: ৩,৬০,০০০ × ০.০১৫ = ৫,৪০০ — মাসের budget-এর ৫৪% একটি ঘটনায়। Canary ৫%: ৩,৬০,০০০ × ০.০৫ × ০.০১৫ = ২৭০ — মাত্র ২.৭%. (ফেরার মিনিটগুলো ধরলে দুটিই কিছুটা বাড়বে।)
(গ) বাড়তি = ২০ × ১ × ১০ = ২০০ instance-ঘণ্টা; মোট ২০ × ৭২০ = ১৪,৪০০; ২০০ ÷ ১৪,৪০০ ≈ ১.৪%. শুধু switch-এর সময়টুকু ভাড়া নিলে “দ্বিগুণ capacity” খরচে ছোট।
-
আউটপুট অনুমান করুন: নিচের উদাহরণ-কোডে
push(name, digest)registry-তে একটি tag-কে একটি digest-এর দিকে বসায় (আগে থাকলে সরিয়ে দেয়), আরpull(ref)tag হলে তখনকার digest, digest হলে সেটিই ফেরত দেয়। চালানোর আগে ছাপা লাইনটি লিখুন, তারপর বলুন বুধবারে কী ঘটবে।push("shop:latest", "sha256:aaa"); // Monday build node1 = pull("shop:latest"); push("shop:latest", "sha256:bbb"); // Tuesday build, same tag node2 = pull("shop:latest"); node3 = pull("shop@sha256:aaa"); push("shop:1.4.0", "sha256:bbb"); print(node1, node2, node3, pull("shop:1.4.0") === pull("shop:latest")); // Wednesday: node1 restarts and pulls "shop:latest" againআউটপুট:
sha256:aaa sha256:bbb sha256:aaa true. node1 সোমবারlatestটেনেছিল, তখন সেটি aaa. মঙ্গলবার একই tag bbb-তে সরল, তাই node2 পেল bbb — দুই যন্ত্রে “একই” tag, ভিন্ন কোড। node3 digest দিয়ে চেয়েছে, তাই tag যা-ই হোক, aaa.1.4.0আরlatestএখন দুটিই bbb, তাইtrue.বুধবার: node1 restart হয়ে আবার
latestটানলে bbb পায় — কেউ deploy করেনি, অথচ node1-এর কোড বদলে গেল। আর মঙ্গলবারের build-এ bug থাকলে “rollback করেlatest-এ ফিরুন” কথাটির কোনো অর্থ নেই —latest-ই তো ভাঙা। ফেরার নিশ্চিত পথ একমাত্র node3-এর মতো digest. -
একটি release-পরিকল্পনার সমালোচনা: একটি কাল্পনিক ই-কমার্স অ্যাপের জন্য AI এই পরিকল্পনা দিয়েছে: “বৃহস্পতিবার রাতে, সপ্তাহের সব পরিবর্তন নিয়ে release. Image-এর tag
latest. Pod চালু হওয়ার সময় অ্যাপ নিজেই migration চালাবে:ALTER TABLE orders RENAME COLUMN total TO amount. Rolling update ডিফল্ট সেটিংয়ে, readiness probe নেই, কারণ অ্যাপ দ্রুত চালু হয়। সমস্যা হলে আগের image-এ rollback.” সমস্যাগুলো ধরুন আর একটি ভালো পরিকল্পনা দিন।সমস্যা: (১) সপ্তাহের সব পরিবর্তন একসাথে — বড় ব্যাচ। (২)
latesttag — যন্ত্রভেদে আলাদা কোড, আর rollback-এর লক্ষ্য অস্পষ্ট (অনুশীলন ২)। (৩) অ্যাপ চালুর সময় migration — দশটি Pod একসাথে একই ALTER চালাতে দৌড়াবে। (৪) এক ধাপে RENAME — rolling-এর সময় চলমান পুরোনো Pod-গুলোtotalখুঁজে ভাঙবে (অংশ ৭-এর big-bang সিমুলেশন), আর বড় টেবিলে lock-এর সারি পুরো সেবা থামাতে পারে। (৫) Readiness নেই — প্রস্তুত হওয়ার আগেই traffic, আর ভাঙা image-এর rollout আটকাবে না। (৬) “আগের image-এ rollback” কাজ করবে না — কলামের নাম বদলে গেছে।ভালো পরিকল্পনা: ছোট ছোট release, কাজের সময়ে, যখন দলের মানুষ আছেন। Image digest দিয়ে। Migration আলাদা job-এ, expand/contract:
amountযোগ → দুই জায়গায় লেখা → ব্যাচে backfill → পড়া বদল → পুরোনো লেখা বন্ধ → কয়েক সপ্তাহ পরেtotalমোছা; প্রতিটি DDL-এlock_timeout. Readiness probe database-সংযোগসহ, preStop আর graceful shutdown. ভিড়ের সময় capacity না হারাতে maxUnavailable ০. প্রথমে ৫% canary, error rate আর p99 SLO-র সাথে স্বয়ংক্রিয় তুলনা আর rollback. নতুন feature flag-এর পেছনে, kill switch-সহ। প্রতিটি ধাপের ফেরার পথ লেখা। -
নকশা করুন — একটি নতুন সুবিধার পুরো release: একটি কাল্পনিক mobile banking সেবায় “দৈনিক লেনদেনের সীমা নিজে ঠিক করুন” সুবিধা আসছে। লাগবে
accountsটেবিলে নতুন কলামdaily_limit, নতুন API, আর অ্যাপে নতুন পর্দা। পুরোনো অ্যাপ কয়েক মাস ফোনে থাকবে। Deploy থেকে পূর্ণ release পর্যন্ত ধাপগুলো সাজান।ধাপ ১ — Expand:
daily_limitnullable কলাম, default ছাড়া; NULL মানে “পুরোনো নিয়মের সীমা”।lock_timeout-সহ, ভিড়ের বাইরে।ধাপ ২ — Backend deploy, flag বন্ধ অবস্থায়: নতুন কোড NULL-কে পুরোনো সীমা হিসেবে পড়ে; নতুন API আছে কিন্তু flag বন্ধ। Canary ১% → ৫% → ২৫% → ১০০%, error rate, p99 আর “সফল লেনদেনের হার” — এই ব্যবসার metric — SLO-র সাথে মিলিয়ে। এখনো ব্যবহারকারীর কাছে কিছুই বদলায়নি, তাই rollback সহজ।
ধাপ ৩ — পুরোনো অ্যাপ: পুরোনো API-র উত্তরে কিছু মোছা বা অর্থ বদলানো হয়নি; সীমা যাচাই হয় সার্ভারে, অ্যাপে নয়।
ধাপ ৪ — Release, flag দিয়ে: প্রথমে কর্মীদের account, তারপর ১% গ্রাহক, তারপর ধাপে ধাপে। Kill switch বন্ধ করলে সবাই পুরোনো সীমায়।
ধাপ ৫ — পরিষ্কার: সবাই চালু হওয়ার কয়েক সপ্তাহ পরে flag আর পুরোনো পথ কোড থেকে মোছা (flag debt নয়)। NULL-এর অর্থ বদলানোর প্রয়োজন হলে সেটি আবার একটি expand/contract — backfill করে পুরোনো সীমার মান লিখে তারপর NOT NULL.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি commit থেকে সবার কাছে পৌঁছানো পর্যন্ত CI/CD-র ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🔨 Commit থেকে একবার image build, digest লেখা
- 🧪 Unit আর integration test
- 🔍 Dependency scan, SBOM আর image-এ স্বাক্ষর
- 📦 Registry-তে push
- 🐤 অল্প traffic-এ canary deploy আর SLO-র সাথে তুলনা
- 🚀 ধাপে ধাপে ১০০%-এ promote
একটি Kubernetes Deployment-এ replicas = 10, maxSurge = 2, maxUnavailable = 0. Rolling update চলাকালে একসাথে সর্বোচ্চ কতগুলো Pod থাকতে পারে, আর কমপক্ষে কতগুলো প্রস্তুত থাকবে?
- সর্বোচ্চ ১০, কমপক্ষে ৮
- সর্বোচ্চ ১২, কমপক্ষে ১০
- সর্বোচ্চ ১২, কমপক্ষে ৮
- সর্বোচ্চ ২০, কমপক্ষে ১০
দুটি cluster-এ একই container image-এর ঠিক একই bytes চলছে, এটি নিশ্চিত করার সবচেয়ে ভালো উপায় কোনটি?
- দুটিতেই
:latesttag ব্যবহার করা - দুটিতেই
:1.4.0tag ব্যবহার করা - Manifest-এ
@sha256:…digest লেখা - প্রতিটি cluster-এর জন্য একই commit থেকে আলাদা করে build করা
একটি canary ১% traffic-এ ৪০০টি অনুরোধে ৫টি error দিল (১.২৫%), stable-এ হার প্রায় ০.৫৮%; z ≈ 1.73, নিয়ম z > 2.33 হলে rollback. এই ফল থেকে সবচেয়ে সঠিক সিদ্ধান্ত কোনটি?
- Canary প্রমাণিতভাবে সুস্থ, সরাসরি ১০০%-এ নেওয়া যায়
- হার দ্বিগুণ দেখাচ্ছে, তাই নিয়ম যা-ই বলুক, এখনই rollback
- z-test এখানে অচল, তাই canary analysis বাদ দেওয়া উচিত
- এখনো যথেষ্ট তথ্য নেই — বেশি অনুরোধ জমিয়ে, যেমন পরের ধাপে, আবার পরীক্ষা
একটি চলমান সেবায় rolling deploy চলাকালে downtime ছাড়া users.phone কলামের নাম mobile করতে হবে। প্রথম ধাপ কোনটি?
- নতুন nullable কলাম
mobileযোগ করা ALTER TABLE users RENAME COLUMN phone TO mobileচালানো- প্রথমে
mobileথেকে পড়ে এমন কোড deploy করা phoneকলাম মুছে নতুন কলাম বানানো
একটি web server-এর Pod SIGTERM পেয়েছে, তখন ৯টি অনুরোধ চলছে আর grace period ৩০ সেকেন্ড। ভালো আচরণ কোনটি?
- সঙ্গে সঙ্গে exit করা, যাতে নতুন Pod দ্রুত জায়গা পায়
- স্বাভাবিকভাবে নতুন অনুরোধ নিতে থাকা, যতক্ষণ SIGKILL না আসে
- নতুন সংযোগ নেওয়া বন্ধ করে চলমান ৯টি শেষ করা, তারপর ৩০ সেকেন্ডের আগে নিজে exit
- Liveness probe ব্যর্থ করে নিজেকে restart করানো
কোড আজ production-এ deploy হলো, কিন্তু নতুন বোতামটি সবাই দেখবেন আগামী সপ্তাহে, ধাপে ধাপে। নতুন কোনো deploy ছাড়া এটি সম্ভব করে কোনটি?
- Blue-green deployment
- maxSurge বাড়ানো rolling update
- Shadow traffic
- Feature flag
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Build একবার, একই immutable artifact সব পরিবেশে; manifest-এ digest, tag নয়; পরিবেশভেদে শুধু config. হাতে সংশোধন নয় — বদলে দেওয়া।
- CI/CD: build → test → scan ও sign → deploy → verify; ছোট ব্যাচ আর trunk-based development ঝুঁকি কমায়। DORA: deployment frequency, lead time, change failure rate, time to restore.
- Kubernetes declarative: Pod, ReplicaSet, Deployment, Service; rolling update-এ maxSurge বাড়তি যন্ত্র, maxUnavailable capacity-র ছাড় ঠিক করে; readiness ছাড়া traffic নয়।
- Deploy ≠ release — feature flag আর kill switch; recreate, rolling, blue-green (দ্রুত ফেরা, দ্বিগুণ capacity), canary (ছোট blast radius, পরিসংখ্যানে যাচাই), shadow. আগে rollback, পরে কারণ; ফেরা অসম্ভব হলে roll-forward.
- Zero-downtime: preStop, SIGTERM, নতুন বন্ধ, চলমান শেষ, তারপর exit; দুই সংস্করণ মানানসই। Database-এ expand → dual write → backfill → পড়া বদল → পুরোনো লেখা বন্ধ → contract, আর lock-ভারী DDL-এ সাবধান।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৪ · Case study: URL shortener — ID তৈরি, redirect আর analyticsপরবর্তী পর্বপ্রথম পূর্ণ নকশা — এ পর্যন্ত শেখা সবকিছু একসাথে।
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ৫০ · Observability — log, metric, trace, SLI-SLO আর error budgetফিরে দেখুনCanary-র তুলনা যে metric আর SLO-র ওপর দাঁড়ায়।
- পর্ব ১২ · Data format আর serialization — JSON, Protobuf, Avro আর schema evolutionফিরে দেখুনদুই সংস্করণ একসাথে চলার জন্য backward আর forward compatibility.
- Container-এর ভেতর — namespace, cgroup আর Dockerপাশের ট্র্যাকঅপারেটিং সিস্টেম কোর্সে image-এর layer আর container আসলে কী।
- Cloud আর container নেটওয়ার্কিং — VPC, Docker আর Kubernetesপাশের ট্র্যাকPod, Service আর Ingress-এর নেটওয়ার্কের দিক।