M9 โ Advanced Kubernetes Internals & Production Patterns¶
Core question: Probes, QoS, DNS, graceful shutdown, HPA, RBAC, Ingress โ what really happens inside, and how do I debug it?
โฑ๏ธ Time: ~75 min padho + 40 min lab ยท ๐๏ธ Level: Advanced ยท ๐ Pehle chahiye: M4, M5, M8 (recall gate M8 ka p99-vs-average poochta hai)
Is module ke baad tum kar paoge: -
kubectl applyke saaton steps trace karo โ apiserver se etcd, scheduler, kubelet tak - Liveness, readiness, aur startup probe ke alag fail-actions explain karo aur liveness footgun se bacho - Ingress Host-header routing debug karo, HPA formula apply karo, aur RBAC four-object model implement karo
MODULE MAP
00-INDEX ยท 01-M0-foundations ยท 02-M1-terraform ยท 03-M2-ansible ยท 04-M3-docker ยท 05-M4-kubernetes-core ยท 06-M5-sizing-and-cost ยท 07-M6-cicd ยท 08-M7-gitops ยท 09-connected-system ยท 10-M8-observability-sre ยท 11-M9-advanced-k8s-internals ยท 12-capstone-url-shortener ยท 13-capstone-microshop ยท 14-interview-bank ยท 15-roadmap ยท 16-reference-appendix
M4 (05-M4-kubernetes-core) taught you the vocabulary: pods, Deployments, Services, ReplicaSets. This chapter goes under the hood โ the internal machinery every senior engineer and every technical interviewer expects you to own. Concepts are production-grade; debug commands are real; Hinglish intuition anchors are for the brain, not the exam.
โฉ๏ธ Recall gate โ shuru karne se pehle¶
Pichhle modules se 3 sawaal. Pehle memory se jawab do, phir kholo. (Yeh retrieve karna hi lifetime yaad rakhta hai โ dobara padhna nahi.)
- (M4) Kubernetes mein Service ka kya role hai โ pod delete hone ke baad bhi traffic kyun nahi rukti?
- (M5) Ek pod ka
requests.memory: 128Miaurlimits.memory: 256Miset hai โ yeh pod kaunsi QoS class mein aayega aur memory pressure pe kaunse pod ke baad evict hoga?- (M8) "p99 latency = 2s" aur "average latency = 200ms" โ dono mein se interviewer ko kaun sa metric zyada batata hai, aur kyun?
Jawab
- Service = fixed ClusterIP + DNS name; selector se Ready pods ko traffic route hota hai. Pod IP change ho ya pod delete ho โ Service name same rehta, kube-proxy new pod ko EndpointSlice mein add karta hai. 2. Burstable (requests < limits). Guaranteed ke baad, BestEffort se pehle evict hoga. 3. p99 โ average mein outliers chhup jaate hain; p99 batata hai worst 1% users ki experience jo sabse zyada feel karte hain.
The 60-second version¶
Six machines (apiserver, etcd, scheduler, controller-manager, kubelet, kube-proxy) run independent reconciliation loops watching the same shared source of truth (etcd). None waits for a boss โ each checks desired vs actual and acts. kubectl apply is just you writing a new "desired" into that truth. Every component responds within milliseconds.
Traffic reaches a pod only when it passes a readiness probe. When you pull the plug on a pod, Kubernetes removes it from the load-balancer slice and sends it SIGTERM โ but these two events race, so a preStop: sleep is the production-grade fix. QoS classes decide which pod dies first when RAM runs out. HPA adds replicas using a ceiling-division formula, but refuses to remove them for five minutes to avoid flapping. RBAC constrains what each identity inside the cluster can touch. Ingress translates Host headers to backend Services. StatefulSets give each replica a persistent disk that survives pod restarts.
That is the full surface. Read on for the internals.
A. Control Plane & the kubectl apply Journey¶
The five actors (plus two)¶
| Actor | Node | Responsibility | Port |
|---|---|---|---|
| kube-apiserver | Control-plane | Every request gateway โ kubectl, components, kubelet all talk only to this | 6443 |
| etcd | Control-plane | Distributed key-value store; cluster's only persistent truth | 2379 (client), 2380 (peer) |
| kube-scheduler | Control-plane | Assigns unscheduled pods to nodes (resources, taints, affinity) | โ |
| kube-controller-manager | Control-plane | Runs all reconciliation loops (Deployment, ReplicaSet, Node, EndpointSlice, โฆ) | โ |
| kubelet | Every node | Reads pod specs; tells containerd to start/stop containers; reports status back | 10250 |
| kube-proxy | Every node | Programs iptables / IPVS rules so Service IPs route to real pod IPs | โ |
| containerd | Every node | The actual container runtime (pulls images, runs Linux namespaces/cgroups) | โ |
Rule: Only apiserver touches etcd directly. All other components talk to apiserver, which reads/writes etcd. Break this rule mentally and you will misdiagnose cluster failures.
๐ฎ๐ณ Hinglish intuition: Office analogy โ apiserver = reception desk (sab isse baat karte), etcd = company ki register (sach yahaan likha hai), scheduler = seating manager (kaun kahan baithega), controller-manager = chowkidar (har cheez check karta), kubelet = floor worker (actual kaam karta), kube-proxy = trafficwala (call forward karta).
The seven-step kubectl apply journey¶
You Control Plane Worker Node
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1. kubectl apply โโโบ apiserver validates YAML
โ
2. apiserver writes Deployment โ etcd
โ
3. Deployment controller watches etcd
sees "need ReplicaSet" โ creates it โ etcd
โ
4. ReplicaSet controller watches
sees "need 2 pods, have 0" โ creates Pod objects โ etcd
โ
5. Scheduler watches etcd
sees 2 unscheduled pods โ assigns each to a worker node
writes nodeName onto Pod object โ etcd
โ
6. kubelet (worker) watches apiserver
sees pod assigned to it
calls containerd โ pulls image โ starts container
โ
7. kubelet writes status (Running/Ready) โ apiserver โ etcd
Each arrow is a watch (long-poll/gRPC stream from etcd), not a cron poll. Events are pushed; response is sub-second end-to-end on healthy clusters.
sequenceDiagram
participant KU as kubectl
participant API as APIserver
participant E as etcd
participant DC as DeployCtrl
participant RC as RSCtrl
participant SC as Scheduler
participant KL as kubelet
KU->>API: apply manifest
API->>E: validate + write Deployment
Note over API: watch fires
DC->>API: create ReplicaSet
API->>E: write ReplicaSet
Note over API: watch fires
RC->>API: create Pods (nodeName empty)
API->>E: write Pods
Note over API: watch fires
SC->>API: patch nodeName on each Pod
API->>E: write updated Pods
Note over API: watch fires
KL->>API: pulls image + starts container
KL->>API: status Ready
koi kisi ko seedha call nahi karta โ sab list-watch se apiserver ke through.
etcd internals: Raft quorum, SPOF, backup¶
Raft consensus requires a quorum: majority of nodes must agree before a write commits. With 3 etcd nodes, 2 must be alive (survives 1 failure). With 5, survives 2. The rule: always deploy odd numbers (1/3/5) โ even numbers buy zero extra fault tolerance.
3-node etcd (port :2380 peer, :2379 client)
[etcd-1] โโโโโ Raft โโโโโบ [etcd-2]
โโโโโโโโโ Raft โโโโโโโโโโบ [etcd-3]
Write: leader gets request โ replicates to follower โ majority ACK โ commit
Read (linearizable): always goes to leader
Single control-plane = SPOF. The capstone runs one control-plane node. Production clusters run 3 or 5. etcd backup:
etcdctl snapshot save /backup/snap.dbโ treat it like Terraform state (lose it = cluster amnesia).
The restore drill โ an untested backup is not a backup¶
Taking the snapshot is the easy half. The half that actually saves you is the one nobody rehearses:
export ETCDCTL_API=3
CERTS="--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key"
# 1. BACKUP (cron this โ and ship it OFF the node)
etcdctl snapshot save /backup/etcd-$(date +%F).db --endpoints=https://127.0.0.1:2379 $CERTS
etcdctl snapshot status /backup/etcd-$(date +%F).db --write-out=table # verify it is readable
# 2. RESTORE (disaster) โ restores into a NEW data dir, it does not overwrite in place
sudo mv /etc/kubernetes/manifests /etc/kubernetes/manifests.bak # stop static pods (apiserver+etcd)
sudo etcdctl snapshot restore /backup/etcd-2026-07-17.db \
--data-dir=/var/lib/etcd-restored
# 3. point etcd at the restored dir, then bring the control plane back
sudo sed -i 's#/var/lib/etcd#/var/lib/etcd-restored#' /etc/kubernetes/manifests.bak/etcd.yaml
sudo mv /etc/kubernetes/manifests.bak /etc/kubernetes/manifests # kubelet restarts the static pods
# 4. VERIFY (this is the step that proves the backup was real)
kubectl get nodes && kubectl get pods -A
The three things that make backups fail when you need them
- Never restored it. A snapshot you have never replayed is a hypothesis, not a backup. Rehearse quarterly on a throwaway cluster.
- Stored on the same disk/node. If the node dies (or the disk fills โ see ch30's disk-full incident), the backup dies with it. Ship snapshots to S3.
- Snapshot โ whole cluster. etcd holds K8s objects. It does not hold your PersistentVolume data โ that needs its own backup (Velero / volume snapshots). Restoring etcd gives you back the PVC object pointing at a volume whose contents you never backed up.
๐ฎ๐ณ Ek line:
snapshot saveaasaan hissa hai. Asli sawaal โ "kya tumne kabhi restore karke dekha?" Aur yaad rakho: etcd = objects, PV data alag (Velero chahiye). Untested backup = backup nahi.
Debug commands โ control plane:
# Are control-plane components alive? (static pods in /etc/kubernetes/manifests)
kubectl get pods -n kube-system
# Node-level: is kubelet running?
systemctl status kubelet
# Node-level: what containers is containerd running (bypasses apiserver)?
crictl ps
# etcd health
etcdctl endpoint health --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/peer.crt \
--key=/etc/kubernetes/pki/etcd/peer.key
Aโฒ. The full wiring โ who talks to whom (and how)¶
Section A told you what each component does. This one is the part that separates "K8s pata hai" from "K8s production mein chalaya hai": how they actually connect, who gives the request, who takes it, on which port, over which protocol, and where it breaks at 2 a.m.
One analogy that makes ALL of it click: the restaurant ๐ฝ๏ธ¶
Picture the cluster as one big, busy restaurant. Every component is a role, and โ crucially โ everyone follows one communication rule.
| Component | Restaurant role | Its ONE job | How it talks |
|---|---|---|---|
| etcd | The order-book locked in the back office | Hold the single truth; nightly photocopy = backup | Only the Manager may open it |
| kube-apiserver | The Manager at the pass | The only one who opens the order-book; everyone speaks to them | Checks your ID โ permissions โ house-rules, then writes |
| watch | The live order-screen (KDS) everyone stares at | Push new tickets instantly | Manager pushes; no one keeps asking "koi naya order?" |
| kube-scheduler | The seating host | Seat a new party (pod) at the best table (node) | Reads the screen โ tells Manager "table = node-2" |
| kube-controller-manager | The shift supervisors | "Reality = order-book" โ 3 chefs on the book, only 2 present โ hire one | File requests with the Manager |
| kubelet | The station chef on each branch | Actually cook the tickets for my station; report ready/burnt | Watches the screen; reports to Manager |
| kube-proxy | The floor signage & runners | "Pasta counter" always reaches an open pasta table | Wires the arrows from the screen |
| Pod | A table setting / the dish | Serve, then get cleared & reset โ never repaired | Ephemeral; replaced, not fixed |
๐ฎ๐ณ Yeh sentence poora control plane unlock karta hai: "Restaurant mein har koi sirf Manager se baat karta hai โ kabhi seedha order-book se, kabhi ek doosre se nahi. Manager sabki ID check karta, phir order-book mein likhta. Aur ek ulti call bhi hai: jab customer bole 'us station pe abhi kya pak raha dikhao', to Manager khud us chef ke paas jaata hai." โ wahi apiserver โ kubelet wali
kubectl logscall hai.
The golden rule of direction: kaun client, kaun server¶
99% confusion isse clear hota hai โ kaun connection shuru karta hai:
- Almost everyone is a CLIENT of the apiserver. Scheduler, controller-manager, kubelet, kube-proxy, CoreDNS โ sab apiserver ko outbound connect karte hain (watch + write ke liye). Wo apiserver ko dhoondte hain, apiserver unhe nahi.
- Sirf apiserver โ etcd โ order-book ka darwaza sirf Manager kholta.
- Do REVERSE calls jahan apiserver khud client banta: apiserver โ kubelet :10250 (
logs/exec/port-forward/metrics) aur apiserver โ admission webhooks.
Happy path โ sab APIserver KI TARAF bolte:
flowchart LR
KCU["kubectl"]:::ext --> API["APIserver<br/>:6443"]:::api
SC["Scheduler"]:::ctl --> API
CM["ControllerMgr"]:::ctl --> API
KL["kubelet"]:::run --> API
KP["kube-proxy"]:::net --> API
API --> ETCD[("etcd")]:::store
classDef ext fill:#e8eaf6,stroke:#3f51b5,color:#1a237e;
classDef api fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100;
classDef ctl fill:#ede7f6,stroke:#5e35b1,color:#311b92;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef net fill:#f3e5f5,stroke:#8e24aa,color:#4a148c;
Star topology โ har component ka ek hi kaam: APIserver ko request karo.
Reverse calls โ APIserver BAHAR call karta:
flowchart LR
API2["APIserver"]:::api -.->|"exec / logs / port-forward"| KLR["kubelet<br/>:10250"]:::run
classDef api fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
Sirf yeh ek ulta teer โ jab tum kubectl exec ya kubectl logs chalate ho, APIserver khud kubelet ko call karta hai.
The connection matrix (give request / take request)¶
| Client (request deta) | โ Server (leta) | Port | Protocol | Kya maangta | Auth |
|---|---|---|---|---|---|
kubectl / apps |
apiserver | 6443 | HTTPS/REST | CRUD + watch objects | kubeconfig cert / SA token (mTLS) |
| apiserver | etcd | 2379 | gRPC | read/write state | etcd client cert (mTLS) |
| etcd | etcd peers | 2380 | gRPC | Raft consensus | peer certs |
| scheduler | apiserver | 6443 | HTTPS (watch) | unscheduled pods โ POST Binding | client cert |
| controller-manager | apiserver | 6443 | HTTPS (watch) | reconcile read/write | client cert |
| kubelet | apiserver | 6443 | HTTPS (watch) | "mere node ke pods" โ POST status | node cert system:node:<name> |
| kube-proxy | apiserver | 6443 | HTTPS (watch) | Services + EndpointSlices | SA token |
| apiserver | kubelet | 10250 | HTTPS | logs/exec/metrics |
apiserver client cert |
| kubelet | containerd | unix sock | gRPC (CRI) | pull image ยท start/stop | local socket perms |
How they actually "talk" โ the live order-screen (watch)¶
Components apiserver ko poll nahi karte ("kuch naya? kuch naya?" โ apiserver mar jaayega). Instead โ list-watch:
sequenceDiagram
participant C as controller/kubelet
participant A as apiserver
participant E as etcd
C->>A: LIST pods (all + resourceVersion)
A->>E: read
A-->>C: all pods + RV=1050
C->>A: WATCH pods since RV=1050 (long-lived stream)
Note over A,C: connection OPEN (HTTP2 / chunked)
E-->>A: pod changed โ RV=1051
A-->>C: push event: MODIFIED pod
Note over C: local cache (informer) โ work-queue โ reconcile
- LIST = ek baar pura snapshot + ek
resourceVersion(yeh etcd ka revision number hai). - WATCH = us RV se aage har change (ADDED/MODIFIED/DELETED) ek open connection pe push โ polling nahi.
- Client ise informer + local cache + work-queue mein rakhta โ har controller ke paas apni in-memory copy (apiserver pe load kam).
- Watch peechhe reh gaya (
410 Gone โ too old resource version)? Client dobara re-LIST karta. Yeh level-triggered hai โ controller current desired state pe react karta, missed events se nahi tootta.
๐ฎ๐ณ Bada insight: apiserver stateless hai โ saara sach etcd mein. Isliye apiserver ki latency โ etcd ki disk latency. Slow etcd disk = poora cluster slow. (
etcd_disk_wal_fsync_duration_secondswatch karo.)
Har hop mTLS hai โ aur yahan sabse bada production gotcha¶
Upar ka har arrow TLS (aksar mutual TLS) pe โ dono side certificate se verify. kubeadm mein certs /etc/kubernetes/pki/, kubeconfigs /etc/kubernetes/*.conf.
The #1 self-managed cluster outage every 2โ3 yr engineer eventually hits
kubeadm ke certs sirf 365 din valid โ par kubeadm upgrade apply inhe auto-renew kar deta hai (kubeadm 1.15+). Asli khatra un clusters pe hai jo kabhi upgrade nahi hue โ ek hi version pe saal bhar chale, cert expire โ kubelet โ apiserver trust khatam โ nodes NotReady, kubectl dead, cluster "achanak" tut gaya. Managed K8s (EKS/GKE) isse handle karta; self-managed pe tumhara dard hai โ isliye regular upgrades (ya kubeadm certs renew) discipline chahiye.
Identity ka role: kubelet โ apiserver node-identity system:node:<name> (group system:nodes) se โ Node Authorizer + NodeRestriction ensure karta ek node sirf apne pods dekhe (blast-radius chhota). Pods โ apiserver projected ServiceAccount JWT (/var/run/secrets/kubernetes.io/serviceaccount/token) se, kubernetes.default.svc pe.
Node ke andar ki plumbing (station chef ke tools)¶
kubelet do "neeche" wale layers se local unix-socket gRPC pe baat karta (network nahi):
kubelet โโCRI (gRPC ยท /run/containerd/containerd.sock)โโโถ containerd โโโถ runc (container start)
kubelet โโCNI (binary: /opt/cni/bin/<plugin>)โโโโโโโโโโโถ Calico/Cilium (pod ko IP milta)
kubelet โโCSI (gRPC ยท unix socket)โโโโโโโโโโโโโโโโโโโโโโถ EBS/EFS driver (volume attach+mount)
CRI = kubeletโcontainerd ka contract ยท CNI = container ban-ne ke baad pod ko IP + routing ยท CSI = storage attach.
Production connection gotchas โ jo actually debug karni padti hain¶
| Symptom | Asli wajah (connection) | Check |
|---|---|---|
kubectl hang / cluster frozen |
apiserver down ya etcd quorum lost | crictl ps, etcd health, apiserver logs |
| Nodes NotReady | kubelet โ apiserver :6443 block (network/cert) | node se curl -k https://<api>:6443 ยท journalctl -u kubelet |
kubectl logs/exec hang |
apiserver โ kubelet :10250 block (SG/firewall) | control-plane โ nodes :10250 rule kholo |
| Sab slow (p99 spike) | etcd disk latency | fast SSD ยท etcd_disk_wal_fsync_duration |
| Cluster "achanak" tut gaya (~1 saal) | certs expire | kubeadm certs check-expiration |
| Pods Pending | scheduler down / no fitting node | kubectl describe pod (events) |
| Pod-to-pod / DNS fail | CNI / CoreDNS / SG (overlay port) | Calico IPIP=proto 4 ยท BGP :179 ยท CNI pods |
๐ฎ๐ณ AWS self-managed pe firewall/SG rule (interview classic): nodes โ apiserver 6443, apiserver โ nodes 10250, etcd 2379-2380 (control-plane aapas mein), CNI overlay (Calico IPIP / BGP 179). Ek rule missing = "cluster half-working" โ wahi 2 baje wala incident.
โก 20-second recall: Sab apiserver ke client ยท sirf apiserver โ etcd (mTLS gRPC 2379) ยท talk = LIST+WATCH (push, not poll) ยท resourceVersion = etcd revision ยท ulti call apiserver โ kubelet 10250 (logs/exec) ยท har hop mTLS ยท kubeadm certs 365 din ยท etcd slow = sab slow, etcd = backup #1.
B. Networking Internals¶
Before the pieces (CNI, Service, kube-proxy, CoreDNS) โ here's the whole journey as one story, so the pieces below click as parts of a single path instead of isolated facts.
๐๏ธ The unifying analogy: a city with a postal system¶
You never mail a person's home address (a pod IP) โ it changes when they move. You mail the PO Box; a directory tells you the box number; a clerk's rules forward it to whoever's on shift; and the roads carry it there.
| K8s thing | Postal role | The point |
|---|---|---|
| Pod IP | Home address | Ephemeral โ never address it directly |
| Service (ClusterIP) | PO Box + phone number | Stable; forwards to on-shift staff |
| CoreDNS | Directory (411) | Service name โ the ClusterIP |
| EndpointSlice | Staff roster | Which pods are ready right now |
| kube-proxy | Mailroom clerk | Writes the forwarding rules (once) |
| iptables / IPVS | Sorting-machine rules (kernel) | Do the per-packet DNAT |
| CNI | Roads + postal network | Connect every home so any can reach any |
| Ingress | City front gate / receptionist | Route outside visitors by host/path |
One-sentence unlock: A pod calls a Service name โ CoreDNS returns the stable ClusterIP โ kube-proxy's iptables/IPVS rules DNAT it to a ready pod IP from the EndpointSlice โ the CNI carries the packet across nodes.
The complete journey โ external user to pod¶
flowchart LR
U(["๐ User"]):::ext
PDNS["Public DNS<br/>(Route 53)"]:::net
LB["Cloud LB<br/>(ALB/NLB)"]:::net
ING["Ingress controller<br/>nginx pod ยท TLS + L7"]:::run
CDNS["CoreDNS<br/>(internal directory)"]:::net
SVC["Service<br/>ClusterIP (PO Box)"]:::net
EP[("EndpointSlice<br/>ready pods")]:::store
KUBE["kube-proxy rules<br/>iptables/IPVS DNAT"]:::net
POD{{"orders-api pod"}}:::run
U -->|"1 ยท resolve shopfast.com"| PDNS --> U
U -->|"2 ยท HTTPS"| LB -->|"3 ยท node:NodePort"| ING
ING -.->|"4 ยท name โ ClusterIP"| CDNS
ING -->|"5 ยท to Service"| SVC
SVC --> KUBE
KUBE -. "picks from" .-> EP
KUBE -->|"6 ยท DNAT โ pod IP (CNI carries it)"| POD
classDef ext fill:#e8eaf6,stroke:#3f51b5,color:#1a237e;
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100;
External users hit public DNS โ cloud LB โ Ingress (TLS + host/path); from the Service onward it's identical to any internal pod-to-pod call.
๐ฎ๐ณ Golden debug reflex: "Service pe traffic nahi aa raha" โ hamesha pehle
kubectl get endpointslicesdekho. Khaali = koi ready pod nahi (readiness fail ya selector mismatch). Yahi ~80% "Service down" incidents hai. โ 502/503 = no ready backends.
Now the pieces, in detail:
CNI (Container Network Interface) โ cross-node pod routing¶
Problem: Pod-A is on worker-1 (10.0.1.10). Pod-B is on worker-2 (10.0.1.11). Their pod IPs (192.168.x.x) are inside the same flat /16 CIDR โ but those IPs do not exist on the physical network between nodes. How does a packet from Pod-A reach Pod-B?
Answer: The CNI plugin (Calico is common) installs kernel routes on each node so that each node knows "packets for pod-CIDR of worker-2 go out eth0 toward 10.0.1.11". Three main modes:
| Mode | Mechanism | Tradeoff |
|---|---|---|
| BGP (Calico native) | Real kernel routes via BGP advertisements โ no encapsulation | Fastest; requires L2/L3 reachability between nodes |
| Overlay (IPIP / VXLAN) | Wraps pod packet inside a host packet โ works on any underlay | ~5โ10% throughput overhead; universal compatibility |
| eBPF dataplane (Calico/Cilium) | Replaces kube-proxy entirely; kernel programs via eBPF hooks | Lowest latency; best observability; newer, needs kernel โฅ5.3 |
Without CNI:
kubeadm initcompletes but CoreDNS pods stayPendingand nodes stayNotReady. Fix:kubectl apply -f calico.yamlimmediately after init.
๐ฎ๐ณ Hinglish intuition: CNI = inter-city highway. Har pod ek ghar hai. Bina highway ke sirf apne mohalle (node) mein jaa sakte ho. CNI highways banata hai node se node tak โ har ghar se har ghar seedha.
The postal analogy โ VXLAN vs BGP, step by step¶
The mode table above is precise but terse. Here is the same thing in a way that sticks โ because where each mode applies is what trips people up.
Set the scene (three separate networks โ never confuse these):
1. Node network 192.168.1.x โ real machines (physical)
2. Pod network 10.244.x.x โ pods (virtual) โ the hard part
3. Service network 10.96.x.x โ services (virtual)
Each node owns a slice of the pod network, so the IP itself tells you the node:
Node A โ 10.244.0.0/24 (a pod here is 10.244.0.x)
Node B โ 10.244.1.0/24 (a pod here is 10.244.1.x)
โโ the middle octet = which node
The core problem: a pod's IP (10.244.1.8) only means something inside Kubernetes. The physical network between nodes only understands node IPs (192.168.1.x). So how does a pod-to-pod packet cross that gap?
Restaurant / postal analogy:
Pod-1 writes a letter to Pod-2 (inner address: 10.244.1.8)
โ
But the postman only knows BUILDING addresses (192.168.x)
โ
CNI = the receptionist. Two ways it can help:
| ๐ฆ VXLAN (overlay) | ๐บ๏ธ BGP (routing) | |
|---|---|---|
| What the receptionist does | puts the letter inside a bigger envelope addressed "Building B" | hands the postman a map once, so they route it directly |
| Applied where | on every packet, at runtime (kernel wraps it) | in each node's routing table, once (routes shared, then nothing per-packet) |
| Extra header | yes (~50 bytes/packet) | none |
| Needs from the network | nothing โ works on any underlay | the network must be able to route pod IPs |
| Speed | slightly slower (wrap/unwrap) | faster |
| Pick it when | cloud / mixed / "just make it work" | performance-critical, you control the network |
| Real examples | Flannel, Calico-VXLAN | Calico-BGP, Cilium |
Where does the CNI itself run? On every node, as a DaemonSet (ch30 ยท DaemonSet) โ "one pod per node." That agent (a) programs the node's routing table and (b) does the wrap/unwrap (VXLAN) or pure routing (BGP). See it in any cluster:
The cross-node packet journey (VXLAN):
โ Pod-1 (10.244.0.5, Node A) โ packet "to: 10.244.1.8"
โก out of the pod via its veth pair to Node A
โข Node A routing table: "10.244.1.x lives on Node B (192.168.1.20)"
โฃ CNI WRAPS it: [ outer: nodeAโnodeB (192.168.x) [ inner: pod1โpod2 (10.244.x) ] ]
โค physical network carries it (sees only node IPs)
โฅ Node B UNWRAPS โ recovers the original pod packet
โฆ Node B: "10.244.1.8 is my local pod" โ veth โ Pod-2
With BGP there is no step โฃ/โฅ โ the routes were shared ahead of time, so the packet travels as-is, the network already knowing where 10.244.1.x lives.
VXLAN overlay โ har packet ko lifafe mein band karo:
flowchart LR
P1["Pod1 eth0"]:::run --> VT1["veth"]:::net --> BR1["bridge"]:::net --> ENC["ENCAP<br/>outer header added"]:::store --> NI1["NIC NodeA"]:::net --> SW1["switch<br/>sees only node IPs"]:::ext --> NI2["NIC NodeB"]:::net --> DEC["DECAP<br/>header removed"]:::store --> BR2["bridge"]:::net --> VT2["veth"]:::net --> P2["Pod2 eth0"]:::run
classDef ext fill:#e8eaf6,stroke:#3f51b5,color:#1a237e;
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100;
VXLAN = 2 extra steps (ENCAP + DECAP har packet pe) โ MTU cost aur CPU overhead.
BGP / Calico โ routes pehle se set, packet seedha jaata:
flowchart LR
P3["Pod1 eth0"]:::run --> VT3["veth"]:::net --> BR3["bridge"]:::net --> RT["route table lookup<br/>10.244.1.x via NodeB"]:::api --> NI3["NIC NodeA"]:::net --> SW2["switch<br/>sees only node IPs"]:::ext --> NI4["NIC NodeB"]:::net --> BR4["bridge"]:::net --> VT4["veth"]:::net --> P4["Pod2 eth0"]:::run
classDef ext fill:#e8eaf6,stroke:#3f51b5,color:#1a237e;
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef api fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
VXLAN = 2 extra steps (encap/decap, MTU/CPU cost) โ BGP unhe hata deta.
๐ฎ๐ณ Ek line: VXLAN har packet ko lifafe mein wrap karta (network ko pod IP jaanne ki zaroorat nahi โ kahin bhi chalta, thoda slow). BGP nodes ke beech routes share karta (network khud pod IP tak pahunchata โ fast, par network support chahiye). Dono har node ke CNI agent (DaemonSet) chalate hain.
โ ๏ธ Your projects: VANTA runs Flannel (VXLAN overlay) โ portable, runs anywhere, but see the MTU and NetworkPolicy gotchas below (both bite Flannel specifically). Managed EKS often uses VPC-native routing โ AWS's own network knows the pod IPs, so no wrapping is needed at all.
Your two projects picked two different CNIs โ and it was the right call each time¶
This is the clearest possible illustration of why CNI choice matters, because your own repos made opposite choices for opposite reasons:
| ๐ ฐ๏ธ VANTA-Boutique | ๐ ฑ๏ธ billfree-techops | |
|---|---|---|
| CNI | Flannel (VXLAN overlay) | Calico v3.28.0 |
| Installed by | Ansible playbook (ansible/playbook.yml) |
cloud-init (kubectl apply -f calico.yaml at boot) |
| NetworkPolicy | โ silently ignored | โ enforced |
| Why this CNI | just needs pods to talk โ simple, portable | needs security segmentation between services |
The deciding factor is NetworkPolicy โ and it is not optional trivia for billfree. billfree ships real network firewall rules in deploy/platform/networkpolicies.yaml:
kind: NetworkPolicy
name: default-deny-ingress # nobody may talk to anyoneโฆ
kind: NetworkPolicy
name: allow-intra-namespace # โฆexcept within the same namespaceโฆ
kind: NetworkPolicy
name: allow-ingress-controller # โฆand inbound only via the ingress controller
This is a zero-trust posture: deny everything, then allow the minimum. It is exactly what a payments platform needs โ if analytics is ever compromised, it still cannot reach auth's pods, because the NetworkPolicy blocks it at the CNI layer.
Now the punchline: had billfree used Flannel (like VANTA), those three policies would be silently ignored. default-deny-ingress would be written, reviewed, and merged โ and every pod would still be wide open. A firewall that isn't enforcing is worse than no firewall, because you believe you're protected. billfree chose Calico precisely so its NetworkPolicies actually mean something.
๐ฎ๐ณ Ek line โ dono projects: VANTA ko sirf connectivity chahiye thi โ Flannel kaafi (simple, portable). billfree ko connectivity + firewall (NetworkPolicy) chahiye thi โ Calico zaroori, kyunki Flannel NetworkPolicy enforce hi nahi karta. Same problem, alag zaroorat, alag CNI โ aur dono sahi.
โญ Interview answer: "CNI ka choice zaroorat pe depend karta. Sirf pods connect karne hain โ Flannel (simple, kahin bhi). NetworkPolicy / security segmentation chahiye โ Calico ya Cilium โ kyunki Flannel NetworkPolicy implement hi nahi karta, apply karo to silently ignore hoti hai. Mere do projects mein exactly yahi split hai: VANTA=Flannel (connectivity), billfree=Calico (zero-trust NetworkPolicy)."
๐ก Same-node is different: two pods on the same node never leave it โ
Pod-A โ veth โ node bridge โ veth โ Pod-B, pure in-kernel, no wrap. Encapsulation only happens cross-node. That's why same-node calls are a touch faster.
How a pod actually gets its IP¶
The apartment-building analogy:
| Real world | K8s equivalent |
|---|---|
| Building | Node โ the physical or virtual machine running many pods |
| Apartment | Pod โ each pod gets its own network namespace (netns): its own eth0, its own routing table, isolated from every other pod on the same node |
| Two-ended pipe connecting flat to hallway | veth pair โ a virtual cable: one end (eth0) lives inside the pod's netns; the other end plugs into the node bridge (cni0 / cbr0) |
| Building manager handing out apartment numbers | IPAM (IP Address Management) โ the CNI plugin allocates a unique IP from this node's slice of the pod CIDR (e.g. node-1 owns 10.244.1.0/24, node-2 owns 10.244.2.0/24) |
The assignment flow โ what happens between kubectl apply and eth0 existing inside the pod:
flowchart LR
KL["kubelet"]:::run
PAUSE["pause container<br/>(holds the netns)"]:::run
CNI["CNI plugin<br/>(/opt/cni/bin/)"]:::net
IPAM["IPAM<br/>(allocate from<br/>node CIDR slice)"]:::store
VETH["veth pair<br/>(eth0 in pod<br/>peer on node bridge)"]:::net
READY["Pod has eth0<br/>and a routable IP"]:::run
KL -->|"1 ยท create pause container"| PAUSE
PAUSE -->|"2 ยท netns ready"| CNI
CNI -->|"3 ยท allocate IP"| IPAM
IPAM -->|"4 ยท IP returned"| VETH
VETH -->|"5 ยท configure IP and routes"| READY
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92;
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100;
kubelet โ pause container (holds the netns) โ CNI ADD โ IPAM (allocate from node's pod CIDR slice) โ veth pair + IP + routes โ pod has eth0.
Step by step:
- kubelet creates the pause (infra) container first. Its only job: hold the pod's network namespace open. All application containers in the pod then join this same namespace โ that is why every container in a pod shares the same IP and
localhost. - kubelet calls the CNI plugin binary (
/opt/cni/bin/calico,flannel,cilium, โฆ) with a JSONCNI ADDrequest. - The plugin calls its IPAM backend (host-local, Calico's etcd, etc.), which returns a free IP from the node's allocated CIDR slice.
- The plugin creates a veth pair:
eth0inside the pod's netns and a peer interface (e.g.cali<hash>orveth<hash>) attached to the node bridge (cni0/cbr0). The IP and default route are written into the pod's netns. - The pod can now send and receive packets.
Same-node vs cross-node (cross-link):
- Same-node:
Pod-A eth0โ veth โ node bridge โ veth โPod-B eth0. Pure in-kernel bridging; no encapsulation, full MTU available. - Cross-node: packet exits the bridge and hits the CNI's cross-node routing โ overlay (VXLAN/IPIP) or BGP, as described in the mode table earlier in this section.
NetworkPolicy is enforced by the CNI plugin: Calico and Cilium install kernel hooks (eBPF or iptables) at the veth interface to enforce NetworkPolicy rules. Flannel does not implement NetworkPolicy โ if you apply NetworkPolicy objects on a Flannel cluster, they are silently ignored and all pod traffic remains permitted.
MTU mismatch โ big payloads hang; health checks pass; TLS handshakes stall
Overlay encapsulation (VXLAN or IPIP) adds ~50 bytes of header to every packet. If the pod eth0 MTU stays at the default 1500, any pod packet near the 1500-byte mark becomes too large once the overlay header is prepended โ it is silently dropped or fragmented at the underlay.
The deceptive pattern: small HTTP requests and DNS (small UDP) work perfectly. Large REST responses, TLS ClientHello + certificate exchange, and gRPC streams intermittently stall. Everything appears fine until traffic carries real payloads or is tested under load.
# Inside any pod โ check eth0 MTU
kubectl exec -it <pod> -- ip link show eth0
# healthy on VXLAN cluster: "mtu 1450" โ NOT 1500
# mtu 1500 on an overlay cluster โ MTU mismatch confirmed
Fix: configure the CNI MTU to hostMTU โ overhead:
- Calico VXLAN โ 1450 ยท Calico IPIP โ 1480 ยท Cilium VXLAN โ 1450
- Calico: vethMTU in FelixConfiguration; Flannel: Backend.MTU in the kube-flannel-cfg ConfigMap
MTU mismatch is the classic "works on dev, breaks under real load" cluster bug. Always check ip link inside a pod when TLS handshakes or large payloads are unreliable while small requests succeed.
Service โ EndpointSlice โ pod (with kube-proxy)¶
kubectl apply Service (selector: app=api)
โ
โผ
EndpointSlice controller watches:
"which pods match selector AND are Ready?"
โ
โผ
EndpointSlice object: [ pod-A:8080, pod-C:8080 ]
(pod-B excluded: readiness probe failing)
โ
โผ
kube-proxy on every node reads EndpointSlice
programs iptables/IPVS rules:
"ClusterIP:80 โ DNAT โ one of [pod-A:8080, pod-C:8080]"
โ
โผ
Traffic from any pod/node โ ClusterIP:80 โ real pod
EndpointSlice vs legacy Endpoints:
| Feature | Endpoints (legacy) | EndpointSlice (default since K8s 1.21) |
|---|---|---|
| Max entries per object | unbounded (performance cliff) | ~100 per slice |
| Scale | O(n) updates on any pod change | Only affected slice updated |
| Protocol support | TCP/UDP | TCP/UDP/SCTP |
| Topology hints | No | Yes (zone-aware routing) |
| Debug command | kubectl get endpoints |
kubectl get endpointslices |
๐ฎ๐ณ Hinglish intuition: EndpointSlice = abhi kaun apne desk pe ready hai ki live list. Service clerk hai โ woh list dekh ke call forward karta hai sirf ready logon ko.
iptables vs IPVS:
| iptables | IPVS (IP Virtual Server) | |
|---|---|---|
| Data structure | Linked list of rules | Hash table |
| Lookup time | O(n) โ slows with cluster size | O(1) hash lookup |
| 1,000-service cluster | Noticeable latency | Negligible |
| Load-balancing algos | Round-robin only | RR, least-conn, source-hash |
| Default | Yes (most clusters) | Opt-in via kube-proxy --proxy-mode=ipvs |
For >500 Services, switch to IPVS. iptables with 10,000+ rules adds measurable per-packet overhead.
CoreDNS โ Service DNS¶
Every Service gets a DNS name: <service>.<namespace>.svc.cluster.local
# From a pod, same namespace:
curl http://api-service/endpoint # short name, same namespace
# Cross-namespace:
curl http://api-service.payments.svc.cluster.local/endpoint
# CoreDNS lives at:
kubectl get svc -n kube-system kube-dns # typically 10.96.0.10
# Pod's /etc/resolv.conf (set by kubelet):
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
Debug DNS failures:
# From inside a running pod:
kubectl exec -it <pod> -- nslookup api-service
kubectl exec -it <pod> -- nslookup api-service.default.svc.cluster.local
# Is CoreDNS itself healthy?
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns
๐ฎ๐ณ Hinglish intuition: Phone contact list. Number (IP) yaad nahi rakhte โ naam se call karte. CoreDNS = cluster ka contacts app. Naam likho, IP automatically milta.
Ingress + TLS โ production HTTP routing¶
Why not NodePort for production?
NodePort (30000โ32767) exposes a raw TCP port on every node. No TLS termination. No host-based routing. No path-based routing. You get one port per Service. For production HTTP/HTTPS with multiple services, you need Ingress.
Internet
โ :443 (HTTPS)
โผ
[ Load Balancer / NodePort ]
โ
โผ
[ Ingress Controller โ nginx pod(s) ]
โ reads all Ingress objects in cluster
โ programs nginx.conf dynamically
โ
โโโ Host: api.example.com โ Service: api-svc:80
โโโ Host: app.example.com โ Service: frontend-svc:80
โโโ Host: api.example.com/admin โ Service: admin-svc:80
Ingress resource YAML (example):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-tls-secret # cert-manager writes cert here
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
cert-manager + Let's Encrypt flow:
cert-manager watches Ingress objects
โ sees annotation: cert-manager.io/cluster-issuer
โ
โผ
Creates CertificateRequest โ ACME challenge
โ Let's Encrypt validates domain (HTTP-01 or DNS-01)
โ
โผ
TLS certificate stored in Kubernetes Secret (api-tls-secret)
โ
โผ
nginx Ingress Controller reads Secret โ terminates TLS โ forwards HTTP internally
Production gotcha "404 from nginx / Host header not matched": If you
curl http://<node-ip>:30080/but your Ingress rule sayshost: api.example.com, nginx cannot match the request โ it returns 404 or the nginx default page. Fix: send the correct Host header (curl -H 'Host: api.example.com' ...) or set up real DNS. The Ingress controller routes on the HTTPHost:header, not the IP.๐ง War story:
curl http://<node-ip>:30080/api/pe nginx ka default page aa raha tha โ backend pods bilkul theek the; culprit thaHost:header jo request mein absent tha, Ingress rule se match nahi kiya.curl -H 'Host: api.example.com' ...se 200 mila, phir samjha. Poori kahani + lesson โ Interview Bank.
Debug Ingress:
kubectl get ingress # shows ADDRESS (load balancer IP)
kubectl describe ingress api-ingress # shows rules + backend status
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller # nginx error logs
kubectl get certificate # cert-manager certificate status
kubectl describe certificate api-tls-secret # see ACME challenge progress
NetworkPolicy โ pod-to-pod firewall inside the cluster¶
By default, every pod can talk to every other pod in the cluster โ a flat allow-all network. NetworkPolicy lets you add firewall rules inside K8s.
NetworkPolicy complements AWS Security Groups: SGs operate at the node (EC2 instance) level. NetworkPolicy operates at the pod level โ same node, different pods can be isolated.
# Default-deny all ingress to namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: payments
spec:
podSelector: {} # matches ALL pods in namespace
policyTypes:
- Ingress # deny all inbound; no ingress rules = deny all
---
# Allow only api pods to reach db pods on :5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: payments
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: api
ports:
- protocol: TCP
port: 5432
Pattern: default-deny first, then allow-list. Create default-deny-ingress for every namespace, then add explicit allow policies. NetworkPolicy requires a CNI that supports it (Calico, Cilium โ not all do).
B2. TLS / HTTPS โ how a request is secured¶
The Ingress section above showed cert-manager's annotation hook and where the certificate lands (a Kubernetes Secret). This section goes inside the TLS protocol itself โ the part interviewers ask about when they say "walk me through a HTTPS request."
The postcard problem + three guarantees¶
Plain HTTP is a postcard: readable by any router, ISP, or cloud provider between sender and receiver. TLS wraps it in a sealed, verified envelope.
| TLS guarantee | What it means | Without it |
|---|---|---|
| Confidentiality | Payload encrypted โ only client and server can read it | Any network hop reads passwords, tokens, PII |
| Integrity | MAC detects tampering โ altered bytes are caught | Attacker modifies the response in transit |
| Authenticity | Certificate proves the server is who it claims to be | You might connect to an imposter, not your bank |
๐ฎ๐ณ Hinglish intuition: HTTP = postcard (daaiya bhi padh sakta). HTTPS = sealed registered envelope with verified sender stamp โ band karo, address confirm karo, phir trust karo.
The certificate analogy โ passport + padlock¶
- Certificate = verified passport. It says "I am api.example.com" and a trusted Certificate Authority (CA) has verified and signed that claim. Like a passport it has an expiry date and an issuer's seal.
- Asymmetric key pair = public padlock + private key. The server shares its padlock (public key) freely โ anyone can lock something with it. Only the server's private key opens it. Sharing the padlock does not expose the private key.
Chain of trust¶
Browsers and operating systems come pre-installed with a small set of Root CA certificates they trust unconditionally. A Root CA signs Intermediate CA certificates; Intermediate CAs sign your domain's leaf certificate. The server must send both the leaf cert AND the intermediate โ the browser walks the chain to the trusted root.
flowchart TD
ROOT["Root CA<br/>pre-installed in OS and browser"]:::ok
INT["Intermediate CA<br/>signed by Root CA"]:::ctl
LEAF["Leaf certificate<br/>api.example.com"]:::run
BROWSER["Browser trust store"]:::net
ROOT --> INT
INT --> LEAF
ROOT -. "pre-installed in" .-> BROWSER
BROWSER -. "validates chain up to" .-> LEAF
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
classDef ctl fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92;
Root CA trusts the Intermediate; Intermediate signs the leaf cert for your domain; browser validates the full chain back to the pre-installed root.
Classic gotcha โ "works in browser, fails in
curl": Browsers cache intermediate certificates from previous visits.curlneeds the full chain in the server response. If the server only sends the leaf cert (missing intermediate),curlreturnsSSL certificate problem: unable to get local issuer certificateeven while the browser shows a padlock.
Symmetric vs asymmetric โ why TLS uses both¶
| Asymmetric (ECDHE) | Symmetric (AES-GCM) | |
|---|---|---|
| Key sharing | Public key is safe to share openly | Same key both sides โ must be exchanged securely first |
| Speed | Slow โ heavy elliptic-curve math | Very fast โ hardware-accelerated |
| Role in TLS | Authenticate server + agree on session key | Encrypt the actual HTTP payload |
TLS uses asymmetric crypto to securely agree on a symmetric session key, then switches to fast symmetric encryption for all data. ECDHE (Elliptic-Curve Diffie-Hellman Ephemeral) generates a fresh key pair per session โ even if the server's private key is stolen later, recorded past sessions cannot be decrypted. This property is forward secrecy.
The TLS 1.3 handshake¶
sequenceDiagram
participant C as Client
participant S as Server
C->>S: ClientHello - TLS version + cipher list + SNI hostname
S->>C: ServerHello - chosen cipher suite
S->>C: Certificate chain - leaf plus intermediate
Note over C: verify chain back to trusted Root CA
C->>S: ECDHE public key share
S->>C: ECDHE public key share
Note over C,S: both independently derive the same session key
C->>S: Finished - first encrypted message
S->>C: Finished - encrypted
Note over C,S: encrypted HTTP traffic begins
TLS 1.3 handshake: SNI tells the server which certificate to present; ECDHE establishes a shared session key without ever transmitting it; all traffic after Finished is AES-encrypted.
SNI (Server Name Indication): The client sends the target hostname in cleartext at the very start of the handshake โ before any encryption is applied. This lets one load balancer serve TLS certificates for many different domains from a single IP; the server picks the right certificate based on the SNI field.
TLS termination in Kubernetes¶
The Ingress controller terminates TLS at the cluster edge:
- The TLS certificate lives in a Kubernetes Secret (
type: kubernetes.io/tls) holdingtls.crt(leaf + intermediate chain) andtls.key(private key). - The Ingress reads
spec.tls[].secretNameand loads the cert into the nginx controller's memory. - Traffic from the Ingress to backend Services travels as plain HTTP inside the cluster (the default; you can force TLS to the backend with
nginx.ingress.kubernetes.io/backend-protocol: HTTPSor ssl-passthrough, and true pod-to-pod mTLS needs a service mesh like Istio/Linkerd โ but plain HTTP is the standard internal hop). - For pod-to-pod encryption, you need mTLS via a service mesh (Istio or Linkerd) โ each sidecar proxy handles encryption transparently. This is the same mTLS used by the control plane internally (see section Aโฒ).
cert-manager โ automated certificate lifecycle¶
cert-manager is a Kubernetes controller that automates the full TLS lifecycle: request โ issue โ store โ auto-renew. The Ingress section above covered the annotation; here is the complete flow:
flowchart TD
ING["Ingress object<br/>cluster-issuer annotation"]:::ctl
CM["cert-manager<br/>controller"]:::run
CI["ClusterIssuer<br/>letsencrypt-prod"]:::ok
ACME["ACME challenge<br/>HTTP-01 or DNS-01"]:::net
LE["Let's Encrypt CA"]:::ok
SEC["Kubernetes Secret<br/>tls.crt and tls.key"]:::store
RENEW["Auto-renew<br/>30 days before expiry"]:::ok
ING -->|"watches"| CM
CM --> CI
CI -->|"initiates"| ACME
ACME -->|"prove domain ownership"| LE
LE -->|"issues cert"| CM
CM -->|"writes"| SEC
SEC -->|"Ingress controller reads"| ING
CM -. "schedules" .-> RENEW
classDef ctl fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
classDef net fill:#ede7f6,stroke:#5e35b1,color:#311b92;
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100;
cert-manager watches Ingress annotations, creates an ACME challenge with Let's Encrypt to prove domain ownership, stores the issued certificate in a Secret, and schedules auto-renewal 30 days before expiry.
HTTP-01 challenge: cert-manager creates a temporary HTTP endpoint at http://<domain>/.well-known/acme-challenge/<token>. Let's Encrypt fetches this URL to confirm domain ownership. Requires the domain's DNS to point to the cluster's public IP and port 80 to be open inbound.
DNS-01 challenge: cert-manager writes a TXT record to your DNS provider (Route 53, Cloudflare) via their API. Let's Encrypt queries the TXT record to confirm ownership. Works for wildcard certs and private clusters where inbound HTTP is not available.
Expired certificate = hard outage, no bypass
Browsers reject expired certs with a non-bypassable block โ users cannot reach your service at all, not even with a "proceed anyway" click. cert-manager renews 30 days early, but if renewal fails silently (HTTP-01 challenge blocked by a firewall, misconfigured ClusterIssuer, DNS-01 credentials expired), you get zero warning until the cert expires and the site goes dark.
TLS gotchas table:
| Symptom | Root cause | Fix |
|---|---|---|
curl fails, browser shows padlock |
Missing intermediate cert in the server's chain | Ensure fullchain.pem in Secret โ leaf plus intermediate |
SSL: hostname mismatch |
Cert SAN does not include the request hostname | Cert must list api.example.com or *.example.com in Subject Alternative Name |
certificate has expired |
cert-manager renewal failed silently | Check order and challenge objects; fix HTTP-01 firewall or DNS-01 credentials |
HTTP-01 challenge stuck Pending |
Port 80 blocked โ Let's Encrypt cannot reach the cluster | Open port 80 on LB and SG; ACME requires inbound HTTP |
| Cert rejected with clock skew error | Server or client clock drifts outside cert validity window | Sync NTP on nodes โ common on bare-metal clusters |
Inspect commands:
# What does the server actually present?
curl -vI https://api.example.com 2>&1 | grep -E "subject|issuer|expire|SSL"
# Full chain โ shows every cert the server sends and expiry
openssl s_client -connect api.example.com:443 -servername api.example.com \
| openssl x509 -noout -text | grep -E "Subject|Issuer|Not After"
# cert-manager lifecycle objects
kubectl get certificate,order,challenge -A
kubectl describe certificate api-tls-secret -n default
๐ฎ๐ณ Hinglish intuition: cert-manager = dedicated employee jo automatically 30 din pehle passport renewal ke liye apply karta hai. Agar ek baar wrong address (misconfigured issuer) โ passport expire, site band, raat ko fix karo.
Three-sentence interview summary: TLS provides confidentiality, integrity, and authenticity via a certificate chain rooted in a pre-installed CA. The handshake uses ECDHE asymmetric key exchange to agree on a per-session symmetric key (forward secrecy); all HTTP payload is then AES-encrypted with that key. In Kubernetes, the Ingress controller terminates TLS using a cert stored in a Secret โ cert-manager automates the full lifecycle via ACME (Let's Encrypt), renewing 30 days before expiry.
C. Pod Lifecycle & Health¶
The three probes โ distinct actions, distinct timing¶
| Probe | Question it answers | Fail action | When it runs |
|---|---|---|---|
| Startup | "Has the application finished booting?" | Restarts container (but holds readiness/liveness until it passes) | Only during initial startup |
| Readiness | "Is the pod ready to receive traffic right now?" | Removes pod from EndpointSlice โ traffic stops, pod stays alive | Entire pod lifetime |
| Liveness | "Is the process stuck / deadlocked?" | Restarts container (kills it) | Entire pod lifetime |
flowchart TD
BOOT["Container<br/>starting up"]:::run
SP{"Startup Probe<br/>pass?"}:::cd
READY{"Readiness Probe<br/>pass?"}:::cd
LIVE{"Liveness Probe<br/>pass?"}:::cd
TRAFFIC(["In EndpointSlice<br/>traffic ON"]):::run
NOTRAF(["Removed from<br/>EndpointSlice"]):::warn
KRESTART(["kubelet restarts<br/>container"]):::warn
BOOT --> SP
SP -->|"fail ร threshold"| KRESTART
SP -->|"pass โ boot done"| READY
SP -->|"pass โ activates"| LIVE
READY -->|"pass"| TRAFFIC
READY -->|"fail"| NOTRAF
NOTRAF -->|"re-check"| READY
LIVE -->|"fail ร threshold"| KRESTART
classDef shared fill:#fff9c4,stroke:#f9a825,color:#4a3800;
classDef cd fill:#f3e5f5,stroke:#8e24aa,color:#4a148c;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e;
classDef net fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030;
Probe lifecycle: startup probe gates liveness + readiness during slow boot; readiness failure pulls the pod from traffic without killing it; liveness failure restarts the container.
Key interaction: Startup probe blocks liveness and readiness probes until it passes. Without it, a slow-starting app (30-second JVM warmup) gets killed by liveness before it finishes booting โ CrashLoopBackOff despite correct code.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0 # startup probe handles the delay
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 10
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 30 ร 10s = 300s max boot time allowed
periodSeconds: 10
Debug probes:
kubectl describe pod <name> # Events section: "Liveness probe failed", "Readiness probe failed"
kubectl get events --field-selector involvedObject.name=<pod>
๐ฎ๐ณ Hinglish intuition: Naya employee analogy. Startup = "training poori hui?" (sirf joining ke waqt). Readiness = "abhi kaam ke liye available ho?" (poori zindagi). Liveness = "behosh to nahi?" (poori zindagi). Teen alag sawaal, teen alag consequences.
๐ฎ Predict pehle (socho, phir aage padho): Tum liveness probe ke andar database ka health check daal dete ho. DB thodi slow ho jaati hai. Saare pods ka kya hota hai?
The liveness footgun โ cascading restarts¶
This is one of the most common production disasters. Do not put external dependency checks in the liveness probe.
WRONG: liveness hits /healthz-deep which checks DB connection
โ
DB hits load spike โ response slows to 3s
โ liveness timeoutSeconds:1 โ FAIL
โ
kubelet restarts ALL pods (liveness failed)
โ
Restarted pods ALL try to reconnect to DB simultaneously
โ DB load spikes further
โ Liveness fails again โ ALL pods restart again
โ
โ ๏ธ Cascading restart loop. DB glitch became full outage.
Rule: - Liveness = only checks whether this process itself is alive/not-deadlocked. An in-process health flag is enough. Never check DB, cache, or external APIs. - Readiness = checks whether the pod is ready to serve traffic, including dependency availability. Readiness failure removes the pod from load balancing (traffic stops) without killing it โ the correct response to a slow DB.
๐ฎ๐ณ Hinglish intuition: Liveness = smoke detector. Chhoti dhuaan pe poori building khali kara di โ rescue ke bajaye aur bada haadsa. External dependency ko liveness mein daalna = building-level panic trigger for a neighbour's cigarette.
QoS classes โ who dies first when RAM runs out¶
K8s derives a QoS (Quality of Service) class from the requests and limits you set. When node memory pressure occurs, the kubelet evicts in QoS order.
| QoS Class | How to get it | OOM eviction order |
|---|---|---|
| Guaranteed | requests.cpu == limits.cpu AND requests.memory == limits.memory (both set, both equal) |
Last to be evicted |
| Burstable | requests < limits, or only one of cpu/memory set |
Middle |
| BestEffort | No requests, no limits at all | First to die |
Critical workloads: always set
requests == limits(Guaranteed class). A monitoring pod accidentally left as BestEffort will be the first victim when the node runs hot.
๐ฎ๐ณ Hinglish intuition: Titanic lifeboat priority. Guaranteed = first class (pehle life-jacket). BestEffort = ticket nahi tha (paani mein pehle). requests/limits hi teri ticket class decide karte hain.
PodDisruptionBudget (PDB) โ kaun kis time zinda rahega¶
QoS decide karta hai involuntary disruption (RAM khatam โ eviction) me kaun marega. PDB decide karta hai voluntary disruption me โ jab tum ya cluster jaan-boojh ke pods hataate ho: kubectl drain (node maintenance), cluster upgrade, autoscaler node hataana.
PDB batata hai: "in pods me se itne HAMESHA zinda rahne chahiye." Bina PDB ke, ek node drain karte hi uske saare pods ek saath ja sakte hain โ app us second down.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: auth-service-pdb
spec:
minAvailable: 1 # drain ke beech kam se kam 1 pod zinda rahe
selector:
matchLabels:
app: auth-service # billfree: same label jo Deployment pe hai
minAvailable: Nโ "N hamesha up rahein" (absolute number ya50%).maxUnavailable: Nโ "ek waqt me N se zyada down na hon." (Dono me se ek hi do.)
The #1 PDB gotcha โ drain HANG ho jaata hai
minAvailable: 2 par Deployment me sirf 2 replicas โ kubectl drain us node ko kabhi khaali nahi kar paayega (2 hataao to 0 bachega, budget allow nahi karta) โ command hamesha ke liye atak jaati hai, node upgrade ruk jaata hai. Rule: minAvailable hamesha replicas se kam rakho (replicas: 3, minAvailable: 2), ya percentage use karo.
Interview me PDB + rolling update + probes ek saath poochhe jaate hain โ teeno milke "safe deploy/upgrade" banate hain. Drain + PDB ka live demo โ ch24 Gauntlet Build aur ch25 Chaos. Billfree ki chart me pdb.yaml maujood hai โ usse dekho.
๐ฎ๐ณ Hinglish intuition: Restaurant me lunch rush ke beech tu ek waiter ko break pe bhejta hai โ par rule hai "counter pe hamesha 1 waiter rahe." Wahi PDB hai: maintenance chalti rahe, par service kabhi poori band na ho.
Graceful shutdown โ the other half of zero-downtime¶
Zero-downtime has two halves: the new pod becoming ready (readiness) and the old pod draining cleanly (graceful shutdown). Both are required.
Pod termination sequence:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
t=0 kubelet sends SIGTERM to container
AND
endpoint controller removes pod from EndpointSlice
Problem: these two events are async.
SIGTERM may arrive before kube-proxy has propagated
the slice update to all nodes.
โ New requests still arrive while app is shutting down.
โ App gets SIGTERM and closes โ connection reset โ 502.
Fix: preStop hook adds a sleep buffer
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
lifecycle:
preStop:
exec:
command: ["sleep", "5"] # wait for slice propagation before SIGTERM
Then:
t=0 preStop hook starts (sleep 5) โ grace clock ALSO starts at t=0
t=5 SIGTERM sent (slice already removed โ no new traffic)
t=5 App drains in-flight requests
t=30 terminationGracePeriodSeconds expires (counted from t=0) โ SIGKILL
The grace clock starts at pod deletion, not at SIGTERM
terminationGracePeriodSeconds is measured from t=0 (pod deletion) โ the preStop hook runs inside it, not before it. So:
If your app needs 30s to drain and you add a 5s preStop with the default 30s grace, it gets SIGKILLed with 5s of work left โ connections reset, and it looks exactly like the bug you were trying to fix. Rule: terminationGracePeriodSeconds > preStop + worst-case drain time, with headroom.
# Check if graceful shutdown is configured:
kubectl get pod <name> -o yaml | grep -A5 lifecycle
kubectl get pod <name> -o yaml | grep terminationGracePeriodSeconds
๐ฎ๐ณ Hinglish intuition: Dukaan band karna. "Closed" sign lagao (slice se hato) โ naye customer mat aao. Andar jo hain unhe finish karne do (in-flight requests). Phir shutter girao (SIGKILL). SIGTERM = "andar walon ko niklne do" signal.
Pod lifecycle phases, conditions, and container states¶
Pod phases (top-level status):
| Phase | Meaning | First debug command |
|---|---|---|
Pending |
Not yet scheduled, or image pulling | kubectl describe pod (Events) |
Running |
At least one container running (may not be Ready) | kubectl describe pod, kubectl logs |
Succeeded |
All containers exited with code 0 (Jobs) | kubectl logs |
Failed |
Container exited non-zero | kubectl logs --previous |
Unknown |
kubelet unreachable | kubectl describe node |
Container states (under each container):
| State | Common reason | Fix direction |
|---|---|---|
Waiting: ContainerCreating |
Image pulling | Check image tag, registry auth |
Waiting: ImagePullBackOff |
Image not found / registry auth failure | kubectl describe pod โ check image name, imagePullSecrets |
Waiting: CrashLoopBackOff |
Container starts then exits | kubectl logs --previous |
Terminated: OOMKilled (137) |
RAM limit exceeded | Raise limits or fix memory leak |
READY 0/1: Container is Running but readiness probe fails. Pod is alive but receiving zero traffic. Check kubectl describe pod for readiness probe events.
CrashLoopBackOff backoff: 10s โ 20s โ 40s โ 80s โ 160s โ capped at 300s. The doubling delay is why a recently-crashed pod seems "fine" but was restarting minutes ago.
D. Autoscaling Internals (HPA)¶
HPA (Horizontal Pod Autoscaler) scales the number of pods. It does not scale nodes (that is Cluster Autoscaler โ different component, see 06-M5-sizing-and-cost).
The formula¶
desiredReplicas = ceil( currentReplicas ร (currentMetricValue / targetMetricValue) )
Example:
currentReplicas = 3
currentCPU = 90%
targetCPU = 50%
desiredReplicas = ceil(3 ร 90/50) = ceil(5.4) = 6
What HPA needs to function¶
- metrics-server installed in the cluster โ it scrapes kubelet for pod CPU/memory every 15s. Without it,
kubectl get hpashows<unknown>/50%and never scales. requestsset on pods โ the percentage is computed ascurrentUsage / requests. Without a request baseline, the percentage cannot be calculated.
kubectl get hpa # shows current metrics and target
kubectl top pods # if this works, metrics-server is up
kubectl describe hpa <name> # full event log of scale decisions
Scale-up fast, scale-down slow¶
| Direction | Default behavior | Why |
|---|---|---|
| Scale up | Immediate (next check cycle, ~15s) | Under-provisioning hurts users now |
| Scale down | Waits 300s stabilization window | Prevents "flapping" โ remove pods, load comes back, add pods, remove again |
spec:
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # wait 5 min before removing pods
scaleUp:
stabilizationWindowSeconds: 0 # scale up immediately
๐ฎ๐ณ Hinglish intuition: Cruise control. Target speed set karo (CPU 50%). Load badha โ system accelerate karta (fast). Load ghata โ brakes dheere-dheere lagata (slow, taaki bump par baar-baar accelerate/brake na karna pade = flapping).
E. State & Security¶
StatefulSet / PV / PVC lifecycle¶
Use StatefulSets when each replica needs its own stable identity and its own persistent disk โ databases, message brokers, distributed stores. Deployments are wrong for these: they share identity and have no persistent disk association.
StatefulSet "postgres" (replicas: 3)
โ
โโโ postgres-0 โโโ PVC: data-postgres-0 โโโ PV (10Gi disk)
โโโ postgres-1 โโโ PVC: data-postgres-1 โโโ PV (10Gi disk)
โโโ postgres-2 โโโ PVC: data-postgres-2 โโโ PV (10Gi disk)
Ordered startup: 0 starts first, becomes Ready, then 1, then 2.
Ordered delete: 2 first, then 1, then 0.
Stable identity:
- DNS hostname: postgres-0.postgres-headless.default.svc.cluster.local
- This hostname is stable across restarts โ if postgres-0 restarts, it comes back as postgres-0 on the same disk.
PV (Persistent Volume) and PVC (Persistent Volume Claim) lifecycle:
| Concept | What it is |
|---|---|
| PV | A piece of storage in the cluster (provisioned by admin or dynamically) |
| PVC | A request for storage by a pod/StatefulSet โ binds to a matching PV |
| StorageClass | Defines how PVs are dynamically provisioned (e.g., local-path on bare clusters, gp3 on EKS) |
Critical behavior โ deleting a StatefulSet does NOT delete PVCs. The PVCs (and thus the PVs and the data) survive. This is intentional: data outlives the controller. You must explicitly delete PVCs to free storage.
kubectl get pvc # see all claims and their bound PVs
kubectl describe pvc data-postgres-0 # see binding status and capacity
kubectl get pv # see all PVs in cluster
# Dynamic provisioning (StorageClass local-path on bare-metal):
kubectl get storageclass
Bare cluster gotcha: Without a StorageClass + provisioner (e.g.,
rancher/local-path-provisioner), PVCs stay inPendingforever โ no PV is created. Install a provisioner before deploying StatefulSets.
Storage lifecycle โ provision โ attach โ mount¶
The section above showed that a PVC binds to a PV. Here is what actually happens under the hood โ three sequential stages that explain the majority of storage production incidents.
The rental-unit analogy:
| K8s concept | Rental analogy |
|---|---|
| Pod | Tenant who needs a room |
| PVC | Rental application form |
| PV | The actual physical unit |
| StorageClass | Rental company โ sets disk type, speed, reclaim policy |
| CSI driver | Warehouse staff who physically build and deliver the unit |
flowchart LR
PVC["PVC created<br/>tenant rental form"]:::ctl
SC["StorageClass<br/>rental company"]:::store
CSI["CSI driver<br/>createVolume"]:::run
EBS["EBS volume<br/>one AZ only"]:::store
NODE["Node<br/>single attach"]:::ok
POD["Pod filesystem<br/>volume mounted"]:::run
PVC -->|"1 provision"| SC
SC --> CSI
CSI -->|"disk created"| EBS
EBS -->|"2 attach to node"| NODE
NODE -->|"3 mount and bind"| POD
classDef ctl fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef store fill:#fff3e0,stroke:#ef6c00,color:#e65100;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
Three-stage storage lifecycle: PVC triggers CSI to provision a disk (EBS built in one AZ); the disk attaches to a single node; kubelet formats and bind-mounts it into the pod's filesystem.
Stage details:
- โ Provision: The StorageClass instructs the CSI driver (
ebs.csi.aws.com) to call the cloud API and create a disk. EBS volumes are provisioned in one specific Availability Zone โ baked in at creation time, cannot be changed later. - โก Attach: The EBS volume is attached to the node where the pod is scheduled (equivalent to
aws ec2 attach-volume). EBS is a block device and supports one node at a time โ it is not a network file system. - โข Mount: kubelet detects the attached block device, formats it if new, and bind-mounts it into the pod's container filesystem at the path specified in
volumeMounts.
Access modes:
| Mode | Code | Meaning | Typical backend |
|---|---|---|---|
| ReadWriteOnce | RWO | One node mounts read-write | EBS, local-path โ block devices |
| ReadWriteMany | RWX | Many nodes mount read-write simultaneously | EFS, CephFS โ network file systems |
| ReadOnlyMany | ROX | Many nodes mount read-only | Pre-populated datasets, model weights |
The "3 replicas can't share one EBS" trap: A Deployment with
replicas: 3and a single RWO PVC can only schedule one pod โ the other two cannot attach the same EBS volume and stayPending. Use EFS (RWX) for shared storage across pods, or use a StatefulSet withvolumeClaimTemplatesso each replica gets its own dedicated PVC.
Two storage patterns that cause outages or data loss
Multi-Attach error (RWO + pod rescheduling): A pod is rescheduled to a new node but the old node has not released the EBS attachment โ node is slow to drain, or the kubelet crashed. The new pod's start blocks on Unable to attach or mount volumes for up to 6 minutes on EKS (the volume-detach timeout). Mitigation: use volumeBindingMode: WaitForFirstConsumer on the StorageClass so the disk is always provisioned in the same AZ the pod lands in, reducing reschedule-to-new-AZ scenarios.
reclaimPolicy: Delete = silent data loss: The default reclaim policy on most dynamic StorageClasses is Delete. When the PVC is deleted, the underlying EBS volume is immediately and permanently deleted with no confirmation prompt. For production databases set reclaimPolicy: Retain on the StorageClass before first use, or patch individual PVs after provisioning.
StatefulSet volumeClaimTemplates โ per-replica stable identity:
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 10Gi
This generates PVCs named data-db-0, data-db-1, data-db-2. When the db-0 pod restarts โ even after a crash or node failure โ it always re-binds data-db-0. The primary always gets its own disk, never a replica's. This stable identity is what makes StatefulSets safe for databases with leader/follower roles.
๐ฎ๐ณ Hinglish intuition: EBS = personal locker (ek banda, ek locker, ek sheher). EFS = shared office fridge (sab ek saath use kar sakte). StorageClass RWO le liya aur teen pods ko share karna tha? Wahi fridge-locker confusion hai.
Storage gotchas quick-reference:
| Symptom | Root cause | Fix |
|---|---|---|
PVC stuck Pending |
No StorageClass or provisioner not installed | kubectl describe pvc โ install provisioner or create StorageClass |
PVC stuck Pending on EKS |
AZ mismatch โ volume in us-east-1a, pod on us-east-1b node |
Use volumeBindingMode: WaitForFirstConsumer on StorageClass |
Multi-Attach error on pod reschedule |
EBS (RWO) still attached to old node | Wait for detach timeout; force-delete stuck pod; check old node health |
Data deleted after kubectl delete pvc |
reclaimPolicy: Delete (StorageClass default) |
Set Retain on StorageClass before first PVC; patch PV if already provisioned |
2 of 3 Deployment replicas stuck Pending |
RWO PVC โ only one node can attach EBS | Switch to RWX backend (EFS) or use StatefulSet with volumeClaimTemplates |
RBAC โ Role-Based Access Control¶
RBAC (Role-Based Access Control) controls what identities (users, service accounts) can do inside the K8s API. It is additive โ by default, a ServiceAccount can do nothing. You grant permissions explicitly.
Four objects:
| Object | Scope | What it does |
|---|---|---|
| Role | Namespace | Grants permissions within one namespace |
| ClusterRole | Cluster-wide | Grants permissions across all namespaces (or non-namespaced resources like nodes) |
| RoleBinding | Namespace | Binds a Role or ClusterRole to a subject (user, group, ServiceAccount) in one namespace |
| ClusterRoleBinding | Cluster-wide | Binds a ClusterRole to a subject across the whole cluster |
ServiceAccount (SA): Every pod runs as a ServiceAccount. Default SA exists in every namespace. Tokens are auto-mounted at /var/run/secrets/kubernetes.io/serviceaccount/token.
# Minimal: read pods in the "monitoring" namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: monitoring
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: monitoring
subjects:
- kind: ServiceAccount
name: metrics-collector
namespace: monitoring
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Least-privilege principle: Pods should run as a dedicated ServiceAccount with only the permissions they need. Running as cluster-admin (a ClusterRole that allows everything on every resource) from a pod means that if the pod is compromised, the attacker has full cluster control โ including reading Secrets, creating privileged pods, and deleting workloads.
# Test what a ServiceAccount can do:
kubectl auth can-i list pods --namespace=default --as=system:serviceaccount:monitoring:metrics-collector
# โ yes / no
kubectl auth can-i delete deployments --namespace=production \
--as=system:serviceaccount:ci:deployer
# Use this to verify before granting or before troubleshooting "Forbidden" errors
kubectl get rolebindings,clusterrolebindings -A | grep <serviceaccount>
Interview answer alert: "Why shouldn't pods run as cluster-admin?" โ blast radius. Compromised pod = attacker reads all Secrets (API keys, DB passwords), creates new privileged pods, exfiltrates data. RBAC limits blast radius to what that specific pod legitimately needs.
Hardening a pod: securityContext¶
RBAC controls what the pod's identity can do via the K8s API. securityContext controls what the running process can do at the Linux level โ two complementary layers. Both are required for defence-in-depth.
spec:
automountServiceAccountToken: false # don't mount the SA token unless the app calls the K8s API
securityContext:
runAsNonRoot: true # refuse to start if the image runs as root (UID 0)
runAsUser: 1000
fsGroup: 2000 # files created in mounted volumes are owned by this GID
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # process can't gain more privileges than it started with
readOnlyRootFilesystem: true # filesystem is read-only (mount an emptyDir for /tmp if needed)
capabilities:
drop: ["ALL"] # drop all Linux capabilities; add back only what's needed
Field-by-field:
| Field | What it does |
|---|---|
runAsNonRoot: true |
K8s refuses to start the pod if the image's USER is root. Pairs with the non-root image from M3 โ the image must actually be non-root or this rejects it at admission. |
allowPrivilegeEscalation: false |
Prevents setuid/sudo tricks from gaining more permissions than the process started with. |
readOnlyRootFilesystem: true |
Stops an attacker writing a payload (reverse-shell, crypto miner) to the container filesystem. Mount emptyDir volumes for any path that genuinely needs writes (e.g., /tmp). |
capabilities: drop: ["ALL"] |
Strips all Linux capabilities (raw sockets, binding ports < 1024, etc.) โ least privilege at the kernel level. Add back only what the app truly needs, e.g. add: ["NET_BIND_SERVICE"] to listen on port 443. |
automountServiceAccountToken: false |
Prevents a compromised pod from calling the K8s API with the SA token auto-mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. Set true only for pods that legitimately need to call the K8s API (operators, admission webhooks). |
๐ฎ๐ณ Hinglish intuition: RBAC = office ID card (kaun sa door khulta hai). securityContext = job description (andar jaake kya kar sakta hai). Dono chahiye โ card bhi, rules bhi.
# Verify the effective security settings on a running pod:
kubectl get pod <name> -o yaml | grep -A20 securityContext
Spreading replicas for HA: anti-affinity & topology spread¶
WHY: By default the K8s scheduler may place all three replicas of a Deployment on the same node or in the same Availability Zone. That node crashes or that AZ goes down โ total outage despite having three "replicas."
The modern fix โ topologySpreadConstraints:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone # spread evenly across AZs
whenUnsatisfiable: DoNotSchedule # hard constraint โ never violate the skew
labelSelector:
matchLabels:
app: myapp
maxSkew: 1 means the replica count difference between the most-loaded and least-loaded zone must not exceed 1. With 3 replicas across 3 AZs: 1-1-1. DoNotSchedule makes this a hard constraint โ the scheduler will not place a pod that would violate the skew. (ScheduleAnyway is the soft-preference alternative.)
The older approach โ podAntiAffinity:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: myapp
topologyKey: kubernetes.io/hostname # no two replicas on the same node
podAntiAffinity says "don't co-locate two replicas on the same node." It is simpler but operates at a single topology level. topologySpreadConstraints works at any topology level (node, zone, region, rack) and can balance across multiple dimensions simultaneously. Use topologySpreadConstraints for new workloads; podAntiAffinity remains valid for strict "never two on the same node" rules.
๐ฎ๐ณ Hinglish intuition: Teen employees โ teen alag office branches mein bhejo. Ek branch band ho jaaye toh baaki kaam karte rahein.
topologySpreadConstraints= HR policy "equal distribution across cities."podAntiAffinity= "same cabin mein do log nahi" โ useful, par sirf ek level pe kaam karta.Cross-link: HA replica count and multi-AZ sizing decisions โ M5 sizing and cost.
Helm โ templated, versioned K8s packages¶
Helm is the Kubernetes package manager. Instead of maintaining 10 YAML files per application and copying them between environments, you write a chart with templates and inject environment-specific values.
Three core concepts:
| Term | What it is |
|---|---|
| Chart | A directory of YAML templates + Chart.yaml metadata |
| Values | A values.yaml file (or --set flags) that fills in template variables |
| Release | A named, versioned deployment of a chart into a cluster (helm install my-api ./chart) |
# Install a chart (creates a Release named "my-api")
helm install my-api ./chart -f values-prod.yaml
# Upgrade (creates new release revision)
helm upgrade my-api ./chart -f values-prod.yaml
# Rollback to previous revision
helm rollback my-api 1
# See all releases
helm list
# View rendered YAML before applying (dry run)
helm template my-api ./chart -f values-prod.yaml
Helm vs Kustomize vs raw YAML:
| Approach | When to use |
|---|---|
| Raw YAML | Simple, single-environment apps; learning; capstone projects |
| Kustomize | Multi-environment overlays (base + prod-patch); built into kubectl; no templating language |
| Helm | Third-party software installs (helm install cert-manager, helm install prometheus); team-shared charts with complex parameterization |
Reach for Helm when you are consuming community charts (cert-manager, nginx ingress, Prometheus/Grafana) โ they all ship as Helm charts. For your own app in small teams, Kustomize often suffices. See
12-capstone-url-shortenerand13-capstone-microshopfor practical examples.
F. The Full Instrumented Request Lifecycle¶
This traces a single HTTP request from the internet to the database and back, with per-hop latency and the debug command that isolates each hop.
flowchart TD
CLIENT(["Client<br/>browser or curl"]):::net
LB{{"Cloud LB<br/>AWS ALB"}}:::net
ING{{"nginx Ingress<br/>Host header routing"}}:::net
SVC["Service<br/>ClusterIP"]:::net
KP["kube-proxy<br/>iptables DNAT"]:::net
EPS[("EndpointSlice<br/>Ready pod IPs")]:::net
POD["App Pod<br/>:8080"]:::run
DB[("Database<br/>:5432")]:::run
CLIENT -->|"HTTPS :443"| LB
LB -->|"NodePort :30080"| ING
ING -->|"Host match<br/>to Service"| SVC
SVC --> KP
KP -.->|"reads"| EPS
KP -->|"DNAT to pod IP"| POD
POD -->|"SQL query"| DB
classDef shared fill:#fff9c4,stroke:#f9a825,color:#4a3800;
classDef cd fill:#f3e5f5,stroke:#8e24aa,color:#4a148c;
classDef run fill:#e0f2f1,stroke:#00897b,color:#004d40;
classDef obs fill:#f1f8e9,stroke:#689f38,color:#33691e;
classDef net fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef warn fill:#fdeeee,stroke:#d64545,color:#b23030;
Instrumented request lifecycle: Client โ cloud LB โ nginx Ingress (TLS + Host-header routing) โ Service ClusterIP โ kube-proxy reads EndpointSlice (DNAT) โ App Pod โ Database. DB round-trip dominates latency; cluster overhead is ~1โ3 ms.
Text version (ASCII)
INTERNET
โ
โ โ TCP :443 (HTTPS)
โผ latency: network RTT (ms)
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Load Balancer / DNS โ debug: curl -v https://api.example.com
โ (AWS ALB / NodePort) โ latency: ~1ms LB routing
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ โก NodePort :30080 or Ingress controller pod
โผ latency: ~0.1ms kernel
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ nginx Ingress Pod โ debug: kubectl logs -n ingress-nginx deploy/...
โ Host: api.example.com โ common fail: 404 = host header not matched
โ TLS termination โ debug: kubectl describe ingress
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ โข Service ClusterIP โ kube-proxy (iptables/IPVS)
โผ latency: ~0.1ms iptables DNAT
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ kube-proxy โ debug: kubectl get endpointslices
โ ClusterIP โ pod IP โ common fail: empty slice = readiness failed
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ โฃ Pod network (CNI: Calico)
โผ latency: ~0.1โ0.5ms cross-node
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ App Pod :8080 โ debug: kubectl exec pod -- curl localhost:8080/health
โ FastAPI / Express โ debug: kubectl logs <pod>
โ Handles request logic โ common fail: CrashLoopBackOff / OOMKilled
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ โค CoreDNS lookup โ DB Service DNS
โผ latency: ~0.5ms DNS
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Database โ debug: kubectl exec pod -- nc -zv <db-host> 5432
โ RDS :5432 / ClusterIP โ debug: kubectl exec pod -- nslookup db-service
โ Persistent data layer โ common fail: SG :5432 blocked / DNS fail
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
Dominant latency: DB round-trip (โ +โค) >> network (โกโขโฃ) in ยตsโms
Total per-request overhead vs bare metal: ~1โ3ms on healthy cluster
Systematic debug ladder (hop-by-hop, never guess):
# โ Can the load balancer reach the node?
curl -v http://<node-ip>:30080/
# โก Is the Ingress controller routing correctly?
kubectl describe ingress <name>
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50
curl -H 'Host: api.example.com' http://<node-ip>:30080/ # test host header
# โข Does the Service have endpoints?
kubectl get endpointslices -l kubernetes.io/service-name=api-svc
kubectl describe svc api-svc # check selector matches pod labels
# โฃ Is the pod healthy?
kubectl get pods -l app=api # check READY column and STATUS
kubectl describe pod <name> # check probe events
kubectl logs <name> --previous # logs from last crash
# โค Can the pod reach the database?
kubectl exec -it <pod> -- nc -zv <db-host> 5432
kubectl exec -it <pod> -- nslookup db-service
Beginner Mistakes vs Senior Insights¶
| Topic | Beginner says | Senior knows |
|---|---|---|
| Liveness probe | "Check DB in liveness to verify health" | DB check in liveness โ cascading restarts. DB checks belong in readiness. |
| Probes | "Readiness and liveness are the same" | Readiness: removes from traffic. Liveness: restarts. Startup: holds the other two during boot. |
| QoS | "Pod with highest memory usage gets killed" | QoS class is primary: BestEffort dies first regardless of usage |
| Graceful shutdown | "Rolling update is always zero-downtime" | Only if app handles SIGTERM and preStop sleep covers slice propagation race |
| HPA | "HPA scales nodes automatically" | HPA scales pods. Cluster Autoscaler scales nodes. Different components. |
| HPA debug | "HPA not working" | First check: metrics-server running? pods have requests set? |
| DNS | "Use pod IP in config" | Pod IPs are ephemeral. Use Service DNS name โ it survives pod restarts. |
| Ingress | "404 from nginx = app is down" | 404 from nginx often means Host header not matched in Ingress rules |
| RBAC | "Pods need cluster-admin to do anything" | Least-privilege SA with targeted Role. cluster-admin = full blast radius if compromised |
| StatefulSet | "Delete StatefulSet = delete data" | PVCs survive StatefulSet deletion by design. Explicit kubectl delete pvc needed. |
| etcd | "etcd is like a cache" | etcd is the source of truth. Lose etcd without backup = lose cluster state entirely. |
| EndpointSlice | "Service routes traffic directly" | Service is virtual. kube-proxy reads EndpointSlice โ programs iptables rules โ routes packets. |
| iptables | "iptables and IPVS are equivalent" | iptables is O(n); at scale (>500 svcs) IPVS is measurably faster (O(1) hash). |
| NetworkPolicy | "AWS SG protects pod traffic" | SGs protect at node level. NetworkPolicy protects at pod level (same node, different pods). |
NOT to say in interviews¶
- "Readiness and liveness are the same thing" โ they have opposite fail actions.
- "Liveness probe should check database connectivity" โ this is the cascading restart footgun.
- "The pod with the most memory usage gets evicted first" โ QoS class is the primary factor.
- "Rolling update is always zero-downtime" โ requires readiness probe + graceful SIGTERM handling.
- "HPA adds more nodes when CPU is high" โ HPA adds pods; Cluster Autoscaler adds nodes.
- "Pod IP is stable, I can use it in config" โ pod IPs change on restart; use Service DNS names.
- "404 from nginx means the backend service is broken" โ check Ingress host/path rules and Host header first.
- "Just give the pod cluster-admin, it's simpler" โ maximum blast radius if the pod is compromised.
- "Delete the StatefulSet and the data is gone" โ PVCs survive; must be explicitly deleted.
- "etcd is just a cache" โ etcd is the cluster's only persistent truth; treat it like production database with backups.
Memory Shortcuts¶
| Concept | One-liner |
|---|---|
| apiserver | Only component that touches etcd directly |
| etcd | Cluster's hard drive (persistent, WAL + snapshots) โ lose it without backup = amnesia |
| Raft quorum | โฅ3 control-plane nodes (odd); majority must agree to write |
| CNI/Calico | Cross-node pod routing (BGP / overlay / eBPF) |
| EndpointSlice | Live list of Ready pod IPs; kube-proxy reads this |
| iptables vs IPVS | O(n) vs O(1) โ switch to IPVS above ~500 Services |
| Startup probe | Holds liveness/readiness during slow boot |
| Readiness fail | Traffic removed, pod alive |
| Liveness fail | Container restarted (kill signal) |
| Liveness footgun | External check in liveness โ cascading restarts |
| QoS Guaranteed | requests == limits (both); safest from eviction |
| QoS BestEffort | No requests/limits; first to die on memory pressure |
| SIGTERM + preStop | Graceful shutdown: slice removed โ sleep 5 โ SIGTERM โ drain โ SIGKILL |
| HPA formula | ceil(current ร currentMetric / targetMetric) |
| HPA needs | metrics-server + pod requests |
| Scale-down slow | 300s stabilization window โ prevents flapping |
| CoreDNS FQDN | <svc>.<ns>.svc.cluster.local โ ClusterIP |
| Ingress | Host-header routing at nginx layer โ Service |
| 404 from nginx | Host header not matched in Ingress rules |
| NetworkPolicy | Pod-to-pod firewall (SGs are node-level, this is pod-level) |
| StatefulSet identity | pod-0/1/2, stable hostname, own PVC per replica |
| PVC survival | PVCs outlive StatefulSet deletion โ explicit delete needed |
| RBAC least privilege | SA + Role + RoleBinding (scoped); never cluster-admin for app pods |
| Helm | Chart + Values + Release (templated versioned K8s packages) |
Summary¶
This chapter completed the K8s picture that M4 deferred:
- Control plane (six actors) and the seven-step
kubectl applyjourney through etcd โ scheduler โ kubelet. - Networking โ CNI for cross-node routing, EndpointSlice (not legacy Endpoints) for service discovery, iptables O(n) vs IPVS O(1) at scale, CoreDNS for stable service names, Ingress + cert-manager for production HTTPS, NetworkPolicy for pod-level firewall.
- Pod lifecycle โ the three probes (startup / readiness / liveness) with distinct fail actions, the liveness footgun, QoS classes determining OOM eviction order, graceful shutdown race and the preStop sleep fix.
- HPA โ ceiling-division formula, metrics-server dependency, asymmetric scale-up/down timing.
- State and security โ StatefulSet stable identity with per-replica PVCs that outlive the controller, RBAC four-object model with least-privilege ServiceAccounts, Helm for templated packaging.
- Full request lifecycle with per-hop latency and the debug command that isolates each hop.
The capstone (12-capstone-url-shortener, 13-capstone-microshop) exercises all of this live. The interview bank (14-interview-bank) covers the full rapid-fire set.
Self-Check Quiz¶
Pehle memory se jawab do, phir neeche kholo.
-
You run
kubectl applyon a Deployment. Trace the exact sequence: which component acts in which order, and what does each write/read? -
A pod shows
READY 0/1. The pod phase isRunning. What is the most likely cause, and what two commands confirm it? -
Your liveness probe points to
/healthzwhich internally calls the database. Load spikes. What happens, step by step? What is the correct fix? -
You have three pods. Node runs out of RAM. Pod A has no requests/limits. Pod B has
requests: cpu 100mandlimits: cpu 200m. Pod C hasrequests: cpu 100m, memory: 128Miandlimits: cpu 100m, memory: 128Mi. Which pod is evicted first? Why? -
An HPA shows
<unknown>/50%and never changes replica count. Name two root causes and the commands to confirm each. -
Users get 404 from your nginx Ingress controller but the backend Service and pods are healthy. What is the most likely cause, and how do you test it with one
curlcommand? -
You delete a StatefulSet with 3 replicas. The PVCs are still present. Is this a bug? What must you do to actually free the storage?
-
A new microservice pod needs to call the Kubernetes API (to list ConfigMaps). It returns
403 Forbidden. Trace the RBAC objects you need to create, and give thekubectl auth can-icommand to verify before deploying.
Jawab dekho
- kubectl โ apiserver (YAML validate) โ writes Deployment to etcd โ Deployment controller creates ReplicaSet โ etcd โ ReplicaSet controller creates Pod objects โ etcd โ Scheduler assigns nodes โ writes nodeName to Pod โ etcd โ kubelet starts containers via containerd โ writes Running/Ready status back to apiserver/etcd.
- Readiness probe failing. Pod alive, zero traffic.
kubectl describe pod <name>(Events: "Readiness probe failed") aurkubectl get endpointslices(pod IP absent from slice). - DB spike โ liveness timeoutSeconds exceeded โ FAIL โ kubelet restarts ALL pods โ reconnect storm on DB โ further spike โ cascading restart loop. Fix: DB check sirf readiness mein; liveness = in-process health flag only (never external dep).
- Pod A (no requests/limits) = BestEffort โ FIRST to die. Pod B (cpu requests < limits, memory nahi) = Burstable. Pod C (requests == limits for both) = Guaranteed โ LAST. Eviction order: A โ B โ C.
- (a) metrics-server install nahi โ confirm:
kubectl top podsfails. (b) pods meinrequestsset nahi โ confirm:kubectl describe hpa <name>shows "unable to get metrics for". - nginx 404 with healthy backend = Host header mismatch in Ingress rules. Test:
curl -H 'Host: api.example.com' http://<node-ip>:30080/โ 200 aaye to Host header hi missing tha. - Intentional, not a bug โ PVCs survive StatefulSet deletion by design; data outlives the controller. Free storage:
kubectl delete pvc data-postgres-0 data-postgres-1 data-postgres-2explicitly. - Create: ServiceAccount โ Role (verbs:
get,list,watchonconfigmaps, scoped to namespace) โ RoleBinding (SA โ Role). Verify:kubectl auth can-i list configmaps -n <ns> --as=system:serviceaccount:<ns>:config-readerโ must returnyes.
Hands-On Lab¶
โ
Sahi hua to aisa dikhega: kubectl describe pod guaranteed | grep "QoS Class" returns Guaranteed; Lab 2 mein kubectl get pods -w pe RESTARTS counter dheere-dheere badhta dikhta hai; Lab 3 ka naya pod kubectl get endpointslices mein Ready hone ke 10s ke andar appear karta hai; Lab 4 mein curl -H 'Host: myapp.example.com' http://<node-ip>:80/ returns HTTP 200 (nginx default 404 nahi); Lab 5 mein kubectl auth can-i list secrets -n default --as=system:serviceaccount:default:limited-reader returns no.
Goal: experience each major concept by breaking and fixing it.
Lab 1: Hit each QoS class and trigger OOM¶
# BestEffort (no requests/limits)
kubectl run besteffort --image=nginx
kubectl describe pod besteffort | grep "QoS Class" # โ BestEffort
# Burstable (request < limit)
kubectl run burstable --image=nginx \
--requests='cpu=100m,memory=64Mi' --limits='cpu=500m,memory=256Mi'
kubectl describe pod burstable | grep "QoS Class" # โ Burstable
# Guaranteed (request == limit)
kubectl run guaranteed --image=nginx \
--requests='cpu=100m,memory=128Mi' --limits='cpu=100m,memory=128Mi'
kubectl describe pod guaranteed | grep "QoS Class" # โ Guaranteed
Lab 2: Break liveness, watch the restart¶
# Deploy with an liveness probe that will fail after 30s
livenessProbe:
exec:
command: ["sh", "-c", "test $(date +%s) -lt $(($(cat /tmp/start) + 30))"]
periodSeconds: 5
kubectl get pods -w # watch RESTARTS counter climb
kubectl describe pod <name> # see "Liveness probe failed" events
kubectl logs <name> --previous # logs from last run
Lab 3: Add probes + resources to an existing Deployment¶
Start from a minimal Deployment (no probes, no resources). Add startup + readiness + liveness probes and requests == limits. Verify:
kubectl rollout status deploy/<name>
kubectl describe pod <new-pod> # verify probe configuration
kubectl get endpointslices # pod should appear within ~10s of Ready
Lab 4: Add an Ingress¶
# Install nginx ingress controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
# Create Ingress for your Service
kubectl apply -f ingress.yaml
# Test with Host header (before DNS is configured)
curl -H 'Host: myapp.example.com' http://<node-ip>:80/
# Debug if 404:
kubectl describe ingress <name> # check rules
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller
Lab 5: RBAC โ kubectl auth can-i with a limited ServiceAccount¶
# Create a ServiceAccount
kubectl create serviceaccount limited-reader -n default
# Create Role (pods only, no secrets)
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n default
# Bind SA to Role
kubectl create rolebinding pod-reader-binding \
--role=pod-reader --serviceaccount=default:limited-reader -n default
# Verify permissions
kubectl auth can-i list pods -n default \
--as=system:serviceaccount:default:limited-reader # โ yes
kubectl auth can-i list secrets -n default \
--as=system:serviceaccount:default:limited-reader # โ no
kubectl auth can-i list pods -n kube-system \
--as=system:serviceaccount:default:limited-reader # โ no (scoped to default only)
Interview Rapid-Fire (~15 Qs)¶
Pehle memory se jawab do, phir neeche kholo.
Full bank in 14-interview-bank.md. These are the highest-signal M9 questions:
Jawab dekho
| # | Question | One-line answer |
|---|---|---|
| 1 | Which K8s component is the only one that touches etcd? | kube-apiserver |
| 2 | etcd Raft quorum โ minimum nodes for 1-failure tolerance? | 3 nodes (majority = 2) |
| 3 | What is the difference between readiness and liveness probe on failure? | Readiness: removes from traffic (pod lives). Liveness: restarts container. |
| 4 | What does the startup probe do that the other two can't? | Holds liveness + readiness until app finishes booting โ prevents crash-loop on slow start |
| 5 | Liveness probe calls the database โ what happens during a DB spike? | Cascading restart loop. Fix: DB checks go in readiness, not liveness. |
| 6 | Node RAM full. Which pod dies first โ BestEffort or Guaranteed? | BestEffort first. QoS class is the eviction order determinant. |
| 7 | How do you make a pod Guaranteed QoS? | Set requests == limits for both CPU and memory |
| 8 | Why does HPA show <unknown>/50%? |
metrics-server not installed, or pods missing requests |
| 9 | HPA scales up immediately but down slowly โ why? | 300s stabilization window prevents flapping on transient load drops |
| 10 | Pod is Running but EndpointSlice is empty โ two possible causes? | Readiness probe failing, or Service selector doesn't match pod labels |
| 11 | iptables vs IPVS for large clusters โ key difference? | iptables O(n) rule scan; IPVS O(1) hash lookup โ measurably faster above ~500 Services |
| 12 | nginx Ingress returns 404 but the Service is healthy โ first thing to check? | Host header in the request must match the Ingress host: rule exactly |
| 13 | Delete a StatefulSet โ what happens to the PVCs? | PVCs survive by design. Must kubectl delete pvc explicitly to free storage. |
| 14 | Why shouldn't an app pod run as cluster-admin? | Compromised pod = full cluster access. Blast radius includes all Secrets, all namespaces. |
| 15 | What does kubectl auth can-i list secrets --as=system:serviceaccount:ns:name do? |
Tests what a specific ServiceAccount is permitted to do โ use before deploying or debugging 403s |
Production Challenge¶
Challenge 1: "Pod is Running but zero traffic"¶
You deployed a new version. kubectl get pods shows Running. Users report zero responses. Traffic dropped to zero.
Walkthrough:
# Step 1: Is the pod in the EndpointSlice?
kubectl get endpointslices -l kubernetes.io/service-name=<svc>
# โ Empty? Pod is not receiving traffic. Investigate why.
# Step 2: Is readiness probe passing?
kubectl describe pod <name>
# โ Look for: "Readiness probe failed" in Events
# Step 3: Does the Service selector match pod labels?
kubectl describe svc <name> # shows Selector
kubectl get pod <name> --show-labels # shows actual labels
# โ Mismatch? Fix the selector or the pod labels.
# Step 4: Can the probe endpoint respond?
kubectl exec <pod> -- curl -s localhost:8080/ready
# โ Non-200? Fix the /ready endpoint in the application.
# Root causes in order of frequency:
# 1. Readiness probe endpoint returns non-200 (app bug / not ready yet)
# 2. Service selector label typo
# 3. Startup probe failing (slow-start app โ add startupProbe)
# 4. Pod has no requests set and readiness path wasn't fixed (OOMKilled on boot)
Challenge 2: "502 Bad Gateway walkthrough"¶
Users report intermittent 502 errors during deployments. No errors between deployments.
Walkthrough โ graceful shutdown diagnosis:
# Confirm 502s correlate with rollout events:
kubectl rollout history deploy/<name>
# Check error-rate spike in Prometheus/Grafana during each rollout (โ 10-M8)
# Check if preStop hook exists:
kubectl get pod <name> -o yaml | grep -A10 lifecycle
# โ Missing preStop? That's your problem.
# Check terminationGracePeriodSeconds:
kubectl get pod <name> -o yaml | grep terminationGracePeriodSeconds
# โ Default 30s. Should be > your max request duration + preStop sleep.
# Fix: add preStop sleep and verify SIGTERM handling in app code
lifecycle:
preStop:
exec:
command: ["sleep", "5"]
# Then redeploy. Monitor error rate during next rollout โ should drop to zero.
# If still 502 after fix: app is not handling SIGTERM gracefully.
# Check: does the app server (gunicorn/express/spring) have graceful shutdown configured?
# Python gunicorn: --graceful-timeout 30
# Node.js: process.on('SIGTERM', () => server.close(...))
Next: 30 โ K8s Complete Reference (plug any remaining workload/scheduling gaps) โ 23 โ Production Incident Playbook (keep it open during every lab) โ then Capstone I, where every concept in this chapter gets applied to a real production-grade build. See 14 โ interview-bank for the 30+ rapid-fire set across all modules.