kubectl debug, ephemeral containers and crictl
How to debug when the image has no shell: ephemeral containers, debug copies, node debugging Pods and crictl.
Key ideas in this lesson · tap one for a simple explanation
What you will be able to do
- Add an ephemeral debug container to a running Pod and see the app's processes, without restarting the Pod.
- Debug a crashing Pod through a changed copy, and delete the copy afterwards.
- Start a node debugging Pod, read the node's files through it, and delete it when you are done.
- Find a Pod's containers on the node with crictl and run a command in one.
Many production images are distroless: they hold the app and nothing else. No shell, no ps, no wget. So kubectl exec into them fails with executable file not found. kubectl debug fixes that: it brings a second image that has the tools. The exam expects you to use it quickly, and to clean up what it leaves behind.
When the itself is broken, kubectl cannot help at all. Then crictl, run on the , is the fallback.
Ephemeral container
joins a running Pod, which is not restarted
kubectl debug POD --target=app
Debug copy
a new Pod you are free to break
kubectl debug POD --copy-to=NEW
Node debugger
a Pod on the node, with the node's files at /host
kubectl debug node/NODE
All three bring their own image, such as busybox:1.36, with the tools the app image lacks.
Which tool, when
Ephemeral containers
An ephemeral container is a temporary container you add to a Pod that is already running. The Pod is not restarted. Use one to look inside an app whose image has no tools.
- It is limited. It has no ports, or resource settings, and it is never restarted.
- It stays. You cannot change or remove it. It stays in
spec.ephemeralContainersuntil the Pod is deleted. --target=appputs it in the same process list as containerapp. Thenpsshows the app's processes, and/proc/1/rootis the app's filesystem.- Pass
--profile=general, as the examples do. It gives the debug container the rights it needs to look at the app's processes.
# Interactive, the usual way: you land in a busybox shell next to the app's processes
kubectl -n shop debug -it api-0 --image=busybox:1.36 --target=app -c dbg --profile=general -- sh
# ps # the app's processes, PID 1 is its entrypoint
# ls /proc/1/root/etc # the app container's filesystem
# wget -qO- -T 3 http://localhost:8080/healthz
# Non-interactive: start it with a long-running command, then exec into it
kubectl -n shop debug api-0 --image=busybox:1.36 --target=app -c dbg --profile=general -- sleep 3600
kubectl -n shop exec api-0 -c dbg -- ps
# What it added to the Pod
kubectl -n shop get pod api-0 -o jsonpath='{.spec.ephemeralContainers[*].name}{"\n"}'
kubectl -n shop get pod api-0 -o jsonpath='{.status.ephemeralContainerStatuses[?(@.name=="dbg")].state}{"\n"}'Debugging a copy
Some containers crash before you can look inside. For those, make a copy of the Pod with one change, for example a command that only sleeps. The original Pod is not touched.
# Same Pod plus a busybox container, sharing one process namespace
kubectl -n shop debug api-0 --copy-to=api-debug --image=busybox:1.36 --share-processes -- sleep 3600
# Same Pod, but container app gets a different image
kubectl -n shop debug api-0 --copy-to=api-debug --set-image=app=busybox:1.36
# Same Pod, but container app runs a different command (it crashed on start)
kubectl -n shop debug api-0 --copy-to=api-debug --container=app -- sh -c 'sleep 3600'
kubectl -n shop get pod api-debug
kubectl -n shop delete pod api-debug # the copy is yours to clean upThe copy is a plain Pod with a new name. No controller manages it, so nothing deletes it for you. It also drops the original's and probes, so no sends it traffic. To find why a container crashes, start a copy that only sleeps. Then kubectl exec into it and run the real program by hand, to see its error.
Debugging a node
kubectl debug node/cka-worker --image=busybox:1.36 starts a Pod on that node. Through it you can look at the node as if you had logged in:
- It shares the node's processes and network, so
psshows the kubelet. - The node's whole filesystem is mounted at
/host.chroot /hostmakes the shell use the node's own programs. - The Pod is named
node-debugger-cka-worker-<suffix>and lands in the current . Pass-nso you know where it went. - Exiting the shell does not delete it. It stays as
Completeduntil you delete it. - Add
--profile=sysadminonly when you need a privileged container.
kubectl debug node/cka-worker -n shop -it --image=busybox:1.36 --profile=general -- sh
# cat /host/etc/hostname
# ps | grep kubelet # you see the node's processes
# ls /host/var/log/pods
# chroot /host # from here on, the node's own programs
kubectl -n shop get pods | grep node-debugger
kubectl -n shop delete pod node-debugger-cka-worker-x7k2p
# Non-interactive variant
kubectl debug node/cka-worker -n shop --image=busybox:1.36 --profile=general -- sleep 600
kubectl -n shop exec node-debugger-cka-worker-x7k2p -- cat /host/etc/hostnamecrictl on the node
crictl talks to the container runtime directly, without the API server. You run it in a shell on the node. These three commands find a Pod's containers and run a command in one:
# exam: ssh <node>, then sudo -i lab: docker exec -it cka-worker bash
crictl pods --name api-0 # Pod sandbox ID and state
crictl ps --pod <pod-id> # running containers of that Pod
crictl exec <container-id> cat /etc/hostname # only works if the image has the binarycrictl can also list stopped containers and read their logs. Control plane failures covers those commands.
In the official docs· open on exam day too
- Debug Running Podssearch: debug running pods ephemeral
- Ephemeral Containerssearch: ephemeral containers
- Debugging Kubernetes Nodes With Kubectlsearch: kubectl debug node
- Debugging Kubernetes nodes with crictlsearch: crictl
Debug a Pod that has no shell
payroll in namespace tsx-debug runs an image without a shell, so kubectl exec into it fails. Without restarting or recreating the Pod, add a debug container named dbg using image busybox:1.36 that shares the process namespace of container app and keeps running with the command sleep 3600. Then, from dbg, write the command line of process ID 1 to /tmp/cka/payroll-pid1.txt.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/ts-debug-1/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ts-debug-1/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/ts-debug-1/cleanup | bash
Read a node file through a node debugging Pod
kubectl debug, start a node debugging Pod on node cka-worker in namespace tsx-node with image busybox:1.36. Through that Pod, read the node's file /etc/hostname and write its content to /tmp/cka/worker-hostname.txt. Delete the debugging Pod when you are done.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/ts-debug-2/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ts-debug-2/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/ts-debug-2/cleanup | bash
Finished this lesson?
0 of 2 tasks passed. Your readiness counts tasks, so run them before you move on.