Pod, ReplicaSet, and Deployment as a layered hierarchy, StatefulSet vs Deployment for stateful workloads, DaemonSet for node-level agents, and how to actually diagnose a CrashLoopBackOff.
Published September 23, 2026
A Pod is Kubernetes' smallest deployable unit — one or more tightly-coupled containers that share network (the same IP, same port space) and storage, always scheduled together on the same node. Most pods run a single container; multi-container pods are reserved for genuinely coupled helpers (a "sidecar" that shares the main container's data volume, e.g. a log-shipping agent) — this maps directly onto the microservice-decomposition thinking from Domain Decomposition applied at the container level: containers in the same pod should be as tightly coupled as the pod's own atomic scheduling implies.
Deployment → manages → ReplicaSet → manages → Pods (N replicas)
A ReplicaSet ensures a specified number of pod replicas are running at all times — if a pod dies, the ReplicaSet notices and creates a replacement. A Deployment sits one level above ReplicaSet, managing it and adding what actually matters day-to-day: DECLARATIVE UPDATES (change the pod template, and the Deployment orchestrates a rollout — see Deployments & Rollouts) and ROLLBACK. In practice, you almost never create a bare Pod or a bare ReplicaSet directly — you define a Deployment, and it creates and manages the ReplicaSet (and the ReplicaSet, in turn, the Pods) for you. Creating a bare Pod means losing both self-healing (nothing replaces it if it dies) and update orchestration entirely.
A Namespace partitions a single cluster into logically isolated groups of resources — commonly one namespace per environment (staging, production) or per team, letting resource names be reused across namespaces (payment-service can exist independently in both staging and production) and letting access control (RBAC) and resource quotas be scoped per namespace rather than applying uniformly across the entire cluster.
Deployment's pods: interchangeable — pod-abc123, pod-def456 — any one can be replaced by any other
StatefulSet's pods: stable, ordered identity — myapp-0, myapp-1, myapp-2 — each has its OWN
persistent storage that follows IT SPECIFICALLY across restarts/rescheduling
Deployment's pods are deliberately interchangeable — none has a distinguishing identity, which is exactly right for stateless application services (any replica can serve any request). A StatefulSet exists for the opposite case — workloads like databases, where each replica needs a STABLE, predictable identity (a consistent hostname/ordinal like myapp-0) and its OWN dedicated persistent volume that follows it specifically, not a shared or randomly-assigned one. Running a distributed database inside Kubernetes (each shard/replica needing its own durable, identity-linked storage) is the canonical StatefulSet use case.
A DaemonSet ensures exactly one copy of a pod runs on EVERY node (or a selected subset) in the cluster — used for node-level agents that genuinely need to run everywhere: a log collector (feeding into the Centralized Logging pipeline), a metrics agent (feeding Metrics & Monitoring), or a node-level monitoring daemon. Unlike a Deployment's replica count (a number you choose), a DaemonSet's pod count is automatically tied to node count — adding a node automatically schedules the DaemonSet's pod there too, with zero additional configuration.
kubectl logs mypod --previous # logs from the CRASHED instance, before the restart
kubectl describe pod mypod # events, exit code, resource limits, recent state transitions
CrashLoopBackOff is Kubernetes' signal that a container keeps crashing and being restarted, with an exponentially increasing backoff delay between attempts. The critical diagnostic step most people miss initially: kubectl logs mypod alone shows logs from the CURRENT (freshly restarted, possibly not-yet-crashed-again) instance — --previous is what retrieves logs from the instance that actually crashed, which is almost always where the real error message lives. kubectl describe pod complements this with the EVENT history (exact exit code, OOMKilled vs a regular application exception, recent scheduling/restart events) — the two commands together are the standard first-response diagnostic pair for this failure mode.
When a node runs low on a resource (memory or disk pressure), Kubernetes evicts lower-priority pods first to reclaim capacity, based on each pod's QoS class (derived from its resource requests/limits configuration — see Probes & Autoscaling): BestEffort pods (no requests/limits set at all) are evicted first, Burstable pods next, and Guaranteed pods (requests equal limits) last. This is a direct, practical consequence of NOT setting resource requests/limits thoughtfully — an unconfigured pod isn't just risking its own stability, it's volunteering itself as the first thing sacrificed under any node-level resource pressure.
Q: If a Deployment already manages ReplicaSets, why does the ReplicaSet object need to exist as a separate layer at all? A: The separation is what makes rolling updates and rollback work cleanly — a Deployment update creates a NEW ReplicaSet (with the updated pod template) alongside the OLD one, gradually shifting replica counts between them (Deployments & Rollouts), and a rollback simply shifts back to the old ReplicaSet, which is still there; without this intermediate layer, tracking multiple 'versions' of a pod template simultaneously during a rollout would be far messier.
Q: Can a StatefulSet's pods still be load-balanced like a Deployment's? A: Yes, typically via a headless Service (a Service with no single cluster IP, instead returning each pod's individual stable DNS name) — clients that need to reach a SPECIFIC replica (e.g. a specific database shard) use the stable per-pod DNS name directly, while clients that just need ANY replica can still use normal service discovery, covered further in Services & Ingress.
Q: What's the practical difference between a pod crashing due to an application bug vs being OOMKilled?
A: kubectl describe pod's exit code and event history distinguishes these directly — an OOMKilled pod shows a specific reason/exit code (137, SIGKILL from exceeding its memory limit) rather than the application's own exception/exit code, and the fix is entirely different (raise the memory limit or fix a memory leak, vs fix the actual application bug the crash logs reveal).
Q: Does a DaemonSet respect node taints/tolerations the same way a Deployment's pods do? A: Yes, the same scheduling mechanism applies — a DaemonSet can be configured to run only on nodes matching specific criteria (via node selectors or tolerations for tainted nodes), which is how, for example, a monitoring DaemonSet might still be scheduled onto otherwise-tainted 'control plane only' nodes specifically because monitoring needs visibility everywhere, while regular application DaemonSets would be excluded from those same nodes.