Kubernetes
Docker runs a container on one machine; Kubernetes runs thousands of them across a fleet, healing and scaling them without you watching. This is the largest chapter in the course, and it is built the same way the instructor teaches it — always the why before the how. First you understand the four problems with plain Docker that Kubernetes exists to solve, then its architecture, then you get hands-on with every core object — pods, deployments, services, ingress, config, security, and the extensibility that makes the whole ecosystem possible — then you get quizzed the way an interviewer would, and finally you face the senior scenarios where a crash-loop, a bad rollout, or a missing resource limit is yours to diagnose.
Kubernetes is a container orchestration platform — a cluster of machines that runs your containers, keeps them running, and scales them, all from a description you write in YAML. Where Docker runs on a single host and does nothing when a container dies, Kubernetes is a cluster with a brain (the control plane) and muscle (the worker nodes). You tell the control plane the desired state — "I want three copies of this app running" — and it works continuously to make reality match: if a copy dies it starts another (auto-healing), if load rises it adds more (auto-scaling), and it gives you the enterprise machinery — load balancing, networking, security — that plain Docker lacks. Everything is declared in YAML and driven through one CLI, kubectl.
Five ideas carry this chapter: (1) Kubernetes exists to fix four things Docker can't do alone — it's a cluster, it auto-heals, it auto-scales, and it's enterprise-ready; (2) that cluster is a control plane plus worker nodes, each with a handful of named components; (3) you never deploy a bare container — you deploy a Deployment, which manages a ReplicaSet, which manages Pods; (4) you reach those pods through a Service, and the outside world through Ingress or the newer Gateway API; and (5) everything is declared in YAML, and Kubernetes' real superpower is that you can extend it with your own resources — which is why the whole cloud-native ecosystem is built on top of it.
- ① Theory — the concepts (Day 30–32)
- Why Kubernetes — the four problems it solves
- The architecture — control plane and worker nodes
- Production clusters — distributions and kops
- ② Lab — hands-on with Kubernetes (Day 33–44)
- A cluster with minikube, and kubectl
- Pods — deploy your first app
- Deployments & ReplicaSets — healing and scaling
- Services — discovery, load balancing, exposure
- Ingress — enterprise routing into the cluster
- ConfigMaps & Secrets — configuration
- RBAC — who can do what
- Custom Resources & Operators — extending Kubernetes
- The Gateway API — the successor to Ingress
- ③ Interview Q&A — the questions the instructor flags
- ④ Scenario drills — Kubernetes under pressure (28)
Why Kubernetes — the four problems it solves
Kubernetes is easy once you see it as the answer to four specific things Docker cannot do on its own. Learn the why and the rest follows.
The textbook line is that Docker is a container platform and Kubernetes is a container orchestration platform — true, but empty until you see the four practical gaps. (1) Single host. Docker runs on one machine; if one container hogs memory, the Linux kernel may kill a neighbour, and if the whole host dies, every container with it. There's no notion of spreading work across machines. (2) No auto-healing. When a container dies — and there are a hundred reasons it might — Docker does nothing; a human has to notice and restart it, which is impossible across ten thousand containers. (3) No auto-scaling. When load spikes from ten thousand users to a million during a sale, Docker won't add copies or balance traffic across them by itself. (4) No enterprise support. Production needs load balancers, firewalls, API gateways, allow/deny lists — plain Docker offers none of it. This is why nobody runs bare Docker in production.
Kubernetes fills all four because it is, by default, a cluster — a group of nodes. If one node's application misbehaves, Kubernetes can move the affected work to another node. It auto-heals through controllers like the ReplicaSet that constantly ensure the number of running copies matches what you asked for. It auto-scales — manually by changing a replica count in YAML, or automatically with the Horizontal Pod Autoscaler that adds pods when a threshold is crossed — and load-balances across them. And it brings enterprise machinery, extended endlessly by the community through custom resources. Kubernetes came out of Google's internal "Borg" system, is backed by the CNCF, and — importantly — is still evolving, not yet a 100% turnkey replacement for the old VM world, which is exactly why understanding it deeply is so valuable.
The architecture — control plane and worker nodes
The clearest way to learn the architecture is to compare it with Docker: what has to happen for one container to run, and what Kubernetes adds around it.
In Docker, running a container needs just a container runtime. Kubernetes keeps that but wraps it in a cluster split into two planes. The worker node (data plane) is where your applications actually run, and it has exactly three components. The kubelet runs and watches your pods — if a pod goes down, the kubelet notices and reports it so the cluster can react. The kube-proxy handles networking: it gives pods IP addresses and does basic load balancing by programming the node's iptables rules. And the container runtime (containerd, CRI-O, or others via the Container Runtime Interface) actually runs the containers — Kubernetes isn't tied to Docker for this.
The control plane (master) is the brain, with five components. The API server is the heart — every request, from you or from other components, goes through it, and it's the cluster's front door to the outside world. The scheduler decides which node a new pod should run on. etcd is a key-value store holding the entire cluster's state as objects — lose it and you lose the cluster's memory, which is why it's what you back up. The controller manager runs the built-in controllers (like the ReplicaSet controller) that keep desired state matching actual state. And the cloud controller manager translates Kubernetes requests into a specific cloud's API — so when you ask for a load balancer on EKS, it's the CCM (with logic contributed by AWS) that provisions a real AWS load balancer; on-premises clusters don't need it. Together: the control plane decides, the data plane executes.
Production clusters — distributions and kops
The clusters you learn on are not the clusters you run in production, and interviewers care about the difference.
Local tools like minikube, kind, k3s, and microk8s are development environments — perfect for learning, explicitly not for production (minikube's own docs say so). In a real organisation you run a distribution: open-source software built on top of Kubernetes that adds support and a nicer experience, exactly as Amazon Linux, RHEL, and Ubuntu are distributions of Linux. The main ones, roughly by usage, are plain Kubernetes itself, then OpenShift (Red Hat), Rancher, VMware Tanzu, and the managed services EKS / AKS / GKE. The difference between self-managed Kubernetes and a managed service like EKS is simply support: Amazon supports your EKS cluster, but not a cluster you hand-install on EC2.
A DevOps engineer doesn't just install a cluster — they own its whole lifecycle: creation, upgrades, configuration, and deletion. The most widely used tool for this on AWS is kops (Kubernetes Operations); it stands up production-grade clusters and manages upgrades, backed by real infrastructure (EC2 instances, EBS volumes, an S3 bucket that stores the cluster config, and Route 53 for the domain). Older or alternative tools include kubeadm (more manual) and kubespray. The interview point is simple and firm: never say you run minikube in production — name a distribution and a lifecycle tool like kops. For learning the rest of this chapter, though, minikube is exactly right.
A cluster with minikube, and kubectl
You need two things: kubectl, the Kubernetes command-line tool (the equivalent of the Docker CLI), and a cluster. For learning, minikube creates a single-node cluster inside a local VM — where that one node is both control plane and worker. Install both from the docs, start the cluster, and confirm kubectl is talking to it:
bashminikube start # creates a local single-node cluster (in a VM)
kubectl get nodes # should list one node, status Ready
kubectl version # confirm the client is connected
kubectl is how you interact with the cluster for everything — kubectl get pods, get deployment, get all, get nodes, and the create/apply/delete commands you'll use throughout. Nobody memorises every command; keep the official kubectl cheat sheet open — even experienced engineers reference it. In production you'd SSH into a real node instead of minikube ssh, but the objects and commands are identical.
Pods — deploy your first app
In Kubernetes you don't run a container directly — you deploy a pod. Understanding why is the first real step.
A pod is "a definition of how to run a container." Everything you'd pass to docker run on the command line — the image, the port, volumes, networking — you instead put into a pod YAML. That's Kubernetes' declarative model: describe the desired result in a file rather than typing imperative commands. A pod usually holds one container, but it can hold several — a main container plus sidecar or init containers — and containers in the same pod share networking and storage, so they can reach each other over localhost and share files. Kubernetes gives the pod (not the container) a cluster IP:
yamlapiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2 # equivalent to: docker run -d --name nginx -p 80:80 nginx
ports:
- containerPort: 80
bashkubectl apply -f pod.yaml # create the pod
kubectl get pods -o wide # see it running, with its IP
kubectl describe pod nginx # full status — the #1 debug command
kubectl logs nginx # the app's logs — the #2 debug command
Those two commands — describe (what state is the pod in, and why) and logs (what is the app saying) — are your go-to debugging pair, and a common interview question. But you almost never create bare pods in production: a pod alone gives you no auto-healing and no scaling. For that you wrap it in a Deployment, which is next.
Deployments & ReplicaSets — healing and scaling
The difference between a container, a pod, and a Deployment is a favourite interview question — and the whole reason you moved to Kubernetes.
A container is the packaged app; a pod is its runtime spec in YAML; a Deployment is the wrapper that gives you the two features you came for — auto-healing and auto-scaling. You never create a pod directly in production; you create a Deployment, which creates a ReplicaSet, which creates the pods. The ReplicaSet is a controller: its whole job is to keep the number of running pods equal to the number you declared. Ask for three, delete one by accident, and the ReplicaSet immediately makes a new one — that's auto-healing. Change the replica count from three to ten and it creates seven more, with no downtime — that's scaling. A controller is simply anything that continuously drives actual state toward desired state.
bashkubectl apply -f deployment.yaml # kind: Deployment, replicas: 3
kubectl get deploy,rs,pods # see the Deployment, its ReplicaSet, and the pods
kubectl get pods -w # watch: delete a pod and a new one appears immediately
A Deployment YAML is essentially a pod spec with kind: Deployment, a replicas count, and a template holding the pod. In real life you deploy Deployments (or their relatives StatefulSets and DaemonSets), never bare pods — and you copy the YAML from the docs rather than memorising it.
Services — discovery, load balancing, exposure
A Deployment gives you healing pods — but each healed pod comes back with a new IP. A Service is what makes that not matter.
Without a Service, three problems appear. When a pod auto-heals it gets a new IP, so anyone who saved the old IP is now broken. There's no single address to spread traffic across replicas. And there's no way for outside users to reach the app. A Service solves all three. It gives you load balancing across the pod replicas (via kube-proxy). It gives you service discovery using labels and selectors instead of IPs — the Service tracks pods by a label like app: payment, so however often a pod dies and returns with a fresh IP, the label stays the same and the Service still finds it. And it can expose the app, in one of three types:
ClusterIP keeps it internal; NodePort opens it to whoever can reach a node; LoadBalancer asks the cloud for a public IP (via the cloud controller manager) so the whole world can reach it.Clients reach a Service by a stable DNS name like payment.default.svc instead of chasing pod IPs. Choose the type by audience: ClusterIP (the default) for internal-only, NodePort for access within your organisation or VPC, LoadBalancer for the public internet. Note that LoadBalancer only works on a cloud provider — not on plain minikube — which is one of the reasons Ingress exists.
Ingress — enterprise routing into the cluster
Services can expose apps, but teams coming from the VM world quickly hit two walls. Ingress is the answer to both.
People migrating from virtual machines had rich enterprise load balancers (nginx, F5, HAProxy) offering path- and host-based routing, sticky sessions, ratio splitting, TLS, allow/deny lists, and a web application firewall. Kubernetes Services gave them only simple round-robin load balancing — that's problem one. And exposing every service as LoadBalancer means a static public IP per service, which cloud providers charge for — thousands of services, thousands of paid IPs — that's problem two. Ingress fixes both. You write one Ingress resource (a YAML) describing your routing rules — path-based, host-based, TLS — and one Ingress can front many services.
The key architectural idea: Kubernetes deliberately has no built-in load balancer, so the load-balancer companies write Ingress controllers, and you deploy one (once, cluster-wide, via Helm or manifests — on minikube, minikube addons enable ingress). Without a controller, an Ingress resource does nothing — exactly like a light switch with no wiring behind it. A frequent interview question is the difference between a LoadBalancer Service and an Ingress: the Service gives basic round-robin and a paid IP per service, while Ingress gives enterprise routing behind a single entry point.
ConfigMaps & Secrets — configuration
Apps need settings — a database port, a username, a password — and hard-coding them is wrong. Kubernetes has two homes for configuration, and the difference is a classic interview question.
A ConfigMap stores non-sensitive configuration (a DB port, a connection type) so your app reads it at runtime instead of baking it into the image. A Secret stores the same kind of data but for sensitive values (a DB username or password). The critical difference: a Secret is encrypted at rest in etcd, while a ConfigMap sits there in plaintext. (Kubernetes' default Secret encoding is only base64, which is weak — for real security you layer on a proper encryption provider or a tool like HashiCorp Vault or Sealed Secrets, and you lock down access with RBAC so only the right people can read Secrets.) Both are injected into pods two ways — as environment variables or as mounted files:
bashkubectl create configmap test-cm --from-literal=DB_PORT=3306
kubectl create secret generic test-secret --from-literal=DB_PASSWORD=abc123
kubectl describe cm test-cm # see the data
kubectl get secret test-secret -o yaml # values are base64; decode with: base64 --decode
There's an important operational gotcha the instructor demonstrates. If you inject config as environment variables, a change to the ConfigMap does not reach the running pod — env vars can't change without restarting the container, which risks downtime. If instead you inject config as a volume mount (a file), Kubernetes keeps re-reading the ConfigMap and the change propagates into the pod without a restart. So for values that change over time, mount them as files rather than passing them as environment variables.
RBAC — who can do what
RBAC is a simple idea that becomes very painful if you get it wrong, because it's security. Understand the concept first; the YAML takes ten minutes.
RBAC — Role-Based Access Control — governs two kinds of actors: users (people) and service accounts (applications running in pods). A key surprise: Kubernetes does not manage users itself. It offloads that to an identity provider — the API server acts as an OAuth server, and you plug in AWS IAM on EKS, or LDAP, Okta, or a broker like Keycloak — just like "log in with Google" on a website. Service accounts, by contrast, are Kubernetes objects you create in YAML, and every pod gets a default one to talk to the API server. On top of either actor, access is defined by two resources: a Role lists permissions ("can read pods, configmaps, secrets in this namespace"), and a RoleBinding attaches that Role to a user or service account. Their cluster-wide versions are ClusterRole and ClusterRoleBinding.
The mental model is three pieces: the actor (user or service account), the Role (the permissions), and the RoleBinding (the glue that joins them). A Role without a binding grants nothing; a binding without a Role grants nothing. This is also where namespaces earn their keep — a namespace is a logical partition of one cluster so many teams can share it safely, and RBAC is how you enforce that isolation. The guiding principle throughout is least privilege: grant the minimum access each person or app actually needs, which is exactly how you'd protect Secrets from a developer who only needs to read pods.
Custom Resources & Operators — extending Kubernetes
This is Kubernetes' real superpower, and the reason the whole cloud-native ecosystem — Argo CD, Istio, Prometheus, and hundreds more — exists on top of it.
Kubernetes ships with native resources (Deployment, Service, Pod, ConfigMap, Secret) but can't possibly build in logic for every tool anyone might want. So it lets you extend its API with three pieces. A Custom Resource Definition (CRD) is a YAML that defines a brand-new type of resource and validates it — exactly as Kubernetes internally has a definition that validates your deployment.yaml. A Custom Resource (CR) is an instance a user then creates of that new type (for example, Istio's VirtualService). And a Custom Controller watches for those CRs and does the real work — just as the built-in deployment controller acts on Deployments.
deployment.yaml (a resource) is validated by Kubernetes' built-in resource definition and acted on by the built-in deployment controller. A custom virtualservice.yaml (a resource) is validated by a CRD and acted on by a custom controller. Same shape — the only difference is that for custom resources you deploy all three pieces yourself. And just like an Ingress resource does nothing without an Ingress controller, a Custom Resource does nothing without its custom controller watching it.In practice a DevOps engineer installs a tool's CRD and controller (usually with a single Helm chart), and users then create the custom resources. Custom controllers are typically written in Go (using client-go and the controller-runtime, because Kubernetes itself is Go), and an Operator is a controller that also encodes operational know-how for running a specific application. For most DevOps roles you don't write the Go code — you deploy these pieces and debug them (check the controller's logs, describe the custom resource) when a teammate's Istio or Argo CD isn't behaving. The place to browse what's out there is the CNCF, which is essentially a home for custom Kubernetes controllers.
The Gateway API — the successor to Ingress
Ingress solved north–south traffic, but after a few years two real drawbacks pushed the community toward a successor. The Gateway API is that successor.
Kubernetes handles east–west traffic (service-to-service, inside the cluster) natively, but has no built-in load balancer for north–south traffic (outside users reaching pods) — Ingress was the fix. Its first drawback: the Ingress spec is tiny. It can only say "which service, on which path." Everything a real production edge needs — rate limiting, a web application firewall, HTTP redirects, canary/traffic splitting, URL rewrites — isn't in the spec, so controllers bolted it on via annotations, which ballooned into thirty or forty complex, controller-specific JSON blobs that made Ingress files unreadable and non-portable. Its second drawback: no separation of roles — platform engineers and DevOps engineers both edit the same Ingress file (the ingressClassName versus the routing rules), so they can clobber each other.
The Gateway API solves the same north–south problem but fixes both drawbacks by splitting Ingress into three CRDs, with clear ownership. The GatewayClass is created by the platform engineer when they install a gateway controller (Envoy, nginx, and others) — it names which controller to use. The Gateway, created by DevOps, picks a GatewayClass and defines the listener (e.g. port 80). And the HTTPRoute, also DevOps-owned, holds the backend and all the production features — expressed as clean fields, not annotations.
weight: 8 / weight: 2) instead of sprawling annotations.Because these are CRDs (see the previous lesson), the community can evolve them quickly. And the features that were painful annotations become one-liners: to send 80% of traffic to one version and 20% to a new one — a canary — you just set weight: 8 and weight: 2 on two backends. Note that traffic splitting is not round-robin: round-robin alternates every request one-for-one, while splitting sends a percentage of overall traffic to each backend. The Gateway API is genuinely harder to grasp than Ingress the first time — the fix is to build the small demo yourself.
Q1What is the difference between Docker and Kubernetes?reveal ▸
Docker is a container platform; Kubernetes is a container orchestration platform. Containers are ephemeral — when one dies, the app is down — so Kubernetes adds what Docker lacks: it's a cluster (so a node failure moves work elsewhere), it auto-heals dead pods, it auto-scales under load, and it brings enterprise capabilities like advanced load balancing plus extensibility through custom resources. That's why bare Docker isn't run in production.
Q2What are the main components of Kubernetes architecture?reveal ▸
Split into control plane and data plane. Control plane: the API server (front door for every request), the scheduler (places pods on nodes), etcd (key-value store of all cluster state), the controller manager (runs built-in controllers like ReplicaSet), and the cloud controller manager (talks to the cloud's API). Data plane, on every worker node: the kubelet (manages pods on the node), kube-proxy (networking via iptables), and the container runtime (runs the containers).
Q3What's the difference between a container, a pod, and a deployment?reveal ▸
A container is the packaged application. A pod is the smallest deployable unit — a runtime spec (in YAML) for one or more containers that share networking and storage. A Deployment is a wrapper over pods (via a ReplicaSet) that adds auto-healing and auto-scaling. In production you deploy Deployments, never bare pods.
Q4What's the difference between a Deployment and a ReplicaSet?reveal ▸
A Deployment is the high-level abstraction you write; it creates and manages a ReplicaSet, which is the controller that actually keeps the desired number of pods running. The ReplicaSet implements auto-healing — if a pod is deleted, it recreates it to match the declared replica count. You interact with the Deployment; it delegates the pod-count enforcement to the ReplicaSet.
Q5What is a namespace in Kubernetes?reveal ▸
A namespace is a logical isolation of resources within one cluster, so many teams or projects can share it without interfering. Each team gets its own namespace, and you enforce the boundary with RBAC and network policies — physically the same cluster, logically separated.
Q6What is the role of kube-proxy?reveal ▸
kube-proxy handles networking on each node by configuring rules (by default in iptables). When you create a NodePort Service, kube-proxy is what programs the node so that a request to the node's IP and port is routed to the right pod. It also provides the basic load balancing across pod replicas.
Q7What is the role of the kubelet?reveal ▸
The kubelet manages the pod lifecycle on a worker node. It continuously watches the pods scheduled to its node; if one goes down, it reports to the API server, which lets the ReplicaSet controller bring the count back up. It's the node-level agent that makes sure the pods that should be running are running.
Q8What are the types of Services, and how do they differ?reveal ▸
ClusterIP (default) — reachable only inside the cluster. NodePort — reachable by anyone who can hit a worker node's IP on the assigned port (your org or VPC). LoadBalancer — the cloud controller manager provisions a public IP via the cloud's load balancer, so anyone on the internet can reach it. All three also give you service discovery and load balancing; the type only changes who can reach the app.
Q9How does a Service do service discovery when pod IPs keep changing?reveal ▸
Through labels and selectors, not IPs. The Service selects pods by a label (like app: payment). When a pod dies and the ReplicaSet recreates it, the new pod comes back with the same label even though its IP changed, so the Service still finds it. Clients use a stable DNS name for the Service and never track pod IPs themselves.
Q10Why is Ingress needed, and how is it different from a LoadBalancer Service?reveal ▸
A LoadBalancer Service only gives simple round-robin load balancing and needs a separate paid public IP per service — expensive at scale. Ingress fixes both: one entry point can route to many services (by path or host), and it supports enterprise features (TLS, host/path routing, and more) that Services lack. Ingress needs an Ingress controller (nginx, F5, HAProxy) deployed to actually do the routing — the resource alone does nothing.
Q11What is an Ingress controller?reveal ▸
Kubernetes has no built-in load balancer, so the load-balancer companies write Ingress controllers — the actual load balancer that watches for Ingress resources and implements the routing they describe. You (or the platform team) deploy one controller cluster-wide, and after that any number of Ingress resources work against it. Without a controller, Ingress resources have no effect.
Q12What is the difference between a ConfigMap and a Secret?reveal ▸
Both pass configuration into pods (as env vars or mounted files). A ConfigMap is for non-sensitive data and sits in etcd as plaintext; a Secret is for sensitive data and is encrypted at rest in etcd. Kubernetes' default Secret encoding is only base64, so for real protection you add a proper encryption provider (or Vault) and restrict access with RBAC.
Q13What is RBAC, and how does Kubernetes manage users?reveal ▸
RBAC (Role-Based Access Control) governs what users and service accounts can do, via a Role (permissions), a RoleBinding (attaches the Role to an actor), and their cluster-wide equivalents. Kubernetes doesn't manage users itself — it offloads user management to an identity provider (AWS IAM, LDAP, Okta, Keycloak) with the API server acting as an OAuth server. Service accounts, used by pods, are native Kubernetes objects. The guiding rule is least privilege.
Q14What are CRDs, Custom Resources, and Custom Controllers?reveal ▸
They're how you extend the Kubernetes API. A CRD defines a new type of resource and validates it; a Custom Resource is an instance of that type a user creates; a Custom Controller watches those resources and does the work. It mirrors how a native Deployment is validated by a built-in definition and acted on by a built-in controller. It's how tools like Istio, Argo CD, and Prometheus add capabilities — and like Ingress, a custom resource does nothing without its controller.
Q15How do you debug a failing pod?reveal ▸
Two go-to commands. kubectl describe pod <name> shows the pod's full status and events — why it isn't running, what error it hit, its current state. kubectl logs <name> (add --previous for a crashed container) shows what the application itself is saying. Together they cover the large majority of pod problems.
Q16What's the difference between Docker Swarm and Kubernetes?reveal ▸
Both are container orchestrators, but Docker Swarm is simple and suited to small-scale setups, while Kubernetes suits mid-to-large enterprises — richer scaling, advanced networking (Calico, Flannel), and a huge third-party ecosystem via custom resources and the CNCF. Swarm is easier to start with; Kubernetes is what the market overwhelmingly uses, which is why it's the one to learn. (Note the fair comparison is Swarm vs Kubernetes — not Compose vs Kubernetes.)
Q17What is a production Kubernetes setup — not minikube?reveal ▸
Local tools (minikube, kind, k3s) are development-only. In production you use a distribution — plain Kubernetes, OpenShift, Rancher, Tanzu, or a managed service like EKS/AKS/GKE — and manage its lifecycle (create, upgrade, configure, delete) with a tool such as kops (or kubeadm/kubespray). Never claim minikube in production; name a distribution and a lifecycle tool.
Q18What are your day-to-day activities on Kubernetes?reveal ▸
Keep it concrete and simple: we manage the organisation's clusters and make sure applications deploy and run cleanly; we've set up monitoring; we act as the Kubernetes subject-matter experts when developers hit pod, service, or routing issues; and we handle maintenance — upgrading node versions, applying mandatory packages, keeping nodes free of security vulnerabilities — often driven through JIRA tickets. It's usually an opening question, so a confident, plain answer sets the tone.
Kubernetes
28 scenarios · 1–28The complete Kubernetes scenario set, worked through in depth. This is the thinking and the story behind each one, not answers to memorise. The goal is that a pod stuck in CrashLoopBackOff or a rollout gone bad is a puzzle you already know how to open, not a page you have to look up.
Almost every pod problem — 70% of them — is solved by the same four-move sequence, in order, before anything cleverer:
kubectl get pods— what state is it actually in? (Pending,CrashLoopBackOff,ImagePullBackOff,Runningbut not ready…)kubectl describe pod— read the Events at the bottom and the Last State block; this is where scheduling failures, image errors, and OOM/exit codes actually show up in plain English.kubectl logs --previous— the logs of the crashed container, not the new one that just started; this is what "the app said right before it died."kubectl get events --sort-by=.lastTimestamp— the cluster-wide timeline, for when the problem isn't the pod itself but something around it (a node, a scheduler decision, an admission webhook).
Almost every scenario below is this same reflex applied to a specific corner of the platform — networking, storage, RBAC, rollouts, scale. Get the reflex automatic and the rest is detail.
---
📋 The full scenario inventory (distinct — no padding)
A. Pod lifecycle & scheduling
- Pod in CrashLoopBackOff
- Pod stuck in Pending — scheduling failures
- Pod OOMKilled
- ImagePullBackOff / ErrImagePull
- Liveness/readiness probe misconfiguration
- Init container stuck / never completes
B. Rollouts & delivery
- A rolling update caused downtime
- Rollback after a bad deployment
- Deployment stuck mid-rollout
C. Networking & service discovery
- Service isn't routing traffic to pods
- DNS resolution failing inside the cluster
- Ingress routing to the wrong service / 404s
- NetworkPolicy blocking traffic unexpectedly
D. Storage
- PVC stuck Pending — storage class / provisioner issues
- StatefulSet pod can't reattach its volume
E. Security & access
- RBAC "forbidden" errors
- A ServiceAccount has too much (or too little) access
- Secrets ending up visible where they shouldn't
F. Capacity, scale & scheduling policy
- HPA isn't scaling the way you expect
- Noisy-neighbour pods on a shared node
- Taints, tolerations & affinity gone wrong
- PodDisruptionBudget blocking a node drain
G. Cluster operations
- A node goes NotReady
- Draining a node safely
- Planning a cluster version upgrade
- etcd backup and restore
H. Extensibility & platform
- An admission webhook is blocking valid deployments
- A CRD/controller stops reconciling
I. Production readiness
- Designing a workload for production-grade HA
---
1Pod in CrashLoopBackOffScenario▸
A pod keeps restarting — kubectl get pods shows CrashLoopBackOff with a climbing restart count, and the backoff between restarts keeps growing.
What is actually happening (the mental model).
CrashLoopBackOff means the container starts, exits, and Kubernetes is restarting it with an exponentially increasing delay — it isn't a failure state itself, it's a description of a pattern (start → exit → wait → repeat). The real cause is always in why the process exited, which the crash-loop label doesn't tell you.
How to work through it.
kubectl describe pod— check the Last State block for the exit code (0 = clean exit but the process shouldn't have exited; 1 = generic app error; 137 = SIGKILL, usually OOM; 143 = SIGTERM).kubectl logs --previous— the crashed container's logs, which usually show the actual application error.- If logs are empty, the process may be exiting before it can log anything — check the command/entrypoint and confirm the image actually runs standalone (
docker runit locally). - Common root causes: a missing ConfigMap/Secret key the app expects at startup, a bad DB connection string, a misconfigured liveness probe killing a slow-starting app, or a genuine application bug.
Root causes, and how to tell them apart.
Exit code 137 → OOM (check limits and actual usage). Exit code 1 with a stack trace in --previous logs → application bug or bad config. Empty logs → the process died before logging, often a missing env var/secret the entrypoint reads immediately. Crashes only under load → look at readiness/liveness probes killing it prematurely, not the app itself.
The trap that less-experienced engineers fall into.
Reading the current logs (of the new, still-starting container) instead of --previous, and concluding "there are no errors" when the actual crash information is one restart behind.
🎯 Interviewer follow-up questions you should expect.
- "What does exit code 137 mean?" It's SIGKILL, almost always from an OOM kill — you check the memory limits against actual usage.
- "Why are the logs empty?" Because the process died before it could log anything — you check the entrypoint and the required startup configuration.
- "How do you see the logs of a container that already crashed?" With
kubectl logs --previous.
describe pod for the exit code and logs --previous for what the crashed container actually said. A 137 sends me to memory limits, a 1 with a stack trace sends me to the app config, and empty logs tell me the process died before it could log anything, which usually means a missing secret or env var at startup."---
2Pod stuck in Pending — scheduling failuresScenario▸
A pod has been sitting in Pending for several minutes — it was never even placed on a node.
What is actually happening (the mental model).
Pending means the scheduler hasn't been able to find a node that satisfies the pod's requirements. This is entirely a placement problem — the pod's own container hasn't even started yet, so logs won't help; describe and its Events are the whole story.
How to work through it.
kubectl describe pod— the Events section spells out the scheduler's reason: "Insufficient cpu/memory," "didn't match node selector," "had taints the pod didn't tolerate," or "no nodes available."- Check requested resources against real node capacity (
kubectl describe nodes, orkubectl top nodes) — over-requesting is the most common cause. - Check node affinity/selectors and taints/tolerations if resources look fine — a pod can be unschedulable purely on policy, with plenty of free capacity elsewhere.
- If using a cluster autoscaler, confirm it's actually configured to add capacity — otherwise the pod waits forever for a node that will never appear.
Root causes, and how to tell them apart.
"Insufficient cpu/memory" → real capacity shortage, add nodes or lower requests. "Didn't match node selector/affinity" or "had taint X the pod didn't tolerate" → a policy mismatch, not a capacity problem — fix the pod spec or the node labels. No autoscaler and no free capacity → the cluster is simply full.
The trap that less-experienced engineers fall into.
Checking pod logs first (there are none — it never started) instead of going straight to describe and reading the scheduler's Events, which almost always states the exact reason in plain text.
🎯 Interviewer follow-up questions you should expect.
- "A pod is Pending — what's the first thing you check?" The
describe podEvents, which name the scheduling failure directly. - "Node has free capacity but the pod still won't schedule — why?" It's usually a taint or toleration mismatch, or an affinity or selector mismatch, not a resource problem.
describe pod and read the Events, which name the exact reason: insufficient resources, an affinity or selector mismatch, or an untolerated taint. If it's resources I check real node capacity; if it's a policy mismatch, the fix is in the pod spec or the node labels, not more capacity."---
3Pod OOMKilledScenario▸
A pod keeps dying with reason OOMKilled in describe, even though the application "shouldn't" be using that much memory.
What is actually happening (the mental model).
The container hit its memory limit and the kernel's cgroup OOM killer terminated it — this is a hard ceiling, not a soft warning. It can mean the limit is genuinely too low, or that the app has a real memory leak/spike, or — very commonly — that a JVM/Node runtime is sizing its heap against the host's memory rather than the container's actual limit.
How to work through it.
- Confirm it with
describe pod—Last State: Terminated, Reason: OOMKilled, exit code 137. - Compare the limit against actual usage over time (metrics-server / Prometheus) — is this a spike, a slow leak, or just an under-sized limit for normal operation?
- If the runtime is JVM/Node/similar, confirm it's cgroup-aware (recent versions are; older ones need explicit heap flags sized to the container limit, not the host).
- Fix by right-sizing the limit to real usage with headroom, or fixing an actual leak — don't just keep raising the limit as a reflex.
Root causes, and how to tell them apart.
Usage climbs steadily over the pod's lifetime → a real leak. Usage is fine normally but spikes under specific load → the limit needs headroom for peaks, or the workload needs to be optimised. Usage looks fine in metrics but the pod still OOMs → the runtime isn't respecting the cgroup limit at all.
The trap that less-experienced engineers fall into.
Reflexively doubling the memory limit every time this happens, without ever looking at whether it's a leak (which will eventually hit any limit) or a cgroup-unaware runtime (which no limit increase truly fixes).
🎯 Interviewer follow-up questions you should expect.
- "How do you confirm a pod was OOMKilled?" You run
describe podand look for Last State Terminated, with Reason OOMKilled and exit code 137. - "You raised the limit and it still OOMs eventually — why?" It's likely a real memory leak, not an under-sized limit.
---
4ImagePullBackOff / ErrImagePullScenario▸
A pod is stuck with ImagePullBackOff — Kubernetes can't pull the container image, and it's retrying with backoff.
What is actually happening (the mental model).
The kubelet on the node tried to pull the image and failed — this is entirely about the image reference and registry access, not the application. Common causes: a typo in the image name or tag, the image genuinely doesn't exist at that tag, or the node lacks credentials for a private registry.
How to work through it.
kubectl describe pod— the Events show the exact pull error: "manifest not found," "unauthorized," "not found," or a network timeout to the registry.- Double-check the image name and tag for typos, and confirm that tag actually exists in the registry.
- If private, confirm an
imagePullSecretis attached to the pod (or its ServiceAccount) and that the credentials in it are still valid — expired registry credentials are a very common cause. - If it's intermittent, check registry rate limits (Docker Hub's anonymous-pull limits are a classic culprit) or node-to-registry network/firewall issues.
Root causes, and how to tell them apart.
"Manifest not found"/"not found" → wrong name or tag, or the image was never pushed. "Unauthorized"/"denied" → missing or expired imagePullSecret. Works sometimes, fails other times → registry rate limiting or a flaky network path from the node.
The trap that less-experienced engineers fall into.
Assuming the application is broken and digging through app logs — there are no app logs, because the container image was never even pulled.
🎯 Interviewer follow-up questions you should expect.
- "ImagePullBackOff — first thing you check?" The
describe podEvents for the exact pull error, then the image name and tag, and the imagePullSecret. - "It works most of the time but fails intermittently — why?" It's likely registry rate limiting or a flaky network path, not the image itself.
describe pod for the exact pull error. 'Not found' means the name or tag is wrong; 'unauthorized' means the imagePullSecret is missing or expired; and if it's only intermittent I look at registry rate limits or the node's network path to the registry before I look at anything application-side."---
5Liveness/readiness probe misconfigurationScenario▸
A perfectly healthy application keeps getting restarted by Kubernetes itself, or never shows as "ready" even though it's clearly serving traffic fine.
What is actually happening (the mental model).
Liveness and readiness probes are Kubernetes actively testing the app and acting on the result — a liveness failure gets the container killed and restarted; a readiness failure gets the pod pulled out of Service endpoints (traffic stops, but the pod keeps running). Misconfigured probes cause Kubernetes to "fix" a problem that doesn't exist.
How to work through it.
- Identify which probe is failing and what it actually checks —
describe podshows probe failure events. - The classic mistake:
initialDelaySecondstoo short for a slow-starting app, so the liveness probe kills it before it's even finished booting. - Confirm the probe endpoint/command genuinely reflects health — a liveness probe that checks a downstream dependency (like a database) will kill the app for someone else's outage, which is usually wrong; that belongs in readiness, not liveness, or not at all.
- Tune
periodSeconds,timeoutSeconds, andfailureThresholdto match how the app actually behaves, rather than leaving defaults that don't fit a slow or bursty app.
Root causes, and how to tell them apart.
Restarts right after startup, repeatedly → initialDelaySeconds too short for the app's real boot time. Never becomes Ready despite being healthy → the readiness check is testing the wrong thing or the wrong port/path. Restarts correlate with a downstream outage → the liveness probe is checking a dependency it shouldn't.
The trap that less-experienced engineers fall into.
Making the liveness probe check the same deep dependency health as readiness — so one flaky downstream service causes Kubernetes to kill and restart every pod in the fleet, turning a partial outage into a total one.
🎯 Interviewer follow-up questions you should expect.
- "App is healthy but keeps restarting — what do you check?" The liveness probe configuration, especially
initialDelaySecondsagainst the real startup time. - "Difference between what a liveness and a readiness failure does?" A liveness failure kills and restarts the container; a readiness failure removes it from the Service's endpoints without restarting it.
initialDelaySeconds too short for how long the app really takes to boot. And I specifically check that liveness isn't testing a downstream dependency — I don't want one flaky database to make Kubernetes restart every pod in the fleet."---
6Init container stuck / never completesScenario▸
A pod shows Init:0/1 forever — the main application container never even starts.
What is actually happening (the mental model).
Init containers run to completion, in order, before any main container starts — they're for setup work (waiting for a dependency, running a migration, fetching config). If an init container hangs or keeps failing, the whole pod is stuck, because Kubernetes will never start the main containers until every init container has exited successfully.
How to work through it.
kubectl describe podandkubectl logs <pod> -c <init-container-name>— you must name the specific init container; plainlogsdefaults to the main container, which hasn't started.- Check what the init container is actually waiting on (commonly: a dependency like a database that isn't up yet, or a network policy silently blocking it).
- If it's crash-looping rather than hanging, check its exit code and logs the same way you would a main-container crash.
The root causes and trade-offs.
Sometimes the init container is simply waiting on a dependency that's slow or down, and the fix there is either fixing that dependency or adding a bounded retry with a timeout to the init logic itself. Other times there's a genuine bug or misconfiguration in the init container. And sometimes a NetworkPolicy is blocking the init container's outbound call even though the main container's own policy would allow it.
The trap that less-experienced engineers fall into.
Running kubectl logs without -c and seeing an error or nothing useful, because it defaulted to a main container that has never started — instead of explicitly targeting the init container by name.
🎯 Interviewer follow-up questions you should expect.
- "Pod stuck at Init:0/1 — how do you see what's wrong?" With
kubectl logs <pod> -c <init-container>, naming the init container explicitly. - "What are init containers for?" Sequential setup work that must complete before the main containers start — waiting on a dependency, running migrations, fetching configuration.
kubectl logs targets the main container by default, which hasn't started yet — you have to name the init container explicitly with -c. From there it's usually waiting on a dependency that isn't ready, or a network policy quietly blocking it."---
7A rolling update caused downtimeScenario▸
A routine Deployment update — meant to be zero-downtime — caused real request failures for users during the rollout.
What is actually happening (the mental model).
A rolling update is only zero-downtime if Kubernetes actually knows when a new pod is ready to take traffic and when an old pod has finished in-flight work before it's killed. Without a correct readiness probe and a graceful shutdown, the Service sends traffic to a new pod before it's really ready, or kills an old pod mid-request.
Why a rolling update caused downtime (the root causes).
- No or too-lenient readiness probe — Kubernetes adds the new pod to the Service before it can actually handle requests.
- No graceful shutdown — the app doesn't handle
SIGTERMto stop accepting new work and finish in-flight requests; Kubernetes sends SIGTERM, waitsterminationGracePeriodSeconds, then SIGKILLs, possibly mid-request. - Aggressive rollout parameters —
maxUnavailabletoo high relative to real capacity, taking down more old pods than the new ones can immediately absorb.
How to fix it, verify it, and prevent it.
Add an accurate readiness probe that only passes once the app can truly serve traffic; implement graceful shutdown (stop accepting new connections on SIGTERM, drain in-flight requests, then exit) and size terminationGracePeriodSeconds to match; tune maxUnavailable/maxSurge conservatively. Verify with a load test during a rollout, watching for error-rate spikes. Prevent recurrence by making a zero-downtime rollout check part of your deployment pipeline, not a one-off fix.
The trap that less-experienced engineers fall into.
Assuming "rolling update" automatically means zero downtime by default — it only removes downtime if readiness and graceful shutdown are both correctly implemented; neither is automatic.
🎯 Interviewer follow-up questions you should expect.
- "Why did a rolling update cause errors?" A missing or weak readiness probe, no graceful shutdown handling for SIGTERM, or a maxUnavailable setting that's too aggressive.
- "How do you achieve a truly zero-downtime deploy?" Accurate readiness gating, graceful shutdown, and conservative rollout parameters, all verified under real load.
---
8Rollback after a bad deploymentScenario▸
A newly rolled-out version is misbehaving in production and you need to get back to the last known-good version, fast.
What is actually happening (the mental model).
A Deployment keeps a revision history of its ReplicaSets, so "rolling back" is really just telling the Deployment controller to make an older ReplicaSet the active one again — the same rolling-update machinery runs in reverse.
How to work through it (the safe sequence).
- Confirm it's actually the new version at fault (check
describe pod/logs/metrics) before rolling back — rolling back a red herring wastes the window while the real cause keeps running. kubectl rollout undo deployment/<name>— reverts to the previous revision; add--to-revision=Nto target a specific one fromkubectl rollout history.- Watch the rollback itself like any other rollout —
kubectl rollout status— since it goes through the same readiness-gated process. - Afterward, keep the bad image/manifest around long enough to diagnose why it failed, rather than only reverting and moving on.
The root causes and trade-offs.
Manually patching forward under pressure is higher risk and slower, while a clean rollout undo is fast and uses machinery that's already been tested. Rolling back trades away the speed of fixing forward in exchange for safety, which is the right trade whenever the actual cause isn't yet understood.
The trap that less-experienced engineers fall into.
Deleting the revision history or the Deployment itself in a panic instead of using rollout undo — or rolling back without confirming the new version is actually the cause, masking the real problem.
🎯 Interviewer follow-up questions you should expect.
- "How do you roll back a bad deployment?" With
kubectl rollout undo deployment/<name>, optionally targeting a specific revision. - "How does Kubernetes know what the previous version was?" From the Deployment's ReplicaSet revision history.
kubectl rollout undo does it using the same tested rolling-update machinery — I watch it with rollout status like any other rollout. Before I pull that trigger I make sure the new version is actually the cause, not just correlated with the incident, and afterward I keep the bad revision around long enough to actually diagnose it rather than just moving on."---
9Deployment stuck mid-rolloutScenario▸
kubectl rollout status hangs — some new pods are up, some old ones remain, and it's been stuck like that for a while.
What is actually happening (the mental model).
A rollout only proceeds when new pods pass their readiness probe; if the new ReplicaSet's pods never become ready, the Deployment controller simply stops progressing exactly where it is — it won't automatically roll back on its own by default.
How to work through it.
kubectl get pods— look at the new ReplicaSet's pods specifically; are theyCrashLoopBackOff,Pending, orRunningbut neverReady?- Apply the standard pod-debugging reflex (
describe,logs --previous) to those specific new pods to find why they're failing readiness or crashing. - If the new version is simply broken,
kubectl rollout undorather than waiting indefinitely. - Consider setting
progressDeadlineSecondsgoing forward so a stuck rollout is flagged/failed automatically instead of hanging silently.
Root causes, and how to tell them apart.
New pods crash-looping → an app-level bug in the new version. New pods Pending → a scheduling problem unrelated to the app (resources, affinity). New pods Running but never Ready → a readiness probe/config issue specific to the new version.
The trap that less-experienced engineers fall into.
Watching the overall rollout status and waiting, without drilling into the new ReplicaSet's specific pods to see which failure mode is actually blocking it.
🎯 Interviewer follow-up questions you should expect.
- "Rollout is stuck halfway — what do you check?" The new ReplicaSet's pods specifically, with the standard describe and logs reflex.
- "Does Kubernetes auto-rollback a stuck rollout?" Not by default — you need to run
rollout undoyourself, thoughprogressDeadlineSecondscan mark it as failed.
---
10Service isn't routing traffic to podsScenario▸
Pods are Running and look healthy, but requests to the Service in front of them time out or fail.
What is actually happening (the mental model).
A Service finds its pods purely through label selectors — if the selector doesn't match the pods' actual labels (a typo, or a label that changed), the Service has zero endpoints and traffic goes nowhere, even though the pods themselves are perfectly healthy.
How to work through it.
kubectl get endpoints <service>— if this is empty, the Service isn't matching any pods; that's the whole problem right there.- Compare the Service's
selectoragainst the actuallabelson the pods (kubectl get pods --show-labels) — look for a typo or a mismatch. - If endpoints are populated but traffic still fails, the pods may be listed but failing their readiness probe — not-ready pods are excluded from endpoints too.
- If endpoints are correct and populated, the problem has moved one layer up — check kube-proxy/iptables on the node, or a NetworkPolicy blocking the path.
Root causes, and how to tell them apart.
Empty endpoints with matching-looking labels → check for whitespace/typo mismatches carefully, string-exact. Endpoints populated but requests still fail → readiness gating pods out, or a NetworkPolicy, or a port-name/targetPort mismatch in the Service spec.
The trap that less-experienced engineers fall into.
Debugging the application itself (assuming a code bug) when the pods are perfectly healthy and the actual fault is a label selector that simply doesn't match — get endpoints would have shown this immediately.
🎯 Interviewer follow-up questions you should expect.
- "Service isn't reaching healthy pods — first command?"
kubectl get endpoints <service>— an empty result means a selector or label mismatch. - "Endpoints look right but it still fails — what next?" You check readiness gating, a NetworkPolicy, or a port versus targetPort mismatch.
kubectl get endpoints — if that's empty, the pods are healthy but invisible to the Service, almost always a selector/label mismatch. If endpoints are populated and it still fails, I look one layer further: readiness excluding pods, a NetworkPolicy, or a targetPort mismatch."---
11DNS resolution failing inside the clusterScenario▸
A pod can't resolve another Service's name (payment.default.svc.cluster.local) — connection attempts fail with a DNS error, not a connection-refused.
What is actually happening (the mental model).
In-cluster DNS is provided by CoreDNS, itself running as pods in the cluster — so cluster DNS can fail for the same reasons any other workload can (CoreDNS pods down, misconfigured, or unreachable), on top of ordinary lookup mistakes.
How to work through it.
- Test resolution directly from a throwaway pod:
kubectl run -it --rm dnsutils --image=... -- nslookup payment.default.svc.cluster.local. - Check CoreDNS itself is healthy:
kubectl get pods -n kube-system -l k8s-app=kube-dnsand its logs. - Confirm the name is actually correct — the full form is
<service>.<namespace>.svc.cluster.local; a common mistake is omitting the namespace when calling cross-namespace. - If CoreDNS is healthy and the name is right, check NetworkPolicies aren't blocking pods from reaching CoreDNS's IP/port (UDP/TCP 53) at all.
Root causes, and how to tell them apart.
CoreDNS pods down/crash-looping → a cluster-wide DNS outage, fix CoreDNS first. Only some pods fail → likely a NetworkPolicy blocking their path to CoreDNS. One specific name fails everywhere → the name itself is wrong (missing namespace, typo) rather than a DNS-system problem.
The trap that less-experienced engineers fall into.
Treating "DNS is a cluster thing that just works" and not realising CoreDNS is a regular workload that can itself be down, resource-starved, or blocked by policy like anything else.
🎯 Interviewer follow-up questions you should expect.
- "In-cluster DNS is failing — what do you check?" Whether the CoreDNS pods themselves are healthy, then the exact service name form, then any NetworkPolicy.
- "What's the full DNS name of a Service?"
<service>.<namespace>.svc.cluster.local.
---
12Ingress routing to the wrong service / 404sScenario▸
Requests through the Ingress return 404s or hit the wrong backend, even though the Service and pods behind it are fine on their own.
What is actually happening (the mental model).
An Ingress resource does nothing by itself — it's read and implemented by an Ingress controller, and routing bugs live in the gap between "what the YAML says" and "what the controller actually configured." Path-type semantics (Prefix vs Exact), host-header matching, and TLS/annotation quirks are the usual suspects.
How to work through it.
- Confirm the Ingress controller is actually running and has picked up the resource — check its logs, which usually state what it synced.
- Check
pathTypecarefully —PrefixvsExactbehave very differently for the same-looking path, and a wrong choice silently misroutes or 404s. - Confirm the
hostfield matches the actual request's Host header exactly, and that the backendservice/portnamed in the rule is correct. - Check for a second, unexpected Ingress or a default backend catching the request first — rule ordering/precedence across multiple Ingress resources is a common surprise.
The root causes and trade-offs.
A pathType mismatch, confusing Prefix with Exact, is the single most common cause. Host-header mismatches and multiple competing Ingress resources are the next most common causes after that. All of these are configuration precision issues, not capacity or code problems.
The trap that less-experienced engineers fall into.
Debugging the Service and pods (which are fine) instead of the Ingress controller's own logs and its interpretation of the rule — the fault is almost always in how the routing rule was written or how multiple rules interact.
🎯 Interviewer follow-up questions you should expect.
- "Ingress returns 404 even though the Service works directly — where do you look?" The Ingress controller's own logs and the exact
pathTypeand host rules, not the Service. - "Prefix vs Exact pathType — why does it matter?" Because they match differently for what looks like the same path, and getting it wrong silently misroutes traffic.
---
13NetworkPolicy blocking traffic unexpectedlyScenario▸
After a NetworkPolicy was introduced (by you or someone else), a previously-working connection between two pods now times out.
What is actually happening (the mental model).
The moment any NetworkPolicy selects a pod for a given direction (ingress or egress), that pod's traffic in that direction becomes default-deny for anything not explicitly allowed — NetworkPolicies are additive/whitelist, not a set of exceptions to an otherwise-open default.
How to work through it.
- Identify every NetworkPolicy that selects either pod involved (source and destination) — a policy on either end can be the blocker.
- Check whether the blocked traffic is ingress (into the destination) or egress (out of the source) — you need an explicit allow rule in the right direction, on the right pod.
- Remember that allowing ingress on the destination doesn't help if the source's own egress policy blocks it first, and vice versa — both sides need to permit the connection.
- Confirm the policy's pod/namespace selectors actually match what you think they match — a subtly wrong label selector can lock out traffic you meant to allow.
Root causes, and how to tell them apart.
Missing ingress rule on the destination, missing egress rule on the source, or a selector that doesn't match the intended pods precisely. Testing from both directions (can the source send, can the destination receive) narrows it quickly.
The trap that less-experienced engineers fall into.
Assuming a NetworkPolicy only restricts what it explicitly lists as denied, when in fact selecting a pod at all flips it to default-deny in that direction — "I didn't deny it" doesn't mean "it's allowed."
🎯 Interviewer follow-up questions you should expect.
- "A NetworkPolicy broke traffic that used to work — what's the mental model?" Selecting a pod makes that direction default-deny, so you need an explicit allow rule, not just the absence of a deny.
- "Traffic still blocked after adding an ingress rule — why?" The source pod's own egress policy may be blocking it independently — both sides need to allow it.
---
14PVC stuck Pending — storage class / provisioner issuesScenario▸
A PersistentVolumeClaim has been sitting in Pending and the pod that needs it can't start.
What is actually happening (the mental model).
A PVC stays Pending until something can provision or bind a matching PersistentVolume — with dynamic provisioning, that "something" is a StorageClass and its provisioner, which can fail for reasons that have nothing to do with your pod spec at all.
How to work through it.
kubectl describe pvc— the Events show why binding/provisioning hasn't happened (no matching StorageClass, provisioner errors, quota exceeded).- Confirm the
storageClassNamerequested actually exists (kubectl get storageclass) — a typo or a class that was never created is a very common cause. - Check the underlying cloud-provider quota/permissions for the provisioner (e.g. an EBS volume limit, or the provisioner's ServiceAccount lacking cloud API permissions).
- For static provisioning, confirm a matching PersistentVolume actually exists with compatible size/access modes/StorageClass.
The root causes and trade-offs.
Sometimes the StorageClass is missing or misnamed. Sometimes the provisioner itself lacks the cloud permissions it needs, or it's hit a quota. And for static PVs specifically, sometimes there's simply no matching volume available at all. All of these are infrastructure-layer issues that the pod's own spec has no way to fix on its own.
The trap that less-experienced engineers fall into.
Treating a Pending PVC as a pod problem and debugging the Deployment, when the real information is entirely in describe pvc and the storage layer underneath it.
🎯 Interviewer follow-up questions you should expect.
- "PVC stuck Pending — where do you look?" The
describe pvcEvents first, then whether the named StorageClass actually exists and the provisioner has the quota and permissions it needs.
describe pvc for the binding/provisioning error. Most of the time it's a StorageClass name that doesn't exist, or the provisioner hitting a cloud quota or lacking the permissions to create the volume — none of which shows up anywhere in the pod's own events."---
15StatefulSet pod can't reattach its volumeScenario▸
A StatefulSet pod was rescheduled to a different node and now won't start — it's stuck waiting to attach its volume.
What is actually happening (the mental model).
Many block-storage volumes (like EBS) can only be attached to one node at a time. If a node dies or is drained abruptly, the volume may still show as attached to the old node until it's forcibly detached, and the new pod can't proceed until that's resolved — this is a real safety mechanism (preventing two nodes writing to the same disk), not a bug.
How to work through it.
kubectl describe pod— look for a "multi-attach" or volume-attach error naming the old node.- Confirm whether the old node is genuinely gone (in which case the attachment needs to be cleaned up at the cloud/CSI level) or just temporarily unreachable (in which case waiting, or fencing it properly, is safer than forcing a detach).
- Understand this is inherent to ReadWriteOnce volumes tied to StatefulSets — it's the trade-off for strong per-instance identity and storage, versus a stateless Deployment that doesn't care which node it lands on.
The root causes and trade-offs.
The root cause is usually a node that failed or was removed without a clean volume detach, combined with the volume's access mode, ReadWriteOnce, which inherently limits it to a single attachment point at a time. Forcing a detach too early risks real data corruption if the old node isn't actually dead, so the trade-off here is genuinely between availability and data safety.
The trap that less-experienced engineers fall into.
Forcibly deleting the old pod/node record to "fix" it quickly without confirming the old node is genuinely gone — risking two writers on the same volume if it wasn't.
🎯 Interviewer follow-up questions you should expect.
- "StatefulSet pod won't start on a new node — why?" Its RWO volume is still considered attached to the old node, which is a safety mechanism, not a bug.
- "Is it safe to force-detach?" Only once you've confirmed the old node is genuinely gone, not just unreachable — otherwise you risk two writers on one volume.
---
16RBAC "forbidden" errorsScenario▸
A kubectl command or an in-cluster application call fails with Error from server (Forbidden): ... is forbidden: User "..." cannot ....
What is actually happening (the mental model).
The API server checked the request against RBAC and found no Role/ClusterRole plus RoleBinding/ClusterRoleBinding that grants the actor (user or ServiceAccount) that specific verb on that specific resource — RBAC is deny-by-default, so any gap in the chain shows up as Forbidden.
How to work through it.
- Read the error message carefully — it names the exact actor, verb (get/list/create/delete…), and resource that was denied.
kubectl auth can-i <verb> <resource> --as=<user-or-serviceaccount>— directly tests the permission and confirms the gap without guessing.- Check whether a Role/ClusterRole granting that permission exists at all, and if so, whether a RoleBinding/ClusterRoleBinding actually attaches it to this specific actor (and in the right namespace, for a namespaced Role).
- Fix by adding the minimum necessary permission — resist the urge to grant a broad ClusterRole just to make the error go away.
The root causes and trade-offs.
Sometimes no Role exists at all with the needed permission. Sometimes a Role does exist but it was never bound to this particular actor. And sometimes a RoleBinding exists but it references the wrong Role, or the wrong namespace. Over-granting access, like handing out cluster-admin just to unblock things fast, trades a real security regression for a quick fix that isn't actually worth it.
The trap that less-experienced engineers fall into.
Reflexively granting cluster-admin or a wildcard Role to make a Forbidden error disappear, instead of granting the specific verb/resource that was actually denied.
🎯 Interviewer follow-up questions you should expect.
- "How do you debug a Forbidden error precisely?" With
kubectl auth can-i --as=<actor>to confirm the exact gap, then check the Role and its Binding. - "Why not just grant cluster-admin to unblock it?" Because it fixes the symptom by removing all least-privilege protection — you should grant only the specific verb and resource that's actually needed.
kubectl auth can-i --as and then check whether a Role with that permission exists and is actually bound to that actor in the right namespace. I fix it by granting exactly that permission, not by reaching for cluster-admin to make the error go away."---
17A ServiceAccount has too much (or too little) accessScenario▸
A security review finds an application's ServiceAccount can do far more than the app needs (or, conversely, the app keeps hitting permission walls it shouldn't).
What is actually happening (the mental model).
Every pod runs as some ServiceAccount — the default one if you didn't specify — and if nobody has deliberately scoped it, it's easy to end up either over-privileged (broad ClusterRole bound out of convenience) or under-privileged (nobody granted the one specific permission the app actually needs).
How to reason about it.
- Give every application its own dedicated ServiceAccount rather than sharing the namespace default — this makes auditing and scoping possible at all.
- Enumerate what the app actually calls the API for (read its own ConfigMaps? watch its own CRDs? nothing at all?) and grant exactly that, via a Role scoped to the namespace, not a ClusterRole, unless cluster-wide access is genuinely required.
- Audit existing bindings with
kubectl get rolebindings,clusterrolebindings -A -o wideand cross-reference against what's actually used. - Disable auto-mounting the ServiceAccount token (
automountServiceAccountToken: false) for pods that don't call the Kubernetes API at all.
The root causes and trade-offs.
A convenience-driven broad grant, the "just give it cluster-admin, we'll fix it later" instinct, trades away real security for the upfront work of scoping a Role precisely never getting done. The trade-off is a bit more initial setup, in exchange for a real reduction in blast radius if that pod is ever compromised.
The trap that less-experienced engineers fall into.
Every application sharing the namespace's default ServiceAccount with a broad Role bound to it — so a vulnerability in any one app hands an attacker the combined permissions of every app in the namespace.
🎯 Interviewer follow-up questions you should expect.
- "How do you scope permissions for an application?" With a dedicated ServiceAccount per app, and a namespaced Role granting exactly the verbs and resources it uses.
- "Why not just use the default ServiceAccount?" Because it's usually shared and over-scoped, so a compromise in one app can reach everything bound to it.
---
18Secrets ending up visible where they shouldn'tScenario▸
A review finds a Secret's value showing up somewhere it shouldn't — in logs, in a describe output, or readable by a team that shouldn't have access.
What is actually happening (the mental model).
Kubernetes Secrets are base64-encoded, not encrypted, by default — base64 is trivially reversible, so anyone with API read access to the Secret (or etcd access) can read the real value. "It's a Secret object" is not, by itself, a security control.
How to work through it.
- Enable encryption at rest for Secrets in etcd (an
EncryptionConfiguration) so a raw etcd compromise doesn't expose plaintext values. - Restrict RBAC read access to Secrets tightly —
get/liston secrets is one of the most sensitive permissions in the cluster and should be scoped far more carefully than most other resources. - Never echo Secret values into logs — application code and init scripts should read them directly into memory/env, not print them for "debugging."
- For real secret management maturity, use a dedicated tool (Vault, Sealed Secrets, an external-secrets operator) rather than relying on raw Kubernetes Secrets as the system of record.
The root causes and trade-offs.
One cause is treating base64 encoding as if it were actual encryption. Another is leaving RBAC on Secrets just as loose as on any other resource. A third is debug logging that carelessly prints environment variables or configuration, secret values included. The fix — encryption at rest, tight RBAC, and a real secrets manager — costs real setup effort, in exchange for a genuine security guarantee.
The trap that less-experienced engineers fall into.
Believing a Kubernetes Secret is inherently secure because it's a special object type, and not realising base64 offers no confidentiality at all — it's an encoding, not a cipher.
🎯 Interviewer follow-up questions you should expect.
- "Are Kubernetes Secrets encrypted?" Not by default — they're only base64 encoded, so you have to enable encryption at rest for real protection.
- "How do you restrict who can read Secrets?" With tight, deliberate RBAC specifically on the secrets resource, tighter than most other objects.
---
19HPA isn't scaling the way you expectScenario▸
The HorizontalPodAutoscaler either never scales up under real load, or scales wildly up and down (flapping) every few minutes.
What is actually happening (the mental model).
HPA makes decisions from metrics (usually CPU/memory via the metrics-server, or custom metrics) compared against a target you set — if the metrics pipeline is broken, or requests/limits are set oddly, or there's no cooldown, the HPA's math either never triggers or triggers constantly.
How to work through it.
kubectl describe hpa— shows the current vs target metric value and any errors reaching the metrics source (metrics-server down, or a custom metrics adapter misconfigured).- Confirm pods actually have CPU/memory requests set — HPA's percentage-of-request calculation is meaningless without them.
- If it's flapping, check for missing/short stabilization windows — without them, a brief metric dip or spike triggers an immediate scale event in the other direction.
- For request-driven or bursty workloads, consider whether CPU/memory are even the right signal — a custom metric (queue depth, request rate) may better reflect real load.
Root causes, and how to tell them apart.
"Unknown" or missing metrics in describe hpa → the metrics pipeline itself is broken (metrics-server down, adapter misconfigured). Never scales despite real load → requests aren't set, so the percentage math never crosses target. Flaps constantly → no stabilization window smoothing decisions.
The trap that less-experienced engineers fall into.
Assuming HPA "just works" off raw CPU usage, without realising it's calculated as a percentage of the pod's requested CPU — pods with no request set, or a request wildly mismatched to real usage, make the whole calculation meaningless.
🎯 Interviewer follow-up questions you should expect.
- "HPA won't scale up under load — what do you check?"
describe hpafor metric errors, then whether CPU and memory requests are actually set on the pods. - "HPA keeps flapping — why?" Because it's missing stabilization windows, so brief metric swings trigger immediate opposite scaling.
describe hpa tells me if the metrics pipeline itself is broken. If it's flapping instead, that's almost always a missing stabilization window letting a brief metric blip trigger an immediate reversal."---
20Noisy-neighbour pods on a shared nodeScenario▸
One pod on a shared node starts consuming most of its CPU/memory, and every other pod on that node slows down — even ones from a completely different team.
What is actually happening (the mental model).
Without resource requests and limits, pods compete for a node's finite resources with no fairness guarantee, and Kubernetes has no way to prioritise or contain one workload over another — the classic "noisy neighbour" problem, now at cluster scale instead of a single Docker host.
How to reason about it.
- Set both requests and limits on every workload — requests inform scheduling and fairness, limits cap worst-case consumption.
- Understand QoS classes follow directly from this:
Guaranteed(requests = limits on all resources) is evicted last under pressure;Burstable(requests set, limits higher or unset) is in the middle;BestEffort(neither set) is evicted first. - For genuinely latency-sensitive workloads, this is where you'd also reach for node affinity/taints to physically separate them from bursty or untrusted workloads rather than relying on limits alone.
- Diagnose an active incident with
kubectl top pods/top nodesto identify the actual offender before changing anything.
The root causes and trade-offs.
Setting no requests or limits at all gives you maximum flexibility but zero fairness between workloads. Setting them everywhere costs some upfront tuning effort, but it buys real isolation and a predictable, QoS-based eviction order. For shared, multi-tenant nodes, the trade-off strongly favours setting them.
The trap that less-experienced engineers fall into.
Leaving requests/limits unset "to keep things flexible," not realising that's exactly what makes one workload able to starve every neighbour on the node with no mechanism to stop it.
🎯 Interviewer follow-up questions you should expect.
- "How do you prevent one pod starving others on a node?" You set requests and limits on every workload, and understand the resulting QoS class.
- "What are the three QoS classes?" Guaranteed, where requests equal limits; Burstable, where some are set; and BestEffort, where none are set — that's also the eviction order under pressure.
---
21Taints, tolerations & affinity gone wrongScenario▸
Pods are landing on the wrong nodes (a general workload lands on a specialised GPU node pool), or a dedicated node pool sits empty when it should be in use.
What is actually happening (the mental model).
Taints (on nodes) repel pods unless the pod has a matching toleration; affinity (on pods) attracts pods toward or away from nodes with certain labels. They solve related but different problems — tainting a node stops the wrong workloads from landing there uninvited; affinity expresses a preference from the pod's side — and mixing them up is the most common source of scheduling surprises.
How to work through it.
- A dedicated node pool should be tainted so ordinary pods (with no toleration) can never land there by accident, and the pods that belong there should have both the toleration (permission to land) and node affinity/selector (actual preference to land there) — a toleration alone doesn't attract a pod, it only permits it.
- If a node pool sits empty, check whether pods have the toleration but nobody gave them the affinity actually pointing at it — with only a toleration, the scheduler has no reason to prefer that node.
- If a pod lands somewhere unwanted, check its affinity/selector for being too loose, or check whether the node was ever tainted at all.
- Use
requiredDuringScheduling(hard) vspreferredDuringScheduling(soft) affinity deliberately — a soft preference can be silently ignored if it can't be satisfied.
The root causes and trade-offs.
A toleration without affinity permits a pod to land on the node but doesn't attract it there, so the node often just sits empty. Affinity without a matching taint attracts the pod, but nothing stops other, unrelated pods from landing there too. And a soft affinity preference can silently go unhonoured whenever it can't actually be satisfied. The fix is using a taint and toleration together with affinity, deliberately, for any genuinely dedicated node pool.
The trap that less-experienced engineers fall into.
Adding a toleration and assuming that alone routes the pod to the tainted node — a toleration only removes the repulsion, it creates no attraction; without matching affinity, the pod might land anywhere else entirely.
🎯 Interviewer follow-up questions you should expect.
- "Difference between taints/tolerations and affinity?" Taints repel unless tolerated, which is a permission; affinity attracts or repels based on preference — you usually need both for a truly dedicated node pool.
- "A pod has the toleration but the dedicated node is empty — why?" Because a toleration only permits landing there, it doesn't attract the pod — it needs matching affinity too.
---
22PodDisruptionBudget blocking a node drainScenario▸
kubectl drain on a node hangs — it won't evict some pods, and the drain command sits there or errors out.
What is actually happening (the mental model).
A PodDisruptionBudget (PDB) tells Kubernetes the minimum number/percentage of a workload's pods that must stay available during voluntary disruptions like a drain — if evicting a pod would violate the PDB (e.g. a Deployment with minAvailable already at its floor), the eviction is refused, and the drain stalls on that pod.
How to work through it.
- Identify which pod the drain is stuck on and check its PDB (
kubectl get pdb) — isminAvailable/maxUnavailableset so tightly that even one eviction would violate it? - Check whether the Deployment actually has enough healthy replicas right now to satisfy the PDB during the drain — a PDB that's fine in normal times can block a drain if some replicas are already unhealthy for an unrelated reason.
- This is a real safety mechanism, not a bug — the fix is usually to ensure enough healthy replicas exist before draining, or to accept a temporarily wider PDB for planned maintenance, rather than forcing the eviction.
- As a last resort
kubectl delete pod --forcebypasses the PDB, but that reintroduces exactly the disruption risk the PDB exists to prevent — use it deliberately, not as a default unblock.
The root causes and trade-offs.
Sometimes the PDB is set correctly, but the workload currently has fewer healthy replicas than usual because of some unrelated issue compounding on top. Other times the PDB itself was simply set unrealistically tight for the actual replica count. Forcing past it trades a controlled disruption for an uncontrolled one, which is rarely a good trade.
The trap that less-experienced engineers fall into.
Force-deleting the blocked pod to "just get the drain done," without checking why the PDB is blocking it — sometimes it's correctly protecting against a real availability drop that the force-delete then causes anyway.
🎯 Interviewer follow-up questions you should expect.
- "A drain is stuck — what's likely blocking it?" A PodDisruptionBudget refusing an eviction that would drop availability below its floor.
- "Is it safe to force past a PDB?" Only once you understand why it's blocking — forcing it defeats the exact protection the PDB is providing.
---
23A node goes NotReadyScenario▸
A node shows NotReady in kubectl get nodes, and its pods are either stuck or being rescheduled elsewhere.
What is actually happening (the mental model).
NotReady means the control plane has stopped hearing healthy heartbeats from that node's kubelet — the node itself might be down, the kubelet might have crashed, or it might just be a network partition between the node and the control plane, and Kubernetes can't tell which from the status alone.
How to work through it.
kubectl describe node <name>— the Conditions section usually narrows it down: disk pressure, memory pressure, or a kubelet that's stopped reporting entirely.- If you have host access, check the kubelet's own status/logs on that node (
systemctl status kubelet, its logs) — is it running at all? - Check basic node health: disk full, out of memory, a kernel/container-runtime issue, or a network problem isolating it from the control plane.
- Kubernetes will, after a timeout, start evicting the node's pods to reschedule elsewhere — know your
pod-eviction-timeout/tolerations so you understand when that kicks in, especially for StatefulSets with attached volumes (see the reattach scenario above).
Root causes, and how to tell them apart.
Disk/memory pressure conditions shown explicitly → resource exhaustion on the node itself. No conditions updating at all, kubelet unresponsive → the kubelet process or the node itself is down. Node responds to ping/SSH but still NotReady → likely a network path issue specifically between node and control plane, or a certificate/auth problem for the kubelet.
The trap that less-experienced engineers fall into.
Immediately rebooting or replacing the node without checking describe node Conditions first — sometimes it's a fixable resource-pressure issue, and other times replacing the node is exactly right, but you want to know which before acting.
🎯 Interviewer follow-up questions you should expect.
- "A node is NotReady — first thing you check?"
describe nodeConditions, for disk or memory pressure and kubelet status, then the kubelet's own logs if you have host access. - "What happens to its pods?" After a timeout, Kubernetes reschedules them elsewhere, with extra care needed for StatefulSet pods that have attached volumes.
describe node Conditions to narrow that down before deciding whether to reboot, replace, or just wait it out. I also keep in mind that StatefulSet pods on that node need the volume-reattach care from earlier, not just a blind reschedule."---
24Draining a node safelyScenario▸
You need to take a node out of service (maintenance, decommission, an upgrade) without disrupting the applications running on it.
What is actually happening (the mental model).
kubectl drain combines two steps: cordon (mark the node unschedulable, so no new pods land there) and evict (gracefully remove the pods already there, respecting PodDisruptionBudgets), so that existing workloads move elsewhere in a controlled way rather than being yanked out from under you.
How to work through it (the safe sequence).
kubectl cordon <node>first if you want to stop new scheduling without evicting yet (useful to observe before disrupting anything).kubectl drain <node> --ignore-daemonsets --delete-emptydir-data— evicts pods respecting PDBs; DaemonSet pods are excepted by design (they're meant to run on every node) and emptyDir data is expected to be lost.- If it stalls, it's very likely a PodDisruptionBudget blocking eviction — see that scenario rather than forcing past it blindly.
- Confirm the workloads rescheduled cleanly elsewhere before actually decommissioning the node (
kubectl delete node, or terminating the instance).
The root causes and trade-offs.
Draining is inherently a trade-off between speed and safety. Cordoning, then draining, then verifying is slower, but it stays controlled the whole way. Forcing an eviction past PDBs or DaemonSet warnings is faster, but it reintroduces exactly the disruption risk that draining exists to avoid.
The trap that less-experienced engineers fall into.
Deleting or terminating the node directly without draining first — pods don't get a graceful eviction, in-flight requests can be cut off, and StatefulSet volume-reattach issues (see above) become far more likely.
🎯 Interviewer follow-up questions you should expect.
- "Walk me through safely taking a node out of service." You cordon it to stop new scheduling, drain it to gracefully evict while respecting PDBs, verify the workloads rescheduled, then decommission it.
- "Why does drain sometimes need --ignore-daemonsets?" Because DaemonSet pods are meant to run on every node by design, and aren't meant to be evicted by a drain.
---
25Planning a cluster version upgradeScenario▸
You need to upgrade the cluster's Kubernetes version — control plane and nodes — without an outage, and without breaking workloads that depend on removed APIs.
What is actually happening (the mental model).
A Kubernetes upgrade is really two upgrades — the control plane and the nodes — that must happen in order (control plane first), plus a separate, often-forgotten risk: each release can deprecate and remove API versions, so manifests using a removed apiVersion will fail to apply on the new version even if nothing else changed.
How to reason about a good setup.
- Check for deprecated/removed APIs first — tools like
kubectl-convert/plutoscan manifests for APIs that won't exist on the target version, so you fix them before upgrading, not after something breaks. - Upgrade the control plane before any nodes, one minor version at a time (Kubernetes doesn't support skipping minor versions) — a managed service (EKS/GKE/AKS) or kops handles much of this orchestration for you.
- Upgrade nodes via cordon → drain → replace (or in-place upgrade) → uncordon, one at a time or in small batches, never all at once.
- Test in a lower environment first with the same version jump, and have a rollback/redeploy plan for the previous node AMI/image if something goes wrong.
The root causes and trade-offs.
One cause is skipping the deprecated-API check, which can break on apply and sometimes isn't discovered until the upgrade is already underway. Another is upgrading the nodes before the control plane, which creates an unsupported version skew. A third is upgrading everything at once instead of in batches, which creates a large blast radius if anything turns out to be wrong. The safe path costs more time and process, in exchange for a dramatically lower risk of an upgrade-day outage.
The trap that less-experienced engineers fall into.
Upgrading straight to the latest version without checking API deprecations first — discovering only after the upgrade that a Deployment or Ingress manifest uses an apiVersion that no longer exists.
🎯 Interviewer follow-up questions you should expect.
- "How do you plan a cluster upgrade safely?" You check for deprecated APIs first, upgrade the control plane before the nodes, move one minor version at a time, and upgrade nodes in batches with cordon and drain.
- "Why can't you skip minor versions?" Because Kubernetes only supports upgrading one minor version at a time, due to its API and skew compatibility guarantees.
---
26etcd backup and restoreScenario▸
Leadership asks: if etcd were lost or corrupted right now, how would you get the cluster back, and how long would it take?
What is actually happening (the mental model).
etcd holds the entire state of the cluster — every object, every secret, everything kubectl get could ever show you. Losing it without a backup doesn't just lose some data, it loses the cluster's entire memory of what it's supposed to be running; a snapshot-based backup strategy is non-negotiable for anything beyond a disposable dev cluster.
How to reason about setting it up.
- Take regular etcd snapshots (
etcdctl snapshot save) on a schedule, and store them off the control-plane nodes — a backup sitting on the same disk as the thing it backs up isn't a real backup. - Test the restore process before you need it for real — a backup you've never restored from is a hope, not a plan; practice on a throwaway cluster.
- Understand a restore effectively means the control plane comes back to the state at snapshot time — anything created/changed since then is gone, so snapshot frequency directly determines your recovery point.
- For managed Kubernetes (EKS/GKE/AKS), the cloud provider manages etcd for you — know whether that's the case for your cluster, since it changes who's actually responsible for this.
The root causes and trade-offs.
Having no backup strategy at all means a total loss the moment etcd fails. Regular snapshots with genuinely tested restores give you a recovery point objective you can actually state and defend. The trade-off is maintaining backup infrastructure and running periodic restore drills, in exchange for a real disaster-recovery story instead of a hopeful one.
The trap that less-experienced engineers fall into.
Having a backup script that runs on a schedule but has never once been used to actually restore anything — discovering it's broken or incomplete only during a real incident, when it's too late to fix calmly.
🎯 Interviewer follow-up questions you should expect.
- "How do you back up a self-managed cluster's state?" With a scheduled
etcdctl snapshot save, stored off the control-plane nodes, with restores actually tested. - "What's your recovery point if etcd is lost?" Whatever changed since the last snapshot, which is exactly why snapshot frequency matters and untested backups are a real risk.
---
27An admission webhook is blocking valid deploymentsScenario▸
A kubectl apply that looks completely correct is rejected with an error pointing at a webhook, or every deployment in the cluster is suddenly failing at once.
What is actually happening (the mental model).
Admission webhooks (validating and mutating) sit in the request path before an object is persisted to etcd — they're how policy tools (OPA/Gatekeeper, Kyverno, cert-manager, service meshes) enforce or modify rules cluster-wide. But that means if the webhook's own backend is down, slow, or misconfigured, it can block every matching request cluster-wide, not just the one you're looking at.
How to work through it (when a pod is rejected).
- Read the rejection error carefully — it usually names the specific webhook and the policy/reason it failed, if the webhook is behaving correctly and just enforcing a real rule.
- If the error instead looks like a timeout or connection failure to the webhook itself, the webhook's backing service/pod is the actual problem — check its health, not your manifest.
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations— confirm what's actually registered and itsfailurePolicy(Failblocks everything if the webhook is unreachable;Ignorelets requests through, which is its own risk).- In a genuine emergency, a webhook can be temporarily disabled/scoped down to unblock the cluster — but treat that as a stopgap, not a fix, and re-enable it once the underlying service is healthy.
The root causes and trade-offs.
Sometimes this is a correctly-enforced policy rejecting a genuinely non-compliant manifest, and the fix is simply fixing the manifest. Other times the webhook's own backend is down or slow while it's configured with failurePolicy: Fail, which blocks the entire cluster until the webhook service itself is fixed. And with failurePolicy: Ignore, a down webhook instead lets bad objects through silently during an outage, which is a different, quieter risk to watch for.
The trap that less-experienced engineers fall into.
Assuming their own manifest must be wrong and endlessly tweaking it, when the real problem is the webhook's backend being unreachable — the fix is restoring the webhook service, not further editing an already-correct manifest.
🎯 Interviewer follow-up questions you should expect.
- "Every deployment in the cluster suddenly fails — what do you suspect?" An admission webhook whose backend is down, with
failurePolicy: Failblocking everything. - "Fail vs Ignore failurePolicy — what's the trade-off?" Fail is safe, but it can block the whole cluster if the webhook is down; Ignore keeps the cluster moving, but it silently skips enforcement during an outage.
---
28Designing a workload for production-grade HAConcept▸
"Make this workload production-ready" — it's deployed and technically Running, but what's actually missing before it can survive a real node failure or a bad day?
What is actually happening (the mental model).
"Deployed and Running" only means the happy path works right now — production-readiness is a checklist, and every item on it exists to remove one specific, realistic failure mode (a node dying, an AZ outage, a bad deploy, silent degradation) rather than being generic best-practice box-ticking.
The checklist (each item removes a specific failure mode).
- At least 2–3 replicas, so no single pod is a SPOF.
- Spread across nodes and AZs with topology spread constraints/anti-affinity, so one node or AZ loss doesn't take out every replica at once.
- Requests and limits set, giving it a real QoS class and preventing it from starving or being starved by neighbours.
- Accurate readiness and liveness probes, so traffic only reaches instances that can truly serve it, and hung instances actually get restarted.
- Graceful shutdown (handles SIGTERM, drains in-flight work) so rollouts and node drains don't drop requests.
- A PodDisruptionBudget, so voluntary disruptions (drains, upgrades) can't take out too many replicas at once.
- An HPA (or planned capacity) for real load variation, not a fixed replica count sized for an average day.
- Observability — metrics, logs, and alerts wired up — so degradation is visible before a customer reports it, not discovered after.
The bigger question a senior asks.
Is 3 replicas actually enough, or is it "3 replicas that all happen to be on the same node"? Every item above only counts if it's verified, not just declared in YAML — a topology spread constraint that's never been tested against an actual node failure is a hope, not a guarantee.
The trap that less-experienced engineers fall into.
Equating "deployed and Running" with "production-ready" — missing PDBs, spread, graceful shutdown, and observability, so the workload works fine until the first node failure, AZ event, or rollout, at which point every gap becomes an incident at once.
🎯 Interviewer follow-up questions you should expect.
- "Make this workload production-grade — what do you add?" The checklist above, with each item tied to a specific failure mode.
- "You have 3 replicas — is that enough?" Only if they're actually spread across nodes and AZs — you check the topology, you don't assume it.
- "How do you know it's actually production-ready, not just declared as such?" You verify each control under a simulated failure — kill a node, kill a pod, run a drain — rather than trusting the YAML alone.
---
- Drill the universal reflex first (
get → describe (Events + Last State) → logs --previous → events) until it's automatic — nearly every scenario above is this reflex applied to a specific corner of the platform. - Break and fix, in a real cluster. Kill a pod and watch the ReplicaSet heal it; misconfigure a liveness probe and watch a healthy app get restarted; remove a StorageClass and watch a PVC hang; write a NetworkPolicy and watch it default-deny; drain a node with a tight PDB and watch it stall. Reproducing each teaches far more than reading it.
- One scenario, three passes. Reason it out, then say the spoken version without looking, then explain it to a teammate — especially CrashLoopBackOff, Service/endpoints, and RBAC, which come up constantly.
- Keep an "incident → root cause" log — the OOMKill you diagnosed, the PDB that blocked a drain, the webhook that was actually down. These become the concrete stories you tell in the behavioural round.
Next in the course order: Observability, at this same depth.