এই পর্বে যা শিখবেন
- OpenGitOps-এর চারটি নীতি, আর push ও pull deployment-এর আসল পার্থক্য
- একটি আসল run-এ commit থেকে Pod, drift ঠিক হওয়া, ভুল commit আর revert
- Pruning, sync wave আর health check কীভাবে কাজ করে
- App repo বনাম config repo, ফোল্ডার বনাম branch, আর secret রাখার তিনটি পথ
- Argo CD আর Flux-এর তুলনা, আর কোথায় GitOps ভালো খাটে না
১ · “কে কখন কী বদলেছিল?” — GitOps কোন সমস্যা সমাধান করে
একটি কাল্পনিক অনলাইন বাজার অ্যাপের কথা ভাবুন। রাত ১১টায় অ্যাপ ধীর হলো। একজন প্রকৌশলী তাড়াহুড়ো করে kubectl scale চালালেন। পরদিন আরেকজন kubectl set image দিলেন। YAML ফাইল রইল আগের মতো। এক মাস পরে cluster-এ কী চলছে আর ফাইলে কী লেখা, দুটি আর মেলে না। এই অমিলের নাম drift. পর্ব ৫১-এ এটি দেখেছেন।
এখন দ্বিতীয় সমস্যাটি দেখুন। Pipeline থেকে deploy করতে হলে CI server-এর হাতে cluster-এর চাবি থাকতে হয়। পাঁচটি cluster মানে পাঁচটি চাবি। CI server একবার ফাঁস হলে পাঁচটি cluster-ই খোলা।
GitOps দুটি সমস্যা একসাথে ধরে। নিয়মটি ছোট: cluster-এ যা থাকবে, তা Git-এ লিখুন; আর cluster-এর ভেতরের একটি agent সেটি পড়ে নিজেই মিলিয়ে নেবে।
গুদামঘরের খাতা। একটি গুদামে ম্যানেজারের স্বাক্ষর করা স্টক-খাতা আছে: ৩ বস্তা চাল, ২ টিন তেল। গুদামরক্ষক প্রতি আধা ঘণ্টায় তাক গুনে খাতার সাথে মেলান। কেউ চুপিচুপি এক বস্তা সরালে তিনি আবার তুলে রাখেন। বদলাতে চাইলে খাতায় লিখতে হয়, আর খাতা বদলানোর জন্য ম্যানেজারের স্বাক্ষর লাগে।
এখানে খাতা হলো Git. ম্যানেজারের স্বাক্ষর হলো code review. গুদামরক্ষক হলো controller. বাইরের লোকের হাতে গুদামের চাবি নেই, কারণ রক্ষক ভেতরেই থাকেন।
পর্ব ৪২-এর reconciliation loop আপনার মনে আছে। Kubernetes নিজেই desired state আর বাস্তব অবস্থা মেলায়। GitOps সেই একই ধারণাকে এক ধাপ বাইরে নিয়ে যায়। এবার desired state-এর উৎস API server নয়, Git.
নামটি কোথা থেকে এলো: “GitOps” কথাটি জনপ্রিয় হয় ২০১৭ সালে, Weaveworks-এর Alexis Richardson-এর “Operations by Pull Request” লেখা থেকে। ধারণাগুলো নতুন ছিল না। Puppet-এর মতো tool আগেই pull করত, আর পর্ব ৫৬-এর pull agent-ও একই পথের। নতুন ছিল Git, declarative Kubernetes আর অবিরাম মিলিয়ে দেখা, একসাথে।
চারটি নীতি — OpenGitOps কী বলে
“GitOps” শব্দটি একসময় যে যার মতো ব্যবহার করছিলেন। তাই CNCF-এর GitOps Working Group OpenGitOps প্রকল্পে একটি নিরপেক্ষ সংজ্ঞা লেখে। Version 1.0.0 আসে ২০২১ সালে। লক্ষ করুন, নীতিগুলোর কোথাও “Git” বা “Kubernetes” শব্দটি বাধ্যতামূলক নয়। Git সবচেয়ে প্রচলিত উপায়, একমাত্র নয়।
| নীতি | মানে | আমাদের run-এ |
|---|---|---|
| Declarative | System-এর desired state কাজের ধাপ হিসেবে নয়, ফল হিসেবে লেখা | replicas: 3 |
| Versioned and immutable | Desired state এমনভাবে রাখা, যাতে পুরো ইতিহাস থাকে আর পুরোনো সংস্করণ বদলানো না যায় | প্রতিটি commit, যেমন 92fc01b |
| Pulled automatically | Software agent নিজে থেকে উৎস থেকে desired state টেনে আনে | source-controller প্রতি ২০ সেকেন্ডে দেখে |
| Continuously reconciled | Agent বাস্তব অবস্থা অবিরাম দেখে আর desired state বসানোর চেষ্টা করে | kustomize-controller প্রতি ৩০ সেকেন্ডে মেলায় |
চতুর্থ নীতিটিই GitOps-কে সাধারণ “Git থেকে deploy” থেকে আলাদা করে। একটি pipeline যদি merge-এর পরে একবার kubectl apply চালায়, তাহলে Git আছে, declarative আছে, কিন্তু পরে কেউ হাতে বদলালে কেউ ফেরায় না। সেটি GitOps নয়, CI/CD. এটি নিন্দা নয়, শুধু আলাদা জিনিস।
২ · Push বনাম pull — চাবি কার হাতে
পর্ব ৫৭ আর ৫৮-এর pipeline-এর শেষ ধাপটি ভাবুন। Push deployment-এ CI নিজে kubectl apply বা helm upgrade চালায়। তার জন্য তিনটি জিনিস লাগে: cluster-এর credential, cluster-এর API server পর্যন্ত network পথ, আর সেখানে লেখার অনুমতি।
Pull deployment-এ দিকটি উল্টে যায়। CI শুধু test চালায়, image বানায়, আর config repo-তে একটি বদল প্রস্তাব করে। Cluster-এর ভেতরের controller বাইরে গিয়ে Git পড়ে। সব connection ভেতর থেকে বাইরে যায়। তাই API server-কে CI-এর জন্য খুলে রাখতে হয় না।
Developer → Git → controller → cluster
Controller নিজে বাইরে যায়, বাইরে থেকে কেউ ভেতরে ঢোকে না
এবার গুনে দেখা যাক। নিচের মডেল শুধু গণনা, কোনো পরিসংখ্যান নয়। clusters বদলে চালিয়ে দেখুন।
// Who holds which credential? A counting model, not a statistic.
const clusters = 5; // try 1, 5, 20
const push = [
{ holder: "CI", cred: "registry push", scope: "images" },
{ holder: "CI", cred: "repo read", scope: "source" },
];
for (let i = 1; i <= clusters; i++) push.push({ holder: "CI", cred: `deploy to cluster ${i}`, scope: "cluster" });
const pull = [
{ holder: "CI", cred: "registry push", scope: "images" },
{ holder: "CI", cred: "config repo PR only", scope: "proposal" },
];
for (let i = 1; i <= clusters; i++) {
pull.push({ holder: `cluster ${i}`, cred: "config repo read", scope: "read" });
pull.push({ holder: `cluster ${i}`, cred: "registry pull", scope: "read" });
}
function report(name, creds, inbound) {
const inCI = creds.filter((c) => c.holder === "CI");
const direct = inCI.filter((c) => c.scope === "cluster").length;
console.log(name);
console.log(` credentials held by CI : ${inCI.length}`);
console.log(` held inside the clusters : ${creds.length - inCI.length}`);
console.log(` clusters CI can change alone : ${direct}`);
console.log(` API endpoints CI must reach : ${inbound}`);
console.log(` if CI leaks, direct cluster access: ${direct} of ${clusters}`);
}
report("PUSH (CI runs kubectl/helm)", push, clusters);
report("PULL (agent in each cluster)", pull, 0);
console.log("What pull does NOT remove:");
const risks = [
["merge to main in config repo", "reaches every cluster that follows main"],
["agent inside a cluster", "still holds strong rights in that cluster"],
["central Argo CD hub", `holds ${clusters} cluster credentials in one place`],
];
for (const [what, why] of risks) console.log(` ${what}: ${why}`);৫টি cluster-এ push মডেলে CI ধরে credentials held by CI : 7, আর if CI leaks, direct cluster access: 5 of 5. Pull মডেলে CI ধরে 2, আর ফাঁস হলেও 0 of 5. Credential কমেনি, বরং মোট সংখ্যা বেড়ে ১২ হয়েছে। কিন্তু তার ১০টি শুধু পড়ার অনুমতির, আর প্রতিটি নিজের cluster-এর ভেতরে থাকে।
শেষ তিনটি লাইন সবচেয়ে জরুরি। merge to main এখনো সব cluster-এ পৌঁছায়। Controller-এর নিজের অনুমতি অনেক শক্তিশালী। আর একটি কেন্দ্রীয় Argo CD আবার সব চাবি এক জায়গায় জড়ো করে (অংশ ৯)।
ন্যায্য তুলনা করুন। “GitOps মানেই বেশি নিরাপদ” কথাটি অর্ধসত্য। আধুনিক push pipeline OIDC দিয়ে ক্ষণস্থায়ী temporary credential নিতে পারে, তখন দীর্ঘমেয়াদি চাবি জমা থাকে না (পর্ব ৬১)। Pull আসলে blast radius সরিয়ে দেয়: CI থেকে Git-এ। তাই branch protection, বাধ্যতামূলক review আর commit-এর নিরাপত্তা এখন আর ঐচ্ছিক নয়।
৩ · আসল run — commit থেকে চলমান Pod
এবার কথা ছেড়ে হাতে দেখি। এই পর্বের সব আউটপুট একটি local single-node cluster-এ (Kubernetes ১.৩৪) Flux ২.৯ দিয়ে আসল run থেকে নেওয়া। নাম, hash, IP আর সময় আপনার ক্ষেত্রে আলাদা হবে। AGE কলাম বাদ দেওয়া হয়েছে, আর লম্বা লাইন … দিয়ে ছাঁটা। কোনো বাইরের account বা remote repo ব্যবহার হয়নি। ছোট একটি alpine:3.20 Pod-এ HTTP Git server চালিয়ে repo-টি cluster-এর ভেতরেই রাখা হয়েছে, কারণ Flux-এর GitRepository http, https বা ssh URL চায়।
Flux বসানো হয়েছে Helm দিয়ে, শুধু দুটি controller-সহ। source-controller Git পড়ে। kustomize-controller সেটি cluster-এ বসায়। বাকি controller (Helm, notification, image) এখানে বন্ধ। দুটি object তাদের কাজ বলে দেয়:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: shop-config
namespace: abcl-go
spec:
interval: 20s # how often to look at Git
url: http://abcl-go-git.abcl-go.svc.cluster.local:8080/git/shop-config.git
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: shop
namespace: abcl-go
spec:
interval: 30s # how often to compare with the cluster
sourceRef:
kind: GitRepository
name: shop-config
path: ./apps/shop
prune: true # delete what left Git
wait: true # applied is not enough, wait until healthy
timeout: 90s
targetNamespace: abcl-go
serviceAccountName: gitops-deployer # namespaced "edit", not cluster-adminএই ছোট interval শুধু demo-র জন্য। বাস্তবে কয়েক মিনিট বা তার বেশি রাখা হয়, আর webhook দিয়ে তাড়াতাড়ি জাগানো হয়। এখানে Flux-এর Kustomization আর পর্ব ৫০-এর Kustomize ফাইলের Kustomization এক জিনিস নয়, নামটিই মেলে।
শেষ লাইনটি দেখুন। Chart-এর default-এ kustomize-controller cluster-admin পায়। তাই এখানে একটি namespace-সীমিত ServiceAccount-এর impersonation ব্যবহার হয়েছে, যাতে বসানোর সময় least privilege থাকে।
প্রথম commit-এ দুটি replica-র একটি Deployment আর একটি Service ছিল। Push করে ওপরের YAML apply করার পরে:
$ git log --oneline
f4a4441 add shop app: 2 replicas
$ git push -u origin main
* [new branch] main -> main
$ kubectl get gitrepository,kustomization -n abcl-go
NAME READY STATUS
gitrepository.source.toolkit.fluxcd.io/shop-config True stored artifact for revision 'main@sha1:f4a4441d15…'
NAME READY STATUS
kustomization.kustomize.toolkit.fluxcd.io/shop True Applied revision: main@sha1:f4a4441d15…
# Events on the Kustomization
Service/abcl-go/shop created
Deployment/abcl-go/shop created
Health check passed in 5.031050527s
Reconciliation finished in 5.223067505s, next run in 30s
$ kubectl get pods -l app=shop -n abcl-go
NAME READY STATUS RESTARTS
shop-5d58d9cb68-jk5ld 1/1 Running 0
shop-5d58d9cb68-lm6nl 1/1 Running 0লক্ষ করুন, এখানে কেউ kubectl apply দিয়ে Deployment বসাননি। Deployment-এর YAML শুধু Git-এ আছে। এবার একটি commit কীভাবে Pod-এ পৌঁছায়, ধাপে ধাপে দেখি।
| Git-এর main | |
| Source artifact | |
| spec.replicas | |
| Ready Pod |
- বদল। মাত্র একটি লাইন, তাও ফাইলে। Cluster এখনো কিছু জানে না। বাস্তবে এখানে একটি pull request আর review থাকত।
- Commit আর push. Developer-এর কাজ এখানেই শেষ। তাঁর হাতে cluster-এর কোনো credential লাগেনি।
- Source.
source-controllerনিজে থেকে Git দেখে নতুন commit পেল, আর সেটি একটি artifact হিসেবে জমা রাখল। Artifact মানে ওই commit-এর ফাইলের একটি প্যাকেট। - Apply.
kustomize-controllerartifact থেকে manifest বানিয়ে cluster-এর সাথে মেলাল। পার্থক্য পেল শুধুspec.replicas-এ। Pod নিজে Flux বানায় না, পর্ব ৪৪-এর Deployment controller বানায়। - হয়ে গেল। Push-এর ৪ সেকেন্ডে ৩টি Pod ready. Polling-এর সময়টি কাকতালীয়ভাবে ভালো মিলেছিল, তাই এত কম। সবচেয়ে খারাপ ক্ষেত্রে দুটি interval যোগ হতে পারে। ঠিক এই মুহূর্তে Kustomization-এর
lastAppliedRevisionতখনওf4a4441ছিল, কারণ health check শেষ হওয়ার পরেই সেটি বদলায়।
কত দেরি হতে পারে — একটি হিসাব
৪ সেকেন্ড দেখে ভাববেন না, সবসময় এমন হবে। Polling-এর কারণে সময়টি ভাগ্যের ওপর নির্ভর করে। ধরুন, source দেখার interval ২০ সেকেন্ড। ঠিক একটি পরীক্ষার পরপরই push হলে নতুন commit দেখতে প্রায় ২০ সেকেন্ড লাগে। ঠিক আগে push হলে প্রায় ০ সেকেন্ড। গড়ে ধরা যায় অর্ধেক, মানে ১০ সেকেন্ড। এরপর যোগ হয় apply আর health check, আমাদের run-এ প্রায় ৫ সেকেন্ড।
এখন বড় সংখ্যায় দেখুন। Interval ৫ মিনিট হলে শুধু অপেক্ষাই গড়ে আড়াই মিনিট। তাই ছোট interval কি ভালো? না। ১০০টি repo যদি প্রতি ১০ সেকেন্ডে দেখা হয়, মিনিটে ৬০০টি অনুরোধ যায়। Hosted Git সেবায় rate limit-এ আটকে যাওয়া একটি বাস্তব সমস্যা। সমাধান হলো interval বড় রেখে Git hosting-এর webhook দিয়ে controller-কে জাগানো। তবে এটি ছবি ৬০.১-এর “কোনো inbound নেই” নিয়মের একটি ছোট ব্যতিক্রম: webhook-এর জন্য একটি পথ খুলতে হয়। সেই পথ শুধু জাগানোর সংকেত নেয়, deploy করার ক্ষমতা নয়, আর সেটিকেও যাচাই ও সীমিত করতে হয়। Webhook হারিয়ে গেলেও interval থাকায় বদল শেষ পর্যন্ত পৌঁছায়।
৪ · Drift — কেউ হাতে বদলালে কী হয়
এটিই GitOps-এর সবচেয়ে মজার অংশ। Git-এ replicas: 3 আছে। এবার কেউ রাত ৩টায় হাতে ৬ করে দিলেন।
| Git বলছে | |
| Cluster-এ spec | |
| Ready / মোট | |
| অবস্থা |
- শুরু। Git আর cluster মিলছে। কেউ কোনো কাজ করছেন না।
- হাতের বদল।
kubectlঠিকই কাজ করল, কোনো বাধা দিল না। GitOps আপনাকে থামায় না, পরে শুধরে দেয়। অনুমতি কেড়ে নিতে চাইলে সেটি RBAC-এর কাজ। - Controller জাগল। Interval ৩০ সেকেন্ড। এই run-এ ৩৪ সেকেন্ড পরে পরের মেলানোতে পার্থক্য ধরা পড়ল, আর spec ৩-এ ফিরল। Git একবারও বদলায়নি।
- আবার শান্ত। বাড়তি ৩টি Pod সরে গেল। Git-এর ইতিহাসে এই ঘটনার কোনো চিহ্ন নেই। চিহ্ন আছে শুধু Event-এ, তাই audit-এর জন্য সেগুলো পাঠিয়ে রাখা দরকার।
একটি উদাহরণে থামিনি। কয়েক রকম বদল করে দেখা হয়েছে, Flux কী করে:
| হাতের কাজ | এই run-এ ফল | কত পরে |
|---|---|---|
kubectl scale --replicas=6 | ৩-এ ফিরল | ৩৪ সেকেন্ড |
kubectl set image … 1.27.5-alpine | 1.27-alpine-এ ফিরল | ৩৮ সেকেন্ড |
kubectl label deploy/shop hotfix=true | Label মুছে গেল, যদিও Git-এ এই field-এর উল্লেখ নেই | ৪৫ সেকেন্ডের মধ্যে |
kubectl delete svc shop | Service ফিরল, কিন্তু নতুন ClusterIP নিয়ে | ২৬ সেকেন্ড |
$ kubectl get svc shop -n abcl-go # before the delete
NAME TYPE CLUSTER-IP PORT(S)
shop ClusterIP 10.100.80.34 80/TCP
$ kubectl delete svc shop -n abcl-go
service "shop" deleted from abcl-go namespace
$ kubectl get svc shop -n abcl-go # 26s later
NAME TYPE CLUSTER-IP PORT(S)
shop ClusterIP 10.100.44.254 80/TCPদুটি শিক্ষা আছে। প্রথমত, মিলিয়ে দেওয়া মানে আগের object ফেরানো নয়, নতুন একটি বানানো। ClusterIP Git-এ লেখা ছিল না, তাই নতুন IP এলো। DNS নাম ঠিক থাকায় app-এর সমস্যা হয় না, কিন্তু কেউ IP মুখস্থ রাখলে ভাঙবে। Volume-এর ক্ষেত্রে এই পার্থক্য আরও গুরুতর হতে পারে।
দ্বিতীয়ত, label-এর ফল সব জায়গায় একই হবে না। Flux server-side apply ব্যবহার করে, আর kubectl-এর বসানো field নিজের নিয়মে সরায়। অন্য controller-এর লেখা field, যেমন HPA-র বদলে দেওয়া সংখ্যা, এই নিয়মে আলাদা আচরণ করতে পারে। Version আর tool-ভেদে নিয়ম বদলায়, তাই নিজের cluster-এ পরীক্ষা করে নিন। Git-এ replicas লেখা থাকলে আর HPA-ও চালু থাকলে দুজন টানাটানি করবে। সাধারণ সমাধান হলো manifest থেকে replicas বাদ দেওয়া।
এবার মডেলটি নিজের হাতে চালান। selfHeal, prune আর syncEvery বদলে দেখুন, মোট কতক্ষণ Git আর cluster অমিল থাকে।
// A GitOps sync loop on a simulated clock (seconds). Change anything here.
const syncEvery = 30, selfHeal = true, prune = true;
const events = [
{ t: 10, by: "git", note: "commit: replicas 3", git: { replicas: 3 } },
{ t: 75, by: "kubectl", note: "scale to 6", live: { replicas: 6 } },
{ t: 140, by: "git", note: "commit: add banner", git: { banner: "Eid sale" } },
{ t: 200, by: "git", note: "commit: drop banner", git: { banner: undefined } },
];
const clean = (o) => JSON.stringify(Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)));
let desired = { replicas: 2 }, live = { replicas: 2 }, commit = 1, applied = 1, outOfSync = 0;
for (let t = 0; t <= 270; t += 5) {
if (clean(desired) !== clean(live)) outOfSync += 5;
for (const e of events.filter((x) => x.t === t)) {
if (e.git) { desired = { ...desired, ...e.git }; commit++; }
if (e.live) live = { ...live, ...e.live };
console.log(`t=${t}s ${e.by}: ${e.note}`);
}
if (t === 0 || t % syncEvery !== 0) continue;
const newCommit = commit !== applied, drift = clean(desired) !== clean(live);
if (!drift && !newCommit) continue;
if (!newCommit && !selfHeal) { console.log(`t=${t}s sync: OutOfSync, waiting for a human`); continue; }
const next = { ...desired };
if (!prune) for (const k in live) if (!(k in next) || next[k] === undefined) next[k] = live[k];
const removed = Object.keys(live).filter((k) => live[k] !== undefined && next[k] === undefined);
live = next; applied = commit;
console.log(`t=${t}s sync: commit ${commit} applied, live = ${clean(live)}` + (removed.length ? `, pruned ${removed}` : ""));
}
console.log(`final desired = ${clean(desired)}`);
console.log(`final live = ${clean(live)}`);
console.log(`out of sync for ${outOfSync}s of 270s`);t=75s-এ হাতের বদল, t=90s-এ ঠিক হয়ে গেল। t=210s-এ pruned banner. শেষ লাইন out of sync for 55s of 270s মানে অমিলের সময় চারটি ছোট টুকরো: ২০, ১৫, ১০ আর ১০ সেকেন্ড। প্রতিটির সীমা একটি interval. syncEvery বাড়ালে API-র ওপর চাপ কমে, কিন্তু অমিলের জানালা বড় হয়। Argo CD-তে selfHeal default-এ বন্ধ থাকে, আর সেই আচরণ এখানে selfHeal = false দিয়ে দেখা যাবে (অনুশীলন ৩)।
জরুরি অবস্থায় — controller-কে থামানো
রাত ৩টায় সত্যিই হাতে কিছু করতে হলে? Controller প্রতি মিনিটে সেটি উল্টে দিলে বিপদ। তাই প্রতিটি tool-এ থামানোর উপায় থাকে। একে একধরনের break-glass পথ বলা যায়। আসল run-এ:
$ kubectl patch kustomization shop -n abcl-go --type=merge -p '{"spec":{"suspend":true}}'
kustomization.kustomize.toolkit.fluxcd.io/shop patched
$ kubectl scale deploy/shop --replicas=4 -n abcl-go
deployment.apps/shop scaled
# 40s later, two sync intervals have passed
$ kubectl get deploy shop -n abcl-go
NAME READY UP-TO-DATE AVAILABLE
shop 4/4 4 4
$ kubectl patch kustomization shop -n abcl-go --type=merge -p '{"spec":{"suspend":false}}'
kustomization.kustomize.toolkit.fluxcd.io/shop patched
# replicas back to 3, about 1s after resumesuspend চালু থাকায় ৪০ সেকেন্ড পরেও 4/4 রইল। ফেরার পরে প্রায় ১ সেকেন্ডেই ৩-এ এলো। তাই নিয়মটি হলো: থামান, ঠিক করুন, আর ফেরানোর আগে একই বদল Git-এ commit করুন। নইলে আপনার জরুরি সমাধান resume-এর সাথে সাথে মুছে যাবে। Flux CLI থাকলে একই কাজ flux suspend kustomization shop দিয়েও করা যায়।
৫ · Rollback মানে git revert
পর্ব ৪৪-এ kubectl rollout undo দেখেছেন, পর্ব ৫০-এ helm rollback. GitOps-এ এগুলো চালালে সেটিও একধরনের drift, Git আবার তা উল্টে দেবে। তাই rollback-এর পথ একটাই: Git-কে পেছনে নেওয়া। git revert ভুল commit মোছে না, তার উল্টো একটি নতুন commit বানায়। খাতায় ভুল লেখা কেটে ফেলা হয় না, পরের লাইনে সংশোধনী লেখা হয়।
এবার একটি ভুল commit দিই: image-এর নামে একটি বাড়তি e, nginx:1.27-alpinee. এমন tag registry-তে নেই।
| Git-এর main | |
| Kustomization Ready | |
| পুরোনো Pod | |
| নতুন Pod | |
| Ready Pod |
- ভুল commit. YAML ঠিক, API-ও গ্রহণ করবে। ভুলটি ধরা পড়বে শুধু চালাতে গিয়ে।
- Rolling update বাঁচাল। পর্ব ৪৪-এর নিয়মে নতুন Pod ready না হলে পুরোনো Pod সরে না। Deployment-এর আউটপুট
3/3দেখায়, তাই সাইট চলছে। - Health check ধরল।
wait: trueথাকায় Flux ৯০ সেকেন্ড অপেক্ষা করে ব্যর্থতা জানাল। এটি না থাকলে “Applied” দেখেই সবাই নিশ্চিন্ত থাকতেন। Flux-এর Kustomization নিজে থেকে আগের commit-এ ফেরে না, মানুষকে ঠিক করতে হয়। - Revert. এক কমান্ডে নতুন commit
2548e44. ইতিহাসে ভুলটিও রইল, সংশোধনও রইল। পরে postmortem-এ এটি কাজে লাগবে।git resetআর force push এখানে ঠিক পথ নয়। - অপ্রত্যাশিত দেরি। Source নতুন commit পেয়ে গেছে, কিন্তু Kustomization তখনো আগের ভুল commit-এর পুনরায় চেষ্টার health check-এ আটকে। এই run-এ চেষ্টাটি আবার ৯০ সেকেন্ডের timeout পর্যন্ত চলেছে।
- শেষ। Push-এর ১০৪ সেকেন্ড পরে সব ঠিক। Pod-এর নাম দেখুন, পুরোনো তিনটি একই আছে। মানে তারা কখনো সরেনি, আর পুরো সময়ে Service-এর পেছনে ৩টি ready Pod ছিল।
এই run-এর শিক্ষা: timeout rollback-এর গতিও ঠিক করে। Health check-এর timeout বড় হলে একটি ভুল ধরতে দেরি হয়, আর এখানে revert বসতেও দেরি হয়েছে। নতুন revision এলে চলমান health check বাতিল করার সুবিধা controller-এর version আর feature gate-এর ওপর নির্ভর করতে পারে। আপনার version-এর docs দেখুন, আর ব্যর্থতার মহড়া দিয়ে সময় মেপে নিন।
৬ · Pruning, sync wave আর health — কী মুছবে, কোনটি আগে
Git থেকে একটি ফাইল মুছলে cluster থেকে কি object-টিও যাবে? সাধারণ kubectl apply এটি করে না। Pruning হলো Git থেকে হারানো object cluster থেকেও সরানো। কিন্তু controller জানবে কীভাবে, কোনটি তার? সে নিজে বসানো সবকিছুর একটি তালিকা রাখে।
# A ConfigMap "banner" added through Git, and one made by hand
$ kubectl create configmap handmade --from-literal=owner=me -n abcl-go
$ kubectl get kustomization shop -n abcl-go \
-o jsonpath='{range .status.inventory.entries[*]}{.id}{"\n"}{end}'
abcl-go_banner__ConfigMap
abcl-go_shop__Service
abcl-go_shop_apps_Deployment
$ git rm -q apps/shop/banner.yaml # and its line in kustomization.yaml
$ git commit -qm "remove banner" && git push
bcfdf64..87e97aa main -> main
# 22s later
ConfigMap/abcl-go/banner deleted # Event on the Kustomization
$ kubectl get cm banner handmade -n abcl-go
NAME DATA
handmade 1
Error from server (NotFound): configmaps "banner" not foundbanner তালিকায় ছিল, তাই মুছল। handmade তালিকায় ছিল না, তাই বেঁচে গেল। অর্থাৎ Flux namespace-এর সবকিছু মোছে না, শুধু নিজের inventory-র জিনিস মোছে। Argo CD-তেও ধারণাটি একই: সে tracking label বা annotation দেখে নিজের object চেনে।
সতর্কতা: pruning শক্তিশালী অস্ত্র। ভুল করে একটি ফোল্ডার মুছে merge করলে PersistentVolumeClaim বা namespace-ও মুছতে পারে। তাই গুরুত্বপূর্ণ object-এ annotation দিয়ে রক্ষা করা হয় (Flux-এ kustomize.toolkit.fluxcd.io/, Argo CD-তে argocd.argoproj.io/).
ক্রম — আগে namespace, পরে app
CRD বসার আগে তার custom resource বসানো যায় না। Database migration শেষ হওয়ার আগে নতুন app চালালে ভাঙতে পারে। দুটি tool এই ক্রম আলাদাভাবে বলে। নিচের দুটি কেবল ধারণা দেখানোর জন্য। দুটি YAML-ই এই cluster-এর API-তে --dry-run=server পার হয়েছে। তবে Argo CD এখানে বসানো হয়নি, তাই sync-wave annotation-এর আচরণ এই run-এ দেখা হয়নি।
# Lower waves go first. Each wave must be healthy before the next.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: registry.example.com/shop-migrate:1.5.0# "apps" waits until "infra" is Ready.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: abcl-go
spec:
dependsOn:
- name: infra
interval: 5m
sourceRef:
kind: GitRepository
name: shop-config
path: ./clusters/prod
prune: trueArgo CD দুটি প্রশ্ন আলাদা করে। Sync status: Git-এর সাথে মিলছে কি (Synced, OutOfSync)? Health status: জিনিসটি কাজ করছে কি (Healthy, Progressing, Degraded)? একটি app Synced আর Degraded দুটোই হতে পারে, ঠিক আমাদের ভুল commit-এর মতো: Git যা বলেছে তাই বসেছে, কিন্তু চলছে না। Sync wave ছাড়া Argo CD-তে PreSync আর PostSync hook-ও আছে। আমাদের run-এ wait: true একই কাজ করেছে: health check পাস না হওয়া পর্যন্ত Kustomization সফল নয়।
৭ · Repo-র বিন্যাস — app, config আর environment
প্রথম সিদ্ধান্ত: application-এর কোড আর deployment-এর YAML কি এক repo-তে? Argo CD-র best-practice docs আলাদা রাখার পরামর্শ দেয়। কারণগুলো বাস্তব।
shop-app/ # app repo: developers work here
├── src/
├── Dockerfile
└── .ci/pipeline.yaml # test, build, push image
shop-config/ # config repo: the cluster reads this
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
└── envs/
├── dev/kustomization.yaml # image tag, replicas: 1
├── staging/kustomization.yaml # image tag, replicas: 2
└── prod/kustomization.yaml # image tag, replicas: 6App repo
কোড, test, Dockerfile. প্রতিটি commit-এ CI চলে। অনেকে লিখতে পারেন।
Config repo
শুধু desired state. ইতিহাস মানেই deployment-এর হিসাব। লেখার অধিকার কম মানুষের।
আলাদা রাখার লাভ
একটি replica বদলে পুরো CI চলে না। CI-এর commit-এ loop তৈরি হয় না। Audit পরিষ্কার থাকে।
এক repo-র লাভ
ছোট দলের জন্য সহজ। কোড আর config-এর বদল একটি PR-এ। শুরুতে এটিই যথেষ্ট হতে পারে।
দ্বিতীয় সিদ্ধান্ত: environment ফোল্ডারে, না branch-এ? dev, staging, prod তিনটি branch রাখা স্বাভাবিক মনে হয়। কিন্তু branch-গুলো একসময় আলাদা হয়ে যায়, আর merge সবকিছু একসাথে নিয়ে আসে। এখন অনেক GitOps practitioner একটি branch-এ environment-ভিত্তিক ফোল্ডার পছন্দ করেন, পর্ব ৫০-এর base আর overlay দিয়ে। পর্ব ৫৮-এর promotion তখন শুধু একটি field কপি করা।
// Promotion: one folder per environment, versus one branch per environment.
const shared = { image: "shop:1.4.0", replicas: 2, debug: false };
const show = (o) => `image=${o.image} replicas=${o.replicas} debug=${o.debug}`;
const log = [];
// Layout A: folders on one branch. Promotion copies ONE field, as one commit.
const folders = {
dev: { ...shared, replicas: 1, debug: true },
staging: { ...shared },
prod: { ...shared, replicas: 6 },
};
function promote(from, to, field) {
folders[to][field] = folders[from][field];
log.push(`commit: promote ${field}=${folders[to][field]} ${from} -> ${to}`);
}
folders.dev.image = "shop:1.5.0";
log.push("commit: dev image shop:1.5.0");
promote("dev", "staging", "image");
promote("staging", "prod", "image");
console.log("A. folders");
for (const env in folders) console.log(` ${env.padEnd(8)}${show(folders[env])}`);
// Layout B: branches. A merge carries EVERY difference that is not protected.
const branches = { dev: { ...folders.dev }, prod: { ...shared, replicas: 6 } };
const protectedFields = ["replicas"]; // try: [] or ["replicas", "debug"]
const before = { ...branches.prod };
for (const k in branches.dev) if (!protectedFields.includes(k)) branches.prod[k] = branches.dev[k];
console.log("B. branches: merge dev into prod");
console.log(` before ${show(before)}`);
console.log(` after ${show(branches.prod)}`);
const leaked = Object.keys(before).filter((k) => k !== "image" && before[k] !== branches.prod[k]);
console.log(` changed besides image: ${leaked.length ? leaked.join(", ") : "nothing"}`);
console.log("History"); log.forEach((l, i) => console.log(` ${i + 1}. ${l}`));ফোল্ডারে prod-এর লাইন image=shop:1.5.0 replicas=6 debug=false: শুধু image এগিয়েছে। Branch-এ merge-এর পরে debug=true, আর শেষে changed besides image: debug. অর্থাৎ dev-এর debug সেটিং চুপচাপ production-এ চলে গেছে। protectedFields-এ "debug" যোগ করলে আউটপুট nothing হয়, কিন্তু এমন তালিকা হাতে রাখতে হয়, আর মনে রাখাটিই ঝুঁকি। History-র তিনটি লাইন দেখুন: প্রতিটি promotion একটি আলাদা commit, তাই revert-ও সহজ।
নতুন image কীভাবে Git-এ আসে
CI নতুন image বানাল। কিন্তু GitOps-এ deploy হবে Git বদলালে। তিনটি প্রচলিত পথ আছে।
| পথ | কীভাবে | খেয়াল রাখুন |
|---|---|---|
| CI PR খোলে | Build শেষে CI config repo-তে tag বদলের PR দেয় | সবচেয়ে সহজ। CI-এর config repo-তে সীমিত লেখার অধিকার লাগে |
| Flux image automation | Controller registry দেখে, নীতি মিলিয়ে Git-এ commit করে | Cluster-এর হাতে Git-এ লেখার credential চলে আসে |
| Argo CD Image Updater | আলাদা component, প্রায় একই কাজ করে | আলাদা প্রকল্প, পরিপক্বতা যাচাই করে নিন |
# Sample, not run here. Needs Flux's two image controllers.
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: shop
namespace: flux-system
spec:
image: registry.example.com/shop
interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: shop
namespace: flux-system
spec:
imageRepositoryRef:
name: shop
policy:
semver:
range: ">=1.4.0 <2.0.0"
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: shop
namespace: flux-system
spec:
interval: 30m
sourceRef:
kind: GitRepository
name: shop-config
git:
checkout:
ref:
branch: main
commit:
author:
name: image-bot
email: bot@example.com
push:
branch: image-updates # a branch for review, not main
update:
path: ./envs/dev
strategy: Setters
# And in the manifest, a marker on the image line:
# image: registry.example.com/shop:1.4.0 # {"$imagepolicy": "flux-system:shop"}Dev-এ সরাসরি commit চলে। Production-এ একটি আলাদা branch আর review ভালো, নইলে registry-তে যে কেউ tag তুলতে পারলে সেটি সরাসরি production-এ পৌঁছাবে। পর্ব ৪১-এর মতো tag বদলানো যায় বলে ঝুঁকি থাকে, তাই production-এ digest বাঁধা নিরাপদ। latest GitOps-এর শত্রু: Git একই থাকে, অথচ চালু জিনিস বদলে যায়, তাই “versioned and immutable” নীতিই ভাঙে।
৮ · Secret — Git-এ সবকিছু রাখা যায় না
“সবকিছু Git-এ” নিয়মের প্রথম ধাক্কা আসে password-এ। পর্ব ৪৭-এ দেখেছেন, Kubernetes Secret-এর base64 এনক্রিপশন নয়। আর Git একবার যা দেখে তা ইতিহাসে থেকে যায়। সমাধানের তিনটি পথ।
| Sealed Secrets | SOPS | External Secrets | |
|---|---|---|---|
| Git-এ কী থাকে | এনক্রিপ্ট করা SealedSecret object | এনক্রিপ্ট করা মানসহ YAML ফাইল | শুধু একটি reference, secret নয় |
| চাবি কোথায় | Cluster-এর controller-এর কাছে | KMS, age বা PGP | বাইরের secret manager-এ |
| খোলে কে | Cluster-এর controller | GitOps tool (Flux-এ built-in) | Operator, সেখান থেকে মান এনে |
| সুবিধা | বাইরের service লাগে না | Git-এ diff দেখা যায়, key-র নাম খোলা থাকে | Rotation আর audit কেন্দ্রীয় |
| দুর্বলতা | এক cluster-এর চাবিতে বাঁধা; চাবির backup দরকার | Decrypt key cluster-এ দিতে হয় | বাইরের service-এ নির্ভরতা আর secret zero |
# Sample, not run here. Only a pointer lives in Git.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: shop-db
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: example-store
target:
name: shop-db
data:
- secretKey: password
remoteRef:
key: shop/db-password# Flux decrypts SOPS files itself. Passed --dry-run=server on the run's cluster.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: secrets
namespace: abcl-go
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: shop-config
path: ./secrets
prune: true
decryption:
provider: sops
secretRef:
name: sops-ageতিনটি পথেই শেষে cluster-এ একটি সাধারণ Secret তৈরি হয়। তাই পর্ব ৪৭ আর ৪৯-এর etcd এনক্রিপশন আর RBAC তখনও দরকার। আর এনক্রিপ্ট করা secret Git-এ থাকলেও চাবি ফাঁস হলে পুরোনো সব commit খুলে যায়, তাই ফাঁস হলে rotation-ই একমাত্র সমাধান (পর্ব ৩৩)। কোনটি বেছে নেবেন? Provider-এর secret manager আগে থেকে থাকলে External Secrets প্রায়ই স্বাভাবিক পথ। ছোট দলে বা নিজের cluster-এ অন্য দুটি সহজ হতে পারে।
৯ · অনেক cluster, Argo CD বনাম Flux, আর কোথায় GitOps খাটে না
একজন গুদামরক্ষক, না প্রতিটি cluster-এ একজন
দল বড় হলে cluster হয় dev, staging, prod, তারপর region-ভিত্তিক। দুটি প্রধান নকশা আছে।
| প্রতি cluster-এ agent | কেন্দ্রীয় hub | |
|---|---|---|
| কোথায় চলে | প্রতিটি cluster-এ নিজের controller (Flux-এর স্বাভাবিক ধরন, Argo CD-তেও সম্ভব) | একটি cluster-এর Argo CD অনেক cluster সামলায় |
| চাবি | ছড়ানো; প্রতিটির নিজের | Hub-এ সব cluster-এর credential |
| Network | শুধু outbound | Hub থেকে প্রতিটি API server-এ পথ লাগে |
| এক নজরে দেখা | আলাদা dashboard বা সংগ্রহ লাগে | এক জায়গায় সব দেখা যায় |
| Hub পড়ে গেলে | অন্যগুলো চলে | Workload চলে, কিন্তু sync থামে |
লক্ষ করুন, hub-এর সারিগুলো আবার push-এর মতো শোনায়। সত্যিই তাই: নেটওয়ার্কের দিক থেকে কেন্দ্রীয় Argo CD ভেতরের agent নয়, দূরের cluster-এ সে বাইরে থেকে আসে। Argo CD-র ApplicationSet একটি template থেকে অনেক cluster-এর জন্য Application বানায়। আর Flux-এ সাধারণত clusters/<name> ফোল্ডার ধরে প্রতিটি cluster নিজেরটি পড়ে।
আরেকটি বিপদ: একটি merge একসাথে ৫০টি cluster-এ যেতে পারে। তাই fleet-এ বদল ধাপে ছড়ানো হয়, যেমন আগে একটি ছোট cluster, পরে একটি region, তারপর বাকিগুলো। পর্ব ৫৯-এর canary ধারণাই এখানে cluster-এর স্তরে খাটে। একটি cluster-এর ভেতরে এই কাজের জন্য Argo Rollouts আর Flagger-এর মতো progressive delivery tool আছে।
Argo CD বনাম Flux
দুটিই পরিপক্ব, দুটিই CNCF-এর graduated project (২০২২ সালে, লেখার সময়ের তথ্য; CNCF-এর সাইটে যাচাই করে নিন)। বিতর্কটি “কোনটি ভালো” নয়, “আপনার দলের কোনটি মানায়”। নিচের তুলনা সাধারণ ধারণা দেয়, version-ভেদে বদলাতে পারে।
| Argo CD | Flux | |
|---|---|---|
| গড়ন | একটি application: server, repo server, controller, UI | আলাদা ছোট controller-এর toolkit, দরকারমতো বসানো যায় |
| মূল object | Application, AppProject, ApplicationSet | GitRepository, Kustomization, HelmRelease |
| UI | শক্তিশালী web UI সঙ্গেই আসে | মূলত CLI আর API; UI তৃতীয় পক্ষের |
| Helm | Render করে নিজে apply করে; Helm release থাকে না | Helm-এর আসল release তৈরি করে |
| ক্রম | Sync wave আর hook | dependsOn, health check |
| Prune আর self-heal | আলাদা করে চালু করতে হয় | prune field বাধ্যতামূলক (true বা false); drift সংশোধন নিয়মিত reconcile-এর অংশ |
| বহু দল | নিজের project আর RBAC, SSO | মূলত Kubernetes RBAC আর impersonation |
| কার জন্য বেশি মানায় | অনেক দল, UI দেখে কাজ করতে চান | Platform দল, Git আর CLI-কেন্দ্রিক, ছোট footprint |
# Sample, not run here: the Argo CD form of the same idea.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shop
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/team/shop-config.git
targetRevision: main
path: envs/prod
destination:
server: https://kubernetes.default.svc
namespace: shop
syncPolicy:
automated:
prune: true
selfHeal: trueআরও গভীরে: OpenGitOps-এর principles v1.0.0 “Git” শব্দটি বলে না, “source” বলে; তাই OCI registry-তে manifest রাখলেও নীতি ভাঙে না, আর Flux-এর OCIRepository সেই পথ দেয়। Flux v2 আগের Flux-এর পুনর্লিখন, যা GitOps Toolkit নামের controller-সংগ্রহে ভাগ করা। Flux server-side apply ব্যবহার করে, আর Argo CD-তে এটি একটি বিকল্প; এতে field-এর মালিকানার হিসাব API server-ই রাখে। Argo CD-র default পর্যবেক্ষণ চক্র কয়েক মিনিট (সাধারণত ৩), আর দ্রুত জাগানোর জন্য webhook ব্যবহার হয়।
কোথায় GitOps ভালো খাটে না
কোনো পদ্ধতিই সর্বরোগের ওষুধ নয়। এই জায়গাগুলোতে দুবার ভাবুন।
| পরিস্থিতি | কেন কষ্ট | কী করা যায় |
|---|---|---|
| একটি VM, একজন প্রকৌশলী, মাসে দু-একটি deploy | একটি controller বাড়তি বোঝা | সাধারণ pipeline-ই যথেষ্ট |
| Kubernetes নেই | Agent আর reconcile-এর সহজ পথ নেই | Terraform pipeline, পর্ব ৫৬-এর পদ্ধতি |
| Database migration, চলমান data | Git পিছিয়ে দিলেও data পিছিয়ে যায় না | Backward-compatible migration, hook, আলাদা পরিকল্পনা |
| খুব ঘন ঘন বদলানো মান (autoscaler-এর সংখ্যা) | Git-এ commit-এর বন্যা, controller-এর টানাটানি | Git-এ না রেখে সেই controller-কে দেওয়া |
| “এখনই এক মিনিটে” জরুরি বদল | PR-এর পথে সময় লাগে | Break-glass পথ আগে থেকে লিখে রাখা |
| দলের Git অভ্যাস দুর্বল | Review ছাড়া merge মানে সরাসরি production | আগে branch protection আর review |
আর Git পড়ে গেলে কী হবে? চলমান workload চলতেই থাকবে, কারণ controller শেষ পাওয়া অবস্থাই ধরে রাখে। শুধু নতুন deploy থামবে। পুরো cluster নতুন করে বানাতে হলে অবশ্য Git-এর ওপর নির্ভরতা বড় হয়ে ওঠে, তাই পর্ব ২৪-এর disaster recovery পরিকল্পনায় Git-এর backup আর mirror-কেও রাখুন।
পরীক্ষা ও ইন্টারভিউতে:
- GitOps-এর চার নীতি কী? Declarative, versioned and immutable, pulled automatically, continuously reconciled (OpenGitOps).
- Push আর pull-এর মূল পার্থক্য? Pull-এ cluster-এর ভেতরের agent বাইরে থেকে পড়ে, তাই CI-এর হাতে cluster credential লাগে না।
- GitOps-এ rollback কীভাবে?
git revertদিয়ে নতুন commit; controller সেটি মিলিয়ে নেয়। - Sync আর health-এর পার্থক্য? Sync মানে Git-এর সাথে মিল, health মানে সত্যিই কাজ করছে; একটি থাকলেও অন্যটি না থাকতে পারে।
- Secret Git-এ কীভাবে রাখবেন? সরাসরি নয়। Sealed Secrets, SOPS, বা External Secrets দিয়ে শুধু reference.
১০ · AI-এর যুগে GitOps
AI Application বা Kustomization YAML চোখের পলকে লিখে দেয়। কিন্তু GitOps-এ merge মানেই deploy, তাই AI-এর একটি ভুল review পেরোলে নিজে থেকেই cluster-এ পৌঁছায়। আর prune: true থাকলে মুছেও দিতে পারে।
| AI-এর সাধারণ ভুল | কীভাবে ধরবেন |
|---|---|
| Git-এ সাধারণ Secret বা plain password | Secret scanner, PR-এর diff পড়া |
image: latest | নির্দিষ্ট tag বা digest বাধ্যতামূলক করা |
পুরোনো API, যেমন Flux-এর v1beta1 | kubectl api-resources, --dry-run=server |
| নেই এমন field বা annotation | Tool-এর docs আর CRD schema মিলিয়ে দেখা |
Agent-কে cluster-admin, দল-প্রতি সীমা নেই | Project বা ServiceAccount দিয়ে namespace-সীমিত করা |
“OutOfSync সারাতে” kubectl delete বা force sync | আগে diff পড়া, কী মুছবে তা জানা |
| পুরোনো ফোল্ডার মুছে দেওয়ার PR | Prune কী কী মুছবে, merge-এর আগে দেখে নেওয়া |
| Registry বা Git-এর credential YAML-এ | Read-only deploy key, secret manager |
AI-কে GitOps PR-এর পর্যালোচক হিসেবে ব্যবহার করুন। Secret সরিয়ে শুধু diff দিন।
AI-এর দেওয়া GitOps YAML হাতে পেলে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Knight Capital-এর ২০১২ সালের ঘটনা থেকে “অবিরাম মিলিয়ে দেখা” কেন জরুরি বোঝা যায়?
২০১২ সালের আগস্টে মার্কিন শেয়ার ব্যবসায়ী প্রতিষ্ঠান Knight Capital একটি নতুন trading software বসাচ্ছিল। মার্কিন নিয়ন্ত্রক SEC-এর পরের প্রকাশিত আদেশ অনুযায়ী, প্রকৌশলীরা আটটি server-এর সাতটিতে নতুন কোড বসিয়েছিলেন, একটিতে বাদ পড়ে যায়। সেই server-এ পুরোনো, অব্যবহৃত একটি কোড জেগে ওঠে। প্রায় ৪৫ মিনিটে প্রতিষ্ঠানটি ৪৬ কোটি ডলারের বেশি ক্ষতির মুখে পড়ে। পরে প্রতিষ্ঠানটি আর স্বাধীনভাবে টিকতে পারেনি।
এখানে কোনো অসাধারণ কোডের ভুল মূল কারণ ছিল না। মূল ঘটনাটি ছিল drift: একটি server বাকিদের সাথে মেলেনি, আর সেটি ধরার মতো কোনো স্বয়ংক্রিয় যাচাই ছিল না। হাতে deploy-এর দুর্বলতা হলো, “বসিয়েছি” আর “বসেছে” আলাদা কথা।
একটি reconcile loop এই শ্রেণির ঝুঁকি কমায়, কারণ সে প্রতিটি লক্ষ্য বারবার দেখে। আমাদের run-এ set image দিয়ে একটি ভিন্ন version বসানো হলেও ৩৮ সেকেন্ডে ফিরে এসেছিল। অবশ্য এটি বলা ঠিক হবে না যে GitOps থাকলে ওই ঘটনা ঘটত না। সেটি কাল্পনিক। তাদের system Kubernetes ছিল না, আর পুরোনো কোড বন্ধ করা, feature flag-এর নাম পুনর্ব্যবহার না করা, ঝুঁকির সীমা বসানো, এমন অনেক শিক্ষাও সেখানে আছে।
তবে সাধারণ শিক্ষাটি টেকে: কতগুলো server বা cluster সামলাচ্ছেন, সেই সংখ্যা বাড়লে মানুষের হাতের নির্ভুলতার ওপর ভরসা করা চলে না। একবারের apply আর অবিরাম মিলিয়ে দেখার পার্থক্য এখানেই। প্রথমটি একটি ঘটনা, দ্বিতীয়টি একটি চলমান প্রতিশ্রুতি।
মূল উপলব্ধি: Deploy একটি মুহূর্ত নয়, একটি অবস্থা ধরে রাখা। যে system নিজের অবস্থা বারবার যাচাই করে, তার drift বেশিক্ষণ লুকিয়ে থাকে না।
প্র ০২CI ফাঁসের বাস্তব ঘটনাগুলো দেখলে pull মডেলের যুক্তি কতটা শক্ত, আর দুর্বল জায়গা কোথায়?
২০২১ সালের এপ্রিলে Codecov জানায়, তাদের Bash Uploader script-এ অননুমোদিত বদল হয়েছিল, যা ব্যবহারকারীদের CI পরিবেশের variable বাইরে পাঠাতে পারত। ২০২৩ সালের জানুয়ারিতে CircleCI একটি নিরাপত্তা ঘটনার পরে গ্রাহকদের CI-তে রাখা সব secret বদলাতে বলে। বিস্তারিত দুই ক্ষেত্রেই ভিন্ন, তবে শিক্ষা এক: CI হলো একটি লোভনীয় লক্ষ্য, কারণ সেখানে অনেক চাবি এক জায়গায় থাকে।
Pull মডেলের যুক্তি এখানে শক্ত। CI-এর হাতে cluster credential না থাকলে এমন ফাঁসে cluster সরাসরি খোলে না। আমাদের গণনার মডেলে ৫টি cluster-এ সংখ্যাটি 5 of 5 থেকে 0 of 5 হয়েছিল। তবে সেখানে থামলে ভুল হবে।
- Git এখন সবচেয়ে দামি: যিনি
main-এ লিখতে পারেন, তিনি সব cluster-এ পৌঁছান। CI-এর হাতে config repo-র লেখার অধিকার থাকলে CI ফাঁস ঘুরপথে cluster পর্যন্ত যায়। তাই CI শুধু PR খুলবে, merge নয়। - Image-এর পথ খোলা: CI registry-তে push করে। সে একই tag-এ ক্ষতিকর image রাখতে পারলে Git বদলায় না, কিন্তু Pod ক্ষতিকর হয়। Digest বাঁধা আর signing (পর্ব ৪১) এই ফাঁক কমায়।
- Agent-এর ক্ষমতা: Controller cluster-এর ভেতরে শক্তিশালী। আমাদের chart default-এ তাকে
cluster-adminদিত।
আর push-ও স্থির নয়। OIDC দিয়ে ক্ষণস্থায়ী credential নিলে জমা চাবির সমস্যা অনেকটাই কমে (পর্ব ৬১)।
মূল উপলব্ধি: Pull ঝুঁকিকে অদৃশ্য করে না, এক জায়গা থেকে অন্য জায়গায় সরিয়ে আনে। ঠিক প্রশ্নটি হলো, এখন সবচেয়ে দামি চাবি কোনটি, আর সেটি পাহারা দিচ্ছে কে?
প্র ০৩Self-heal চালু রাখলে on-call প্রকৌশলীর জীবন সহজ হয়, নাকি কঠিন?
দুটোই হতে পারে, আর এই টানাপোড়েনটিই আসল শিক্ষা।
সহজ হয়, কারণ: ভুল করে করা বদল নিজে থেকে মুছে যায়। রাতে কেউ পরীক্ষার জন্য কিছু বদলে ভুলে গেলেও সকালে cluster আবার ঠিক। Git-এ যা আছে, cluster-এ তাই আছে, এই ভরসায় সমস্যা খোঁজা অনেক দ্রুত হয়। “এই সেটিংটি কে বদলেছিল?” প্রশ্নের উত্তর git log-এ।
কঠিন হয়, কারণ: ধরুন, একটি incident চলছে। আপনি memory limit বাড়ালেন, আর ৩০ সেকেন্ড পরে সেটি ফিরে গেল। Pod আবার মরতে শুরু করল। আপনি ভাবছেন, সমাধান কাজ করেনি। আসলে controller আপনার সাথে লড়ছিল। চাপের মধ্যে এটি ধরা কঠিন। পর্ব ৬৭-এর incident-এর সময় মানুষকে এমন অদৃশ্য প্রতিপক্ষ দেওয়া বিপজ্জনক।
এই কারণেই Argo CD-তে self-heal আলাদা করে চালু করতে হয়, আর কিছু দল production-এ শুধু alert রাখে, নিজে থেকে ফেরায় না। Flux-এ reconcile নিয়মিত হয়, তাই suspend আগে থেকে জানা জরুরি। আমাদের run-এ suspend দিলে ৪০ সেকেন্ড পরেও হাতের বদল টিকে ছিল, আর resume-এর এক সেকেন্ডে মুছে গিয়েছিল।
ভালো দল তাই runbook-এ এই ধাপগুলো লিখে রাখে: কোন কমান্ডে থামাবেন, কে থামাতে পারেন, থামানো অবস্থায় alert দেবে কে, আর ফেরানোর আগে কীভাবে বদলটি Git-এ তুলবেন। থামিয়ে ভুলে যাওয়াও একটি বাস্তব ঝুঁকি, তাই suspended অবস্থার ওপর alert রাখা ভালো।
মূল উপলব্ধি: স্বয়ংক্রিয়তার সবচেয়ে কঠিন অংশ স্বাভাবিক দিন নয়, অস্বাভাবিক রাত। জরুরি পথ আগেই নকশা করে রাখুন, মহড়া দিন।
প্র ০৪Weaveworks বন্ধ হওয়ার পরেও তাদের শুরু করা Flux কেন টিকে গেল? একটি tool বাছাইয়ে এর শিক্ষা কী?
Flux শুরু করে Weaveworks, যে প্রতিষ্ঠান “GitOps” শব্দটিও ছড়িয়েছিল। ২০১৯ সালে Flux CNCF-এর sandbox-এ আসে, আর ২০২২ সালে graduated হয়। ২০২৪ সালের শুরুতে Weaveworks তাদের কার্যক্রম গুটিয়ে নেওয়ার ঘোষণা দেয়। একটি একক প্রতিষ্ঠানের পণ্য হলে এখানেই গল্প শেষ হতে পারত। কিন্তু Flux-এর কোড আর ট্রেডমার্ক নিরপেক্ষ foundation-এর কাঠামোয় ছিল, আর maintainer অন্য প্রতিষ্ঠানেও ছিলেন। প্রকল্প তাই চলতে থাকে। আমাদের run-এ ব্যবহৃত ২০২৬ সালের chart-ই তার প্রমাণ, যদিও কর্মীর সংখ্যা ও অর্থায়নে ধাক্কা লাগা স্বাভাবিক ছিল।
Argo CD-র পথ আলাদা। Argo পরিবারের শুরু Applatix নামের একটি প্রতিষ্ঠানে। Intuit সেই প্রতিষ্ঠান অধিগ্রহণ করে, আর সেখানেই Argo CD বেড়ে ওঠে। Argo প্রকল্প ২০২০ সালে CNCF-এ incubating আর ২০২২ সালে graduated হয়। আজ একাধিক প্রতিষ্ঠান এর ওপর পণ্য বানায়।
তুলনার জন্য পর্ব ৫২-এর গল্পটি মনে করুন। ২০২৩ সালে HashiCorp Terraform-এর licence বদলায়, আর community OpenTofu নামে fork করে। একক প্রতিষ্ঠানের নিয়ন্ত্রণে থাকা প্রকল্পে এমন বাঁক সম্ভব। Foundation-এ থাকলে এই ঝুঁকি কমে, তবে শূন্য হয় না, কারণ maintainer-দের সময় তো কেউ না কেউ দেন।
একটি tool বাছাইয়ের সময় তাই feature-এর পাশাপাশি জিজ্ঞেস করুন: কোড কার নিয়ন্ত্রণে? Maintainer ক’টি প্রতিষ্ঠানের? Licence কী? Tool বদলালে আপনার কত কাজ নষ্ট হবে? GitOps-এ শেষ প্রশ্নের উত্তর তুলনামূলক ভালো, কারণ আসল সম্পদ সাধারণ Kubernetes YAML, আর সেটি আপনার Git-এই থাকে।
মূল উপলব্ধি: Tool-এর আয়ু শুধু কোডের মানে নয়, তার মালিকানার কাঠামোতেও লেখা থাকে।
প্র ০৫“Git-ই একমাত্র সত্য” কথাটি কি আক্ষরিকভাবে ঠিক?
পুরোপুরি নয়। কথাটি একটি দরকারি নিয়ম, দর্শন নয়। Cluster-এ এই মুহূর্তে কী চলছে, তার সত্য Git-এ নেই, সেটি API server-এ আছে। Git-এ আছে আমাদের অভিপ্রায়: কী থাকা উচিত। GitOps-এর কাজ হলো এই দুটির দূরত্ব কম রাখা।
এই পর্বের আসল run-ই সীমাগুলো দেখায়।
- ভুল commit: Git বলছিল
nginx:1.27-alpinee, কিন্তু সেই সময়ে তিনটি পুরোনো Pod চলছিল। Git আর বাস্তব দুটিই “সঠিক”, তবে আলাদা প্রশ্নের উত্তরে। - হাতে বানানো object:
handmadeConfigMap কোনো Git-এ ছিল না, তবু cluster-এ টিকে ছিল। Controller তাকে দেখেনি। - ClusterIP: Service ফিরে এলো ঠিকই, কিন্তু নতুন IP নিয়ে। সেই IP কোথাও Git-এ লেখা নেই।
- অন্য ধরনের অবস্থা: HPA-র সংখ্যা, database-এর data, runtime-এর certificate. এগুলো Git-এ রাখা হয় না, রাখা উচিতও নয়।
তাছাড়া render-এর ধাপও আছে। Git-এ Helm values থাকলে আসল YAML কী হবে, তা chart-এর version আর build-এর ওপর নির্ভর করে। Git-এ যা পড়ছেন, cluster-এ হুবহু তাই না-ও হতে পারে। তাই কিছু দল render করা YAML-ও আলাদা branch বা repo-তে রাখে।
পরিপক্ব ধারণাটি হলো: Git হলো অভিপ্রায়ের সত্য, cluster হলো অবস্থার সত্য, আর monitoring বলে দেয় ব্যবহারকারী কী পাচ্ছেন। OpenGitOps-এর নীতিগুলোও “desired state” কথাটিই ব্যবহার করে। Crossplane-এর মতো tool একই নীতি cloud resource-এ নিয়ে যাচ্ছে, তাই এই সীমা নিয়ে ভাবনাটি আরও জরুরি হচ্ছে।
মূল উপলব্ধি: স্লোগান মুখস্থ না করে প্রশ্ন করুন, এই নির্দিষ্ট তথ্যটির মালিক কে? Git, API server, না অন্য কোনো controller?
অনুশীলন
Cluster ছাড়াই বেশিরভাগ করা যাবে। নিজের cluster বা cloud ব্যবহার করলে খরচ হতে পারে, শেষে অবশ্যই পরিষ্কার করুন।
-
সময়ের হিসাব: একটি GitOps সেটআপে
GitRepository-এর interval ১ মিনিট,Kustomization-এর interval ৫ মিনিট, আর health check ৩০ সেকেন্ডে শেষ হয়। ধরুন, দুটি চক্রের মাঝে সবচেয়ে অসুবিধাজনক সময়ে push হলো, আর নতুন source এলে Kustomization নিজে থেকে না জেগে তার interval পর্যন্ত অপেক্ষা করে। Push থেকে সফল সংকেত পর্যন্ত সবচেয়ে খারাপ ক্ষেত্রে কত সময়? আর শুধু interval-এর হিসাবে, এক ঘণ্টায় Git কতবার আর cluster কতবার মেলানো হয়?সবচেয়ে খারাপ: ৬০ + ৩০০ + ৩০ = ৩৯০ সেকেন্ড, মানে ৬.৫ মিনিট। ঘণ্টায় Git দেখা হয় ৬০ বার, আর cluster মেলানো হয় ১২ বার। বাস্তবে Flux source বদলালে Kustomization সাধারণত আগেই জেগে ওঠে (আমাদের run-এ ৪ সেকেন্ড), তাই এই হিসাব অনুশীলনের ধরে নেওয়া সীমা। Webhook-ও এই সময় কমায়।
-
নকশা সমালোচনা: একটি দল বলছে, “আমরা GitOps করি। প্রতিটি push-এ CI একটি
kubeconfigদিয়েkubectl apply -k envs/prodচালায়। dev, staging আর prod আলাদা branch-এ। Secret plain YAML-এ, কিন্তু repo private. Image সবlatest.” অন্তত পাঁচটি সমস্যা ধরুন, আর প্রতিটির সমাধান বলুন।- এটি GitOps নয়: Pull আর অবিরাম মিলিয়ে দেখা নেই। একটি controller বসান।
- CI-তে kubeconfig: প্রয়োজন থাকে না। Push রাখলে অন্তত OIDC-এর ক্ষণস্থায়ী credential নিন।
- Branch-ভিত্তিক environment: ফোল্ডার আর overlay-তে যান।
- Plain secret: Private repo এনক্রিপশন নয়। Secret বদলে ফেলুন, ইতিহাস ধরে নিন ফাঁস হয়েছে, তারপর External Secrets, SOPS বা Sealed Secrets.
latest: ইতিহাস অর্থহীন হয়, rollback কাজ করে না। নির্দিষ্ট tag বা digest বাঁধুন।- বাড়তি: Branch protection আর review-এর কথা নেই, health check-এরও উল্লেখ নেই।
-
আউটপুট অনুমান: ওপরের
sync_loop.js-এ শুধুselfHeal = falseকরলেt=90s-এ কী ছাপবে?t=150s-এlive-এ replicas কত হবে? শেষ লাইনের সংখ্যা কত হবে? আগে ভাবুন, পরে চালিয়ে মিলিয়ে নিন।t=90s:sync: OutOfSync, waiting for a human, আরt=120s-এও একই।t=140s-এ নতুন commit এলো।t=150s-এ সেই commit বসার সময় পুরো desired state বসে, তাইlive = {"replicas":3,"banner":"Eid sale"}. হাতের ৬ মুছে গেল, যদিও self-heal বন্ধ। শেষ লাইন:out of sync for 105s of 270s. শিক্ষা: self-heal বন্ধ থাকলেও পরের sync হাতের বদল মুছতে পারে। তাই “এখন বন্ধ আছে” মানে “বদলটি নিরাপদ” নয়। -
কমান্ড লেখা:
main-এa1b2c3dcommit-টি production ভাঙছে, আর তার পরে একটি ভালো commit এসেছে। Git history না মুছে শুধু ভুল commit-টির প্রভাব উল্টে দেওয়ার কমান্ড লিখুন। তারপর বলুন,kubectl rollout undoকেন স্থায়ী সমাধান নয়, আর revert-এর পরে কীভাবে নিশ্চিত হবেন।$ git revert --no-edit a1b2c3d $ git push $ kubectl get kustomization -n my-team # Applied revision: the new commit? $ kubectl rollout status deploy/shop -n my-teamপরের ভালো commit অক্ষত থাকে, কারণ
revertশুধু ওই commit-এর উল্টো পরিবর্তন যোগ করে। সংঘাত হলে হাতে মেটাতে হয়।rollout undocluster বদলায়, Git নয়, তাই controller চালু থাকলে পরের চক্রে ভুল version ফিরে আসবে। নিশ্চিত হবেনApplied revision, rollout status আর app-এর আসল metric দেখে। জরুরি হলে আগে সাময়িকভাবে controller থামিয়েrollout undoচলতে পারে, তবে শেষে Git ঠিক করতেই হবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি GitOps বদলের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Developer config repo-তে PR খোলেন
- Review শেষে PR
main-এ merge হয় - Controller Git-এ নতুন commit দেখে
- Controller manifest আর cluster-এর পার্থক্য বের করে apply করে
- Deployment controller নতুন Pod চালায়
- Health check পাস হলে sync সফল হিসেবে দেখায়
নিচের কোনটি OpenGitOps-এর চার নীতির একটি নয়?
- Declarative
- Versioned and immutable
- Continuously reconciled
- Kubernetes-only deployment
Pull-ভিত্তিক GitOps-এ CI server-এর ফাঁস হওয়া সাধারণত কম বিপজ্জনক কেন?
- কারণ pull মডেলে CI বলে কিছু থাকে না
- কারণ CI-এর হাতে cluster-এর credential থাকে না
- কারণ Git সব credential এনক্রিপ্ট করে
- কারণ সব image নিজে থেকেই signed হয়
Flux দিয়ে সামলানো একটি Deployment-এ Git-এ replicas: 3 আছে, আর কেউ kubectl scale --replicas=6 চালালেন। সাধারণ সেটিংয়ে কী হবে?
- API server অনুরোধটি প্রত্যাখ্যান করবে
- Git নিজে থেকে replicas: 6 হয়ে যাবে
- কিছুক্ষণ ৬ থাকবে, তারপর controller ৩-এ ফিরিয়ে আনবে
- চিরকাল ৬ থাকবে, কেউ জানবে না
GitOps-এ একটি ভুল deployment উল্টানোর সবচেয়ে উপযুক্ত পদ্ধতি কোনটি?
git revertকরে push করাkubectl rollout undoচালিয়ে ছেড়ে দেওয়াgit reset --hardকরে force push করা- পুরো namespace মুছে দেওয়া
একটি Flux Kustomization-এ prune: true. একটি ConfigMap Git থেকে বসেছিল, আরেকটি হাতে kubectl create দিয়ে বানানো। Git থেকে প্রথমটির ফাইল মোছা হলে কী হবে?
- দুটিই থেকে যাবে
- দুটিই মুছে যাবে
- Git থেকে বসানোটি মুছবে, হাতে বানানোটি থাকবে
- হাতে বানানোটি মুছবে, Git-এরটি থাকবে
GitOps-এ database password রাখার ক্ষেত্রে কোন কথাটি সঠিক?
- Private repo-তে base64 করে রাখলেই নিরাপদ
- Git-এ এনক্রিপ্ট করা মান বা শুধু একটি reference রাখা হয়
- Secret সবসময় হাতে kubectl দিয়ে বসানোই একমাত্র পথ
- Argo CD আর Flux নিজে থেকে সব মান এনক্রিপ্ট করে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে আছে।
মনে রাখুন
- চার নীতি: declarative, versioned and immutable, pulled automatically, continuously reconciled.
- Pull cluster credential-কে pipeline থেকে সরায়, কিন্তু সবচেয়ে দামি চাবি হয়ে ওঠে Git-এর
main. - Drift নিজে থেকে মুছে যায়, তাই জরুরি অবস্থায় আগে
suspend, আর ফেরানোর আগে Git ঠিক করুন। - Rollback =
git revert. Health check-এর timeout rollback-এর গতিও ঠিক করতে পারে। - নকশা: app আর config আলাদা, environment ফোল্ডারে, secret Git-এ খোলা নয়, আর Argo CD বা Flux দলের ধরন দেখে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৬১ · DevSecOps — pipeline-এ নিরাপত্তা, OIDC আর SLSAপরবর্তী পর্বPull-ভিত্তিক deploy-এর পর এবার পুরো pipeline-এর নিরাপত্তা, OIDC থেকে SLSA পর্যন্ত।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ৫০ · Helm, Kustomize আর operatorআগের পর্বOverlay, CRD আর operator, যার ওপর Flux আর Argo CD দাঁড়িয়ে।
- পর্ব ৪৪ · Deploymentআগের পর্বRolling update, যা ভুল commit-এর সময়ে সাইট বাঁচিয়ে রাখল।
- পর্ব ৩৩ · Secret আর KMSআগের পর্বEnvelope encryption আর rotation-এর ভিত্তি।
- Software Engineering · Git-এর নিরাপত্তাপাশের ট্র্যাকConfig repo-কে পাহারা দেওয়ার ধারণা।
- System Design · Deployment ও Releaseগভীরে যানRolling, canary আর rollback-এর নকশা।