এই পর্বে যা শিখবেন
- Kubernetes-এর network model, আর Pod IP-তে ভরসা না করার কারণ
- ClusterIP, NodePort, LoadBalancer, ExternalName আর headless-এর পার্থক্য
port,targetPortআরnodePort: কোনটি কোথায় খাটে- Selector, EndpointSlice আর readiness মিলিয়ে traffic-এর হিসাব
- CoreDNS-এর নাম, search domain আর
ndots-এর আসল খরচ - “Service-এ পৌঁছাতে পারছি না” সমস্যা ধাপে ধাপে ধরা
১ · Pod-এর ঠিকানা কেন ভরসার নয়
পর্ব ৪৩-এ আপনি Pod দেখেছেন, আর পর্ব ৪৪-এ দেখেছেন Deployment কীভাবে Pod বদলায়। প্রতিবার rolling update-এ পুরোনো Pod যায়, নতুন Pod আসে। এখন প্রশ্ন হলো, একটি frontend Pod যদি একটি API Pod-কে ডাকতে চায়, সে কোন ঠিকানায় ডাকবে?
একটি বড় হাসপাতাল ভাবুন। মেডিসিন বিভাগে ডাক্তাররা shift অনুযায়ী বদলান। কারও ব্যক্তিগত মোবাইল নম্বর রোগীকে দিলে পরের shift-এ সেই নম্বর কাজে আসবে না। তাই হাসপাতাল একটি স্থির রিসেপশন নম্বর দেয়। রিসেপশনের হাতে থাকে ডিউটি তালিকা: এই মুহূর্তে কে আছেন, আর কে রোগী দেখতে প্রস্তুত। Service হলো সেই রিসেপশন নম্বর, EndpointSlice হলো ডিউটি তালিকা, আর “মেডিসিন বিভাগের ডাক্তার” কথাটি হলো selector.
এই পর্বের transcript সম্পর্কে: নিচের সব kubectl আউটপুট একটি local single-node cluster (Kubernetes ১.৩৪)-এ আসল run থেকে নেওয়া। Pod-এর নাম, বয়স, IP আর সময় আপনার cluster-এ আলাদা হবে। কিছু লাইন ছেঁটে … দেওয়া হয়েছে, কিন্তু কোনো মান বানানো হয়নি। যেখানে একাধিক node বা cloud load balancer লাগে, সেখানে আলাদা করে বলা আছে। Transcript-এ --context flag বাদ রাখা হয়েছে।
নিজে চালাতে চাইলে: নিজের মেশিনের local cluster-এ (যেমন Docker Desktop-এর Kubernetes) কোনো বিল আসে না। একটি আলাদা namespace বানিয়ে নিচের YAML apply করুন। পরীক্ষার Pod-টি বানাতে পারেন এভাবে: kubectl run client --image=nginx:1.27-alpine --command -- sleep 3600. এই image-এ curl থাকে। কাজ শেষে নাম ধরে namespace মুছুন: kubectl delete ns <আপনার-namespace>. Cloud-এর cluster-এ LoadBalancer Service খরচ আনতে পারে, তাই সেখানে আগে দাম দেখে নিন।
আসল পরীক্ষা: Pod মরলে ঠিকানার কী হয়
একটি Deployment চালানো হলো, নাম api, তিনটি Pod. প্রতিটি Pod নিজের নাম ফেরত দেয়। পাশে আছে client নামের একটি Pod, যেখান থেকে আমরা curl চালাব। প্রথম Pod-টিকে মুছে দেখি।
$ kubectl get pods -l app=api -o custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
api-7d66f59445-7x6sx 10.1.0.10
api-7d66f59445-fb2fb 10.1.0.13
api-7d66f59445-th48x 10.1.0.11
$ kubectl exec client -- curl -s --max-time 3 http://10.1.0.10:8080/
api-7d66f59445-7x6sx
$ kubectl delete pod api-7d66f59445-7x6sx
pod "api-7d66f59445-7x6sx" deleted from abcl-ks namespace
$ kubectl get pods -l app=api -o custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
api-7d66f59445-9bm9z 10.1.0.16
api-7d66f59445-fb2fb 10.1.0.13
api-7d66f59445-th48x 10.1.0.11
$ kubectl exec client -- curl -sS --max-time 3 http://10.1.0.10:8080/
curl: (28) Connection timed out after 3002 milliseconds
command terminated with exit code 28Deployment নিজের কাজ ঠিকই করেছে: Pod-এর সংখ্যা আবার তিন। কিন্তু নতুন Pod পেয়েছে নতুন নাম আর নতুন ঠিকানা 10.1.0.16. পুরোনো 10.1.0.10-তে হাতে লেখা কোনো config থাকলে এখন সেটি অকেজো। আরও খারাপ হলো, কিছুদিন পর সেই ঠিকানা অন্য কোনো Pod পেতে পারে, তখন আপনার অনুরোধ ভুল জায়গায় যাবে।
Kubernetes-এর network model
ঠিকানা বদলালেও Kubernetes-এ network-এর নিয়মটি খুব সরল। পুরো তত্ত্ব আছে Computer Networks · Cloud ও Container Networking পর্বে। এখানে দরকারি অংশটুকু:
- প্রতিটি Pod নিজের একটি IP পায়, আর Pod-এর সব container সেই ঠিকানা ভাগ করে নেয়।
- যেকোনো Pod অন্য যেকোনো Pod-এ NAT ছাড়াই পৌঁছাতে পারে, Pod দুটি আলাদা node-এ থাকলেও।
- Node-এর নিজের agent (যেমন kubelet) সেই node-এর Pod-এ পৌঁছাতে পারে।
Kubernetes এই network নিজে বানায় না। Pod তৈরির সময় একটি CNI plugin ঠিকানা দেয় আর পথ সাজায়। Flannel, Calico আর Cilium এমন plugin-এর পরিচিত উদাহরণ।
“NAT ছাড়া” কথাটি আমরা যাচাই করলাম। client Pod-এর ঠিকানা 10.1.0.12. সে অংশ ২-এর Service-এর নাম ধরে একটি অনুরোধ পাঠাল, আর উত্তর দেওয়া Pod-এর nginx log-এ দেখা গেল:
10.1.0.12 - - [30/Oct/2026:19:15:05 +0000] "GET / HTTP/1.1" 200 21 "-" "curl/…
Pod যে ঠিকানা দেখল, সেটি client-এর নিজের ঠিকানা। অর্থাৎ উৎস-ঠিকানা মাঝপথে বদলায়নি। Service-এর পথেও এমন কেন থাকে, তা অংশ ৫-এ পরিষ্কার হবে।
২ · Service: স্থির নাম, স্থির ঠিকানা
Service একটি API object. এটি কোনো process নয়, আর কোনো Pod-ও নয়। এটি একটি ঘোষণা: “app=api label-ওয়ালা Pod-গুলোকে একটি নামে আর একটি ঠিকানায় পাওয়া যাবে।” নিচে আমাদের আসল Deployment আর Service. দুটোই একটি namespace-এ চালানো হয়েছে, আর kubectl apply --dry-run=server-ও পাস করেছে।
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: abcl-ks
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: web
image: nginx:1.27-alpine
imagePullPolicy: IfNotPresent
command: ["sh", "-c"]
args:
- |
sed -i 's/listen *80;/listen 8080;/' /etc/nginx/conf.d/default.conf
echo "$HOSTNAME" > /usr/share/nginx/html/index.html
echo ok > /usr/share/nginx/html/ready.html
exec nginx -g 'daemon off;'
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /ready.html
port: http
periodSeconds: 2
failureThreshold: 1
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
memory: 48MiapiVersion: v1
kind: Service
metadata:
name: api
namespace: abcl-ks
spec:
type: ClusterIP
selector:
app: api
ports:
- name: http
port: 80
targetPort: http$ kubectl get svc api
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api ClusterIP 10.105.249.110 <none> 80/TCP 2s
$ kubectl get endpointslices
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
api-k4nqn IPv4 8080 10.1.0.10,10.1.0.13,10.1.0.11 2sClusterIP 10.105.249.110 হলো Service-এর স্থির ঠিকানা। Service মুছে না ফেলা পর্যন্ত এটি থাকে, পেছনের Pod যতই বদলাক। এই ঠিকানাটি একটি virtual IP: কোনো network card বা process এই ঠিকানায় বসে নেই।
তিনটি port: কে কোনটি
নতুনদের সবচেয়ে বেশি গুলিয়ে যায় এই তিনটি নাম। ধরুন একটি বাড়িতে তিনটি দরজা পরপর।
| Field | কার port | আমাদের মান | কে ব্যবহার করে |
|---|---|---|---|
nodePort | প্রতিটি node-এর | 30845 | Cluster-এর বাইরের client |
port | Service-এর (ClusterIP-এ) | 80 | Cluster-এর ভেতরের client |
targetPort | Pod-এর | http (মানে 8080) | Service-এর শেষ গন্তব্য |
containerPort | Container-এর ঘোষণা | 8080 | মূলত নথি; নাম দিলে targetPort তা ধরতে পারে |
আমরা targetPort: http লিখেছি, সংখ্যা নয়। এর সুবিধা হলো, পরে app-এর port বদলালে শুধু Pod template বদলালেই চলে, Service একই থাকে। আসল run-এও Service-এর port 80, কিন্তু EndpointSlice-এর PORTS ঘরে 8080 দেখা যাচ্ছে।
Service-এর ধরনগুলো এক ছবিতে
NodePort আর LoadBalancer আলাদা কোনো জিনিস নয়। এগুলো ClusterIP-এর ওপরে একটি করে বাড়তি স্তর।
Service-এর তিন ধরন, স্তরে স্তরে
LoadBalancer wraps NodePort, NodePort wraps ClusterIP, selector picks Ready Pods
৩ · Selector, EndpointSlice আর readiness
Service আর Pod-এর মধ্যে সরাসরি কোনো জোড়া নেই। জোড়া দেয় একটি controller. সে selector-এর সাথে মেলে এমন Pod খুঁজে বের করে, আর তাদের ঠিকানা EndpointSlice object-এ লিখে রাখে। প্রতিটি ঠিকানার পাশে থাকে অবস্থা: ready, serving, terminating.
Selector মেলানোর নিয়মটি হলো AND: Selector-এ যত label লেখা আছে, Pod-এ তার সবগুলো একই মানে থাকতে হবে। Pod-এ বাড়তি label থাকলে সমস্যা নেই। kubectl দিয়ে আমরা এখন দুটি জিনিস পরীক্ষা করব: Pod-এর সংখ্যা বদলালে কী হয়, আর একটি Pod অসুস্থ হলে কী হয়।
1kubectl get endpointslices2kubectl scale deploy/api --replicas=53# readiness pass করা পর্যন্ত অপেক্ষা4kubectl scale deploy/api --replicas=25# কয়েক সেকেন্ড পরে আবার দেখা
| চাওয়া Pod | |
| তালিকায় ঠিকানা |
- শুরু: তিনটি Pod, তালিকায় তিনটি ঠিকানা। EndpointSlice-এর নাম
api-k4nqn. - Scale 5: Deployment-এর চাহিদা এখন ৫। ঠিক পরমুহূর্তের আউটপুটে ঠিকানা এখনো ৩টি, কারণ নতুন Pod-এর তখনো IP-ই হয়নি, আর IP ছাড়া Pod তালিকায় ঢোকে না।
- কয়েক সেকেন্ড পরে: ঠিকানা ৫টি।
+ 2 more...মানে kubectl শুধু প্রথম তিনটি লিখেছে, বাকি দুটি সংক্ষেপ করেছে। - Scale 2: প্রায় এক সেকেন্ড পরে তালিকায় ৪টি ঠিকানা (৩ + ১)। মুছে যাওয়া Pod একে একে বেরিয়ে যাচ্ছে, তাই সংখ্যাটি মাঝপথে।
- স্থির অবস্থা: দুটি ঠিকানা রইল। Service-এর ClusterIP বা নাম কোথাও বদলায়নি, শুধু পেছনের তালিকা বদলেছে।
একটি Pod অসুস্থ হলে: যে ফাঁদে অনেকেই পড়েন
আমাদের readiness probe /ready.html ফাইলটি চায়। একটি Pod-এর ভেতর থেকে ফাইলটি মুছে দিলে probe ব্যর্থ হবে, কিন্তু nginx চলতেই থাকবে। এবার দেখুন:
$ kubectl exec api-7d66f59445-fb2fb -- rm /usr/share/nginx/html/ready.html
$ kubectl get pods -l app=api
NAME READY STATUS RESTARTS AGE
api-7d66f59445-fb2fb 0/1 Running 0 81s
api-7d66f59445-mn4r9 1/1 Running 0 7s
api-7d66f59445-th48x 1/1 Running 0 81s
$ kubectl get endpointslices -l kubernetes.io/service-name=api
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
api-k4nqn IPv4 8080 10.1.0.13,10.1.0.11,10.1.0.40 81s
$ kubectl exec client -- sh -c 'for i in $(seq 1 30); do curl -s http://api/; done | sort | uniq -c'
14 api-7d66f59445-mn4r9
16 api-7d66f59445-th48xখেয়াল করুন, ENDPOINTS কলামে অসুস্থ 10.1.0.13 এখনো আছে! অথচ ৩০টি curl-এর একটিও সেখানে যায়নি। কারণ এই কলাম শুধু ঠিকানা দেখায়, অবস্থা দেখায় না। অবস্থা দেখতে হলে object-এর ভেতরে তাকাতে হয়:
$ kubectl get endpointslices -l kubernetes.io/service-name=api -o yaml
…
endpoints:
- addresses:
- 10.1.0.13
conditions:
ready: false
serving: false
terminating: false
nodeName: docker-desktop
targetRef:
kind: Pod
name: api-7d66f59445-fb2fb
…
ports:
- name: http
port: 8080
protocol: TCP
# Shorter: one column per field (a later run, other names and IPs)
$ kubectl get endpointslices -l kubernetes.io/service-name=api \
-o custom-columns='NAME:.metadata.name,ADDRESS:.endpoints[*].addresses[0],READY:.endpoints[*].conditions.ready'
NAME ADDRESS READY
api-4rcrm 10.1.0.140,10.1.0.138,10.1.0.139 true,false,true
$ kubectl get endpoints api
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME ENDPOINTS AGE
api 10.1.0.139:8080,10.1.0.140:8080 9sমনে রাখার মতো: kubectl get endpointslices-এর ENDPOINTS কলামে ঠিকানা দেখলেই ধরে নেবেন না যে সেটি traffic পাচ্ছে। পুরোনো Endpoints object-এর কলামে শুধু ready ঠিকানা থাকে, কিন্তু আসল run-এই kubectl জানাচ্ছে যে এটি v1.33 থেকে deprecated. তাই conditions.ready পড়াটাই নিরাপদ অভ্যাস।
অসুস্থ Pod-টি কি সত্যিই মরে গেছে? না। আমরা সরাসরি curl http://10.1.0.13:8080/ চালালে সে উত্তর দিয়েছে api-7d66f59445-fb2fb. Readiness probe ব্যর্থ হলে Pod restart হয় না, শুধু Service-এর তালিকায় “not ready” হয়। ফাইলটি ফিরিয়ে দিতেই কয়েক সেকেন্ডের মধ্যে Pod আবার 1/1 হয়েছে।
নিচের simulator-এ selector, Pod-এর label, readiness আর targetPort বদলে দেখুন। app: "apii" লিখে একবার চালান, তারপর TARGET_PORT = "web" দিয়ে চালান।
// Service selection: selector + readiness = traffic. Edit and run.
const SELECTOR = { app: "api" }; // try { app: "apii" } or {}
const TARGET_PORT = "http"; // try 9090 or "web"
const PODS = [
{ name: "api-1", ip: "10.1.0.11", labels: { app: "api" }, ready: true, ports: { http: 8080 } },
{ name: "api-2", ip: "10.1.0.13", labels: { app: "api" }, ready: false, ports: { http: 8080 } },
{ name: "api-3", ip: "10.1.0.40", labels: { app: "api" }, ready: true, ports: { http: 8080 } },
{ name: "db-1", ip: "10.1.0.50", labels: { app: "db" }, ready: true, ports: { sql: 5432 } },
];
const keys = Object.keys(SELECTOR);
if (keys.length === 0) {
console.log("No selector: Kubernetes manages no endpoints for this Service.");
} else {
const members = PODS.filter(p => keys.every(k => p.labels[k] === SELECTOR[k]));
for (const p of PODS) {
const inSlice = members.includes(p);
console.log(p.name.padEnd(6), p.ip.padEnd(10), inSlice ? "in slice" : "no match",
inSlice ? "ready=" + p.ready : "");
}
const port = p => typeof TARGET_PORT === "number" ? TARGET_PORT : p.ports[TARGET_PORT];
const ready = members.filter(p => p.ready);
const shown = members.length ? port(members[0]) ?? "<unset>" : "<unset>";
console.log("slice PORTS:", shown, "| addresses listed:", members.length);
if (members.length === 0) console.log("Result: no endpoints, connection fails. Check the selector.");
else if (ready.length === 0) console.log("Result: all listed, none ready. Check the probe.");
else if (shown === "<unset>") console.log("Result: port name not found on the Pods.");
else console.log("Traffic goes to:", ready.map(p => p.ip + ":" + port(p)).join(" "));
}আউটপুট: api-2 10.1.0.13 in slice ready=false, তারপর slice PORTS: 8080 | addresses listed: 3, আর শেষে Traffic goes to: 10.1.0.11:8080 10.1.0.40:8080. তিনটি ঠিকানা তালিকায়, কিন্তু traffic দুটিতে। db-1 selector-এই মেলেনি। "apii" লিখলে আসে addresses listed: 0. "web" লিখলে ঠিকানা থাকে, কিন্তু slice PORTS: <unset> হয়; আসল cluster-এও অংশ ৮-এ ঠিক এটিই দেখবেন।
৪ · Cluster DNS: নাম থেকে ঠিকানা
ClusterIP স্থির হলেও সংখ্যা মনে রাখা বা config-এ হাতে লেখা ভালো নয়। Cluster-এর ভেতরে একটি DNS server চলে, সাধারণত CoreDNS. প্রতিটি Service-এর জন্য সে নিজে থেকেই একটি নামের উত্তর দেয়। এটিই Kubernetes-এর প্রধান service discovery. পর্ব ২৯-এ দেখা DNS-এর ধারণাই এখানে খাটে, তবে নামগুলো cluster-এর ভেতরের।
একটি Service-এর পুরো নাম (FQDN) হয় এমন:
api.abcl-ks.svc.cluster.localservice · namespace · svc · cluster domain
Pod-এর ভেতরের DNS সেটিং
$ kubectl exec client -- cat /etc/resolv.conf
nameserver 10.96.0.10
search abcl-ks.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
$ kubectl exec client -- getent hosts api
10.105.249.110 api.abcl-ks.svc.cluster.local api.abcl-ks.svc.cluster.local api
$ kubectl exec client -- getent hosts api.abcl-ks
10.105.249.110 api.abcl-ks.svc.cluster.local api.abcl-ks.svc.cluster.local api.abcl-ks
$ kubectl exec client -- getent hosts kubernetes.default
10.96.0.1 kubernetes.default.svc.cluster.local kubernetes.default.svc.cluster.local kubernetes.default
$ kubectl exec client -- nslookup api.abcl-ks
Server: 10.96.0.10
Address: 10.96.0.10:53
Non-authoritative answer:
** server can't find api.abcl-ks: NXDOMAINএই আউটপুটে তিনটি শিক্ষা আছে। এক, kubelet প্রতিটি Pod-এর /etc/resolv.conf বানিয়ে দেয়। এতে nameserver হলো CoreDNS-এর সামনের Service-এর ClusterIP, আর প্রথম search domain-এ Pod-এর নিজের namespace থাকে। তাই একই namespace-এ শুধু api লিখলেই চলে, অন্য namespace-এর জন্য api.abcl-ks লিখতে হয়।
দুই, getent আর curl যে পথে নাম খোঁজে, app-ও সেই পথে খোঁজে। শেষের nslookup api.abcl-ks NXDOMAIN দিলেও curl http://api.abcl-ks/ আসল run-এ 200 পেয়েছে। এই image-এর ছোট BusyBox nslookup dot-ওয়ালা নামে search list ব্যবহার করেনি। তাই DNS যাচাইয়ে getent hosts বেশি বিশ্বাসযোগ্য।
তিন, সব নাম একই ধরনের নয়। kubernetes.default-ও একই নিয়মে মিলেছে, কারণ default namespace-এ kubernetes নামের Service থাকে।
ndots:5 আসলে কী করে
নিয়মটি হলো: নামে ndots-এর চেয়ে কম dot থাকলে resolver আগে search domain জুড়ে চেষ্টা করে। শেষে dot থাকলে নামটি absolute, তখন search-এর প্রশ্নই আসে না। এই আচরণ চোখে দেখতে আমরা একটি ছোট Pod চালালাম, যা DNS server সেজে শুধু প্রশ্নগুলো লিখে রাখে আর সবকিছুতে NXDOMAIN দেয়। আরেকটি Pod-এর dnsConfig-এ এই server-এর ঠিকানা দেওয়া হলো, search আর ndots:5 একই রইল। তারপর সেই Pod থেকে getent hosts চালানো হলো। Log-এ যা এলো:
# getent hosts shop.example.com (2 dots, 2 < 5: search first)
AAAA shop.example.com.abcl-ks.svc.cluster.local.
AAAA shop.example.com.svc.cluster.local.
AAAA shop.example.com.cluster.local.
AAAA shop.example.com.
A shop.example.com.abcl-ks.svc.cluster.local.
A shop.example.com.svc.cluster.local.
A shop.example.com.cluster.local.
A shop.example.com.
# getent hosts shop.example.com. (trailing dot: absolute)
AAAA shop.example.com.
A shop.example.com.
# getent hosts a.b.c.d.e.example (5 dots, not less than 5)
AAAA a.b.c.d.e.example.
A a.b.c.d.e.example.একটি বাইরের নাম খুঁজতে ৮টি প্রশ্ন, আর শেষে একটি dot দিলে মাত্র ২টি! Log server সব কিছুতে NXDOMAIN দিয়েছে বলে এখানে সব ধাপ দেখা গেছে; আসল DNS-এ যে নাম মিলে যায়, সেখানেই খোঁজা থামে। এই Pod-এর image Alpine-ভিত্তিক, তাই resolver musl. glibc-ভিত্তিক image-এ ক্রম কিছুটা আলাদা হতে পারে, যেমন dot যথেষ্ট থাকলে absolute চেষ্টা ব্যর্থ হওয়ার পর সে search-ও চেষ্টা করে। নিচের simulator এই নিয়মটি হিসাব করে।
// Search-path expansion, musl-style. Edit NAME and NDOTS.
const NAME = "shop.example.com"; // try "api", "api.abcl-ks", "shop.example.com."
const NDOTS = 5; // try 1
const SEARCH = ["abcl-ks.svc.cluster.local", "svc.cluster.local", "cluster.local"];
const ZONE = { // names that really exist
"api.abcl-ks.svc.cluster.local.": "10.105.249.110",
"shop.example.com.": "203.0.113.10",
};
function candidates(name, ndots, search) {
if (name.endsWith(".")) return [name]; // absolute name
const dots = name.split(".").length - 1;
if (dots >= ndots) return [name + "."]; // enough dots
return [...search.map(s => name + "." + s + "."), name + "."];
}
let packets = 0, answer = "NXDOMAIN";
for (const q of candidates(NAME, NDOTS, SEARCH)) {
packets += 2; // one A + one AAAA
const hit = ZONE[q];
console.log((hit ? "HIT " : "miss"), q);
if (hit) { answer = hit; break; }
}
const dots = NAME.split(".").length - 1;
console.log("dots:", dots, "| ndots:", NDOTS, "| packets (A+AAAA):", packets);
console.log("answer:", answer);প্রথম চালানোয় তিনটি miss-এর পর HIT shop.example.com., আর dots: 2 | ndots: 5 | packets (A+AAAA): 8. এটি আসল log-এর ৮টি লাইনের সাথে মেলে। NDOTS = 1 দিলে একবারেই HIT, প্যাকেট ২টি। NAME = "api" দিলে প্রথম চেষ্টাতেই api.abcl-ks.svc.cluster.local. মেলে। ৮ নম্বরটি এসেছে এভাবে: ৩টি search + ১টি আসল নাম = ৪টি নাম, প্রতিটিতে A আর AAAA, মোট ৪ × ২ = ৮।
অভ্যাস: App বাইরের কোনো host-কে বারবার ডাকলে config-এ শেষের dot দেওয়া নাম (shop.example.com.) ব্যবহার করতে পারেন, তবে সব library বা TLS যাচাই এতে স্বচ্ছন্দ নয়, আগে পরীক্ষা করে নিন। আরেকটি পথ হলো Pod-এর dnsConfig-এ ndots কমানো। মনে রাখুন, এই cluster-এর CoreDNS config-এ TTL ৩০ সেকেন্ড; অন্য cluster-এ এটি আলাদা হতে পারে।
৫ · kube-proxy আর traffic ভাগ
ClusterIP-এ কেউ বসে নেই, তাহলে সংযোগ পৌঁছায় কীভাবে? উত্তর লুকিয়ে আছে প্রতিটি node-এর kernel-এ। kube-proxy প্রতিটি node-এ চলে, Service আর EndpointSlice দেখে, আর kernel-এ rule বসায়: “এই ClusterIP:port-এ যাওয়া packet-এর গন্তব্য এই Pod-গুলোর একটিতে বদলে যাবে।” নামে proxy হলেও প্রচলিত mode-এ packet তার ভেতর দিয়ে যায় না; সে শুধু নিয়ম লেখে।
পুরো পথটি এবার ধাপে ধাপে দেখি। মানগুলো আসল run-এর।
- Pod client
10.1.0.12http://api/ - CoreDNS
10.96.0.10নাম → ঠিকানা - Node rule
10.105.249.110:80DNAT - Pod api
10.1.0.13:8080উত্তর দেয়
$ curl http://api/ # resolv.conf: nameserver 10.96.0.10, ndots:5 api: 0 dots, 0 < 5, so try the search list
A? api.abcl-ks.svc.cluster.local. to 10.96.0.10:53 (itself a ClusterIP)
Name: api.abcl-ks.svc.cluster.local
Address: 10.105.249.110
SYN 10.1.0.12:src → 10.105.249.110:80 rule: pick one Ready endpoint DNAT → 10.1.0.13:8080 conntrack remembers this translation
10.1.0.12 - - […] "GET / HTTP/1.1" 200 21 … api-7d66f59445-fb2fb
$ curl -sS -o /dev/null -w '%{remote_ip}:%{remote_port} %{http_code}\n' \
http://api.abcl-ks.svc.cluster.local/
10.105.249.110:80 200
- নাম দিয়ে শুরু: App শুধু
apiজানে। Resolver দেখল নামে dot নেই, তাই search list ব্যবহার করবে। - DNS প্রশ্ন: প্রথম search domain জুড়ে নাম হলো
api.abcl-ks.svc.cluster.local.প্রশ্নটি গেল10.96.0.10-এ, যা নিজেও একটি Service-এর ClusterIP. - DNS উত্তর: CoreDNS দিল
10.105.249.110. লক্ষ করুন, এটি Pod-এর নয়, Service-এর ঠিকানা। Pod বদলালেও এই উত্তর বদলায় না। - Node-এর rule: Client ClusterIP-এর port 80-তে সংযোগ শুরু করল। Packet Pod থেকে বেরোতেই node-এর rule একটি Ready Pod বেছে নিল, আর গন্তব্য বদলে দিল। একে বলে DNAT. conntrack এই বদল মনে রাখল।
- Pod-এ পৌঁছানো: Pod পেল port 8080-এ একটি সংযোগ, উৎস
10.1.0.12. শুধু গন্তব্য বদলেছে, উৎস নয়, তাই অংশ ১-এর log-এ client-এর আসল ঠিকানা দেখা গেছে। - ফেরার পথ: Pod উত্তর দিল। Conntrack উৎসকে আবার ClusterIP বানিয়ে দিল। তাই
curlমনে করল সে10.105.249.110:80-এর সাথেই কথা বলেছে, আর দেখাল200.
Traffic ভাগ: আসল গণনা
# Three Ready Pods, 30 separate connections
$ kubectl exec client -- sh -c 'for i in $(seq 1 30); do curl -s http://api/; done | sort | uniq -c'
8 api-7d66f59445-9bm9z
10 api-7d66f59445-fb2fb
12 api-7d66f59445-th48x
# Same again
8 api-7d66f59445-9bm9z
11 api-7d66f59445-fb2fb
11 api-7d66f59445-th48x
# One curl, six URLs: one TCP connection is reused
$ kubectl exec client -- curl -s http://api/ http://api/ http://api/ http://api/ http://api/ http://api/
api-7d66f59445-fb2fb
api-7d66f59445-fb2fb
api-7d66f59445-fb2fb
api-7d66f59445-fb2fb
api-7d66f59445-fb2fb
api-7d66f59445-fb2fbএখান থেকে সবচেয়ে কাজের শিক্ষাটি পাওয়া যায়। kube-proxy অনুরোধ ভাগ করে না, সংযোগ ভাগ করে। ভাগ এলোমেলো, round-robin-এর মতো সমান পালা নয়, তাই ৮, ১০, ১২ এসেছে। আর একটি keep-alive সংযোগে যত অনুরোধই যাক, সব এক Pod-এ যায়। Connection pool রাখা app-এ scale করার পর নতুন Pod প্রায় খালি বসে থাকতে পারে, পুরোনো সংযোগ বন্ধ না হওয়া পর্যন্ত। এজন্য HTTP/2 বা gRPC traffic-এ অনুরোধ-স্তরের load balancing অনেক সময় আলাদা করে লাগে।
তিনটি mode: iptables, IPVS, nftables
এই cluster-এর kube-proxy config-এ mode ফাঁকা, আর Linux-এ ফাঁকা মানে iptables. Node-এর rule আমরা খুলে দেখিনি, কারণ সেটি আমাদের namespace-এর বাইরের এলাকা। নিচে iptables mode-এর rule-এর আকার দেখানো হলো। এটি নমুনা, এই run-এর আউটপুট নয়; chain-এর নাম আসলে hash-এর মতো দেখায়।
-A KUBE-SERVICES -d 10.96.0.20/32 -p tcp --dport 80 -j KUBE-SVC-EXAMPLE
-A KUBE-SVC-EXAMPLE -m statistic --mode random --probability 0.33333 -j KUBE-SEP-AAAA
-A KUBE-SVC-EXAMPLE -m statistic --mode random --probability 0.50000 -j KUBE-SEP-BBBB
-A KUBE-SVC-EXAMPLE -j KUBE-SEP-CCCC
-A KUBE-SEP-AAAA -p tcp -m tcp -j DNAT --to-destination 10.1.0.11:8080
প্রথম rule ১/৩ সম্ভাবনায় ধরে। বাকি ২/৩-এর ১/২ দ্বিতীয় rule ধরে, অর্থাৎ ২/৩ × ১/২ = ১/৩। যা বাকি থাকে, তৃতীয় rule ধরে: ১/৩। তাই প্রতিটি Pod সমান সম্ভাবনা পায়। সব সংখ্যা আলাদা দেখালেও ফল সমান, এটি পরীক্ষায় প্রিয় হিসাব। নিচে নিজে চালিয়ে দেখুন।
// iptables-mode pick: one random choice per NEW connection.
const PODS = ["api-a", "api-b", "api-c"]; // add or remove Pods
const CONNECTIONS = 3000;
const REQUESTS_PER_CONN = 1; // try 100: keep-alive
function mulberry32(a) {
return function () {
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 rand = mulberry32(45);
const n = PODS.length;
PODS.forEach((p, i) => {
const left = n - i;
console.log("rule", i + 1, "->", p, "probability 1/" + left);
});
const conns = Array(n).fill(0);
for (let c = 0; c < CONNECTIONS; c++) {
for (let i = 0; i < n; i++) {
if (i === n - 1 || rand() < 1 / (n - i)) { conns[i]++; break; }
}
}
PODS.forEach((p, i) => console.log(p, "connections:", conns[i],
"requests:", conns[i] * REQUESTS_PER_CONN));আউটপুট: rule 1 -> api-a probability 1/3, rule 2 -> api-b probability 1/2, rule 3 -> api-c probability 1/1. গণনা: api-a connections: 1014, api-b connections: 1036, api-c connections: 950. প্রত্যাশা প্রতিটিতে ১০০০, কিন্তু এলোমেলো ভাগে ছোট পার্থক্য থাকবেই। এবার CONNECTIONS = 6 আর REQUESTS_PER_CONN = 100 দিন: ফল আসে 100, 200, 300 request. কম সংযোগে অসমতা কত বড় হতে পারে, এটি তার ছবি।
| Mode | কীভাবে খোঁজে | খেয়াল রাখুন |
|---|---|---|
| iptables | Rule-এর তালিকা ধরে এগোয় | লেখার সময়ে (১.৩৭) Linux-এ default, তবে নথি জানায় ভবিষ্যতে default হবে nftables. Service হাজারের ঘরে গেলে rule বদলাতে সময় লাগে। |
| IPVS | Kernel-এর hash table | একাধিক scheduling পদ্ধতি আছে। Kubernetes ১.৩৫-এ deprecated হয়েছে (KEP-5495), পরে সরানোর পরিকল্পনা আছে। |
| nftables | Kernel-এর map | নতুন পথ। Kubernetes ১.৩৩-এ stable. নতুন kernel লাগে (১.৩৩-এর নথিতে ৫.১৩ বা পরের), আর সব network plugin-এর সাথে নাও মিলতে পারে। |
| eBPF-ভিত্তিক | Kernel-এ ছোট প্রোগ্রাম | যেমন Cilium, kube-proxy-র জায়গা নিতে পারে। এটি CNI-ভেদে আলাদা, Kubernetes-এর নিজস্ব mode নয়। |
Session affinity
কোনো কারণে একই client-কে একই Pod-এ রাখতে হলে Service-এ sessionAffinity: ClientIP দেওয়া যায়। এটি sticky session-এর সরল রূপ: ভিত্তি শুধু client-এর IP, cookie নয়।
$ kubectl patch svc api -p '{"spec":{"sessionAffinity":"ClientIP"}}'
service/api patched
$ kubectl get svc api -o jsonpath='{.spec.sessionAffinity} {.spec.sessionAffinityConfig}{"\n"}'
ClientIP {"clientIP":{"timeoutSeconds":10800}}
$ kubectl exec client -- sh -c 'for i in $(seq 1 20); do curl -s http://api/; done | sort | uniq -c'
20 api-7d66f59445-mn4r9
$ kubectl patch svc api -p '{"spec":{"sessionAffinity":"None"}}'
service/api patched
$ kubectl exec client -- sh -c 'for i in $(seq 1 20); do curl -s http://api/; done | sort | uniq -c'
7 api-7d66f59445-fb2fb
6 api-7d66f59445-mn4r9
7 api-7d66f59445-th48x২০টির ২০টিই এক Pod-এ, আর default সময়সীমা ১০৮০০ সেকেন্ড, মানে ৩ ঘণ্টা। সাবধান: অনেক client একটি NAT-এর পেছনে থাকলে সবার IP এক দেখায়, তখন সবাই একটি Pod-এ জমা হয়। আসল সমাধান হলো session Redis-এর মতো বাইরের store-এ রাখা, যাতে যেকোনো Pod উত্তর দিতে পারে।
৬ · NodePort আর LoadBalancer: বাইরে থেকে ভেতরে
ClusterIP শুধু cluster-এর ভেতর থেকে পৌঁছানো যায়। বাইরের ব্যবহারকারীর জন্য দুটি পথ। প্রথমটি NodePort: প্রতিটি node একই একটি port খুলে রাখে। আমাদের Service-এ শুধু ধরন আর port বদলানো হলো:
apiVersion: v1
kind: Service
metadata:
name: api-np
namespace: abcl-ks
spec:
type: NodePort
selector:
app: api
ports:
- name: http
port: 80
targetPort: http
nodePort: 30845$ kubectl get svc api-np
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api-np NodePort 10.103.112.35 <none> 80:30845/TCP 1s
# From this machine, outside the cluster, 12 connections
$ for i in 1 2 3 4 5 6 7 8 9 10 11 12; do curl -s --max-time 2 http://127.0.0.1:30845/; done | sort | uniq -c
3 api-7d66f59445-fb2fb
4 api-7d66f59445-mn4r9
5 api-7d66f59445-th48x
# A nodePort outside the allowed range is refused by the API server
$ kubectl apply -f bad.yaml
The Service "api-bad" is invalid: spec.ports[0].nodePort: Invalid value: 20045: provided port is not in the valid range. The range of valid ports is 30000-32767PORT(S) ঘরের 80:30845/TCP মানে Service port 80, node port 30845. NodePort নিজে থেকেই একটি ClusterIP-ও পেয়েছে, ঠিক ছবি ৪৫.১-এর মতো। nodePort না লিখলে Kubernetes পরিসর থেকে একটি port বেছে দেয়। একটি ছোট পর্যবেক্ষণ: Service তৈরির ঠিক পরপর দ্রুত ৬টি অনুরোধের সবগুলো সংযোগ পায়নি, আর কিছু পরে ১২টির ১২টিই পেয়েছে। Rule বসতে সামান্য সময় লাগে, তাই তৈরির মুহূর্তের একটি ব্যর্থতা দেখেই সিদ্ধান্ত নেবেন না।
- বাইরের client
node:30845যেকোনো node - Node rule
:30845Pod বাছাই - SNAT + DNAT
→ Pod:8080দুই ঠিকানা বদল - Pod
:8080উত্তর
$ curl http://<node-address>:30845/
dst port 30845 → Service api-np pick one Ready endpoint, on any node
before: client → node:30845 after: node → 10.1.0.11:8080
<node address> - - "GET / HTTP/1.… not the client's own address
Pod → node → client
api-7d66f59445-mn4r9
- Client: সে যেকোনো একটি node-এর ঠিকানায় port 30845-এ সংযোগ করে। সব node-এ একই port খোলা থাকে।
- Node-এর rule: এই port-এ কোনো process শুনছে না। kube-proxy-র rule সংযোগ ধরে, আর একটি Ready Pod বেছে নেয়। Pod অন্য node-এও থাকতে পারে।
- দুটি ঠিকানা বদল: গন্তব্য বদলায় Pod-এ, আর default নিয়মে উৎসও বদলায় node-এ, যাতে উত্তর একই node হয়ে ফেরে।
- Pod-এর চোখে: আসল run-এর log-এ উৎস ছিল node-এর ঠিকানা (এখানে ছাঁটা), client-এর নয়। Pod থেকে সরাসরি আসা অনুরোধে ছিল Pod-এর ঠিকানা; এই পার্থক্যটিই SNAT.
- ফেরার পথ: Pod node-কে উত্তর দেয়, conntrack দুটি বদল উল্টে দেয়, আর client পায় Pod-এর নাম।
উৎস ঠিকানা হারানো ঠেকাতে Service-এ externalTrafficPolicy: Local দেওয়া যায়। তখন traffic শুধু সেই node-এর Pod-এ যায়, উৎস টিকে থাকে, কিন্তু যে node-এ Pod নেই সেখানে সংযোগ ব্যর্থ হয়, আর ভাগ অসমান হতে পারে। আমাদের single-node cluster-এ আমরা field-টি বদলে দেখেছি: সব Pod এক node-এ বলে ভাগে পার্থক্য দেখা যায় না, আর Pod-এর log-এও উৎস একই থেকেছে। এই বৈশিষ্ট্য ঠিকভাবে পরীক্ষা করতে বহু-node cluster লাগে।
LoadBalancer: এই cluster-এ আসলে কী হলো
LoadBalancer ধরন নিজে কোনো load balancer বানায় না। সে শুধু status-এ একটি ঠিকানা চায়, আর সেটি পূরণ করে cluster-এর বাইরের কোনো controller. তাই আমরা অনুমান না করে চালিয়ে দেখলাম:
apiVersion: v1
kind: Service
metadata:
name: api-lb
namespace: abcl-ks
spec:
type: LoadBalancer
selector:
app: api
ports:
- name: http
port: 18045
targetPort: http$ kubectl get svc api-lb
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api-lb LoadBalancer 10.108.255.68 localhost 18045:30610/TCP 4s
$ kubectl get svc api-lb -o jsonpath='{.status}{"\n"}'
{"loadBalancer":{"ingress":[{"hostname":"localhost"}]}}
$ for i in 1 2 3 4 5 6; do curl -s --max-time 3 http://127.0.0.1:18045/; done | sort | uniq -c
3 api-7d66f59445-fb2fb
2 api-7d66f59445-mn4r9
1 api-7d66f59445-th48x
$ kubectl delete svc api-lb
service "api-lb" deleted from abcl-ks namespaceএখানে EXTERNAL-IP হলো localhost. Docker Desktop-এর নিজের একটি controller (vpnkit-controller) এই কাজটি করে, আর মেশিনের port 18045 Service-এ জুড়ে দেয়। কোনো আসল load balancer তৈরি হয়নি, আর বিলও আসেনি। লক্ষ করুন, 18045:30610: Service নিজে থেকে একটি NodePort-ও পেয়েছে। আরেকটি সতর্কতা: এই run-এ port 18045 আর NodePort 30845 মেশিনের সব network interface-এ শুনছিল, শুধু 127.0.0.1-এ নয়। তাই local cluster-এ NodePort বা LoadBalancer চালালে কাজ শেষে মুছে ফেলুন।
Provider-এর আচরণ (এই run-এর বাইরের কথা, পরীক্ষা করা হয়নি): Managed cluster-এ cloud controller manager বা provider-এর controller এই Service দেখে আসল load balancer বানায়। পর্ব ১৮-এর target group, health check আর ঘণ্টা-ভিত্তিক খরচ সবই তখন খাটে। Bare-metal বা নিজের server-এ এমন controller না থাকলে EXTERNAL-IP <pending> হয়ে থাকে; তখন MetalLB-এর মতো project লাগে। প্রতিটি LoadBalancer Service সাধারণত একটি আলাদা load balancer, তাই ১০টি app-এ ১০টি বিল হতে পারে। এই খরচ কমানোর পথ পর্ব ৪৬-এর Ingress আর Gateway API.
| ধরন | ClusterIP আছে? | কে পৌঁছাতে পারে | সাধারণ ব্যবহার |
|---|---|---|---|
| ClusterIP | হ্যাঁ | Cluster-এর ভেতর | Service-এ Service কথা |
| NodePort | হ্যাঁ | Node-এ পৌঁছাতে পারে এমন সবাই | পরীক্ষা, বা বাইরের LB-র পেছনে |
| LoadBalancer | হ্যাঁ | LB যতদূর খোলা | একটি app-কে বাইরে দেখানো |
| ExternalName | না | Cluster-এর ভেতর (DNS) | বাইরের নামকে ভেতরের নাম দেওয়া |
| Headless | না (None) | Cluster-এর ভেতর (DNS) | Pod-কে আলাদা করে চেনা |
৭ · ExternalName আর headless: যেখানে proxy নেই
শেষের দুই ধরন শুধু DNS-এর খেলা। এদের জন্য kube-proxy কোনো rule বসায় না। আসল manifest আর আউটপুট দেখুন।
apiVersion: v1
kind: Service
metadata:
name: api-headless
namespace: abcl-ks
spec:
clusterIP: None
selector:
app: api
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: pay-gateway
namespace: abcl-ks
spec:
type: ExternalName
externalName: pay.example.com$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api ClusterIP 10.105.249.110 <none> 80/TCP 45s
api-headless ClusterIP None <none> 80/TCP 4s
pay-gateway ExternalName <none> pay.example.com <none> 4s
$ kubectl exec client -- nslookup api-headless.abcl-ks.svc.cluster.local
…
Name: api-headless.abcl-ks.svc.cluster.local
Address: 10.1.0.13
Name: api-headless.abcl-ks.svc.cluster.local
Address: 10.1.0.11
Name: api-headless.abcl-ks.svc.cluster.local
Address: 10.1.0.16
# Port 80 is only the Service's number: nothing translates it here
$ kubectl exec client -- curl -sS --max-time 2 http://api-headless/
curl: (7) Failed to connect to api-headless port 80 after 1 ms: Could not connect to server
$ kubectl exec client -- curl -sS --max-time 2 http://api-headless:8080/
api-7d66f59445-wjdht
$ kubectl exec client -- nslookup pay-gateway.abcl-ks.svc.cluster.local
…
pay-gateway.abcl-ks.svc.cluster.local canonical name = pay.example.comHeadless Service-এ DNS সরাসরি Pod-এর ঠিকানাগুলো দিল। মাঝে DNAT নেই, তাই client-কে Pod-এর আসল port 8080-এই যেতে হলো; port: 80 এখানে কোনো অনুবাদ করেনি। আমরা আরেকটি run-এ একটি Pod unready করে দেখেছি, DNS-এর উত্তরে তখন ৩টির বদলে ২টি ঠিকানা এসেছে। StatefulSet-এর প্রতিটি Pod-কে আলাদা নাম দিতে এই ধরনটিই লাগে, যা পর্ব ৪৭-এ দেখবেন। Client নিজে ঠিকানা বেছে নিতে চাইলেও এটি কাজে লাগে, যেমন client-side load balancing করা gRPC.
ExternalName শুধু একটি CNAME ফেরত দেয়। সুবিধা হলো, app-এর config-এ pay-gateway লেখা থাকলে পরে শুধু Service বদলেই গন্তব্য বদলানো যায়। সীমা হলো, এখানে কোনো port অনুবাদ নেই, আর HTTP-এর Host header বা TLS certificate-এ client তখনো ভেতরের নামটিই পাঠাতে পারে। তাই বাইরের HTTPS service-এ এটি প্রায়ই ঠিকঠাক খাটে না, আগে পরীক্ষা করুন।
৮ · port-forward আর Service debug
kubectl port-forward: নিজের জন্য একটি ছোট সুড়ঙ্গ
নিজের মেশিন থেকে cluster-এর ভেতরের কিছু একবার দেখতে চাইলে Service-এর ধরন বদলানোর দরকার নেই। kubectl port-forward API server হয়ে একটি সুড়ঙ্গ বানায়, অনেকটা পর্ব ১২-এর SSH port forwarding-এর মতো।
$ kubectl port-forward --address 127.0.0.1 svc/api 18145:80
Forwarding from 127.0.0.1:18145 -> 8080
# In another terminal, 8 connections
$ for i in 1 2 3 4 5 6 7 8; do curl -s --max-time 3 http://127.0.0.1:18145/; done | sort | uniq -c
8 api-7d66f59445-th48x৮টি আলাদা সংযোগ, সব এক Pod-এ! svc/api লিখলেও kubectl শুরুতে Service থেকে একটি Pod বেছে নেয়, আর সেই Pod-এই সুড়ঙ্গ বাঁধে। এটি kube-proxy-র পথ নয়। তাই port-forward কাজ করলেও প্রমাণ হয় না যে Service-এর ভাগ বা kube-proxy ঠিক আছে; আর Pod মরলে সুড়ঙ্গও ভেঙে যায়। kubectl-এর default-ও localhost, আর --address 127.0.0.1 লিখে আমরা সেটি স্পষ্ট করেছি। 0.0.0.0 দিলে একই network-এর অন্যরাও ঢুকতে পারে।
আসল সমস্যা: একটি অক্ষরের ভুল
একটি নতুন Service orders বানানো হলো, কিন্তু selector-এ ভুলে লেখা হলো app: apii. কোথাও কোনো error এলো না, এটিই এই ভুলের বিপদ।
$ kubectl apply -f typo.yaml
service/orders created
$ kubectl get svc orders
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
orders ClusterIP 10.106.98.36 <none> 80/TCP 2s
$ kubectl get endpointslices -l kubernetes.io/service-name=orders
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
orders-8mtx4 IPv4 <unset> <unset> 2s
$ kubectl exec client -- getent hosts orders
10.106.98.36 orders.abcl-ks.svc.cluster.local orders.abcl-ks.svc.cluster.local orders
$ kubectl exec client -- curl -sS --max-time 3 http://orders/
curl: (7) Failed to connect to orders port 80 after 0 ms: Could not connect to server
$ kubectl describe svc orders | grep -E 'Selector|Endpoints|Events'
Selector: app=apii
Endpoints:
Events: <none>
$ kubectl get pods -l app=apii
No resources found in abcl-ks namespace.
$ kubectl get pods --show-labels -l app=api
NAME READY STATUS RESTARTS AGE LABELS
api-7d66f59445-fb2fb 1/1 Running 0 2m46s app=api,pod-template-hash=7d66f59445
api-7d66f59445-mn4r9 1/1 Running 0 92s app=api,pod-template-hash=7d66f59445
api-7d66f59445-th48x 1/1 Running 0 2m46s app=api,pod-template-hash=7d66f59445
$ kubectl patch svc orders -p '{"spec":{"selector":{"app":"api"}}}'
service/orders patched
$ kubectl get endpointslices -l kubernetes.io/service-name=orders
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
orders-8mtx4 IPv4 8080 10.1.0.11,10.1.0.13,10.1.0.40 5s
$ kubectl exec client -- curl -sS --max-time 3 http://orders/
api-7d66f59445-mn4r9লক্ষণটি পড়তে শিখুন। DNS কাজ করছে, ClusterIP আছে, Service-এর status ঠিক, Events ফাঁকা। শুধু EndpointSlice-এ <unset>, আর সংযোগ সঙ্গে সঙ্গে বাতিল (0 ms). সঙ্গে সঙ্গে বাতিল হওয়া আর timeout আলাদা: অংশ ১-এর মৃত Pod IP-তে ৩ সেকেন্ড অপেক্ষা করে timeout হয়েছিল। এবার আমরা একই Service-এ আরও তিনটি ভুল ইচ্ছা করে ঘটালাম:
| ভুল | Slice-এর PORTS | Slice-এর ENDPOINTS | curl |
|---|---|---|---|
| Selector টাইপো | <unset> | <unset> | exit 7 |
targetPort: 9090 (app শোনে 8080) | 9090 | ৩টি ঠিকানা | exit 7 |
targetPort: web (এই নাম নেই) | <unset> | ৩টি ঠিকানা | exit 7 |
| সব Pod unready | 8080 | ৩টি ঠিকানা (ready: false) | exit 7 |
চারটি ক্ষেত্রেই curl-এর একই error! তাই error দেখে কারণ বলা যায় না, কারণ ধরা পড়ে slice আর Pod পড়ে। শেষের সারিতে kubectl get endpoints api-এর ঘর ফাঁকা ছিল আর ৩টি ঠিকানা ছিল notReadyAddresses-এ। নিচের decision tree এই যুক্তিই সাজিয়ে দেয়। যা দেখলেন সেই অনুযায়ী true বা false বদলে চালান।
// "Service not reachable" decision tree. Set what you observed.
const OBSERVED = {
serviceExists: true, // kubectl get svc api
dnsResolves: true, // getent hosts api (from a Pod)
podsMatchSelector: false, // kubectl get pods -l <selector>
somePodReady: true, // READY column, or slice conditions
slicePortSet: true, // PORTS is not <unset>
podIpWorks: true, // curl PodIP:targetPort (from a Pod)
clusterIpWorks: true, // curl ClusterIP:port (from a Pod)
outsideWorks: true, // from outside the cluster
};
const TREE = [
["serviceExists", "Service exists?", "Wrong name or namespace. Check: kubectl get svc -A"],
["dnsResolves", "Name resolves?", "Use the full name; check namespace and CoreDNS Pods."],
["podsMatchSelector", "Pods match selector?", "Selector or label typo. Compare with --show-labels."],
["somePodReady", "Any Pod Ready?", "Read kubectl describe pod: probe, crash or image."],
["slicePortSet", "Slice port set?", "targetPort name not on the Pod. Fix the port name."],
["podIpWorks", "Pod answers directly?", "App not listening there, wrong targetPort, or NetworkPolicy."],
["clusterIpWorks", "ClusterIP answers?", "Service path broken: check port, kube-proxy, CNI, policy."],
["outsideWorks", "Reachable from outside?", "Check type, NodePort, firewall, LB health check."],
];
let step = 0;
for (const [key, question, fix] of TREE) {
step++;
if (OBSERVED[key]) { console.log(step + ". " + question + " yes"); continue; }
console.log(step + ". " + question + " NO");
console.log("STOP -> " + fix);
step = -1;
break;
}
if (step !== -1) console.log("All checks pass: test from the failing client itself.");আউটপুট: 1. Service exists? yes, 2. Name resolves? yes, 3. Pods match selector? NO, তারপর STOP -> Selector or label typo. Compare with --show-labels. এটিই আমাদের orders সমস্যা। ক্রমটি ইচ্ছা করেই ভেতর থেকে বাইরে: Service, নাম, Pod, readiness, port, Pod-এ সরাসরি, ClusterIP, তারপর বাইরের পথ। একটি ধাপে ব্যর্থ হলে পরের ধাপ দেখার দরকার নেই। podIpWorks ঠিক থাকলে app ভালো আছে, সমস্যা তার সামনে কোথাও।
আরও গভীরে: EndpointSlice ডিফল্টে প্রতি slice-এ সর্বোচ্চ ১০০টি ঠিকানা রাখে, আর label kubernetes.io/service-name দিয়ে Service-এর সাথে জোড়া থাকে। Pod বন্ধ হওয়ার সময় ঠিকানাটি ready: false কিন্তু serving থাকতে পারে; কোনো ready ঠিকানা না থাকলে নতুন kube-proxy এমন terminating Pod-কে শেষ ভরসা হিসেবে ব্যবহার করতে পারে। Service-এর trafficDistribution field কাছের Pod-কে অগ্রাধিকার দিতে বলে; এই cluster-এর API server PreferClose, PreferSameZone আর PreferSameNode মান গ্রহণ করেছে, ভুল মান দিলে সে তালিকাটিই error-এ জানায়। DNS-এ named port-এর SRV record-ও থাকে: _http._tcp.api.-এর উত্তরে আসল run-এ এলো service = 0 100 80 api.abcl-ks.svc.cluster.local.
পরীক্ষা ও ইন্টারভিউতে:
- Service আর Deployment-এর সম্পর্ক কী? সরাসরি কোনো সম্পর্ক নেই; দুটোই label দিয়ে Pod ধরে।
- Readiness ব্যর্থ হলে কী হয়? Pod চলতে থাকে, শুধু Service-এর traffic বন্ধ হয়। Restart করে liveness probe.
- ClusterIP ping করা যায় না কেন? এটি virtual, rule শুধু নির্দিষ্ট port-এর TCP বা UDP ধরে, তাই ping-এর উত্তর পাওয়ার নিশ্চয়তা নেই।
- LoadBalancer
<pending>কেন? ঠিকানা দেওয়ার মতো কোনো controller নেই, বা provider-এর দিকে সমস্যা। - Headless কখন? Client-কে প্রতিটি Pod আলাদা করে চিনতে হলে, যেমন StatefulSet-এর database.
৯ · AI-এর যুগে Service আর DNS
AI খুব দ্রুত Service-এর YAML লিখে দেয়, আর তা দেখতেও ঠিক লাগে। কিন্তু অংশ ৮-এর মতো ভুল YAML kubectl apply-এ কোনো error দেয় না। তাই “apply সফল” মানে “traffic পৌঁছাচ্ছে” নয়।
| AI-এর সাধারণ ভুল | ফল | যাচাই |
|---|---|---|
সব Service-এ type: LoadBalancer | প্রতিটিতে বিল, আর database পর্যন্ত internet-এ খোলা | ভেতরের Service ClusterIP রাখা |
| Selector আর Pod label না মেলা | Endpoint নেই, কিন্তু কোনো error-ও নেই | kubectl get pods -l ... |
targetPort-এ ভুল port | ঠিকানা আছে, সংযোগ নেই | Pod IP-তে সরাসরি curl |
বানানো annotation বা sessionAffinity: Cookie | নীরবে উপেক্ষা, বা API-র error | --dry-run=server আর provider-এর ডকুমেন্ট |
port-forward --address 0.0.0.0 | Network-এর অন্যদের জন্যও খোলা | 127.0.0.1-ই রাখা |
| Config-এ Pod IP লেখা, Endpoints API ধরে script | পরের rollout-এ ভেঙে যায় | Service-এর নাম, আর EndpointSlice |
ঠিক না করে kubectl delete বা restart বলা | প্রমাণ মুছে যায়, সমস্যা ফিরে আসে | আগে slice আর Events পড়া |
নিচের prompt-এ AI-কে আপনার আসল আউটপুট দিন, আর সমাধানের আগে কারণ চান। কোনো secret, token বা kubeconfig পেস্ট করবেন না।
AI-এর Service manifest হাতে পেলে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Kubernetes প্রতিটি Pod-কে নিজের IP কেন দিল? Docker-এর মতো node-এর port ভাগ করলে কী সমস্যা ছিল?
উত্তরের শিকড় Google-এর Borg-এ। ২০১৫ সালের Borg paper-এর “lessons” অংশে লেখকেরা বলেন, একটি machine-এর সব task এক IP ভাগ করলে port-কেই একটি সম্পদ হিসেবে সামলাতে হয়। Scheduler-কে port-ও বরাদ্দ করতে হয়, app-কে আগে থেকে জানতে হয় সে কোন port পাবে, আর name service-কে IP-র সাথে port-ও জানাতে হয়।
পর্ব ৩৯-এ আপনি নিজেই এই সমস্যার ছোট রূপ দেখেছেন: দুটি container একই host port চাইলে সংঘাত হয়। এখন ভাবুন ১০০টি node, ৫০টি app, প্রতিটিতে port 8080. হাতে port বরাদ্দ করা অসম্ভব। Dynamic port দিলে প্রতিটি app-কে port-এর হিসাব জানতে হয়, যা পুরোনো software-এ বদলানো কঠিন।
Kubernetes সিদ্ধান্ত নিল, প্রতিটি Pod-কে নিজের IP দেবে। তখন প্রতিটি Pod নিজের port-পরিসরের মালিক, nginx ঠিক 80-তেই চলতে পারে। Pod দেখতে একটি ছোট VM-এর মতো, তাই পুরোনো app, port-এর ধারণা আর DNS, সবই আগের মতো খাটে। সেই সাথে NAT-ছাড়া নিয়মের কারণে একটি Pod যে ঠিকানায় নিজেকে চেনে, অন্যরাও তাকে সেই ঠিকানায় চেনে।
দাম কিন্তু আছে। Network-কে এখন অনেক বেশি ঠিকানা সামলাতে হয়। Cloud-এর VPC-native CNI-তে Pod ঠিকানা subnet থেকে নেয়, তাই পর্ব ২৫-এর CIDR পরিকল্পনা ছোট হলে ঠিকানা ফুরিয়ে যেতে পারে। আর Pod নশ্বর বলে ঠিকানা স্থির রাখা যায় না, যার সমাধান হিসেবেই Service-এর জন্ম।
মূল উপলব্ধি: একটি জটিলতা সরালে তা অন্য কোথাও যায়। Kubernetes port-এর জটিলতা সরিয়ে network স্তরে ঠেলে দিয়েছে, যেখানে সেটি একবার সমাধান করলেই সব app উপকার পায়।
প্র ০২DNS-ই তো একাধিক ঠিকানা দিতে পারে। তাহলে Kubernetes শুধু round-robin DNS না দিয়ে ClusterIP-এর মতো virtual IP কেন বানাল?
Kubernetes-এর নিজের ডকুমেন্টেশনই এই প্রশ্নের উত্তর দেয়, আর কারণগুলো DNS-এর বাস্তব আচরণ থেকে আসে। পর্ব ২৯-এ দেখেছেন, DNS-এর উত্তর cache হয়, আর TTL একটি অনুরোধ, আদেশ নয়।
- TTL না মানা: কিছু DNS client ও library TTL মানেনি, বা নিজের মতো বেশি সময় cache রেখেছে।
- একবার খোঁজা: কিছু app শুরুতে একবার নাম খুঁজে সারা জীবন সেই ফলই রাখে।
- কম TTL-এর দাম: Pod দ্রুত বদলায়, তাই TTL শূন্যের কাছে নামালে DNS server-এ চাপ অনেক বাড়ে, আর সব উত্তরে সব Pod থাকে।
আমরা এই পর্বের পরীক্ষায় Pod মুছেছি, scale করেছি। যদি client Pod IP cache করত, পুরোনো ঠিকানায় অংশ ১-এর মতো timeout হতো। ClusterIP-তে সমস্যাটি উল্টে গেছে: নামের উত্তর কখনো বদলায় না, তাই cache নিরাপদ। বদল ঘটে node-এর rule-এ, যা kube-proxy প্রায় সঙ্গে সঙ্গে হালনাগাদ করে।
তবে virtual IP-রও দাম আছে। সংযোগ-স্তরের ভাগ HTTP/2-তে অসম হয়, প্রতিটি node-এ rule রাখতে হয়, আর debugging-এ DNAT আর conntrack বোঝা লাগে। তাই Kubernetes দুটো পথই রেখেছে। যার client-side load balancing আছে, যেমন gRPC, সে headless Service নিয়ে সরাসরি Pod ঠিকানা পেতে পারে। সে ক্ষেত্রে TTL আর পুনরায় খোঁজার দায় client-এর।
মূল উপলব্ধি: “কে ঠিকানা বদল সামলাবে” এই প্রশ্নটিই নকশার মূল। ClusterIP দায়টি client থেকে platform-এ নিয়েছে; headless দায়টি ফিরিয়ে দেয় তাদের, যারা তা চায়।
প্র ০৩ndots:5 এত খরচের হলে default-এ কেন? Production-এ DNS-এর চাপ কীভাবে কমানো হয়?
আমরা আসল log-এ দেখেছি, shop.example.com খুঁজতে ৮টি প্যাকেট গেছে। তবু মান ৫ রাখার পেছনে একটি যুক্তি আছে। Cluster-এর ভেতরের নামে অনেক dot থাকে। যেমন named port-এর SRV খোঁজা: _http._tcp.api.abcl-ks.svc-এ ৪টি dot. ndots ৫ হলে এমন ছোট রূপও search list দিয়ে মিলে যায়। অর্থাৎ নকশাটি ভেতরের নামকে অগ্রাধিকার দিয়েছে, বাইরের নামের দাম দিয়ে। Kubernetes-এর issue আলোচনায় এই কারণটিই বলা হয়েছে বলে জানি, তবে এটিকে ইতিহাসের চূড়ান্ত দলিল ধরবেন না।
একটি microservice প্রতি সেকেন্ডে বাইরের API-কে ১০০০ বার নতুন করে খুঁজলে আর ৮টি করে প্যাকেট গেলে CoreDNS-এ ৮০০০ প্রশ্ন আসে, যেখানে দরকার ছিল ২০০০ (এখানে cache ধরা হয়নি)। ২০১৭-১৮ সালের দিকে কয়েকটি প্রকৌশল ব্লগে দেখা যায়, conntrack-এর একটি race condition-এ UDP DNS প্যাকেট হারিয়ে ৫ সেকেন্ডের অদ্ভুত দেরি হতো। ৫ সেকেন্ড ছিল resolver-এর default timeout. এমন ঘটনায় বাড়তি DNS প্রশ্ন মানে বাড়তি ঝুঁকি।
প্রচলিত ব্যবস্থাগুলো:
- শেষে dot: Config-এ absolute নাম, যা আমরা log-এ ২টি প্যাকেটে দেখেছি।
- ndots কমানো: নির্দিষ্ট Pod-এর
dnsConfig-এ। - NodeLocal DNSCache: প্রতিটি node-এ ছোট cache, যা conntrack-এর পথও এড়াতে সাহায্য করে।
- CoreDNS scale: Replica বাড়ানো, autoscaling আর
cache; CoreDNS-এরautopathplugin-ও আছে। - App-এ cache: সংযোগ reuse করা, প্রতি অনুরোধে নতুন করে না খোঁজা।
মূল উপলব্ধি: Default মানে সবচেয়ে ভালো নয়, বরং একটি পক্ষ নেওয়া সিদ্ধান্ত। আপনার traffic কোন দিকের, তা মেপে সেই সিদ্ধান্ত যাচাই করতে হয়।
প্র ০৪Endpoints তো কাজ করছিল। তাহলে EndpointSlice কেন এলো, আর একটি Pod বদলালে হিসাবে কত সাশ্রয় হয়?
পুরোনো নকশায় একটি Service-এর সব Pod ঠিকানা থাকত একটিমাত্র Endpoints object-এ। প্রতিটি node-এর kube-proxy সেই object watch করে। সমস্যা হলো, একটি Pod বদলালেও পুরো object নতুন করে প্রতিটি node-এ যায়। ছোট cluster-এ এটি চোখে পড়ে না, বড় cluster-এ rolling update-এর সময় এটি বড় বোঝা হয়।
একটি অনুমান-ভিত্তিক হিসাব করি। ধরুন একটি Service-এ ৫০০০ Pod, cluster-এ ১০০০ node, আর একটি Endpoints object-এর আকার ১ MB. এক Pod বদলালে ১ MB × ১০০০ = ১ GB ডেটা ছড়ায়। Rolling update-এ ৫০০০ Pod বদলালে ৫০০০ GB-র কাছাকাছি, যদিও বাস্তবে কিছু update জোট বেঁধে যায়।
EndpointSlice একই তালিকাকে ছোট ছোট টুকরায় ভাগ করে, ডিফল্টে প্রতিটিতে সর্বোচ্চ ১০০টি। তাহলে ৫০০০ Pod মানে অন্তত ৫০টি slice, প্রতিটি মোটামুটি ১ MB ÷ ৫০ = ২০ KB. এক Pod বদলালে শুধু একটি slice যায়: ২০ KB × ১০০০ = ২০ MB. ১ GB থেকে ২০ MB, মানে প্রায় ৫০ গুণ কম।
EndpointSlice ১.১৬ নাগাদ alpha হয়ে ১.২১-এ GA হয়। আর আসল run-এই kubectl জানাল, v1 Endpoints is deprecated in v1.33+. তবে আগে থেকে থাকা object এখনো দেখা যায়, যা পুরোনো tool-কে সময় দেয়। পুরোনো Endpoints object ১০০০ ঠিকানার পরে ছেঁটে ফেলা হয় বলেও ডকুমেন্টে জানা যায়। Slice-এর আরেকটি লাভ হলো IPv4 আর IPv6 আলাদা রাখা, আর প্রতিটি ঠিকানায় zone বা node-এর তথ্য রাখা, যা trafficDistribution-এ কাজে লাগে।
মূল উপলব্ধি: ছোট স্কেলের সরল নকশা বড় স্কেলে নিজেই সমস্যা হয়ে দাঁড়ায়। বদল যত ছোট, পাঠানো ডেটাও তত ছোট হওয়া উচিত; একটি object-কে ভাগ করাই এখানে সমাধান।
প্র ০৫kube-proxy কেন বারবার mode বদলাল? iptables-এর সীমা কোথায়, আর nftables বা eBPF কী বদলায়?
শুরুর দিকে kube-proxy সত্যিই একটি proxy ছিল: userspace-এ সংযোগ নিয়ে Pod-এ পাঠাত। প্রতিটি packet-কে kernel থেকে userspace-এ গিয়ে আবার ফিরতে হতো, যা ধীর। এই mode পরে পুরোপুরি সরানো হয়, Kubernetes ১.২৬ নাগাদ।
তারপর iptables mode default হলো। Packet kernel-এই থেকে গেল, গতি বাড়ল। কিন্তু iptables মূলত firewall-এর জন্য বানানো, যেখানে rule অল্প। প্রতিটি Service আর endpoint-এর জন্য rule জমতে থাকে। হিসাবের একটি অসুবিধা হলো, মিল খোঁজা তালিকা ধরে এগোয়, তাই গড়ে অনেক rule পার হতে হয়। আরেকটি হলো হালনাগাদ: পরিবর্তনে বড় rule-তালিকা ঘুরে লিখতে হয়। Service হাজারের ঘরে গেলে একবার sync করতেই চোখে পড়ার মতো সময় লাগে, আর সেই সময়টুকু নতুন Pod traffic পায় না বা মৃত Pod পেতে থাকে।
IPVS এলো hash table নিয়ে, Kubernetes ১.১১ নাগাদ GA. খোঁজা দ্রুত, আর least connection-এর মতো পদ্ধতিও আছে। কিন্তু এটি আলাদা kernel subsystem, আর iptables-এর সব আচরণ এতে হুবহু মেলানো কঠিন ছিল। nftables, iptables-এর আধুনিক উত্তরসূরি: map দিয়ে খোঁজা প্রায় ধ্রুব সময়ে হয়, আর ছোট বদল ছোট transaction-এ যায়। eBPF-ভিত্তিক পথ (যেমন Cilium) packet-এর আরও আগের ধাপে কাজ করে, কখনো conntrack-এর বোঝাও কমায়, তবে তা CNI-এর সাথে বাঁধা।
ছোট cluster-এ এই পার্থক্য প্রায় বোঝাই যায় না। আমাদের এখানে iptables-এর default-এই সব পরীক্ষা ঠিকঠাক চলেছে। সংখ্যা যা-ই বলুক, নিজের স্কেলে মেপে তবেই বদলানো উচিত, আর version অনুযায়ী mode-এর অবস্থা release note থেকে মিলিয়ে নেওয়া উচিত।
মূল উপলব্ধি: Concept একই থেকেছে: “virtual ঠিকানা থেকে একটি Pod.” বদলেছে শুধু কোন স্তরে সেই কাজ হয়। ভালো abstraction-এর লক্ষণ হলো, নিচের স্তর বদলালেও আপনার Service YAML একই থাকে।
অনুশীলন
নিজের মেশিনের local cluster-এ করলে কোনো বিল আসে না। Cloud-এর cluster-এ LoadBalancer-এর খরচ হতে পারে। শেষে নিজের namespace মুছুন: kubectl delete ns <আপনার-namespace>.
-
হিসাব (iptables): একটি Service-এর পেছনে ৪টি Ready Pod. iptables mode-এ প্রতিটি rule-এর probability কত? ৩ নম্বর Pod-এ পৌঁছানোর সম্ভাবনা কত? ১২০০টি নতুন সংযোগে তার প্রত্যাশিত ভাগ কত? আর সবগুলো সংযোগ যদি ১২০০-এর বদলে ৪টি keep-alive সংযোগ হয়, ফলাফল কী বদলাবে?
Rule-এর probability: ১/৪, ১/৩, ১/২, ১। ৩ নম্বরে পৌঁছাতে প্রথম দুটি পার হতে হবে, তারপর তৃতীয়টি ধরবে: (৩/৪) × (২/৩) × (১/২) = ১/৪। ১২০০ × ১/৪ = ৩০০টি সংযোগ।
৪টি keep-alive সংযোগে প্রতিটি নিজের Pod বেছে নেয়, আর পরে বদলায় না। সব ৪টি এক Pod-এ যাওয়াও সম্ভব। সম্ভাবনা ৪ × (১/৪)৪ = ১/৬৪। তাই গড় সমান হলেও একটি সময়ে ভাগ অনেক অসমান হতে পারে।
kube_proxy_pick.js-এPODSআরCONNECTIONSবদলে মিলিয়ে নিন। -
ফল আগেই বলুন: একটি Service-এর selector
app=web. শুরুতেapp=web-এর ৩টি Ready Pod (P1, P2, P3). আপনি এই ক্রমে কাজ করলেন: (১) P2-এর readiness ব্যর্থ করলেন, (২) P3-এর label বদলেapp=oldকরলেন, (৩) Deployment selector একই আছে, তাই সে একটি নতুন Pod P4 বানাল, যা Ready. এখনkubectl get endpointslices-এরENDPOINTSঘরে কয়টি ঠিকানা,ready: trueকয়টি, আর ৩০টি নতুন সংযোগ কোন Pod-এ যাবে?Label বদলে P3 selector থেকে বেরিয়ে গেছে, তাই তালিকায় P1, P2, P4: ৩টি ঠিকানা।
ready: true২টি (P1, P4), কারণ P2 তালিকায় থাকলেওready: false. ৩০টি সংযোগ যাবে P1 আর P4-এ, গড়ে ১৫টি করে। P3 চলতেই থাকবে, কিন্তু কোনো traffic পাবে না, আর ReplicaSet-এর হিসাবের বাইরে চলে গেছে। Debug-এর জন্য Pod আলাদা করার এটি একটি পরিচিত কৌশল, তবে পরে নিজ হাতে মুছতে হবে। -
নকশা ও সমালোচনা: একটি কাল্পনিক অনলাইন বাজার অ্যাপে AI এই পরিকল্পনা দিয়েছে: frontend, orders, payments আর PostgreSQL, চারটিতেই
type: LoadBalancer; orders-এর config-এ payments-এর Pod IP লেখা; orders আর payments দুটোরই Pod-এapp: backendlabel, আর Service-এর selector শুধুapp: backend; session Pod-এর memory-তে, তাইsessionAffinity: ClientIP. কী কী বদলাবেন?- ধরন: শুধু frontend বাইরের। অন্যগুলো ClusterIP. Database কখনোই public নয়। বাইরের পথে একটি Ingress বা Gateway রাখলে পর্ব ৪৬-এর মতো একটি load balancer-এই চলবে, খরচও কম।
- ঠিকানা: Pod IP সরিয়ে
paymentsবাpayments.shop.svc.cluster.localনাম। - Selector: Selector এক হলে দুটি Service-এই দুই app-এর Pod মিশে যাবে, আর orders-এর অর্ধেক অনুরোধ payments-এ যাবে। আলাদা label দিন, যেমন
app: orders,app: payments. - Session: Affinity একটি পট্টি। Pod মরলে session হারাবে, আর NAT-এর পেছনের সবাই এক Pod-এ জমবে। Session Redis-এর মতো বাইরের store-এ নিন।
- যাচাই: সব Service-এ EndpointSlice পড়ুন, আর পর্ব ৪৯-এর NetworkPolicy দিয়ে database-এর পথ orders আর payments-এ সীমিত রাখুন।
-
কমান্ড ও নির্ণয়: একজন সহকর্মী জানালেন,
curl http://reports/করলেCould not connect to serverআসছে।getent hosts reportsঠিকানা দিচ্ছে। Slice-এPORTS 9000,ENDPOINTS 10.1.0.21,10.1.0.22, দুটিইready: true. Pod-এর container শোনে 8000-এ। কারণ কী, কোন কমান্ডে প্রমাণ করবেন, আর কীভাবে ঠিক করবেন?DNS, selector আর readiness ঠিক আছে, কারণ ঠিকানা আছে আর ready. Slice-এর port 9000 বলছে Service-এর
targetPortভুল। Readiness probe সম্ভবত সরাসরি আসল port 8000 দেখছে, তাই Pod ready দেখাচ্ছে। প্রমাণ:# From a test Pod: the real port answers, the Service's port does not kubectl exec client -- curl -sS --max-time 3 http://10.1.0.21:8000/ kubectl exec client -- curl -sS --max-time 3 http://10.1.0.21:9000/ # Fix: point targetPort at the real port, then read the slice again kubectl patch svc reports --type=json \ -p '[{"op":"replace","path":"/spec/ports/0/targetPort","value":8000}]' kubectl get endpointslices -l kubernetes.io/service-name=reportsএই কমান্ডগুলো আপনার নিজের cluster-এর উদাহরণ, আউটপুট তাই দেখানো হয়নি। আরও ভালো হলো Pod-এর port-এ নাম দিয়ে
targetPort-এ সেই নাম ব্যবহার করা।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি Pod Service-এর নাম ধরে ডাকলে যা ঘটে, সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Resolver
resolv.confপড়ে search domain জুড়ে পুরো নাম বানায় - CoreDNS Service-এর ClusterIP ফেরত দেয়
- Pod ClusterIP:port-এ সংযোগ শুরু করে
- Node-এর rule একটি Ready Pod বেছে গন্তব্য বদলায়
- Pod-এর targetPort-এ অনুরোধ পৌঁছায়
একটি Service-এ port: 80, targetPort: 8080, nodePort: 31000 দেওয়া আছে। Cluster-এর ভেতরের একটি Pod ClusterIP দিয়ে কোন port-এ সংযোগ করবে?
- 8080
- 31000
- 80
- যেকোনো port
একটি Pod-এর readiness probe ব্যর্থ হচ্ছে, কিন্তু container চলছে। তখন কী ঘটে?
- kubelet container restart করে
- Pod চলতে থাকে, কিন্তু Service তাকে নতুন traffic পাঠায় না
- Deployment Pod মুছে নতুন Pod বানায়
- Service-এর ClusterIP বদলে যায়
একটি Pod-এর resolv.conf-এ ndots:5 আর ৩টি search domain আছে। Alpine-ভিত্তিক image-এ shop.example.com খুঁজলে আর কোনো নাম না মিললে A আর AAAA মিলিয়ে সর্বোচ্চ কতটি DNS প্রশ্ন যায়?
- ২
- ৪
- ৬
- ৮
একটি Service-এ curl ব্যর্থ। EndpointSlice-এ PORTS আর ENDPOINTS দুটোই <unset>. DNS ঠিকানা দিচ্ছে। সবচেয়ে সম্ভাব্য কারণ কোনটি?
- Service-এর selector কোনো Pod-এর label-এ মিলছে না
- CoreDNS বন্ধ হয়ে গেছে
- targetPort-এর সংখ্যা ভুল
- সব Pod-এর readiness ব্যর্থ
৩টি Ready Pod-এর পেছনে থাকা ClusterIP Service-এ একটি client একটি keep-alive সংযোগে ১০০টি HTTP অনুরোধ পাঠাল। iptables mode-এ সাধারণত কী হবে?
- প্রতিটি Pod প্রায় ৩৩টি করে অনুরোধ পাবে
- ১০০টি অনুরোধই একটি Pod-এ যাবে
- অনুরোধগুলো পালা করে Pod ১, ২, ৩-এ যাবে
- Connection reset হবে
একটি নিজের server-এর (bare-metal) cluster-এ type: LoadBalancer Service-এর EXTERNAL-IP অনেকক্ষণ <pending>, আর cloud controller বা MetalLB-র মতো কিছু চালানো নেই। কেন?
- Service-এর selector ভুল
- NodePort পরিসর শেষ হয়ে গেছে
- এই ঠিকানা দেওয়ার মতো কোনো controller চলছে না
- ClusterIP তৈরি হয়নি
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Pod নশ্বর, তার IP-ও। Service দেয় স্থির নাম আর স্থির ClusterIP.
- Selector ঠিক করে কে তালিকায়, readiness ঠিক করে কে traffic পাবে। পড়ুন
conditions.ready, শুধুENDPOINTSকলাম নয়। - নাম:
service.namespace.svc.cluster.local. বাইরের নামেndots:5বাড়তি প্রশ্ন আনে। - kube-proxy সংযোগ ভাগ করে, অনুরোধ নয়। NodePort-এর ওপর LoadBalancer, আর তার আসল কাজ করে provider.
- Debug ভেতর থেকে বাইরে: Service → DNS → selector → ready → port → Pod → ClusterIP → বাইরের পথ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৬ · Ingress আর Gateway API — বাইরের traffic ভেতরে আনাপরবর্তী পর্বএক দরজা দিয়ে অনেক Service.
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ১৮ · Cloud load balancerআগের ভিত্তিL4, L7, target group আর health check.
- পর্ব ১৩ · Server-এর network হাতিয়ারআগের ভিত্তিip, ss, dig আর curl দিয়ে সমস্যা খোঁজা।
- Computer Networks · Cloud ও Container Networkingগভীরে যানCNI, overlay, DNAT আর ফেরার পথের তত্ত্ব।
- System Design · Load Balancingপাশের ট্র্যাকAlgorithm আর L4-L7-এর নকশা।