Debug a CrashLoopBackOff and a Container Without a Shell
Deployment `payments` in namespace `payments` is in CrashLoopBackOff after a configuration change. The team also needs to inspect Pod `ledger` in the same namespace, whose container `app` runs a minimal image without a shell.
Live lab access
This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.
Create an account to continue →
On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Deployment payments in namespace payments is in CrashLoopBackOff after a configuration change. The team also needs to inspect Pod ledger in the same namespace, whose container app runs a minimal image without a shell.
Requirements
1. Save the log output of the most recently crashed payments container to /root/payments-crash.log before changing anything.
2. Fix the Deployment according to the error in that log. Take the value from the existing ConfigMap; do not copy it into the Deployment.
3. Ensure both payments replicas are available and the new Pods start successfully.
4. Attach an ephemeral container named debugger using image busybox:1.36.1 to Pod ledger, targeting container app.
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster.
Read the evidence of a crash loop, then debug a container that has no shell
A container in CrashLoopBackOff is not stuck: the kubelet keeps restarting it, waiting longer each time (10s, 20s, 40s and so on, capped at five minutes). The reason it died is usually printed by the container that already exited, so the skill is collecting that evidence before you change anything. The second skill is getting a shell next to an application whose image ships without one, which is what ephemeral containers are for.
container exits ──→ kubelet waits (back-off) ──→ restarts ──→ exits again
│
└── evidence: kubectl logs --previous, describe (Last State, Exit Code, Events)
1. Build a crash loop you understand
Save this as ckad-debug-banner.yaml. The script exits with code 3 when GREETING is empty. The configMapKeyRef points at a key name that does not exist, and because the reference is marked optional: true, Kubernetes starts the container anyway with no value instead of refusing to start the Pod.
apiVersion: v1
kind: ConfigMap
metadata:
name: banner-settings
namespace: prepium-tutorial
data:
greeting.text: "hello from a ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: banner
namespace: prepium-tutorial
spec:
replicas: 1
selector:
matchLabels:
app: banner
template:
metadata:
labels:
app: banner
spec:
containers:
- name: web
image: busybox:1.28
command:
- sh
- -c
- |
if [ -z "$GREETING" ]; then echo "fatal: GREETING is not set"; exit 3; fi
echo "$GREETING"; sleep 3600
env:
- name: GREETING
valueFrom:
configMapKeyRef:
name: banner-settings
key: greeting_text # deliberate typo: the real key is greeting.text
optional: true
kubectl create namespace prepium-tutorial
kubectl apply -f ckad-debug-banner.yaml
kubectl -n prepium-tutorial get pods -l app=banner --watch
Within a minute or two the STATUS column shows CrashLoopBackOff and RESTARTS keeps climbing.
2. Collect the evidence first
POD=$(kubectl -n prepium-tutorial get pod -l app=banner -o jsonpath='{.items[0].metadata.name}')
kubectl -n prepium-tutorial logs "$POD" -c web --previous > banner-crash.txt
kubectl -n prepium-tutorial describe pod "$POD"
kubectl -n prepium-tutorial get configmap banner-settings -o yaml
--previous prints the log of the earlier instance of the container in the same Pod. Save it now: once a rollout replaces the Pod, that history is gone. In describe, the container shows State: Waiting with reason CrashLoopBackOff, and Last State: Terminated carries the exit code. Comparing the key in the Pod template with the keys under data exposes the typo.
3. Fix the reference, not the symptom
Change key: greeting_text to key: greeting.text in the file and apply it again. The Deployment rolls out a new Pod whose variable is filled from the ConfigMap.
kubectl apply -f ckad-debug-banner.yaml
kubectl -n prepium-tutorial rollout status deployment/banner
kubectl -n prepium-tutorial logs deployment/banner
Had the reference not been optional, the missing key would have stopped the Pod from starting at all, which is a different symptom with the same root cause. Also remember that environment variables taken from a ConfigMap are read when the container starts; editing the ConfigMap later does not change running Pods until they restart.
4. Attach an ephemeral container to a shell-less Pod
kubectl -n prepium-tutorial run quiet --image=registry.k8s.io/pause:3.1 --restart=Never
kubectl -n prepium-tutorial exec -it quiet -- sh # fails: no shell in the image
kubectl -n prepium-tutorial debug -it quiet --image=busybox:1.28 -c inspect --target=quiet
/ # ps
kubectl debug adds a container to the running Pod through the API's ephemeral containers subresource (stable since v1.25). --target places it in the process namespace of the named container, so ps lists the pause process. Without it you would only see your own shell, because Pods do not share a process namespace unless shareProcessNamespace is set. The container runtime must support targeting; if it does not, the debug container may fail to start or may run isolated.
Common mistakes
- Fixing first and reading logs later. After the rollout the crashed instance has been replaced and
--previoushas nothing left to show. - Treating every crash as an application bug. An exit code of 137 with reason
OOMKilledin Last State points at the memory limit instead. - Omitting
--targetand concluding the app is not running becausepsis nearly empty. - Trying to delete an ephemeral container or add one with
kubectl edit. Neither is possible: once added it stays in the Pod spec until the Pod is deleted.
| Example check | Evidence |
|---|---|
| Crash evidence was saved | The saved file contains the fatal message from the earlier instance |
| Value comes from the ConfigMap | env[].valueFrom.configMapKeyRef names an existing key |
| Rollout is healthy | rollout status succeeds and the new Pod's RESTARTS stays at 0 |
| Debug container targets the app | .spec.ephemeralContainers[*].targetContainerName names the app container |
Cleanup: kubectl delete namespace prepium-tutorial. References: Debug Running Pods, Ephemeral Containers, Pod Lifecycle, Configure a Pod to Use a ConfigMap. Next, try health probes or fixing a broken Deployment.
Practice scope
This additional curriculum lab is available in single-task practice with the existing 12-minute limit. The three CKAD mocks keep their original 17-task sets. Hints, an explained solution and requirement-by-requirement feedback are available in the lab.
Browse CKAD practice tasks →