Organize the answer around ownership, limits, failure, and recovery. Definitions become interview-ready when they survive a concrete production scenario.
Question set
13 detailed answers
01What happens after `kubectl apply -f deployment.yaml`? Walk through the full chain to a running pod.
senior
Short answer: kubectl validates the manifest and sends it to the API server: authn → RBAC → admission webhooks → persisted to etcd. That is where the synchronous part ends — controllers, the scheduler, and kubelet notice changes asynchronously via watch and drive the cluster toward the desired state. Nobody pushes anything.
In depth:
- API server — authentication, authorization (RBAC), admission (mutating → validating webhooks), object stored in etcd.
- Deployment controller — sees the new Deployment (watch) and creates a ReplicaSet.
- ReplicaSet controller — creates Pod objects, with no node assigned yet.
- Scheduler — finds pods with an empty nodeName, filters and scores nodes, binds.
- kubelet — on the chosen node sees "its" pod: pulls images, starts containers via CRI, networking comes up via the CNI plugin.
- Readiness — probe passes → the pod joins the EndpointSlice and starts receiving traffic.
kubectl ──► API server (authn → RBAC → admission) ──► etcd
▲ watch ▲ watch ▲ watch ▲ watch
deploy-ctrl → RS rs-ctrl → Pods scheduler → bind kubelet → CRI/CNI
⚠️ Common mistake: "the API server schedules the pod and starts containers." It only stores and serves state — all the work is done by independent controllers via the watch/reconcile model.
02Walk through Kubernetes architecture: apiserver, etcd, scheduler, controller-manager, kubelet, kube-proxy — what does each one do?
middle
Short answer: The apiserver is the single entry point and the only component that talks to etcd; etcd stores the state; the scheduler picks nodes; the controller-manager runs reconcile loops; kubelet runs containers on the node; kube-proxy programs the networking for Services.
In depth:
| Component | Role in one line |
|---|---|
| kube-apiserver | the cluster's REST API: authn/authz, admission, validation; etcd's only client |
| etcd | consistent KV store of all cluster state (Raft) |
| kube-scheduler | picks a node for each pod (filter + score), writes the binding |
| kube-controller-manager | dozens of controllers — Deployment, ReplicaSet, Node, Job… — reconciling desired state |
| kubelet | node agent: runs pods via CRI, executes probes, reports status |
| kube-proxy | programs iptables/IPVS so Service ClusterIPs work |
The first four are the control plane; kubelet and kube-proxy live on every node. The key idea: everything talks only to the apiserver via watch — components don't know about each other and never touch etcd directly. That's why the control plane survives the loss of any component except etcd.
⚠️ Common mistake: saying the scheduler or controller-manager "writes to etcd." They write to the apiserver — it alone holds the etcd connection.
03Why does etcd need an odd number of members, and what happens to the cluster when quorum is lost?
senior
Short answer: etcd runs Raft: a write commits once a majority — (n/2)+1 — acknowledges it. An even member adds no fault tolerance: both 3 and 4 nodes survive exactly one failure. When quorum is lost, etcd stops accepting writes → the API "freezes": kubectl apply fails, controllers and scheduling stall. Already-running pods keep running.
In depth:
| Members | Quorum | Failures tolerated |
|---|---|---|
| 3 | 2 | 1 |
| 4 | 3 | 1 (the even member bought nothing) |
| 5 | 3 | 2 |
- Quorum lost — writes are impossible; the apiserver can still serve reads, but accepts no changes: no rollouts, no scaling, no new pods.
- The data plane lives on — kubelet keeps containers running, kube-proxy keeps its rules; the cluster is frozen, but traffic flows.
- A slow etcd is worse than a dead one — every API request gets slower, watches lag, controllers react late, leader election flaps.
⚠️ Common mistake: "etcd is down — everything is down." No: the management plane is down; workloads keep serving traffic until you need to change something.
04A pod is stuck in Pending: how do you find the cause, and what are the usual suspects?
middle
Short answer: Pending means "the scheduler found no node." The first step is always the same: kubectl describe pod — in Events the scheduler explains exactly what it disliked. The cause is almost always one of five: resources, taints, affinity, PVC, quota.
In depth:
kubectl describe pod app-7d9f # Events: "0/12 nodes are available: ..."
kubectl get nodes # nodes alive and Ready?
kubectl describe node n1 # Allocatable vs Allocated: do the requests fit?
kubectl get pvc # is the PVC Pending too?
kubectl describe quota -n team # hit the ResourceQuota?
- Insufficient resources — the pod's requests don't fit on any node ("Insufficient cpu/memory").
- Taints without tolerations — "node(s) had untolerated taint".
- nodeSelector / affinity — no node matches the labels.
- Unbound PVC — the pod waits for its volume: no StorageClass, zone mismatch.
- ResourceQuota — the nuance: the pod isn't created at all; the error shows in the ReplicaSet's Events, not as Pending.
⚠️ Common mistake: starting with logs. A Pending pod has no logs — it hasn't started anywhere yet; everything you need is in Events.
05A pod is in CrashLoopBackOff: what is your debugging sequence?
middle
Short answer: CrashLoopBackOff means the container starts, dies, and kubelet restarts it with a growing delay (10s, doubling, capped at 5m). The order: describe (Events and Last State with the exit code) → logs --previous (the logs of the container that actually crashed) → interpret the exit code.
In depth:
kubectl describe pod app-7d9f # Events + Last State: Exit Code
kubectl logs app-7d9f --previous # logs of the CRASHED container — the key flag
kubectl logs app-7d9f -c init-db # if an init container is failing
| Exit code | Meaning |
|---|---|
| 1 | application error — read your own log |
| 127 | no such binary — typo in command/entrypoint |
| 137 | SIGKILL: OOMKilled (check describe) or killed during eviction |
Then, in descending frequency: broken config or secret (missing env, volume didn't mount), a failing liveness probe (Events will say "Liveness probe failed… will be restarted"), a dependency or migrations unavailable at startup.
⚠️ Common mistake: running kubectl logs without --previous and wondering why it's empty: the freshly restarted container hasn't written anything yet — the crashed one's logs live behind the flag.
06OOMKilled with exit code 137: who kills the container — Kubernetes or the kernel? And where do QoS classes come in?
senior
Short answer: The Linux kernel kills it. Kubernetes merely wrote the memory limit into the cgroup (memory.max); when the process exceeds it, the kernel OOM killer fires and sends SIGKILL (128+9=137). The pod's QoS class sets oom_score_adj — deciding whom the kernel and kubelet sacrifice first.
In depth:
| QoS | Condition | oom_score_adj |
|---|---|---|
| Guaranteed | requests = limits for CPU and memory | −997 (dies last) |
| Burstable | requests < limits or partially set | in between, depends on request |
| BestEffort | no requests, no limits | +1000 (dies first) |
Two distinct mechanisms people conflate:
- cgroup OOM (kernel) — the container exceeded ITS OWN limit → instant SIGKILL, status OOMKilled. Kubernetes is not in the loop at that moment.
- Node-pressure eviction (kubelet) — the node as a whole runs out of memory → kubelet evicts pods itself, ranking by QoS and usage-over-request; status Evicted, not OOMKilled.
⚠️ Common mistake: "kubelet watches memory and kills the container when it hits the limit." No: the per-container limit is enforced by the kernel via cgroups; kubelet only steps in under whole-node pressure.
07Requests vs limits: what does each control, and what happens when they're exceeded?
middle
Short answer: Requests are the scheduling currency: the scheduler bin-packs pods onto nodes by them, and they set the CPU weight under contention. Limits are the ceiling: exceeding the CPU limit means throttling, exceeding the memory limit means SIGKILL (OOMKilled). CPU is a compressible resource, memory is not — hence the different punishment.
In depth:
| CPU | Memory | |
|---|---|---|
| request | guaranteed share + weight under contention | only used by the scheduler |
| limit exceeded | throttling: the pod slows down but lives | OOM kill: instant death |
- Requests ≠ actual usage — the scheduler only sums requests: a node can be "full" on paper and idle in reality, and vice versa.
- Limits without requests — Kubernetes automatically sets requests = limits.
- Requests without limits — the pod can burst into free resources; often desirable for CPU, but for memory it risks an OOM on an oversubscribed node.
⚠️ Common mistake: "memory gets throttled above the limit, like CPU." Memory can't be taken away from a process gradually — only by killing it: exceeding the memory limit always ends in SIGKILL.
08How does a Service actually route traffic to pods, and what does kube-proxy do?
senior
Short answer: A ClusterIP is a virtual address: nothing listens on it, and no packet ever "reaches" it. kube-proxy on every node programs iptables (or IPVS) rules: a packet to the ClusterIP gets DNAT'ed to the IP:port of one ready pod right in the node's netfilter. kube-proxy itself is not in the packet path.
In depth:
Pod A ──► 10.96.0.10:80 (ClusterIP: virtual, no process)
│ iptables DNAT (rules written by kube-proxy)
▼ a random ready endpoint
10.244.1.5:8080 (Pod B)
- EndpointSlice — a controller maintains the list of the Service's pods that passed readiness; kube-proxy watches it and rewrites the rules.
- Readiness = membership — a pod fails its probe → drops out of the EndpointSlice → no rule points at it anymore.
- iptables vs IPVS — with thousands of Services the linear iptables chain gets slow; IPVS provides a hash table.
- Ingress — that's L7: a separate controller (nginx, envoy) terminates HTTP and routes by host/path; the Service underneath is the same L4 machinery.
⚠️ Common mistake: thinking kube-proxy is an in-path proxy that packets flow through. It only configures kernel rules and never sees the traffic.
09Liveness vs readiness vs startup probes — and how does a wrong liveness probe cause a cascading outage?
middle
Short answer: Readiness controls traffic (EndpointSlice membership), liveness controls the container's life (failure = restart), startup delays the other two while the app boots. The classic outage: a liveness probe that checks a dependency (the DB) — the database blips, and kubelet restarts the entire fleet at once.
In depth:
| Probe | On failure → | Purpose |
|---|---|---|
| readiness | pod leaves load balancing | "can't take traffic right now" — temporary, reversible |
| liveness | kubelet restarts the container | "the process is hopelessly stuck" — about the process only |
| startup | restart after failureThreshold | slow boot (JVM, cache warmup) without inflating liveness |
Anatomy of the incident: liveness hits /health, which calls the DB → the database is down for 30 seconds → liveness fails on every replica → kubelet restarts all containers → the whole service is down, and the mass restarts finish off the barely recovered database.
The rule: liveness is about the process itself (deadlock, stuck event loop); dependencies are readiness territory — and even there, carefully.
⚠️ Common mistake: one endpoint for both liveness and readiness that checks everything. A single dependency blip becomes a fleet restart instead of a traffic pause.
10Deployment, StatefulSet, or DaemonSet: how do you choose and justify it?
middle
Short answer: Deployment — interchangeable stateless replicas with rolling updates. StatefulSet — when replicas need stable identity: a name (pod-0, pod-1), a PVC per replica, ordered rollout, stable DNS via a headless Service. DaemonSet — exactly one pod on every matching node: log, metrics, CNI agents.
In depth:
| Deployment | StatefulSet | DaemonSet | |
|---|---|---|---|
| Identity | none, replicas interchangeable | stable: web-0, web-1 + DNS | tied to the node |
| Storage | shared or none | PVC template: a volume per replica | hostPath or none |
| Ordering | parallel | ordered rollout and scale | per node |
| Use cases | APIs, workers | Kafka, etcd, DB replicas | node-exporter, fluent-bit |
A headless Service (clusterIP: None) gives every StatefulSet replica a predictable DNS name like web-0.web.ns.svc — that's how brokers and replicas find each other.
⚠️ Common mistake: "there's a database, so StatefulSet." If the DB is managed (RDS, Cloud SQL), the app is fine as a Deployment; and serious in-cluster databases usually run under an operator, which decides what to manage itself.
11How does the HPA decide how many replicas to run, and why doesn't it work without resource requests?
middle
Short answer: By the formula: desiredReplicas = ceil(current metric / target × current replicas). For CPU, "utilization" is computed as a percentage of requests — no requests means no denominator, and the HPA can't compute anything at all.
In depth:
desired = ceil( current / target × currentReplicas )
example: CPU at 80% of request, target 50%, 4 replicas
ceil(80/50 × 4) = ceil(6.4) = 7
- Metric sources — metrics-server (resource metrics) or the custom/external metrics API (RPS, queue depth).
- % of requests — "CPU 80%" means "the pod uses 80% of its request," not of the node; undersized requests make the HPA scale too early, oversized ones mean it never scales.
- Stabilization — a 300s window on scale-down by default (scale-up is immediate): protection against flapping on a noisy metric.
- Bounds — it never goes below minReplicas; when metrics are unavailable, scaling freezes rather than "scaling to zero."
⚠️ Common mistake: pointing an HPA at pods without requests and expecting magic — the status will show FailedGetResourceMetric and the replica count won't move.
12During a rolling update some requests get 5xx. Why, and how do you make the rollout truly zero-downtime?
senior
Short answer: Because of a shutdown race: the SIGTERM to the container and the pod's removal from the EndpointSlice happen in parallel, while iptables on the nodes and external LBs update with a lag — for a while traffic keeps hitting an app that is already dying. The cure is a combo: a preStop pause + proper SIGTERM handling + headroom in the grace period + a PDB.
In depth:
t=0 pod marked for deletion
├─► SIGTERM to the app (instant)
└─► removal from EndpointSlice (async: kube-proxy, LB — lag!)
t=0…3s stale rules still send requests → connection refused / 5xx
fix: preStop sleep 5 → traffic drains FIRST, then the process dies
- preStop hook —
sleep 5: gives the rules and LBs time to pull the pod out of rotation before SIGTERM. - SIGTERM in the app — stop accepting new connections, finish in-flight ones, exit.
- terminationGracePeriodSeconds — longer than worst-case drain + in-flight time (default 30s, then SIGKILL).
- PodDisruptionBudget — on node drains and upgrades (
cordon+drainhonors the PDB) it guarantees a minimum of live replicas.
⚠️ Common mistake: fixing only the app (a SIGTERM handler) and missing the endpoint race: without the preStop pause, requests keep arriving at a pod that has already begun shutting down.
13A team must only have access to its own namespace: which RBAC objects do you create, and how do workloads authenticate?
middle
Short answer: A Role (rules scoped to the namespace) + a RoleBinding to the team's group or users. For reuse — one ClusterRole with the standard permission set and a RoleBinding per namespace: the binding is namespaced, so the permissions are too. Pods authenticate with ServiceAccount tokens.
In depth:
- Role vs ClusterRole — a Role lives in a namespace; a ClusterRole is global, but via a RoleBinding its permissions are narrowed to one namespace — the pattern for giving a dozen teams identical rights.
- Subjects — bind to an IdP group (
group: team-a) rather than to individuals by name. - ServiceAccount — a pod receives its SA's projected token; if the app needs API access, the SA gets bound with the same Role/RoleBinding.
- Verify — don't guess, ask the cluster:
kubectl auth can-i list pods -n team-a --as u@corp --as-group team-a # yes
kubectl auth can-i list pods -n team-b --as u@corp --as-group team-a # no
kubectl auth can-i --list -n team-a --as u@corp # the whole permission set at once
⚠️ Common mistake: handing out cluster-admin "temporarily, to skip the hassle." RBAC is additive — you can't subtract permissions later, only revoke bindings; start from least privilege.
Source notes
References and review policy
RecallDeck’s interview answers are editorial material, reviewed against maintained official documentation where a primary reference is available. Tool selections use direct provider links and contain no affiliate placements. Features can change after the review date.
From reading to recall
Practice the full interview loop.
RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.