Kubernetes Pods and Deployments
- Pods are ephemeral and non-self-healing — never deploy individual Pods for production workloads.
- Deployments are controllers that maintain the desired number of Pod replicas and handle lifecycle management.
- Use 'RollingUpdate' with 'maxUnavailable: 0' to achieve zero-downtime application updates.
- Pod: Ephemeral, non-self-healing. Shares a network namespace (localhost) and volumes across containers.
- Deployment: Manages ReplicaSets which manage Pods. Handles rolling updates, rollbacks, and scaling.
- ReplicaSet: The intermediate controller that ensures N replicas exist. You rarely interact with it directly.
- Probes: Liveness (restart if deadlocked) vs Readiness (remove from service endpoints if not ready).
- maxSurge: More surge = faster rollout but higher peak resource usage.
- maxUnavailable: 0 = zero-downtime but slower rollout. Higher = faster but riskier.
- Creating Pods directly instead of Deployments. Direct Pods are not self-healing. If the node dies, the Pod stays dead forever.
Pod in CrashLoopBackOff.
kubectl logs <pod> --previous --tail=50kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'Pod stuck in Pending.
kubectl describe pod <pod> | grep -A 20 Eventskubectl describe nodes | grep -A 5 'Allocated resources'Deployment rollout stuck.
kubectl rollout status deployment/<name> --timeout=30skubectl get pods -l app=<label> -o wide | grep -v Running502 errors during deploy.
kubectl get endpoints <service> -wkubectl get pod -l app=<label> -o jsonpath='{range .items[*]}{.metadata.name} {.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}'Pod evicted from node.
kubectl describe node <node> | grep -A 10 Conditionskubectl get pod <pod> -o jsonpath='{.status.qosClass}'Production Incident
kubectl apply. The rollout completed in 3 minutes but 502 errors persisted for 5 additional minutes. kubectl get pods showed all Pods in Running state. No CrashLoopBackOff. No OOMKill events.maxUnavailable: 0 (correct for zero-downtime) but the readiness probe was misconfigured: initialDelaySeconds: 5 and periodSeconds: 10 with failureThreshold: 1. The application took 45 seconds to warm its cache and connect to the database. During this 45-second window, the readiness probe failed once after 5 seconds, but Kubernetes had already added the Pod to the Service endpoints because the first probe check had not yet run. The kube-proxy rules were updated to route traffic to the new Pod before it was actually ready. Additionally, the old Pods were terminated immediately when the new Pods passed their first readiness check, creating a window where neither old nor new Pods were fully serving traffic.failureThreshold: 60 and periodSeconds: 5 (300 seconds max startup time) to gate liveness and readiness probes until the app is fully booted.
2. Changed readiness probe initialDelaySeconds to 0 (startup probe handles the delay) and set failureThreshold: 3 with periodSeconds: 5.
3. Added terminationGracePeriodSeconds: 60 with a pre-stop hook that sleeps 15 seconds to drain in-flight connections before the container is killed.
4. Set maxSurge: 1 to ensure at least one new Pod is fully ready before old Pods are terminated.
5. Added a PodDisruptionBudget with minAvailable: 2 to prevent simultaneous termination of multiple Pods.terminationGracePeriodSeconds with a pre-stop hook to drain in-flight connections before container shutdown.Test rolling updates in staging with real traffic patterns. The first time you see a rollout failure should not be in production.PodDisruptionBudgets prevent the Deployment from terminating too many Pods simultaneously during updates or node drains.Production Debug GuideSymptom-first investigation path for Pod lifecycle and Deployment rollout failures.
kubectl logs <pod> --previous to see why it crashed. Check for OOMKill (exit code 137), missing environment variables, or failed startup dependencies. If the crash happens during startup, add a startup probe.kubectl describe nodes | grep -A 5 Allocatable. Check for insufficient CPU/memory, PVC binding failures, or taints/tolerations mismatches. If no nodes can schedule the Pod, it stays Pending indefinitely.kubectl rollout status deployment/<name>. If maxUnavailable is 0 and a new Pod cannot become ready, the rollout blocks. Check readiness probe failures. Check for PDB conflicts that prevent Pod termination.failureThreshold is too low or periodSeconds is too aggressive, healthy-but-slow Pods are killed. Check liveness probe endpoint latency. Consider using a startup probe to gate liveness.terminationGracePeriodSeconds and pre-stop hooks. Verify maxSurge and maxUnavailable settings. Check Service endpoints during rollout: kubectl get endpoints <service> -w.kubectl describe node <node> | grep Conditions. Check if the Pod's QoS class is BestEffort (first to be evicted). Set resource requests and limits to achieve Guaranteed QoS.Every production Kubernetes workload ultimately runs as Pods managed by Deployments. The Pod is the atomic scheduling unit — a group of containers sharing a network namespace and volumes. The Deployment is the declarative controller that ensures the right number of Pods exist, handles rolling updates, and rolls back on failure.
Misconfiguring either object causes production incidents: Pods without resource limits cause noisy-neighbor OOMKills, Deployments without proper probes cause 502 errors during rollouts, and direct Pod creation bypasses self-healing entirely. Understanding the reconciliation loop — how the Deployment controller continuously drives current state toward desired state — is the foundation for debugging every higher-level Kubernetes object.
Pod Basics: The Atomic Unit
In the world of Kubernetes, the Pod is the atomic unit of scheduling. While you might be used to thinking in terms of 'containers,' Kubernetes thinks in 'Pods.' A Pod can host a single container, or a tightly coupled group of containers (like an app container and a 'sidecar' logging agent) that need to share the same local network (localhost) and storage volumes.
Crucially, Pods are ephemeral. They are born, they live, and they die. They are never 'repaired'; they are replaced.
# Package: io.thecodeforge.k8s.manifests # pod.yaml — used primarily for local debugging, rarely in production apiVersion: v1 kind: Pod metadata: name: forge-api-pod labels: app: forge-api env: production spec: containers: - name: api-container image: io.thecodeforge/api:1.2.0 ports: - containerPort: 8080 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" env: - name: APP_COLOR value: "blue" - name: DB_PASSWORD valueFrom: secretKeyRef: name: cloud-secrets key: db-password
- Pod IP is assigned at creation and changes on restart. Use Service DNS for discovery.
- Container filesystem is ephemeral. Use PersistentVolumes for data that must survive restarts.
- Pods in the same Pod share localhost networking. Containers in different Pods do not.
- Sidecar containers (logging, proxy) share the Pod's lifecycle. If one crashes, the Pod is restarted.
- Init containers run before the main container. They block Pod startup until they complete successfully.
kubectl run or a Pod manifest) for production workloads. Direct Pods have no self-healing: if the node crashes, the Pod is gone forever with no replacement. The Deployment controller is what provides self-healing, rolling updates, and rollback. Every production workload must use a Deployment, StatefulSet, or DaemonSet — never a raw Pod.Deployments: Orchestrating the Desired State
A Deployment is a high-level object that manages a ReplicaSet, which in turn manages Pods. Its job is to ensure the 'Desired State' matches the 'Current State.' If you tell a Deployment you want 3 replicas, and a node crashes taking one Pod with it, the Deployment controller notices the discrepancy and immediately schedules a new Pod on a healthy node.
Deployments are also the primary vehicle for Rolling Updates. By manipulating the maxSurge and maxUnavailable parameters, you can swap out version 1.0 for 2.0 without dropping a single user request.
# deployment.yaml — The production standard apiVersion: apps/v1 kind: Deployment metadata: name: forge-api-deployment namespace: production spec: replicas: 3 selector: matchLabels: app: forge-api strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 0 template: metadata: labels: app: forge-api spec: containers: - name: api image: io.thecodeforge/api:2.0.1 ports: - containerPort: 8080 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 20 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10
# deployment.apps/forge-api-deployment created
- Deployment creates ReplicaSets. ReplicaSets create Pods. You interact with Deployments.
- Each rollout creates a new ReplicaSet. Old ReplicaSets are kept for rollback (default% of desired pods are always up template: : 10 revisions).
- The Deployment controller manages the transition: scale up new ReplicaSet, scale down old ReplicaSet.
- maxSurge: How many extra Pods can exist above the desired count during rollout.
- maxUnavailable: How many Pods can be missing below the desired count during rollout.
maxSurge and maxUnavailable parameters directly control rollout speed vs resource usage. maxSurge: 25%, maxUnavailable: 0 means: during rollout, create up to 25% extra new Pods before terminating old Pods. This ensures zero-downtime but requires 125% of normal resource capacity. For resource-constrained clusters, set maxSurge: 0, maxUnavailable: 25% to terminate old Pods first (faster, less resource usage, but brief capacity reduction). The worst configuration is maxSurge: 0, maxUnavailable: 0 — the rollout blocks forever because it cannot create new Pods (no surge) and cannot terminate old Pods (no unavailability allowed).Production Operations: Essential kubectl Patterns
Managing Deployments in production requires more than just apply. You need to be able to inspect the rollout history, trigger instant rollbacks, and scale on demand.
# 1. Trigger a manual rolling update by changing the image kubectl set image deployment/forge-api-deployment api=io.thecodeforge/api:2.1.0 # 2. Watch the rollout in real-time kubectl rollout status deployment/forge-api-deployment # 3. Emergency! Roll back to the previous stable version kubectl rollout undo deployment/forge-api-deployment # 4. View the rollout history to find a specific revision kubectl rollout history deployment/forge-api-deployment # 5. Scale the workload horizontally (manual scaling) kubectl scale deployment/forge-api-deployment --replicas=10 # 6. Deep inspection of pod failures kubectl describe pod <pod-name> kubectl logs -f deployment/forge-api-deployment --all-containers
- Rollback = scale up old ReplicaSet + scale down new ReplicaSet. No image pull needed if cached.
- revisionHistoryLimit (default 10): How many old ReplicaSets to keep. Increase to 20-50 for critical services.
- kubectl rollout pause/resume: Pause a rollout mid-way to test a subset of new Pods before completing.
- kubectl rollout undo --to-revision=N: Rollback to a specific revision, not just the previous one.
- kubectl rollout restart: Triggers a rolling restart without changing the image. Useful for picking up ConfigMap changes.
kubectl rollout pause command is the foundation of canary deployments without a service mesh. Pause the rollout after one new Pod is created, send a small percentage of traffic to it via Service endpoint selection, monitor error rates, then either resume or undo. This gives you canary behavior with standard Kubernetes objects. Combine with maxSurge: 1 to ensure only one canary Pod is created during the pause.kubectl apply. Use rollout undo for instant rollbacks, rollout pause for canary deployments, and rollout restart for ConfigMap-driven restarts. Keep old images available — rollback fails if the image is garbage-collected.| Aspect | Deployment | StatefulSet | DaemonSet | Job |
|---|---|---|---|---|
| Use case | Stateless applications (APIs, web servers) | Stateful applications (databases, queues) | Node-level agents (log collectors, monitors) | One-off or batch tasks (migrations, reports) |
| Pod identity | Interchangeable. No stable identity. | Stable identity (ordinal index: pod-0, pod-1). | One Pod per node (or subset of nodes). | Single Pod (or N completions). Runs to completion. |
| Scaling | Horizontal (add/remove replicas freely). | Ordered: scale up 0->1->2. Scale down in reverse. | Automatic: one Pod per matching node. | parallelism controls concurrent Pods. |
| Storage | Ephemeral or shared PersistentVolume. | PersistentVolumeClaim per Pod (stable storage). | HostPath or shared storage. | Ephemeral. Output to external storage. |
| Rolling update | Supported (RollingUpdate strategy). | Supported (OrderedReady or Parallel). | Supported (RollingUpdate or OnDelete). | Not applicable (Pods run to completion). |
| Networking | Pod IP changes on restart. Use Service. | Stable network identity (pod-0.svc.cluster.local). | Pod IP tied to node IP. Use hostNetwork. | Pod IP is ephemeral. |
| Self-healing | Yes. Replaces failed Pods automatically. | Yes. Replaces failed Pods with same identity. | Yes. Replaces failed Pods on the same node. | Yes. Retries failed Pods up to backoffLimit. |
| Production example | Web API, microservice, frontend | PostgreSQL, Kafka, Redis cluster | Fluentd, node-exporter, Cilium agent | Database migration, backup job, report generation |
🎯 Key Takeaways
- Pods are ephemeral and non-self-healing — never deploy individual Pods for production workloads.
- Deployments are controllers that maintain the desired number of Pod replicas and handle lifecycle management.
- Use 'RollingUpdate' with 'maxUnavailable: 0' to achieve zero-downtime application updates.
- Liveness probes manage the container lifecycle (restarts), while Readiness probes manage the traffic flow (service discovery).
- Resource 'requests' determine scheduling (which node); 'limits' determine runtime enforcement (CPU throttling/OOM killing).
- Startup probes are mandatory for applications with warm-up times greater than 30 seconds. Without them, liveness probes kill containers during boot.
- PodDisruptionBudgets prevent simultaneous Pod termination during node drains and rolling updates.
- Rollback is not free — it fails if the old image has been garbage-collected. Keep old images available and increase revisionHistoryLimit for critical services.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QDescribe the 'reconciliation loop' in a Deployment controller. How does it maintain the desired state?
- QYour application takes 60 seconds to warm up its cache. If you don't configure probes correctly, users see 502 errors during a rollout. Which probe do you use and how do you configure it?
- QExplain the 'Sidecar Pattern' in a Pod. Give a real-world example of why two containers would need to share a Pod instead of being in separate Pods.
- QWhat is the difference between 'maxSurge' and 'maxUnavailable' in a RollingUpdate strategy, and how do they impact cluster capacity during a deployment?
- QHow would you perform a 'Canary Deployment' using only standard Kubernetes Deployment objects and Services?
- QA Pod is stuck in CrashLoopBackOff. Walk me through your debugging process from the first command you would run to identifying the root cause.
- QWhat is the difference between a liveness probe and a readiness probe? What happens if you use a liveness probe to check database connectivity?
- QExplain the relationship between a Deployment, a ReplicaSet, and a Pod. Why does Kubernetes have this three-layer hierarchy instead of Deployments managing Pods directly?
- QWhat happens when you run
kubectl rollout undo? Does it redeploy the old image? What are the failure modes? - QHow do you design a zero-downtime deployment strategy in Kubernetes? What configuration parameters matter and why?
Frequently Asked Questions
What is the difference between a liveness probe and a readiness probe?
A Liveness probe tells K8s if your container is 'alive'. If it fails, the kubelet kills the container and restarts it according to the restartPolicy. A Readiness probe tells K8s if the container is 'ready' to handle traffic. If it fails, the Pod's IP is removed from the Endpoints of any associated Service, so no traffic reaches it, but the container is NOT restarted. You use readiness for apps that need to load large cache files or perform migrations before serving requests.
Why should I set both requests and limits for my containers?
Requests ensure your Pod is guaranteed a certain amount of resources on a node. Limits prevent a 'noisy neighbor' scenario where one buggy container consumes all the CPU or RAM on a node, causing other Pods to fail. In production, setting requests equal to limits (Guaranteed QoS) provides the most predictable performance.
How does a Deployment know which Pods it owns?
Deployments use Label Selectors. The spec.selector.matchLabels in the Deployment must match the spec.template.metadata.labels of the Pod. This loose coupling allows you to 'adopt' or 'orphan' Pods simply by changing labels, though this is rarely done manually.
What happens to a Deployment if a Node fails?
The Control Plane detects that the Node is 'NotReady'. After a timeout (pod-eviction-timeout), the Deployment controller notices the missing replicas and schedules new Pods on the remaining healthy nodes to restore the desired state.
What is a startup probe and when should I use it?
A startup probe runs before liveness and readiness probes. While the startup probe has not yet succeeded, liveness and readiness probes are disabled. Use it for applications with startup times greater than 30 seconds. Set failureThreshold high enough to cover the maximum expected startup time (e.g., failureThreshold: 60 with periodSeconds: 5 = 300 seconds max). Without a startup probe, the liveness probe kills the container during boot, causing CrashLoopBackOff.
How do I perform a canary deployment without a service mesh?
Use kubectl rollout pause after the Deployment creates one new Pod. The new Pod will be added to the Service endpoints automatically. Monitor error rates and latency on the canary Pod. If healthy, run kubectl rollout resume to complete the rollout. If unhealthy, run kubectl rollout undo to revert. Combine with maxSurge: 1 to ensure only one canary Pod is created during the pause.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.