Page 4 of 4
The real, day-to-day debugging loop for the failure states you'll actually hit:
CrashLoopBackOff - the container starts, exits (usually non-zero), and Kubernetes keeps retrying with exponential backoff. kubectl logs --previous is the single most useful command here - it shows the log output from the crashed attempt, not the fresh (and often empty) restart.ImagePullBackOff - the node can't pull the image: wrong tag, private registry without imagePullSecrets, or a typo in the image name. kubectl describe pod → Events will show the exact pull error.Pending - the scheduler can't place it: insufficient CPU/memory across all nodes, an unsatisfiable nodeSelector/affinity rule, or an unsatisfied PersistentVolumeClaim. kubectl describe pod → Events again, always.OOMKilled - the container exceeded its memory limit and the kernel killed it. The fix is either raising the limit (if the usage is legitimate) or finding and fixing a real memory leak - kubectl describe pod shows Last State: Terminated, Reason: OOMKilled.Pending (no matching/provisionable PV), or a volume already exclusively attached elsewhere (common with ReadWriteOnce volumes when a Pod reschedules to a new node before the old attachment is released).kubectl get endpoints <service> show that Pod's IP? Does nslookup <service> resolve from inside another Pod? Each of those isolates a different possible failure point.kubectl describe pod my-pod # always the first command - Events explain almost everything
kubectl logs my-pod --previous # the crash that already happened, not the fresh restart
kubectl get events --sort-by=.lastTimestamp # cluster-wide recent events, chronological
kubectl top pod my-pod # live CPU/memory usage, if metrics-server is installed
Writing and maintaining raw YAML for every environment (dev/staging/prod, each with different replica counts, resource sizes, hostnames) gets unwieldy fast. Helm is Kubernetes' package manager: a chart is a templated bundle of manifests, values.yaml supplies the parameters that fill in those templates, and a release is one specific installation of a chart with a specific set of values into a cluster.
helm install my-app ./my-chart -f values-prod.yaml
helm upgrade my-app ./my-chart -f values-prod.yaml # apply a new chart version or changed values
helm rollback my-app 2 # back to release revision 2
helm list # what's currently installed
helm get values my-app # what values a running release was actually installed with
helm template ./my-chart -f values-prod.yaml # render the manifests WITHOUT installing - the first thing to run when a chart misbehaves
Charts can declare dependencies on other charts (a web-app chart depending on a shared Postgres chart, say), pulled in via helm dependency update. When a Helm release goes wrong, helm template (render-only, no cluster interaction) and helm get values (what was actually supplied, vs what you think you supplied) are the two commands that resolve the overwhelming majority of "why did this deploy differently than expected" confusion.
Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.