Kubernetes Architecture, Operations, and Troubleshooting Questions
How Kubernetes works, how to run it, and how to debug it. Covers control-plane and node components, the scheduler and API server, cluster design, high availability and multi-cluster topologies, and platform-level operations; the workload primitives (pods, deployments, services, controllers), cluster upgrades, and designing Kubernetes as an internal platform; and the operational depth inside a cluster including pod and service networking, ingress and the CNI model, service mesh, persistent volumes and storage classes, resource requests and limits, and systematically diagnosing scheduling, networking, and storage failures. The full architecture-through-day-two-operations span of Kubernetes.
What is ImagePullBackOff? List the common causes (authentication, DNS, wrong tag, network), and describe a step-by-step approach using kubectl and node-level tools to determine whether the issue is cluster-level, node-level, or registry-related.
Sample Answer
ImagePullBackOff is the pod status shown when the kubelet has repeatedly failed to pull a container's image and is now backing off further attempts; like CrashLoopBackOff, it names the symptom, not the cause. The four usual causes are registry authentication, DNS resolution to the registry, a wrong image name or tag, and network reachability from the node to the registry.
Causes and their signatures
| Cause | What describe pod shows | How to confirm |
|---|---|---|
| Auth | 401 Unauthorized or unauthorized: authentication required | Check imagePullSecrets on the pod/ServiceAccount and whether the credential is actually valid right now |
| Wrong tag/name | manifest unknown or not found | Confirm the repo path and tag exist in the registry |
| DNS | Timeout or no such host | Resolve the registry hostname from a debug pod in the same namespace |
| Network | Generic timeout, no clear registry error | Test raw connectivity (curl/TCP) from the node, not just from a pod |
Representative event text you'd actually see:
Failed to pull image "myrepo/app:v2": rpc error: code = NotFound desc = failed to pull and unpack image "myrepo/app:v2": failed to resolve reference "myrepo/app:v2": myrepo/app:v2: not found
Failed to pull image "123456789012.dkr.ecr.us-east-1.amazonaws.com/app:v1": rpc error: code = Unknown desc = failed to authorize: 401 Unauthorized
Step-by-step: cluster, node, or registry?
- Read events first.
kubectl describe pod <pod> -n <ns>
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp
- Check the pull secret and service account actually being used.
kubectl get sa <sa> -n <ns> -o yaml
kubectl get secret <secret> -n <ns> -o yaml
- Test from inside the cluster (isolates cluster-level DNS/NetworkPolicy problems from node-level ones):
kubectl run -i --rm debug --image=alpine --restart=Never -- sh
# inside: apk add --no-cache curl bind-tools; nslookup registry.example.com; curl -v https://registry.example.com/v2/
- Test from the node itself. Since dockershim was removed in Kubernetes 1.24, the node-level container runtime on essentially every current cluster is containerd (or CRI-O), so the right tool is
crictl, notdocker.docker pullonly applies if you're intentionally still running Mirantiscri-dockerdor looking at a pre-1.24 node.
kubectl get pod <pod> -o wide -n <ns>
# on the node:
sudo crictl pull <image>
sudo journalctl -u kubelet -e
- Decide scope:
- Registry-related: the error is
401/403/manifest unknownboth from the debug pod and from the node directly. - Cluster-level: the in-cluster debug pod can't resolve or reach the registry, but the node itself can (curl succeeds from the node); points at CoreDNS or a NetworkPolicy blocking egress.
- Node-level: only some nodes fail; others pull the same image fine. Points at a node-specific proxy, MTU, or firewall rule.
- Registry-related: the error is
Two variants worth naming explicitly
Registry rate limiting across many pods at once. If ImagePullBackOff appears on many unrelated pods across the cluster at roughly the same time, rather than one pod, suspect the registry itself: unauthenticated pulls against a public registry are commonly rate-limited (429 Too Many Requests or an equivalent 401 once the anonymous quota is spent), and every node hitting that same registry trips it simultaneously. The fix is authenticated pulls, a pull-through cache/mirror, or spreading pulls out over time, not chasing one pod's config.
ECR token expiry as a time-correlated failure. On EKS Amazon Elastic Kubernetes Service, if pulls are authenticated with a static, manually-refreshed token stored in an imagePullSecret rather than through IRSA (IAM Roles for Service Accounts), that token is only valid for 12 hours. Pulls succeed for a while and then start failing cluster-wide at almost exactly the token's issuance time plus 12 hours, which is the tell that distinguishes this from a one-off misconfiguration: correlate the failure's onset timestamp against when the credential was last refreshed. The fix is to stop relying on a static Secret and either use IRSA so kubelet-side credential retrieval is automatic, or run a credential-refresh mechanism (such as the ECR credential helper or a scheduled Secret-rotation job) instead of a manually maintained token.
Trade-offs and pitfalls
- Don't assume "it's the registry" just because the error message is generic; a node-level DNS or proxy issue can produce an error that looks identical to a registry outage.
- A fix that works from a debug pod but not from the node (or vice versa) is itself diagnostic information: it tells you the failure is layer-specific, not systemic.
- Long-lived static pull secrets are an operational trap even when they work today; anything time-boxed (ECR tokens, expiring service-account tokens) will eventually fail in a way that looks like a random new bug unless the expiry is tracked explicitly.
Explain the Kubernetes networking model in detail for a DevOps team unfamiliar with it. Describe the IP-per-pod concept, the flat cluster network assumption, how pod-to-pod communication works across nodes, the role of the Container Network Interface (CNI) and kube-proxy, and any common limitations or implicit assumptions operators should be aware of when designing cluster networking.
Sample Answer
Kubernetes gives every Pod its own IP address on one flat, cluster-wide network where any Pod can reach any other Pod's IP directly, without port-mapping or NAT (Network Address Translation) in the middle. That single assumption is the whole model; everything else (Services, kube-proxy, the CNI plugin) exists to make that flat network real and to add load-balancing and service discovery on top of it.
The core assumption: IP-per-Pod, flat and routable
- Every Pod gets its own IP, allocated from the cluster's Pod network, not from the node's own network.
- Containers inside the same Pod share that IP and can reach each other over
localhost. - Any Pod's IP is expected to be reachable from any node in the cluster, as if the whole cluster were one big L3 (Layer 3, meaning IP-address-level) network, even though physically it spans many separate machines.
This is a deliberate simplification: application code never has to deal with port-mapping or think about which node it's running on to reach another Pod. The cost of that simplification is pushed down into the networking layer that has to make the "flat network" illusion actually true.
How Pod-to-pod traffic actually crosses nodes
Two Pods on the same node talk through a local bridge or virtual interface, no different in spirit from two processes on one machine. Two Pods on different nodes need their packets to physically cross the network between those nodes, and that's the job of the CNI (Container Network Interface) plugin, using one of a few common approaches:
- Overlay/encapsulation (for example VXLAN, used by Flannel by default): the CNI wraps the Pod packet inside another packet addressed node-to-node, then unwraps it on arrival. Simple to run, but every packet pays an encapsulation cost.
- Native routing (for example Calico's BGP mode): nodes advertise routes to each other's Pod IP ranges directly, so packets travel as ordinary routed IP traffic with no wrapping.
- In-kernel eBPF forwarding (for example Cilium): programs attached to the kernel's networking hooks forward Pod traffic with less per-packet overhead than either of the above.
Whichever mechanism is used, kube-proxy is not involved in raw Pod-to-Pod traffic; that's entirely the CNI's job. kube-proxy only comes into play for Service traffic, described next.
Where kube-proxy fits in
A Pod's IP is not stable: Pods get rescheduled, restarted, and replaced with new IPs constantly. A Service gives client code one stable address (a ClusterIP) that represents a group of Pods, and kube-proxy is the component that makes connections to that stable address actually land on one of the current, healthy backend Pods. It does this by programming the node's kernel with forwarding rules (commonly iptables or IPVS, an in-kernel load-balancing feature) built from the Service's list of ready backend Pods. Put simply: the CNI plugin makes any Pod reachable by its own IP; kube-proxy makes a stable Service IP resolve to whichever real Pod IP should currently handle the traffic.
Common limitations and implicit assumptions to watch for
- The underlying network must actually support it. Cloud VPC (Virtual Private Cloud) routing tables, security groups, or on-prem firewalls have to allow the CNI's chosen traffic pattern (raw routed IP, VXLAN-encapsulated UDP, or eBPF-forwarded packets) between every pair of nodes, or the "flat network" assumption breaks silently.
- IP address planning matters. The Pod CIDR (the address range Pods are allocated from) needs to be sized for cluster growth and must not overlap with the VPC's own address space, or routing becomes ambiguous.
- NetworkPolicy is opt-in. Without it, any Pod can reach any other Pod by default (east-west traffic is wide open); the flat-network model is about reachability, not isolation, and isolation has to be added deliberately.
- Encapsulation costs latency and throughput. An overlay CNI adds a real, if small, per-packet tax versus native routing or eBPF; this matters more as traffic volume grows.
- kube-proxy itself has a scaling ceiling. iptables-based rule chains get slower to evaluate as the number of Services and endpoints grows; IPVS or an eBPF-based CNI's own Service handling scales better at high counts.
Worked example
A Pod on Node A (IP 10.244.1.7) calling a Service backed by a Pod on Node B (IP 10.244.2.4):
- The calling Pod connects to the Service's ClusterIP, say
10.96.10.20:80. - kube-proxy's node-local rules (built from the Service's endpoint list) DNAT that connection to
10.244.2.4:8080, the real backend Pod. - The CNI plugin now has to deliver a packet addressed to
10.244.2.4, a Pod IP on a different node, across the physical network: an overlay CNI wraps it in a VXLAN packet destined for Node B's real IP; a routed CNI simply routes it there directly using advertised Pod-subnet routes. - Node B unwraps (if needed) and delivers the packet to the backend Pod over its local bridge.
Trade-offs and pitfalls for a team new to this model
- Don't assume Pod IPs are stable enough to hardcode anywhere; always address other workloads through a Service name, never a Pod IP directly.
- Introducing a service mesh or NetworkPolicy changes this baseline model by adding sidecar proxies or enforcement points into the path described above; treat this explanation as the foundation those layers build on, not the final picture.
- Multi-cluster or hybrid-cloud setups often break the single-flat-network assumption outright (two clusters don't share one Pod CIDR space by default), which is exactly why solutions in that space exist to stitch separate flat networks back together.
Explain the differences between Deployment and StatefulSet. Provide a scenario (e.g., a database cluster) that requires StatefulSet semantics and explain which StatefulSet features (stable network IDs, ordinal indexes, persistent volumes) are necessary.
Sample Answer
Deployment and StatefulSet both manage a set of pods from one template, but a Deployment treats every pod as interchangeable, while a StatefulSet gives each pod a stable, ordinal identity and its own dedicated storage. Reach for StatefulSet only when the application itself needs to know which replica it is, not just how many replicas exist.
Direct comparison
| Deployment | StatefulSet | |
|---|---|---|
| Pod naming | random suffix, no ordering | ordinal suffix (pod-0, pod-1, ...), stable across restarts |
| Network identity | pod IP changes on recreation; normally fronted by a load-balancing Service | stable hostname per ordinal, usually via a headless Service (clusterIP: None), so pod-0.svc.namespace.svc.cluster.local always resolves to the same replica |
| Storage | usually shared, external, or none | one PersistentVolumeClaim (PVC, a request for storage) bound to a PersistentVolume (PV, the actual storage resource) per ordinal, via volumeClaimTemplates; deleting the StatefulSet does not delete these PVCs |
| Startup and scale order | no ordering guarantee | default OrderedReady policy starts and stops one ordinal at a time, lowest-to-highest on scale-up and highest-to-lowest on scale-down; a Parallel policy is available when the app does not need this ordering |
Worked example: a PostgreSQL primary-replica cluster
A database cluster with one primary and several read replicas needs exactly the guarantees a Deployment cannot give:
- Stable hostnames: replication configuration and client connection strings can target
postgres-0.postgres-svcby name, and that name always means the same replica, even across restarts. - Ordinal indexes: an init script can branch on the pod's ordinal (ordinal 0 initializes as primary, ordinals 1 and above join as replicas) and rely on ordered startup so replicas do not attempt to join before the primary exists.
- Per-ordinal persistent volumes:
volumeClaimTemplatesties each replica's data directory to its own ordinal, sopostgres-1always reattaches topostgres-1's data after a restart, never to another replica's.
Trade-offs and pitfalls
- Not everything with 'stateful' in its name needs a StatefulSet. Kafka brokers do: each broker owns local partition log segments on disk and needs a stable identity for replica assignment. Kafka Connect workers do not: a Connect worker's task state and offsets live in Kafka topics, not on local disk, so the worker pool is effectively stateless and runs fine as a plain Deployment. Confirm where the durable state actually lives before defaulting to StatefulSet.
- StatefulSet's ordered rollout is slower and more conservative than a Deployment's by design; if a stateful workload's replicas are genuinely independent (sharded, no leader election), that ordering guarantee adds rollout latency with no real benefit, and a
Parallelpod-management policy, or even a Deployment with per-shard volumes mounted individually, may fit better. - Deleting a StatefulSet leaves its PVCs behind on purpose, to protect data from an accidental delete. Teams that do not know this get surprised in both directions: unexpected data loss if they assumed cleanup was automatic, or orphaned storage cost if they never clean it up.
Explain Kubernetes NetworkPolicy at a high level. What problems does it solve, how are ingress and egress policies expressed, how do selectors and namespaceSelectors work, and what is the default behavior when no policies are defined?
Sample Answer
NetworkPolicy is a namespaced Kubernetes object that restricts which pods can talk to which other pods (and, via egress rules, which external destinations pods can reach), solving east-west traffic control inside the cluster: without it, any pod can reach any other pod by default, which is a large blast radius for a compromised or misconfigured workload.
The enforcement point that isn't the API server
Creating a NetworkPolicy object only stores intent in etcd via the API server, the same as any other Kubernetes object; the API server itself does not inspect or block a single packet. Enforcement is entirely the responsibility of the CNI (container network interface) plugin running on each node, and only if that plugin actually implements NetworkPolicy support. Calico, Cilium, and several others do; a purely overlay-focused CNI like plain Flannel historically does not. Applying a NetworkPolicy on a cluster whose CNI doesn't enforce it is a silent no-op: kubectl get networkpolicy shows the object as created, but traffic flows exactly as if it didn't exist. Confirming the CNI's NetworkPolicy support is a prerequisite, not an afterthought, before relying on this mechanism for anything security-relevant.
Expressing ingress and egress
A policy selects pods with podSelector, and, once selected, ingress and egress traffic is governed independently:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-policy
spec:
podSelector:
matchLabels: { app: web }
policyTypes: [Ingress, Egress]
ingress:
- from:
- podSelector: { matchLabels: { role: frontend } }
ports:
- port: 80
egress:
- to:
- namespaceSelector: { matchLabels: { env: services } }
ports:
- port: 5432
This says: pods labeled app: web accept ingress only from pods labeled role: frontend on port 80, and may only send egress to pods in namespaces labeled env: services on port 5432. Everything else in that direction is implicitly denied once any rule for that direction exists.
Selectors and namespaceSelector
podSelectorinsidefrom/tomatches pods by label within the same namespace as the rule's target namespace context.namespaceSelectormatches whole namespaces by label; commonly combined withpodSelectorin the samefrom/toentry to mean "these specific pods, but only in namespaces with this label," rather than either alone.- An empty
podSelector: {}at the top level of the spec selects every pod in the namespace; an empty selector inside afrom/toentry means "all pods" for that source/destination.
Default behavior
With no NetworkPolicy selecting a pod at all, every ingress and egress connection to and from it is allowed, no restriction whatsoever. The moment at least one policy selects a pod and declares a policyTypes direction (say, Ingress), that direction flips to default-deny for that pod: only traffic matching an explicit rule is allowed, and everything else in that direction is dropped. This is why a common, deliberate starting point is a namespace-wide default-deny policy (a policy selecting all pods with empty ingress/egress rules) followed by narrow allow rules added on top, rather than trying to enumerate every exception against an open-by-default baseline.
Trade-offs and pitfalls
- NetworkPolicies are additive across multiple policies selecting the same pod (a union of allowed traffic, not an intersection), which means adding a second, broader policy to a namespace can unintentionally widen access that a narrower first policy had restricted.
- Egress policies frequently get overlooked because ingress is the more obvious security concern, but an over-permissive default (no egress policy at all) lets a compromised pod exfiltrate data or reach internal services it has no business reaching; DNS resolution in particular is easy to break by an egress default-deny policy that forgets to allow port 53 to the cluster's DNS service.
- Because enforcement lives in the CNI, NetworkPolicy behavior (which selector combinations are supported, whether egress is supported at all, performance under many rules) varies by CNI choice; a policy that works correctly in one cluster is not guaranteed to behave identically after a CNI migration.
Explain the differences between a Pod, ReplicaSet, Deployment, StatefulSet, and DaemonSet in Kubernetes. For each resource, describe its primary use cases, how it manages lifecycle and scaling, how updates/rollbacks are handled, and provide concrete examples of when you'd choose each resource (stateless web service, per-node agent, stateful database, etc.).
Sample Answer
Pods are the runtime unit; everything else here is a controller deciding how many pods to run and how to replace them. A ReplicaSet just keeps N identical pods alive. A Deployment wraps a ReplicaSet with declarative rolling updates and rollback history. A StatefulSet keeps stable per-replica identity and storage for workloads that care which replica they are. A DaemonSet guarantees exactly one pod per matching node. Job and CronJob run pods to completion, once or on a schedule, rather than keeping them running indefinitely.
Comparison
| Object | Identity model | Storage | Update mechanics | Reach for it when |
|---|---|---|---|---|
| ReplicaSet | anonymous, interchangeable pods | none built in | no native rolling update | almost never directly; it is what a Deployment manages underneath |
| Deployment | anonymous | typically shared, external, or none | rolling update via surge and unavailable settings, with full revision history and rollback | stateless services: web tiers, API servers |
| StatefulSet | stable ordinal identity (pod-0, pod-1, ...) and hostname | one PersistentVolumeClaim per ordinal via volumeClaimTemplates | ordered rolling update by default, highest ordinal first | stateful systems that need to know which replica they are: databases, brokers |
| DaemonSet | one pod per matching node | node-local, if any | rolling update per node, bounded by maxUnavailable | node-level agents: log collectors, CNI (Container Network Interface) plugins, monitoring exporters |
| Job | runs to completion, tracked by completions, parallelism, backoffLimit | ephemeral | retried on failure up to backoffLimit, not a rolling concept | one-off or batch work |
| CronJob | creates a Job on a schedule | ephemeral | governed by concurrencyPolicy (Allow, Forbid, or Replace) | scheduled batch work: nightly reports, cleanup jobs |
Worked example: rolling-update arithmetic
For a Deployment with replicas: 10, maxSurge: 25%, and maxUnavailable: 25%, Kubernetes rounds surge up and unavailable down:
surge=⌈10×0.25⌉=3
unavailable=⌊10×0.25⌋=2
So at the busiest point of the rollout the Deployment can briefly run up to 13 pods (10 plus 3 surge) while guaranteeing at least 8 stay available (10 minus 2 unavailable). This asymmetric rounding is why setting surge and unavailable to the same percentage still lets the total pod count grow slightly during a rollout instead of staying flat.
DaemonSets interact with node taints in a way that is easy to miss: they commonly carry an explicit toleration for the control-plane taint (conventionally node-role.kubernetes.io/control-plane:NoSchedule) so node-level agents like a CNI plugin or kube-proxy itself still run on control-plane nodes that ordinary application pods are excluded from. That toleration is a manifest choice the DaemonSet author makes, not automatic behavior.
Trade-offs and pitfalls
- Choosing Deployment for a workload that actually needs stable identity (a small quorum service, a primary-replica database) forces the team to rebuild that identity logic in application code; StatefulSet gives it for free, at the cost of slower, ordered rollouts.
- A stuck rollout is usually a readiness-probe failure hiding behind the surge and unavailable math: if new pods never pass readiness, the Deployment can stall indefinitely partway through, with old and new pods coexisting. Check pod events and readiness status before assuming a controller bug.
- A PodDisruptionBudget set too strictly (for example,
minAvailableequal to the full replica count) can make a Deployment's own rolling update unable to proceed, because the update's own temporary unavailability trips the budget meant to protect against unrelated disruptions. - DaemonSets bypass normal replica-count thinking: adding nodes silently adds pods and cost. Forgetting a DaemonSet exists across a large node pool is a common source of untracked resource consumption.
Unlock Full Question Bank
Get access to all 13 Kubernetes Architecture, Operations, and Troubleshooting interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.