এই পর্বে যা শিখবেন
- Recreate, rolling, blue-green আর canary-কে ঝুঁকি, খরচ আর blast radius দিয়ে তুলনা করা
- Service selector বদলে blue-green চালানো, ব্যর্থ অনুরোধ মাপা আর মুহূর্তে ফেরা
- Replica ratio কেন মোটা, আর Gateway API বা mesh কীভাবে ঠিক ওজন দেয়
- Canary-র sample size, error rate-এর confidence interval আর স্বয়ংক্রিয় বিশ্লেষণ
- Shadow traffic, feature flag, kill switch আর flag debt
- Database migration, session আর দীর্ঘস্থায়ী সংযোগের ফাঁদ, আর সংখ্যা দিয়ে কৌশল বাছাই
এই পর্বের আউটপুট সম্পর্কে: kubectl আর client-এর আউটপুট একটি স্থানীয় single-node cluster-এর (Kubernetes ১.৩৪) আসল run থেকে নেওয়া। নাম, IP, সময় আর সংখ্যা আপনার ক্ষেত্রে আলাদা হবে। লম্বা অংশ … দিয়ে ছাঁটা। একটি node-এ মাপা সংখ্যা ধারণা দেয়, production-এর প্রতিশ্রুতি নয়। Kubernetes-এর rolling update-এর খুঁটিনাটি পর্ব ৪৪-এ আছে, আর system design-এর দিক System Design কোর্সের deployment পর্বে। এখানে আমরা সেগুলো চালিয়ে মাপব আর তুলনা করব।
১ · চারটি কৌশল, তিনটি প্রশ্ন
পর্ব ৫৮-এ একবার build করা artifact তৈরি হয়েছে। এবার সেটি মানুষের কাছে পৌঁছাবে কীভাবে? আপনি এক ঝটকায় সবাইকে দিতে পারেন, কয়েকজন করে দিতে পারেন, বা আগে অল্প কয়েকজনকে দিয়ে দেখে নিতে পারেন। প্রতিটি পথের নিজস্ব দাম আছে।
বিয়েবাড়ির ক্যাটারিংয়ে নতুন রেজালা রেসিপি এনেছেন। Recreate: রান্নাঘর বন্ধ করে সব হাঁড়ি বদল, অতিথিরা অপেক্ষায়। Rolling: একটি একটি করে হাঁড়ি বদল, কিছু টেবিলে পুরোনো স্বাদ, কিছু টেবিলে নতুন। Blue-green: পাশে পুরো নতুন রান্না তৈরি, পরিবেশকদের এক সংকেতে সব টেবিলে নতুনটি। Canary: আগে এক টেবিলে নতুনটি দিয়ে মুখ দেখা, তারপর বাড়ানো।
চারটি কৌশল পাশাপাশি: সময়ের সাথে traffic কোন version-এ
Each box: time runs left to right, height is the share of live traffic
তিনটি প্রশ্নে সব কৌশল মাপা যায়। এক: নতুন version খারাপ হলে কত ভাগ ব্যবহারকারী আক্রান্ত হবেন? এটিই blast radius. দুই: ফিরতে কত সময়? তিন: এই নিরাপত্তার দাম কত, বাড়তি Pod আর বাড়তি জটিলতায়?
| কৌশল | Blast radius | ফেরার সময় | বাড়তি capacity | কখন মানায় |
|---|---|---|---|---|
| Recreate | সবাই | আবার পুরো deploy | নেই | দুই version একসাথে চলতেই পারে না, আর downtime মেনে নেওয়া যায় |
| Rolling | ধাপে ধাপে সবাই | আবার rolling, মিনিট | সামান্য (maxSurge) | সাধারণ stateless সেবা, দুই version মানানসই |
| Blue-green | এক মুহূর্তে সবাই | সেকেন্ড | দ্বিগুণ, যতক্ষণ দুই দল থাকে | দুই version-এর মিশ্রণ এড়াতে হবে, দ্রুত ফেরা জরুরি |
| Canary | শুরুতে অল্প | সেকেন্ড, যদি ওজন দিয়ে চলে | সামান্য, সাথে metric-এর ব্যবস্থা | অনেক traffic, ভালো metric, ভুলের দাম বেশি |
Recreate আর rolling Kubernetes Deployment-এর নিজের দুই strategy, পর্ব ৪৪-এ মাপা হয়েছে। Blue-green আর canary Deployment-এর কোনো field নয়। এগুলো traffic কোথায় যাবে, সেই সিদ্ধান্তের কৌশল। তাই পরের দুই অংশে আমরা দুটি Deployment আর একটি Service দিয়ে নিজেরাই এগুলো বানাব।
| ধারণা | AWS | Azure | Google Cloud |
|---|---|---|---|
| Blue-green | CodeDeploy-এর blue/green | App Service deployment slot | Load balancer-এর backend বদল |
| ওজন দিয়ে canary | ALB-র weighted target group | Container Apps-এর revision traffic | Cloud Run-এর traffic split, Cloud Deploy-এর canary |
নাম আর সুবিধা বদলায়, তাই লেখার সময়ের তথ্য ধরে নিয়ে provider-এর document দেখে নিন।
২ · Blue-green: একটি selector বদলে switch
কৌশলটি ছোট: দুটি Deployment পাশাপাশি চলে, একটি color: blue, আরেকটি color: green. Service-এর label selector যে রং বলে, traffic সেখানে যায়। Switch মানে Service-এর একটি field বদল, কোনো Pod সরে না।
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-blue
spec:
replicas: 3
selector:
matchLabels: {app: shop, color: blue}
template:
metadata:
labels: {app: shop, color: blue}
spec:
containers:
- name: web
image: nginx:1.27-alpine
env:
- {name: VERSION, value: v1}
command: ["sh", "-c"]
args:
- echo "$VERSION" > /usr/share/nginx/html/index.html;
exec nginx -g "daemon off;"
readinessProbe:
httpGet: {path: /, port: 80}
periodSeconds: 2
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {cpu: 100m, memory: 32Mi}
---
# shop-green is the same with color: green and VERSION v2
apiVersion: v1
kind: Service
metadata:
name: shop
spec:
selector: {app: shop, color: blue}
ports:
- {port: 80, targetPort: 80}মাপার জন্য একই namespace-এ একটি ছোট Python client Pod চালিয়েছি। সেটি প্রতি অনুরোধে নতুন TCP সংযোগ খোলে, সেকেন্ডে প্রায় ৯০টি অনুরোধ পাঠায়, উত্তরের version গোনে। চলার মাঝখানে অন্য terminal থেকে selector বদলানো হলো:
$ kubectl get svc shop -o wide
NAME TYPE CLUSTER-IP … PORT(S) SELECTOR
shop ClusterIP 10.101.71.3 … 80/TCP app=shop,color=blue
# client is running; now, from another terminal:
$ kubectl patch service shop -p '{"spec":{"selector":{"app":"shop","color":"green"}}}'
service/shop patched
# client output: requests per second, by version
sec v1 v2
3 90 0
4 20 69
5 0 90
…
total 1068 {'v1': 376, 'v2': 692}
v1: first 0.000s last 4.223s
v2: first 4.234s last 11.994s
# back to blue, same command, client running again
total 1082 {'v1': 649, 'v2': 433}
v1: first 4.832s last 11.998s
v2: first 0.000s last 4.821sFailed সারিই নেই: দুই দিকের switch মিলিয়ে ১,০৬৮ + ১,০৮২ = ২,১৫০ অনুরোধে ০টি ব্যর্থ। শেষ v1 আর প্রথম v2-এর ফাঁক ১১ মিলিসেকেন্ড, যা client-এর দুই অনুরোধের স্বাভাবিক ব্যবধান। কারণ দুই দলই আগে থেকে Ready, আর প্রতিটি অনুরোধ নতুন সংযোগ। Patch কমান্ড নিজে নিয়েছে প্রায় ০.০৮ সেকেন্ড।
একটি সতর্কতা: এই cluster-এ node একটি। বহু node-এ প্রতিটি node নিজের নিয়ম আলাদা সময়ে আপডেট করে, তাই খুব অল্প সময় দুই রং মিশতে পারে। Cloud load balancer-এর target বদলাতে আরও সময় লাগে, আর DNS দিয়ে switch করলে TTL-এর জন্য অপেক্ষা (পর্ব ২৯)। “মুহূর্তে” কথাটি সব জায়গায় সমান নয়।
খারাপ green: readiness পাস, ব্যবহারকারী ফেল
এবার green-কে ইচ্ছা করে খারাপ করি। /healthz ২০০ দেয়, তাই readiness probe খুশি, 3/3 Ready. কিন্তু আসল পাতা / দেয় ৫০০। এটি অগভীর probe-এর পরিচিত ফাঁদ। Switch করলাম, ৫ সেকেন্ড পরে ফিরলাম:
$ kubectl get deployment shop-green
NAME READY UP-TO-DATE AVAILABLE AGE
shop-green 3/3 3 3 95s
sec http500 v1
1 0 91
2 31 58
3 90 0
…
6 91 0
7 61 29
8 0 89
…
total 1261 {'http500': 454, 'v1': 807}
http500: first 2.654s last 7.675s৫.০২ সেকেন্ডে ৪৫৪টি অনুরোধ ব্যর্থ, আর ওই সময়ে একটি অনুরোধও বাঁচেনি। ফেরানোর patch চলতেই ঠিক হয়ে গেছে। এখানে দুই শিক্ষা। এক: ফেরা সত্যিই দ্রুত, কারণ blue তখনো চলছে। দুই: ক্ষতি ঠিক হয় ধরা পড়ার সময় দিয়ে। আমাদের script ৫ সেকেন্ডে ফিরেছে, আসল alert হয়তো ৫ মিনিট নেবে। ধরে নিন সেকেন্ডে ১,০০০ অনুরোধ, তাহলে ক্ষতি ৩ লাখ অনুরোধ। Blue-green ফেরা দ্রুত করে, blast radius ছোট করে না।
| Service selector | |
| Blue (v1) Pod | |
| Green (v2) Pod | |
| অনুরোধ যাচ্ছে | |
| মোট ব্যর্থ অনুরোধ |
- শুরু। Blue-এ ৩টি Pod সব traffic নিচ্ছে। লঞ্চঘাটের কথা ভাবুন: যাত্রীরা পুরোনো লঞ্চে উঠছেন।
- Green তৈরি। Deployment
shop-greenচালু হলো। Service-এর selector এখনো blue, তাই green-এ কোনো traffic নেই। পাশে নতুন লঞ্চ ভিড়েছে, সিঁড়ি লাগানো হয়নি। - Green যাচাই। সব Pod Ready. Ready যথেষ্ট নয়, তাই আলাদা test Service বা
port-forwardদিয়ে smoke test চালান, শুধু/healthzনয়। এই run-এ আমরা ইচ্ছা করে এই ধাপটি দুর্বল রেখেছি। - Cutover.
kubectl patch serviceselector-কে green করল। প্রায় ০.০৮ সেকেন্ডের কমান্ড, Pod কিছু জানেই না। নতুন সব সংযোগ green-এ। - গোলমাল ধরা পড়ল। Client-এ সব উত্তর 500. ৫.০২ সেকেন্ডে ৪৫৪টি ব্যর্থ। Readiness এই ভুল দেখতে পায়নি।
- Rollback. একই patch উল্টো করে selector blue. ব্যর্থতা সাথে সাথে থামল, কারণ blue একটুও সরেনি।
- পরিষ্কার করা। ব্যর্থ green রেখে log দেখুন, তারপর সরান। সফল switch-এর ক্ষেত্রে উল্টো: blue একটি নির্ধারিত সময় রেখে তারপর সরান। সেই সময়টুকুই দ্রুত ফেরার বিমা, আর তার দাম দ্বিগুণ Pod.
৩ · Canary: replica ratio ৯:১, আর আসল ভাগ কত
Service-এর ভেতরের load balancing কোনো version চেনে না, Pod চেনে। তাই সবচেয়ে সহজ canary: একটি Service-এর পেছনে দুই Deployment, একই app label. shop-stable-এ ৯টি Pod (v1), shop-canary-তে ১টি (v2). আশা ১০%. মেপে দেখি:
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
shop-canary 1/1 1 1 5s
shop-stable 9/9 9 9 5s
# new TCP connection per request, 15 seconds, three runs
RUN 1 2526 {'v1': 2282, 'v2': 244}
RUN 2 2552 {'v1': 2315, 'v2': 237}
RUN 3 2533 {'v1': 2295, 'v2': 238}
# one keep-alive connection for 2 seconds, six separate times
KEEP 1 366 {'v1': 366}
KEEP 2 371 {'v1': 371}
…
KEEP 6 365 {'v1': 365}
$ kubectl scale deployment shop-stable --replicas=4
deployment.apps/shop-stable scaled
RUN 1 2533 {'v1': 2042, 'v2': 491}
RUN 2 2548 {'v1': 2037, 'v2': 511}তিন run-এ v2-র ভাগ ৯.৬৬%, ৯.২৯% আর ৯.৪০%, মিলিয়ে ৭১৯/৭,৬১১ = ৯.৪৫%. Service প্রতিটি সংযোগে দৈব Pod বাছে, তাই ভাগ ১০%-এর আশেপাশে নড়ে। Stable-কে ৪-এ নামাতেই ভাগ হলো ১৯.৩৮% আর ২০.০৫%: কেউ canary-র ওজন বদলায়নি, শুধু scale বদলেছে। আর keep-alive সংযোগ একবার যে Pod পায়, সবটুকু সেখানেই যায়, এখানে ছয়টিই stable-এ।
Replica ratio কেন মোটা হাতিয়ার:
- ভাগ পূর্ণসংখ্যায় বাঁধা: ৫% চাইলে ১৯:১, মানে ২০টি Pod. ১% চাইলে ৯৯:১, মানে ১০০টি Pod. ১০ Pod-এর সেবায় একটি canary-ই প্রায় ১০% মানুষের ঝুঁকি।
- HPA ভাগ নড়ায়: Autoscaler stable-কে বাড়ালে বা কমালে canary-র ভাগও বদলায়, উপরের মাপের মতো।
- সংযোগ গোনে, অনুরোধ নয়: Service সংযোগের সময় Pod বাছে। HTTP/2 বা keep-alive client-এ অনুরোধের ভাগ অনেক দূরে সরে যেতে পারে।
- ফেরা মানে Pod সরানো: ওজন শূন্য করার আলাদা হাতল নেই। Canary Deployment-কে ০-তে নামাতে হয়।
- কে canary-তে, ঠিক করা যায় না: শুধু কর্মীদের, বা শুধু একটি শহরের ব্যবহারকারীদের পাঠানো যায় না।
ঠিক ওজন: traffic layer-এ সিদ্ধান্ত
সমাধান হলো ভাগের সিদ্ধান্ত Pod গোনা থেকে আলাদা করা। একটি L7 proxy প্রতিটি HTTP অনুরোধ দেখে ওজন অনুযায়ী পাঠায়, Pod যতগুলোই থাকুক। এটিই traffic splitting. Gateway API-র HTTPRoute-এ এটি মানক field, যা পর্ব ৪৬-এ দেখেছেন:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
spec:
parentRefs:
- name: shop-gw
rules:
- backendRefs:
- {name: shop-stable, port: 80, weight: 99}
- {name: shop-canary, port: 80, weight: 1}এখন দুই Service দুই version-এর জন্য, আর ওজন ৯৯ আর ১। Canary-তে একটি Pod-ই যথেষ্ট, stable-এর সংখ্যা যা-ই হোক। ওজন শূন্য করা একটি field-এর বদল। এই cluster-এ Gateway API-র CRD বসানো নেই, তাই HTTPRoute-টি এখানে চালানো হয়নি, শুধু YAML পরীক্ষা করা হয়েছে। ঠিক ভাগ নির্ভর করবে আপনার controller-এর ওপর।
Service mesh একই কাজ সার্ভিসের ভেতরের traffic-এ করে, প্রতিটি Pod-এর পাশের sidecar proxy বা node-স্তরের proxy দিয়ে। শুধু বাইরের দরজায় ওজন দিলে সার্ভিসের ভেতরের ডাক ওজন মানে না, mesh সেই ফাঁক পূরণ করে। বিনিময়ে একটি বড় নতুন উপাদান চালাতে হয়।
৪ · Mixed-version window: দুই version একসাথে যে ভাঙন আনে
Rolling আর canary-র একটি দাম ছবিতে দেখা যায় না। কিছুক্ষণ দুই version একসাথে চলে, আর একজন ব্যবহারকারীর পরপর দুটি অনুরোধ দুই version-এ পড়তে পারে। এই সময়টিই mixed-version window.
একটি ছোট পরীক্ষা। প্রতি version নিজের নামে একটি asset রাখে, app-v1.js বা app-v2.js. পাতা বলে কোন asset আনতে হবে। Client আগে পাতা আনে, তারপর নতুন সংযোগে সেই asset. ৪ replica, maxSurge: 1, maxUnavailable: 0, minReadySeconds: 3. Rollout চলার সময় client চলছে:
$ kubectl set env deployment/web VERSION=v2
deployment.apps/web env updated
sec asset-404 pair-ok
4 0 84
5 4 82
6 28 57
…
10 44 40
11 42 43
…
19 30 54
20 5 80
21 0 85
…
total 3835 {'asset-404': 480, 'pair-ok': 3355}
asset-404: first 5.928s last 20.183sRollout সফল, কোনো Pod ব্যর্থ নয়, readiness সব সবুজ। তবু প্রায় ১৪.৩ সেকেন্ডে ৪৮০টি জোড়া ভেঙেছে। দুই version সমান সমান থাকার সময় প্রায় অর্ধেক জোড়া ব্যর্থ, কারণ পাতা এক version থেকে আসে আর asset অন্য version-এ খোঁজে। এই সময়টি স্থির কোনো সংখ্যা নয়: একই পরীক্ষার আগের একটি run-এ জানালা আরও লম্বা ছিল। Canary হলে এই জানালা কয়েক ঘণ্টাও হতে পারে।
একই সমস্যার আরও রূপ: নতুন API field পুরোনো client বোঝে না, queue-তে v2-র লেখা বার্তা v1 consumer পড়তে পারে না, cache-এ v2-র ফরম্যাট v1 ভুল পড়ে। প্রতিকার:
- Asset আলাদা জায়গায়: CDN বা object storage-এ দুই version-এর asset-ই রাখুন, content hash-সহ নামে, আর পুরোনোগুলো কিছুদিন রেখে দিন (পর্ব ৩০)।
- API আর বার্তা backward compatible: নতুন field ঐচ্ছিক, অচেনা field উপেক্ষা, পুরোনো field একবারে মোছা নয়। দুই দিকেই মানানসই না হলে rollback-ও ভাঙবে।
- Version-sticky routing: একজন ব্যবহারকারীকে এক version-এ রাখা, cookie বা header দিয়ে। এটি লাগে L7 layer-এ।
- মিশ্রণই না থাকলে: blue-green. উপরের run-এ switch-এর সময় client মিশ্রণের জানালা পায়নি বললেই চলে।
৫ · Canary-র পরিসংখ্যান আর স্বয়ংক্রিয় রায়
Canary-র মূল্য নির্ভর করে একটি প্রশ্নে: কতটুকু দেখে “ঠিক আছে” বলছেন? ধরুন স্বাভাবিক error rate ০.৫%. ১% ওজনের canary-তে ২০০টি অনুরোধে ১টি error, হার ০.৫%. চমৎকার? না, ২০০ অনুরোধ প্রায় কিছুই বলে না। নিচের script দুটি জিনিস হিসাব করে: কোনো পার্থক্য ধরতে কত নমুনা লাগবে, আর একটি মাপ থেকে আসল হারের কতটুকু জানা যায়।
// Canary sample size and error-rate confidence: a simple binomial model.
const zAlpha = 1.645, zPower = 0.842; // one-sided 5%, power 80%
function sampleSize(p0, p1) { // requests needed in EACH group
const v = p0 * (1 - p0) + p1 * (1 - p1);
return Math.ceil((zAlpha + zPower) ** 2 * v / (p1 - p0) ** 2);
}
function wilson(errors, n, z = 1.96) { // 95% interval for an error rate
const p = errors / n, d = 1 + z * z / n;
const mid = (p + z * z / (2 * n)) / d;
const h = z * Math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d;
return [Math.max(0, mid - h), Math.min(1, mid + h)];
}
const pct = x => (x * 100).toFixed(3) + "%";
console.log("baseline 0.5%: requests needed per group");
const rps = 500, share = 0.05; // assumed traffic, canary share
for (const p1 of [0.02, 0.01, 0.0075, 0.006]) {
const n = sampleSize(0.005, p1);
const mins = n / (rps * share) / 60;
console.log(` detect ${pct(p1)}: n=${n}, ${mins.toFixed(1)} min at 5% of ${rps} rps`);
}
console.log("what a canary result can and cannot say (95% Wilson)");
for (const [e, n] of [[0, 100], [0, 1000], [1, 200], [3, 400], [12, 2000]]) {
const [lo, hi] = wilson(e, n);
console.log(` ${e}/${n}: rate ${pct(e / n)}, interval ${pct(lo)} to ${pct(hi)}`);
}
console.log(`rule of three, 0/1000: upper bound about ${pct(3 / 1000)}`);আউটপুট বলছে: ০.৫% থেকে ২% ধরতে ৬৭৬টি অনুরোধই যথেষ্ট (৫০০ rps-এর ৫%-এ ০.৫ মিনিট), ১% ধরতে ৩,৬৮১টি (২.৫ মিনিট), ০.৬% ধরতে ৬৭,৬৬০টি (৪৫.১ মিনিট)। পার্থক্য অর্ধেক হলে নমুনা প্রায় চার গুণ। আর 1/200: interval 0.088% to 2.777% মানে ২০০-এ ১টি error-এর পরে আসল হার ২.৭৭৭% পর্যন্তও হতে পারে। 0/1000-এর উপরসীমা Wilson-এ ০.৩৮৩%, আর সহজ হিসাব rule of three-এ ০.৩%.
n ≈ (zα + zβ)² · [p₀(1 − p₀) + p₁(1 − p₁)] / (p₁ − p₀)². এখানে p₀ স্বাভাবিক হার, p₁ যে হার ধরতে চান, zα = 1.645 ভুল সতর্কতার মাত্রা ৫%, আর zβ = 0.842 মানে statistical power ৮০%. মডেলটি ধরে নেয় প্রতিটি অনুরোধ স্বাধীন। বাস্তবে একজন ব্যবহারকারীর অনুরোধ, একটি খারাপ Pod বা একটি ধীর নির্ভরতা error-কে দল বাঁধায়, তাই আসল নিশ্চয়তা এর চেয়ে কম। সংখ্যাকে পরিমাপের মাত্রা হিসেবে নিন, প্রমাণ হিসেবে নয়।
Seeded error stream দিয়ে analysis
এবার রায়টি স্বয়ংক্রিয় করি, একেই বলে canary analysis. প্রতি ধাপে canary আর stable-এর error rate তুলনা, পরপর দুই ধাপে পার্থক্য স্পষ্ট হলে থামা। Error-এর ধারা আসে একটি seeded PRNG থেকে, তাই ফল প্রতিবার এক। আপনি rate বা seed বদলে দেখতে পারেন।
// Canary analysis with a seeded error stream: same seed, same result.
function mulberry32(a) {
return () => {
a |= 0; 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 gap = (rng, p) => Math.floor(Math.log(rng()) / Math.log(1 - p));
const binom = (rng, n, p) => { let k = 0; // jump from error to error
for (let i = gap(rng, p); i < n; i += 1 + gap(rng, p)) k++;
return k; };
function analyse(canaryRate, seed) {
const rng = mulberry32(seed), rps = 500;
let fails = 0, log = "";
for (const w of [0.01, 0.05, 0.25, 0.5]) {
const n = Math.round(rps * w * 300), m = Math.round(rps * (1 - w) * 300);
const ce = binom(rng, n, canaryRate), se = binom(rng, m, 0.005);
const pool = (ce + se) / (n + m);
const z = (ce / n - se / m) / Math.sqrt(pool * (1 - pool) * (1 / n + 1 / m));
fails = z > 2.33 ? fails + 1 : 0; // one-sided, about 1%
log += ` ${w * 100}%: canary ${ce}/${n}, z=${z.toFixed(2)}\n`;
if (fails >= 2) return log + " ABORT, weight back to 0";
}
return log + " PROMOTE to 100%";
}
for (const [name, rate] of [["healthy", 0.005], ["2x errors", 0.01], ["5x errors", 0.025]])
console.log(`${name}, seed 42\n${analyse(rate, 42)}`);
const count = (rate, word) => { let c = 0;
for (let s = 1; s <= 100; s++) if (analyse(rate, s).includes(word)) c++;
return c; };
console.log(`healthy aborted: ${count(0.005, "ABORT")} of 100 seeds`);
console.log(`1.3x bug promoted: ${count(0.0065, "PROMOTE")} of 100 seeds`);Seed 42-এ সুস্থ version চার ধাপ পেরিয়ে PROMOTE to 100%. দ্বিগুণ error-এর version ১%-এই সীমা পেরিয়েছে (z=3.73), ৫%-এ দ্বিতীয়বার (z=4.69), তাই ABORT. পাঁচ গুণ error-ও ৫%-এ থেমেছে, কারণ নিয়ম পরপর দুই ধাপ চায়। ১০০ seed-এ সুস্থ version একবারও থামেনি। কিন্তু 1.3x bug promoted: 27 of 100 seeds: ১.৩ গুণের সূক্ষ্ম bug প্রায় প্রতি চারটির একটি পার হয়ে গেছে। ছোট ক্ষতি ধরতে দীর্ঘ bake time লাগে। Error গোনার কৌশল (gap) প্রতিটি অনুরোধ না ঘুরে এক error থেকে পরের error-এ লাফ দেয়, তাই পাতায় দ্রুত চলে।
| Version | |
| Canary-র ওজন | |
| Canary error | |
| z | |
| রায় |
- v2, ১%. v2-র আসল error ১%, stable-এর ০.৫%. ১,৫০০ অনুরোধেই
z = 3.73, সীমা 2.33 পেরোল। একবার পেরোনোকে সতর্কতা ধরা হলো, নিয়ম চায় পরপর দুইবার, যাতে একটি দৈব ঝাঁকুনিতে rollout না থামে। - v2, ৫%, থামা।
z = 4.69, দ্বিতীয় ব্যর্থ ধাপ। Controller rollout থামাল। এই পর্যন্ত v2 পেয়েছে ১,৫০০ + ৭,৫০০ = ৯,০০০ অনুরোধ, ১০ মিনিটের মোট ৩,০০,০০০ অনুরোধের ৩%. - ওজন শূন্য। সব traffic আবার stable-এ। ওজন-ভিত্তিক ব্যবস্থায় এটি একটি config বদল। মনে রাখবেন, ৫ মিনিটের ধাপে থামতে ১০ মিনিট লেগেছে।
- v2.1, নতুন শুরু। Bug সারিয়ে আবার ১%. ১০/১,৫০০,
z = 0.86. পাস মানে “এখনো প্রমাণ নেই”, “ভালো” নয়। - ৫%. ৩২/৭,৫০০,
z = -1.05. ঋণাত্মক z মানে canary-র হার stable-এর চেয়ে সামান্য কম। - ২৫%. ১৮৪/৩৭,৫০০,
z = 0.15. এখন এক-চতুর্থাংশ traffic নতুনে, ভুল হলে ক্ষতিও বড়, তবে নমুনাও বড়। - ৫০%. ৩৬১/৭৫,০০০,
z = -1.63. এই ধাপে দুই দলের নমুনা সমান, তাই তুলনা সবচেয়ে শক্তিশালী। - ১০০%. সব ধাপ পাস, v2.1 নতুন stable. এবার পুরোনো Pod সরানো যায়।
Argo Rollouts আর Flagger: এই কাজটি controller-এর হাতে
উপরের loop হাতে চালানো ক্লান্তিকর, আর রাত তিনটায় মানুষ ভুল করে। Progressive delivery-র controller এটি যন্ত্রকে দেয়। Argo Rollouts Deployment-এর জায়গায় Rollout নামের CRD ব্যবহার করে। Flagger আপনার Deployment রেখে তার পাশে canary বানায়। দুটিই একটি traffic provider (Gateway API, ingress বা mesh) আর একটি metric উৎস (যেমন Prometheus, পর্ব ৬৩) চায়। ধারণাটি একটি নমুনা YAML-এ দেখুন, এই cluster-এ এই CRD বসানো নেই, তাই চালানো হয়নি:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: shop
spec:
replicas: 10
selector:
matchLabels: {app: shop}
template:
metadata:
labels: {app: shop}
spec:
containers:
- {name: web, image: "example/shop:2.0.0"}
strategy:
canary:
steps:
- setWeight: 5
- pause: {duration: 5m}
- analysis:
templates:
- templateName: error-rate
- setWeight: 25
- pause: {duration: 10m}
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate
spec:
metrics:
- name: error-rate
interval: 1m
count: 5
failureLimit: 2
successCondition: result[0] < 0.01
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{app="shop",status=~"5.."}[1m]))
/ sum(rate(http_requests_total{app="shop"}[1m]))এখানে একটি নির্দিষ্ট সীমা (১%) আছে। আরও ভালো হলো canary-কে একই সময়ের stable-এর সাথে তুলনা করা, যেমন উপরের script করেছে, কারণ পিক আওয়ারে সবার error বাড়ে। কম traffic-এর সেবায়, যেখানে নমুনা জমে না, সময়ের ধাপ দীর্ঘ করুন বা synthetic অনুরোধ যোগ করুন।
৬ · Shadow traffic আর feature flag
Shadow: আসল ভার, শূন্য ব্যবহারকারী-ঝুঁকি
Shadow traffic-এ proxy প্রতিটি আসল অনুরোধের একটি কপি নতুন version-এ পাঠায়, আর তার উত্তর ফেলে দেয়। ব্যবহারকারী শুধু পুরোনো উত্তর পান। আসল ভারে নতুন version-এর latency আর crash মাপা যায়। Gateway API-র RequestMirror filter আর mesh-গুলোর mirror সুবিধা এই কাজ করে, সমর্থন implementation-ভেদে আলাদা।
বড় বিপদ: side effect. Shadow-র কোড আসল database-এ লিখলে, পেমেন্ট ডাকলে বা SMS পাঠালে একজন গ্রাহক দুবার টাকা দেবেন। তাই shadow চলে read-only পথে, বা আলাদা database আর নকল (stub) বাইরের সেবায়। খরচও ভুলবেন না: নতুন version পুরো ভার নেয়, মানে সেই সময়টুকুর জন্য প্রায় দ্বিগুণ capacity. আরেকটি কথা, shadow ব্যবহারকারীর অনুভূতি মাপে না, আর পুরোনো সেশনের অবস্থা না থাকায় কিছু অনুরোধ নকল ভুলও দেবে।
Feature flag: deploy আর release আলাদা
Deploy মানে কোড সার্ভারে পৌঁছে চলা। Release মানে ব্যবহারকারী নতুন আচরণ দেখা। Feature flag দুটিকে আলাদা করে: কোড আজ সব Pod-এ, বোতাম বন্ধ। তারপর আগে কর্মীদের জন্য, পরে ১%, পরে সবার জন্য, কোনো নতুন deploy ছাড়াই। ঘরের মেইন লাইনে বিদ্যুৎ এসে গেছে, কিন্তু কোন ঘরের সুইচ কখন অন করবেন, সেটি আলাদা সিদ্ধান্ত।
| ধরন | কাজ | আয়ু |
|---|---|---|
| Release flag | অসম্পূর্ণ বা নতুন feature ধীরে খোলা | দিন বা সপ্তাহ, তারপর মুছুন |
| Ops flag, kill switch | ভারী বা ঝুঁকিপূর্ণ অংশ জরুরিভাবে বন্ধ | দীর্ঘ, মাঝে মাঝে পরীক্ষা করুন |
| Experiment flag | A/B পরীক্ষা, দুই দলের তুলনা | পরীক্ষা শেষ পর্যন্ত |
| Permission flag | কোন গ্রাহক কোন সুবিধা পাবেন | দীর্ঘ, আসলে পণ্যের অংশ |
ধাপে ধাপে খোলার সময় দৈব সংখ্যা নয়, স্থির hash ব্যবহার করুন: hash(userId + flagName) % 100 < 5. একই ব্যবহারকারী প্রতিবার একই ফল পান, আর ৫% থেকে ২৫%-এ গেলে আগের সবাই ভেতরেই থাকেন। Flag-এর সিদ্ধান্ত স্থানীয়ভাবে cache করুন, যাতে flag সেবা বন্ধ হলে পুরো সাইট না পড়ে, আর ঠিক করে রাখুন উত্তর না পেলে ডিফল্ট কী।
Flag debt: প্রতিটি flag কোডে দুটি পথ রাখে, দশটি স্বাধীন flag মানে তাত্ত্বিকভাবে ১,০২৪টি অবস্থা, যার বেশিরভাগ কেউ পরীক্ষা করেনি। প্রতিটি flag-এর একজন মালিক আর একটি মেয়াদ রাখুন, শেষ হলে flag আর পুরোনো পথ দুটোই মুছুন, আর পুরোনো flag-এর নাম নতুন কাজে আবার ব্যবহার করবেন না।
Canary না flag? এগুলো প্রতিযোগী নয়। Canary কোডের নতুন version মাপে: memory, latency, crash. Flag একটি নির্দিষ্ট আচরণ নিয়ন্ত্রণ করে। ভালো দল দুটোই ব্যবহার করে: canary-তে কোড পৌঁছায়, flag বন্ধ থাকে, পরে flag ধীরে খোলে। তবে flag কোডের crash, memory leak বা ভুল library বাঁচায় না।
৭ · Database: expand and contract
কোড ফেরানো যায়, ডেটা ফেরানো কঠিন। Blue আর green সাধারণত একই database ব্যবহার করে, তাই name কলামের নাম এক ধাপে full_name করলে পুরোনো কোড সাথে সাথে ভাঙে, আর ফেরার পথও। প্রতিটি মুহূর্তে যত version চলছে, সবগুলোকে schema-র সাথে মানাতে হবে, এটিই মূল নিয়ম। Migration-এর তত্ত্ব DBMS কোর্সে আছে, এখানে দেখি deploy-এর ক্রম।
পুরোনো সেতু ভেঙে নতুন সেতু বানাতে গেলে কেউ আগে পুরোনোটি ভাঙে না। পাশে নতুন সেতু হয়, দুটিতেই কিছুদিন গাড়ি চলে, সব গাড়ি নতুনটিতে অভ্যস্ত হলে তবেই পুরোনোটি ভাঙা হয়।
| কলাম | |
| চলমান app | |
| কোড লেখে | |
| কোড পড়ে | |
| পুরোনো সারি |
- শুরু। v1 শুধু
nameজানে। লক্ষ্য: কোনো মুহূর্তে কিছু না ভেঙে কলামটিfull_nameকরা। - Expand. ফাঁকা, nullable কলাম যোগ। v1 এটিকে দেখেই না, কিছু ভাঙে না। বড় table-এ সামান্য lock-ও লাগতে পারে, তাই lock timeout রাখুন।
- Release ১: দুই জায়গায় লেখা। v2 লেখে দুই কলামেই, পড়ে পুরোনোটি থেকে। Rollout চলার সময় v1-ও লিখছে, তাই নতুন সারির
full_nameএখনো ফাঁকা থাকতে পারে। এই dual write একই transaction-এ হওয়া উচিত। - Backfill. সব Pod v2 হওয়ার পরেই পুরোনো সারি ভরুন। ছোট batch, মাঝে বিরতি, যাতে replica পিছিয়ে না পড়ে আর lock দীর্ঘ না হয়। আগে চালালে v1 নতুন ফাঁকা সারি রেখে যেত।
- Release ২: পড়া বদল। v3 পড়ে
full_nameথেকে, লেখে দুটিতেই। v2-তে ফেরা নিরাপদ, কারণnameএখনো সত্য। এই ধাপটি flag দিয়েও ধীরে খোলা যায়। - Release ৩: পুরোনো লেখা বন্ধ। v4 শুধু
full_nameলেখে। এই সময় থেকেnameপুরোনো হতে শুরু করে, তাই v2-তে ফেরার দরজা বন্ধ, v3 পর্যন্ত ফেরা যায়। - Contract. সব v4, বেশ কিছুদিন শান্ত, পুরোনো Pod আর worker-ও নেই, backup আছে। তারপর
DROP COLUMN. এই ধাপ ফেরানো যায় না, তাই সবার শেষে আর আলাদা deploy-তে।
এই ক্রম হাতে মেলানোর বদলে একটি ছোট checker লিখি। প্রতিটি version কী পড়ে আর লেখে, তা ঘোষণা করা আছে। ধাপে ধাপে দেখা হয় চলমান সব version, rollout-এর সময় দুটি, কাজ করে কি না:
// Expand-contract order checker. Each app version states what it needs.
const APP = {
v1: { reads: "name", writes: ["name"] },
v2: { reads: "name", writes: ["name", "full_name"] },
v3: { reads: "full_name", writes: ["name", "full_name"] },
v4: { reads: "full_name", writes: ["full_name"] },
};
function check(plan) {
const cols = new Set(["name"]); let running = ["v1"], filled = false;
for (const step of plan) {
const [op, arg] = step.split(" ");
if (op === "add") cols.add(arg);
if (op === "drop") cols.delete(arg);
if (op === "deploy") running = [running[running.length - 1], arg]; // mixed
if (op === "finish") running = running.slice(-1);
if (op === "backfill" && running.some(v => !APP[v].writes.includes("full_name")))
return `FAIL "${step}": an old writer still leaves new rows empty`;
if (op === "backfill") filled = true;
for (const v of running) {
for (const c of [APP[v].reads, ...APP[v].writes])
if (!cols.has(c)) return `FAIL "${step}": ${v} needs missing column ${c}`;
if (APP[v].reads === "full_name" && !filled)
return `FAIL "${step}": ${v} reads full_name before backfill`;
}
}
return "OK: every running version works at every step";
}
const good = ["add full_name", "deploy v2", "finish", "backfill", "deploy v3",
"finish", "deploy v4", "finish", "drop name"];
const plans = { good, "rename at once": ["add full_name", "drop name", "deploy v4"],
"backfill early": ["add full_name", "backfill", "deploy v2", "finish"],
"read too early": ["add full_name", "deploy v2", "finish", "deploy v3"],
"drop in rollout": good.slice(0, 7).concat("drop name") };
for (const [k, p] of Object.entries(plans)) console.log(k.padEnd(16), check(p));good পাস করে। rename at once থামে v1 needs missing column name-এ, পুরোনো কোড তখনো চলছে। backfill early ধরা পড়ে কারণ v1 তখনো নতুন ফাঁকা সারি রেখে যাচ্ছে। read too early-তে v3 ফাঁকা কলাম পড়ত। drop in rollout বলছে v4-এর rollout চলার সময় v3 এখনো name-এ লেখে। Checker-টি নিজে শেখার জন্য: এটি ডেটা মেলায় না, migration চালায় না।
কিছু বাস্তব কথা। Nullable কলাম যোগ সাধারণত দ্রুত, কিন্তু index বা কড়া constraint যোগ বড় table আটকে দিতে পারে। PostgreSQL-এ CREATE INDEX CONCURRENTLY, আর MySQL-এ gh-ost বা pt-online-schema-change-এর মতো টুল এ জন্যই আছে। Migration Pod-এর শুরুতে চালাবেন না, তাহলে দশটি Pod একসাথে চালাবে; pipeline-এর আলাদা ধাপ বা একটি Job-এ দিন।
৮ · Session আর দীর্ঘস্থায়ী সংযোগ
Traffic সরানো মানে নতুন সংযোগ সরানো। চলমান সংযোগের কী হয়? পরীক্ষা: একটি keep-alive সংযোগ, প্রায় ৩ সেকেন্ডে selector green-এ বদল, প্রায় ৭ সেকেন্ডে blue-কে ০-তে নামানো।
sec conn-err v1 v2
3 0 94 0
…
5 0 94 0
6 1 77 17
7 0 0 94
…
total 1316 {'conn-err': 1, 'v1': 638, 'v2': 677}
v1: first 0.000s last 6.809s
v2: first 6.830s last 13.998s
errors {'RemoteDisconnected': 1}Selector বদলের পরেও সংযোগটি আরও প্রায় ৪ সেকেন্ড v1-এই ছিল, Service-এর দৃষ্টিতে blue তখন আর সদস্য নয়। সংযোগ ভেঙেছে তখনই, যখন blue-এর Pod থেমেছে: একটি RemoteDisconnected, তারপর নতুন সংযোগে v2. অর্থাৎ “selector বদলে ১০০% v2” কথাটি শুধু নতুন সংযোগের জন্য সত্য।
এর ফলাফল সরাসরি:
- Blue সরানোর তাড়া নয়: সব দীর্ঘ সংযোগ শেষ হওয়ার সুযোগ দিন, এটি connection draining. Pod-এ termination grace period আর preStop মিলিয়ে সময় দিন, পর্ব ৪৪-এর হিসাব মনে রেখে।
- WebSocket আর gRPC stream: ঘণ্টার পর ঘণ্টা চলতে পারে, draining শেষ হয় না। সার্ভার ধীরে ধীরে সংযোগ বন্ধ করবে, client দৈব বিলম্বসহ পুনঃসংযোগ করবে। নইলে একসাথে পুনঃসংযোগে connection storm.
- Session কোথায়: session Pod-এর memory-তে থাকলে switch-এ সবাই logout. Session বাইরে রাখুন (পর্ব ২৩-এর cache বা database), আর দুই version একই session পড়তে পারে কি না দেখুন।
- Sticky session ভাগ বিকৃত করে: ভারী কয়েকজন ব্যবহারকারী canary-তে আটকে গেলে অনুরোধের ভাগ আর ব্যবহারকারীর ভাগ আলাদা হয়। Service-এর
sessionAffinity: ClientIP-ও একই কথা, আর NAT-এর পেছনের অনেক মানুষ একটি IP. - চলমান দীর্ঘ কাজ: ফাইল আপলোড, background job বা পুরোনো queue consumer. Rollback-এর আগে দেখুন এগুলো পুরোনো version-এ শেষ হবে কি না।
৯ · কোন কৌশল কখন: সংখ্যা দিয়ে বাছাই
“Canary সেরা” কথাটি অর্থহীন, যতক্ষণ না হিসাব করেন। ধরে নিই একটি কাল্পনিক অনলাইন বাজার: ১০টি Pod, Pod-ঘণ্টা $০.০৫, সেকেন্ডে ১,০০০ অনুরোধ, মাসে ২০টি release, তার ১০% খারাপ, খারাপ version-এ ৫% অনুরোধ ব্যর্থ, আর alert আসতে ৫ মিনিট। সবই অনুমান, দাম provider-এর পাতায় দেখে নিন।
// Cost and risk per strategy. All inputs are assumed round numbers.
const podHour = 0.05, rps = 1000; // $0.05 is an assumption
const releases = 20, badShare = 0.10, bugRate = 0.05; // per month
const detectSec = 300; // alert needs 5 minutes
// downtime s, extra Pods, extra hours, share exposed, undo s
const S = [
["recreate", 60, 0, 0, 1.00, 60],
["rolling", 0, 2.5, 0.20, 0.50, 180],
["blue-green", 0, 10, 0.50, 1.00, 5],
["canary 5%", 0, 2, 1.00, 0.05, 30],
["canary+flag", 0, 2, 1.00, 0.05, 2],
];
console.log("strategy $/month planned-lost bad-release-errors month-total");
const rows = S.map(([name, down, extra, hours, share, undo]) => {
const dollars = extra * hours * podHour * releases;
const planned = down * rps * releases; // every release
const perBad = rps * share * bugRate * (detectSec + undo);
const total = planned + perBad * releases * badShare;
return { name, dollars, planned, perBad, total };
});
for (const r of rows) {
console.log(r.name.padEnd(12), r.dollars.toFixed(2).padStart(7),
String(r.planned).padStart(13), String(Math.round(r.perBad)).padStart(19),
String(Math.round(r.total)).padStart(12));
}
const budget = rps * 86400 * 30 * 0.001; // 99.9% SLO
console.log(`monthly error budget at 99.9%: ${budget} requests`);
for (const r of rows) {
console.log(r.name.padEnd(12), (r.total / budget * 100).toFixed(1) + "% of budget");
}Recreate-এর বড় ক্ষতি পরিকল্পিত downtime: প্রতি release-এ ৬০ সেকেন্ড, মাসে ১২ লাখ অনুরোধ, error budget-এর ৪৭.৭%. Rolling আর blue-green-এর খারাপ release-এ ক্ষতি ১২,০০০ আর ১৫,২৫০, canary-তে ৮২৫. Blue-green দ্রুত ফিরলেও এগিয়ে নেই, কারণ ৫ মিনিটের ধরা পড়া-র সময়টিই আসল। মাসিক বিল সব ক্ষেত্রে তুচ্ছ, সর্বোচ্চ $৫: এই আকারে আসল খরচ মানুষের সময় আর metric-ব্যবস্থায়, যা টেবিলে নেই। মডেলের দুর্বলতা: canary-র কম traffic-এ alert আসতে আরও দেরি হতে পারে, আর rolling-এর ৫০% ভাগ একটি গড় অনুমান। সংখ্যা বদলে নিজের সেবা বসান।
| পরিস্থিতি | বাছাই | কারণ |
|---|---|---|
| ভেতরের টুল, রাতে পাঁচ মিনিট বন্ধ চলে | Recreate বা rolling | জটিলতার দাম উঠবে না |
| সাধারণ web সেবা, অল্প traffic | Rolling + flag | Canary-র নমুনাই জমবে না |
| দুই version মিশলে ভাঙে, যেমন বড় protocol বদল | Blue-green | মিশ্রণের জানালা প্রায় শূন্য |
| পেমেন্ট, অনেক traffic, ভালো metric | Canary + flag + analysis | Blast radius ছোট, নমুনা দ্রুত জমে |
| নতুন engine, ভার নিয়ে সন্দেহ | Shadow, তারপর canary | আসল ভার, ব্যবহারকারী নিরাপদ |
| Stateful বা database engine upgrade | আলাদা পরিকল্পনা | এই কৌশলগুলো নিজে ডেটা সামলায় না (পর্ব ২২) |
আরও গভীরে: Blue-green ধারণাটি Humble ও Farley-র Continuous Delivery (২০১০) বইয়ে জনপ্রিয় হয়। Canary নিয়ে Google-এর The Site Reliability Workbook (২০১৮)-এ একটি পুরো অধ্যায় আছে। Netflix আর Google ২০১৮ সালে স্বয়ংক্রিয় canary বিশ্লেষণের টুল Kayenta প্রকাশ করে। একই metric বারবার দেখে থামার সিদ্ধান্ত নিলে ভুল সতর্কতা বাড়ে, এই “peeking” সমস্যার জন্য sequential পরীক্ষা ব্যবহার হয়। আর expand-contract-এর সাধারণ রূপ “parallel change” নামে Danilo Sato ২০১৪ সালে লেখেন, database-এর দিকটি Refactoring Databases (২০০৬) বইয়ে।
পরীক্ষা ও ইন্টারভিউতে:
- Deploy আর release-এর পার্থক্য? Deploy কোড চালায়, release ব্যবহারকারীর কাছে আচরণ খোলে; flag দুটিকে আলাদা করে।
- Blue-green বনাম canary? Blue-green দ্রুত ফেরে কিন্তু blast radius ১০০%; canary blast radius ছোট রাখে কিন্তু দুই version দীর্ঘক্ষণ চলে।
- Replica ratio দিয়ে ১% canary? ১০০টি Pod লাগে; L7 ওজন দিয়ে করুন।
- কলামের নাম বদল zero downtime-এ? Expand, dual write, backfill, পড়া বদল, লেখা বদল, তারপর contract.
- Shadow-র বড় ঝুঁকি? Side effect দুবার হওয়া, যেমন পেমেন্ট।
১০ · AI-এর যুগে release-পরিকল্পনা যাচাই
AI একটি canary YAML মিনিটে দেয়। কিন্তু সে আপনার traffic, metric বা database দেখে না। সে প্রায়ই পুরোনো API version লেখে, latest tag বসায়, নমুনার আকার না ভেবে “৫ মিনিট পরে promote” লেখে, আর migration-কে পরিকল্পনার বাইরে রাখে। আরও বিপজ্জনক: “rollback হলে kubectl delete চালান” বা DROP COLUMN একই release-এ, কোনো সতর্কতা ছাড়া। বানানো field-ও আসে, তাই প্রতিটি field document-এ মিলিয়ে নিন।
পরিকল্পনা চাওয়ার সময় আপনার সংখ্যাগুলো দিন, আর ফাঁক খুঁজতে বলুন:
AI-এর দেওয়া release-পরিকল্পনা হাতে পেলে: চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১কোডের জন্য canary আছে, কিন্তু একটি config বা content ফাইল এক মুহূর্তে সারা বিশ্বে গেলে কী হয়? সবচেয়ে বড় outage-গুলো থেকে কী শেখার আছে?
অনেক দলের release-সুরক্ষা শুধু কোডের পথে। Config, rule আর content প্রায়ই আলাদা, দ্রুত পথে যায়, কারণ “এটি তো কোড নয়”। প্রকাশিত postmortem-গুলো দেখায়, এই পথই বড় ক্ষতির উৎস হতে পারে।
২০১৯ সালের জুলাইয়ে Cloudflare একটি WAF rule প্রকাশ করে, যার একটি regular expression প্রচুর CPU খেয়ে ফেলে। তাদের প্রকাশিত বিবরণ অনুযায়ী এই ধরনের rule ধাপে ধাপে যাওয়ার পথে যেত না, একসাথে সবখানে পৌঁছাত, তাই অল্প সময়ের জন্য বিশ্বজুড়ে তাদের সেবা পড়ে যায়। ঘটনার পরে তারা এই rule-ও ধাপে ধাপে ছাড়ার ব্যবস্থায় আনার কথা জানায়।
২০২৪ সালের জুলাইয়ে CrowdStrike-এর Falcon sensor-এর একটি content update বহু Windows যন্ত্রকে crash করায়, হাসপাতাল, বিমান সংস্থা আর ব্যাংকের কাজ থেমে যায়। প্রতিষ্ঠানটির প্রকাশিত পর্যালোচনায় ভবিষ্যতে content update-ও ধাপে ধাপে ছাড়ার পরিকল্পনা ছিল। এখানে ফেরার পথও কঠিন ছিল, কারণ crash হওয়া যন্ত্র নিজে নতুন update নিতে পারে না, অনেক জায়গায় হাতে সারাতে হয়েছে।
শিক্ষা তিনটি। এক, যে কোনো কিছু যা production-এর আচরণ বদলায়, তাই release: config, flag, rule, ML মডেল, schema. দুই, blast radius ধাপে ধাপে বাড়ানো যায় cluster, zone আর region ধরে। তিন, ফেরার পথটি ভেঙে যাওয়া জিনিসের ওপর নির্ভর করলে সেটি পথই নয়।
Kubernetes-এ একটি ConfigMap সব Pod একসাথে পড়লে একই ফাঁদ। Config-কে version দিয়ে নতুন Pod-এ আনলে সেটিও rollout-এর সুরক্ষা পায়।
এই ঘটনাগুলোর আরেকটি মিল গতি। দ্রুত ছড়ানোই ছিল ব্যবস্থাটির গুণ, কারণ নিরাপত্তা-সংক্রান্ত rule দেরিতে পৌঁছালে ক্ষতি হয়। তাই “ধীরে ছড়ান” বললেই হয় না, ঝুঁকি আর গতির একটি সচেতন মাঝামাঝি বেছে নিতে হয়: হয়তো কয়েক মিনিটের ছোট ধাপ, জরুরি অবস্থার আলাদা পথ, আর সেই পথের নিজস্ব অনুমোদন।
মূল উপলব্ধি: প্রশ্নটি “কোড কীভাবে যায়” নয়, “যা কিছু আচরণ বদলায়, তা কি ছোট blast radius দিয়ে শুরু হয়?”
প্র ০২২০১২ সালের Knight Capital ঘটনায় একটি পুরোনো flag কীভাবে এত ক্ষতি করল? এখানে deploy আর flag-এর কোন নিয়ম ভেঙেছিল?
মার্কিন নিয়ন্ত্রক SEC-এর ২০১৩ সালের আদেশে ঘটনাটির বিবরণ আছে। Knight Capital একটি নতুন trading সুবিধার কোড আটটি সার্ভারে হাতে বসাচ্ছিল, একটিতে নতুন কোড পৌঁছায়নি। নতুন সুবিধা চালু করতে একটি পুরোনো flag-কে নতুন অর্থে ব্যবহার করা হয়। সেই সার্ভারে flag-টি বহু বছর আগে অকেজো হয়ে পড়ে থাকা একটি পুরোনো কোড চালু করে দেয়। প্রায় ৪৫ মিনিটে ক্ষতি দাঁড়ায় কয়েকশো মিলিয়ন ডলারে, আর প্রতিষ্ঠানটি স্বাধীনভাবে টিকতে পারেনি। পুরো গল্পের system design-এর দিক এই পর্বে আছে।
এই পর্বের ভাষায় ভাঙা নিয়মগুলো দেখুন। Flag debt: মরা কোড আর তার flag মোছা হয়নি। Flag-এর পুনর্ব্যবহার: একই নামের দুই অর্থ। Deploy যাচাই: “আটটিতেই একই artifact” কেউ স্বয়ংক্রিয়ভাবে দেখেনি। Mixed-version window এখানে ছিল অনিচ্ছাকৃত আর অদৃশ্য। Canary: ছোট ভাগে শুরু করে দেখার কোনো ধাপের কথা বিবরণে নেই। Kill switch: দ্রুত থামানোর পরিষ্কার পথ ছিল না, আর বিবরণ অনুযায়ী প্রথম প্রতিক্রিয়া সমস্যাকে আরও বাড়ায়, কারণ নতুন কোড সরাতে গিয়ে বাকি সার্ভারেও পুরোনো পথটি খুলে যায়।
লক্ষণীয় যে এখানে “rollback” নিজেই ক্ষতি বাড়িয়েছিল। ফেরা তখনই নিরাপদ, যখন ফেরার গন্তব্যটি পরীক্ষিত। ফেরার পথের অনুশীলন না থাকলে চাপের মুহূর্তে সেটি আরেকটি অপরীক্ষিত deploy.
আজকের একটি দলের জন্য করণীয় ছোট: flag-এর তালিকায় মালিক আর মেয়াদ, মেয়াদ পেরোলে pipeline-এ সতর্কতা, deploy-এর শেষে সব Pod-এর image digest মেলানো, আর জরুরি বন্ধের একটি অনুশীলিত পথ। এই কাজগুলো সস্তা, আর না করার দাম ওই ঘটনাটি দেখিয়েছে।
মূল উপলব্ধি: Flag কোডের একটি চলমান অংশ, মন্তব্য নয়। তার মালিক, মেয়াদ আর পরীক্ষা লাগে, আর artifact digest যাচাই না থাকলে আপনি জানেন না কী চলছে।
প্র ০৩Kubernetes Deployment-এ canary বা blue-green strategy নেই কেন? এত বছর পরেও এগুলো বাইরের টুলে কেন?
Deployment-এর strategy.type-এ আজও দুটি মান: RollingUpdate আর Recreate. কারণটি নকশায়। Deployment Pod-এর সংখ্যা আর version সামলায়। Traffic কোন Pod-এ যাবে, সেটি Service-এর কাজ, আর ওজন-ভিত্তিক ভাগ L7 proxy-র। Blue-green বা canary এই দুই স্তরের মাঝের সমন্বয়, সাথে metric পড়া আর সিদ্ধান্ত নেওয়া। এগুলো core-এ আনলে core-কে Prometheus-এর ভাষা, proxy-র ভাষা আর পরিসংখ্যান জানতে হতো।
ইতিহাসে দেখুন: শুরুতে ওজন বলতে replica ratio, যা এই পর্বে মাপলেন। তারপর ingress controller-গুলো নিজস্ব annotation দিয়ে canary দিল, প্রতিটির ভাষা আলাদা, বদলালে কিছুই বয়ে নেওয়া যায় না। Mesh-গুলোও নিজস্ব object আনল, যেমন Istio-র VirtualService. Service Mesh Interface (২০১৯) একটি সাধারণ ভাষার চেষ্টা করে। শেষে SIG Network-এর Gateway API মানক weight দিল, যার v1 আসে ২০২৩ সালে, আর mesh-এর জন্যও GAMMA উদ্যোগের মাধ্যমে একই object ব্যবহারের চেষ্টা চলছে।
সিদ্ধান্তের অংশটি বাইরেই থেকেছে, CRD আর controller-এর ধরনে: Argo Rollouts আর Flagger. এটি Kubernetes-এর পরিচিত দর্শন: core ছোট, বিস্তার CRD দিয়ে (পর্ব ৫০)। সুবিধা হলো টুল core-এর চেয়ে দ্রুত বদলাতে পারে। দাম হলো আপনাকে একটি নতুন controller চালাতে আর upgrade করতে হয়, আর ব্যর্থ হলে rollout-ও আটকায়।
একটি প্রকৌশল শিক্ষা: যা “সবাই চায়”, তা core-এ আনা সবসময় ঠিক নয়। Canary-র সঠিক সংজ্ঞা দলভেদে আলাদা, মানক API শুধু তার স্থিতিশীল অংশটুকু ধরে, যেমন ওজন।
বাস্তব সিদ্ধান্তে এর অর্থ দাঁড়ায়: দল ছোট, সেবা কম, traffic অল্প হলে হাতে একটি selector বদল বা দুই Deployment-ই যথেষ্ট হতে পারে। প্রতিদিন অনেক release আর অনেক সেবা হলে তবেই একটি controller-এর রক্ষণাবেক্ষণের দাম উঠে আসে।
মূল উপলব্ধি: Rollout তিন স্তরের: Pod সংখ্যা, traffic ভাগ, আর সিদ্ধান্ত। এদের আলাদা ভাবলে কোন টুল কী করে, সহজে বোঝা যায়।
প্র ০৪Canary-তে কোনো error নেই, dashboard সবুজ। তবু কি নিরাপদ বলা যায়? কম traffic-এর সেবায় canary আদৌ কি কাজে লাগে?
সবুজ dashboard মানে “এই নমুনায় কিছু দেখিনি”। Rule of three বলে ১,০০০ অনুরোধে ০ error-এর পরেও আসল হার প্রায় ০.৩% পর্যন্ত হতে পারে। চিকিৎসা গবেষণা থেকে আসা এই সরল নিয়মটি Hanley আর Lippman-Hand ১৯৮৩ সালে “If nothing goes wrong, is everything all right?” শিরোনামে আলোচনা করেন। শিরোনামটিই canary-র সেরা সতর্কবাণী।
আরও ফাঁক আছে। বিরল পথ: bug হয়তো শুধু পেমেন্টের ফেরতে, যা দিনে কয়েকবার হয়। সামগ্রিক error rate সেটি দেখবে না, তাই গুরুত্বপূর্ণ পথের জন্য আলাদা metric লাগে। ধীরে ফোটা সমস্যা: memory leak ঘণ্টা পরে, cache ঠান্ডা থেকে গরম হলে আচরণ বদল, ভোরের batch job. তাই bake time. Error নয় এমন ক্ষতি: সেবা ২০০ দেয়, কিন্তু ভুল দাম দেখায়। ব্যবসায়িক metric, যেমন checkout সম্পন্ন হওয়ার হার, এখানে কাজে লাগে। দল বাঁধা error: একটি বড় গ্রাহকের সব অনুরোধ এক দিকে গেলে স্বাধীনতার অনুমান ভাঙে।
কম traffic-এর সেবায় হিসাবটি নির্মম। ২ rps-এ ৫% canary পায় ০.১ rps. ৩,৬৮১টি অনুরোধ জমতে ১০ ঘণ্টার বেশি। এখানে canary ভান মাত্র। ভালো পথ হলো শক্ত automated test, synthetic অনুরোধ, smoke test, flag দিয়ে নির্দিষ্ট ব্যবহারকারীদের জন্য খোলা, আর দ্রুত ফেরা। Blast radius কমাতে “এক গ্রাহক, তারপর আরও” পদ্ধতিও চলে।
তাই একটি ভালো canary পরিকল্পনা আগে থেকে লেখে: কোন metric, কত নমুনা, কত সময়, কোন সীমায় থামা, আর সবুজ হলেও কোন ধরনের bug এই পরীক্ষা ধরতে পারবে না। শেষ অংশটিই সবচেয়ে সৎ, আর সেটি জানলে বাকি সুরক্ষা কোথায় লাগবে, তা পরিষ্কার হয়। মাঝে মাঝে ইচ্ছা করে একটি খারাপ version পাঠিয়েও দেখা যায় canary সত্যিই থামে কি না।
মূল উপলব্ধি: Canary একটি পরিমাপ-যন্ত্র। যন্ত্রের সংবেদনশীলতা না জেনে তার সবুজ বাতিতে ভরসা করলে সেটি শুধু একটি অনুষ্ঠান।
প্র ০৫“Rollback সবসময় সম্ভব” ধারণাটি কেন ভুল? কখন সামনে এগিয়ে সারানোই (roll forward) ঠিক?
কোড ফেরানো মানে পুরোনো bytes আবার চালানো। কিন্তু সময় ফেরে না। নতুন version চলার সময় পৃথিবী বদলেছে: database-এ নতুন গঠনের ডেটা, queue-তে নতুন ফরম্যাটের বার্তা, গ্রাহকের কাছে পাঠানো ইমেইল, cache-এ জমা মান, বাইরের সেবায় করা পেমেন্ট। পুরোনো কোড সেই পৃথিবীর জন্য লেখা নয়।
Expand-contract-এর stepper-এ দেখুন, v4 চালু হওয়ার পরে v2-তে ফেরা নিরাপদ নয়, কারণ name পুরোনো হয়ে গেছে। আর DROP COLUMN-এর পরে ফেরা মানে backup. ২০১৭ সালের GitLab database ঘটনার প্রকাশিত বিবরণও মনে করিয়ে দেয়, ফেরার উপায় পরীক্ষা না করলে সেটি থাকা না-থাকার মতো (পর্ব ২৪)।
তাই পেশাদার দল প্রতিটি release-এর আগে দুটি প্রশ্ন করে: ফিরলে কী ভাঙবে, আর কোন ধাপের পরে ফেরা বন্ধ? সেই ধাপগুলো আলাদা করা হয়, শেষে রাখা হয়, আর দুই ধাপের মাঝে সময় দেওয়া হয়। Expand-contract মূলত এই ঝুঁকি ছোট টুকরো করে: বড় একটি অফেরতযোগ্য ধাপের বদলে অনেক ফেরতযোগ্য ধাপ আর শেষে একটি ছোট অফেরতযোগ্য ধাপ।
Roll forward তখন ঠিক, যখন ফেরা বেশি ঝুঁকির (ডেটা ফরম্যাট বদলে গেছে), bug ছোট আর কারণ জানা, আর সমাধান দ্রুত pipeline দিয়ে যেতে পারে। কিন্তু চাপের মধ্যে অপরীক্ষিত hotfix নতুন ঝুঁকি। সুস্থ সিদ্ধান্ত হলো আগে ক্ষতি থামানো, kill switch বা ওজন শূন্য, তারপর শান্ত মাথায় সমাধান।
একটি ছোট অভ্যাসও কাজে দেয়: সপ্তাহে একবার পরীক্ষার পরিবেশে rollback চালানো। তখন ধরা পড়ে পুরোনো image মুছে গেছে কি না, পুরোনো version নতুন schema-য় চলে কি না।
মূল উপলব্ধি: Rollback একটি কৌশল, গ্যারান্টি নয়। পরিকল্পনা লিখুন এমনভাবে যেন প্রতিটি ধাপের ফেরার পথ, আর শেষ ফেরার বিন্দু, আগে থেকে জানা থাকে।
অনুশীলন
প্রথমে নিজে চেষ্টা করুন, তারপর উত্তর মিলিয়ে নিন।
-
নমুনার হিসাব: একটি সেবার স্বাভাবিক error rate ০.২%. আপনি ০.৬% ধরতে চান (zα = 1.645, zβ = 0.842). প্রতি দলে কত অনুরোধ লাগবে? ২০০ rps-এর ১০% canary-তে কত সময়? ১%-এ?
(1.645 + 0.842)² = 6.185. প্রকরণ: 0.002 × 0.998 + 0.006 × 0.994 = 0.00796. পার্থক্যের বর্গ: 0.004² = 0.000016. n = 6.185 × 0.00796 / 0.000016 ≈ ৩,০৭৮টি অনুরোধ। ২০০ rps-এর ১০% = ২০ rps, তাই ৩,০৭৮ / ২০ ≈ ১৫৪ সেকেন্ড, প্রায় ২.৬ মিনিট। ১%-এ ২ rps, প্রায় ২৫.৭ মিনিট। মডেলটি সরল, তাই বাস্তবে এর চেয়ে বেশি রাখুন।
-
ফল আগে বলুন: Service-এর selector
app: shop.shop-stable-এ ৯টি Pod,shop-canary-তে ১টি। প্রতিটি অনুরোধ নতুন সংযোগে। এবার (ক) stable-এর ৩টি Pod NotReady হলো। (খ) তারপর কেউ Service-এর selector-এcolor: greenযোগ করল, কোনো Pod-এ সেই label নেই। প্রতি ক্ষেত্রে v2-র ভাগ আর client কী দেখবে?(ক) Service শুধু Ready Pod-এ পাঠায়। বাকি ৬ + ১ = ৭টি Pod, তাই v2-র ভাগ প্রায় ১/৭ = ১৪.৩%. আপনি canary বাড়াননি, তবু ঝুঁকি বেড়েছে।
(খ) কোনো Pod selector মেলে না, EndpointSlice-এ ঠিকানা নেই। Client-এর সংযোগ ব্যর্থ হবে, সব অনুরোধ যাবে। Selector-এর একটি ভুল পুরো সেবা বন্ধ করতে পারে, তাই switch-এর আগে দেখুন লক্ষ্যের দলে Ready Pod আছে কি না, এটি
kubectl get endpointslices-এ দেখা যায়। -
নকশার সমালোচনা: একটি দলের পরিকল্পনা: “Blue-green করব, তাই ১০ সেকেন্ডে rollback নিশ্চিত। Green চালুর আগে migration script
phoneকলামের নামmobileকরবে। Switch-এর পরে blue সরিয়ে খরচ বাঁচাব।” সমস্যা কোথায়, আর ঠিক করবেন কীভাবে?তিনটি ভুল। (১) নাম বদলের মুহূর্তে blue ভাঙে, অথচ তখনো সব traffic blue-তে, মানে switch-এর আগেই outage. (২) ফেরার গন্তব্য blue পুরোনো schema চায়, তাই “১০ সেকেন্ডের rollback” মিথ্যা। (৩) Switch-এর সাথে সাথে blue সরালে দ্রুত ফেরার পথটিই থাকে না, আর চলমান সংযোগও ভাঙে।
সমাধান: নতুন
mobileকলাম যোগ, দুই জায়গায় লেখা, backfill, পড়া বদল, পুরোনো লেখা বন্ধ, শেষে আলাদা release-এphoneমোছা। প্রতি ধাপে blue-green চলতে পারে। Blue কিছু সময় রাখুন, draining শেষে সরান। আরও, smoke test আসল পথে করুন। -
হাতে-কলমে (ঐচ্ছিক, নিজের local cluster-এ): অংশ ২-এর দুটি Deployment আর Service একটি নতুন namespace-এ চালান। একটি Pod থেকে
whileloop-এ Service-কে অনুরোধ পাঠিয়ে selector বদলান। তারপর পরিষ্কার করুন। Cloud-এর cluster হলে খরচ লাগতে পারে।ধাপগুলো (স্থানীয় cluster ধরে):
kubectl create namespace ds-demo kubectl -n ds-demo apply -f shop.yaml kubectl -n ds-demo run client --image=alpine:3.20 -it --rm -- sh # inside the Pod: while true; do wget -qO- http://shop; sleep 0.2; done # from another terminal: kubectl -n ds-demo patch service shop \ -p '{"spec":{"selector":{"app":"shop","color":"green"}}}' # clean up: check the name before you press Enter kubectl delete namespace ds-demoদেখবেন v1 থেকে v2-তে এক ঝটকায় বদল।
wgetপ্রতিবার নতুন সংযোগ খোলে, তাই ফল অংশ ২-এর মতো। Namespace মোছার আগে নামটি মিলিয়ে নিন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমname কলামকে full_name করার নিরাপদ ধাপগুলো সাজান (▲▼ চেপে সরান)
- Nullable
full_nameকলাম যোগ - এমন version deploy, যা দুই কলামেই লেখে
- পুরোনো সারি backfill
- এমন version deploy, যা
full_nameথেকে পড়ে - এমন version deploy, যা শুধু
full_name-এ লেখে - কিছুদিন পরে
nameকলাম মোছা
একটি Kubernetes Service-এর পেছনে ৪টি stable Pod আর ১টি canary Pod, প্রতি অনুরোধে নতুন সংযোগ। Stable-এ HPA চালু, সে Pod ৯টি করল। Canary-র আনুমানিক ভাগ কী হবে?
- ২০% থাকবে
- প্রায় ১০%
- ০%, কারণ HPA canary সরিয়ে দেয়
- ৫০%, কারণ দুটি version
Blue-green-এ green-এর সব Pod Ready, switch-এর পরে সব উত্তর HTTP 500. কোন কথাটি ঠিক?
- Readiness এটি হতে দিত না
- Blast radius ১%-এ সীমিত
- ফেরার জন্য নতুন build লাগবে
- ফেরা দ্রুত, তবে ক্ষতি নির্ভর করে কত দেরিতে ধরা পড়ল তার ওপর
Canary-তে ১,০০০টি অনুরোধে ০টি error. আসল error rate সম্পর্কে সবচেয়ে সৎ কথা কোনটি?
- আসল হার ঠিক ০%
- আসল হার অবশ্যই ১%-এর বেশি
- আসল হার প্রায় ০.৩% পর্যন্ত হতে পারে
- কিছুই বলা যায় না
Shadow traffic-এ নতুন version-এর সবচেয়ে বড় বিপদ কোনটি?
- পেমেন্ট বা ইমেইলের মতো side effect দুবার ঘটা
- ব্যবহারকারী ভুল উত্তর দেখবেন
- পুরোনো version-এর capacity কমে যাবে
- ব্যবহারকারী দুই version-এর উত্তর মিশিয়ে পাবেন
Deployment rollout সফল, সব Pod Ready, তবু rollout-এর সময় কিছু ব্যবহারকারী JS ফাইলে 404 পেলেন, পরে নিজে থেকেই ঠিক। সম্ভাব্য কারণ?
- Readiness probe ব্যর্থ হয়েছে
- Mixed-version window: পাতা আর asset দুই version-এ পড়েছে
- Node-এর disk পূর্ণ হয়েছে
- Cluster DNS ব্যর্থ হয়েছে
Feature flag সম্পর্কে কোন কথাটি ঠিক?
- পুরোনো flag-এর নাম আবার ব্যবহার করলে কোড পরিষ্কার থাকে
- Flag থাকলে canary লাগে না
- Flag deploy আর release আলাদা করে, তবে শেষ হলে মুছতে হয়
- Flag-এর মান বদলাতে নতুন deploy লাগে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- কৌশল বাছুন তিন মাপে: blast radius, ফেরার সময়, খরচ। ক্ষতি = ভাগ × ধরা পড়ার সময়।
- Blue-green-এ ফেরা দ্রুত, blast radius ১০০%; selector বদল শুধু নতুন সংযোগ সরায়।
- Replica ratio মোটা; ঠিক ওজনের জন্য L7: Gateway API বা mesh.
- Canary একটি পরিমাপ: নমুনা হিসাব করুন, সবুজ মানেই নিরাপদ নয়। Flag release নিয়ন্ত্রণ করে, শেষে মুছতে হয়।
- Database-এ expand → migrate → contract: প্রতি মুহূর্তে চলমান সব version কাজ করবে, অফেরতযোগ্য ধাপ সবার শেষে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৬০ · GitOps — pull-ভিত্তিক deployment, Argo CD আর Fluxপরবর্তী পর্বGit থেকে cluster-এ টেনে মিলিয়ে নেওয়া।
- পর্ব ৪৪ · Deployment — ReplicaSet, rolling update আর rollbackপেছনে দেখুনRolling update-এর অঙ্ক আর graceful shutdown.
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- System Design · Deployment আর releaseপাশের ট্র্যাকRelease কৌশলের নকশার দিক।
- Software Engineering · Continuous deliveryপাশের ট্র্যাকDelivery-র প্রক্রিয়া আর দল।