KubernetesVI · kubectl for Administratorskubectl for administrators
kubectl logs, exec, cp, and debug — runtime inspection
What you'll learn
- Use kubectl logs to read container logs, including previous instances
- Use kubectl exec to run commands in a container
- Use kubectl cp to transfer files between local and container filesystems
- Use kubectl debug to attach an ephemeral container for live troubleshooting
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Read commands (get, describe, explain) tell you what the
controller sees. Runtime commands (logs, exec, cp,
debug) tell you what the container sees. They are the
on-call engineer’s stethoscope when a Pod is misbehaving in a
way the controller cannot see — a bug, a missing config, a
stuck process. This lesson covers the runtime-inspection toolkit
every production operator carries.
kubectl logs — container logs
kubectl logs <pod> reads the container’s stdout/stderr from
the kubelet’s log file (or the runtime’s stdout if the runtime
is containerd v1.7+ with the kubelet pipe enabled).
kubectl logs web-7c8 # first container
kubectl logs web-7c8 -c nginx # named container
kubectl logs web-7c8 --previous # previous instance
kubectl logs web-7c8 --tail=200 # last 200 lines
kubectl logs web-7c8 --since=10m # last 10 minutes
kubectl logs web-7c8 --since-time=2026-08-16T12:00:00Z
kubectl logs web-7c8 -f # follow (tail -f)
kubectl logs web-7c8 --timestamps # prepend RFC3339 timestamps
kubectl logs -l app=web --tail=50 # all pods with label
--previous is critical. When a container restarts, the
kubelet retains the previous container’s log file in
/var/log/pods/<ns>_<pod>/<container>/previous.log. --previous
reads that file. Without it, you only see logs from the current
instance.
For multi-container Pods, -c <name> selects the container.
If you omit -c, kubectl returns an error if there is more than
one container; passing -c is mandatory.
Logging drivers and what you actually get
The kubelet passes --log-driver to the runtime. The default
is json-file (writes to disk). With json-file, you get:
- One log file per container per restart
- Stdout and stderr merged
- A 10 MB rotation limit (configurable)
- Logs from
previousinstances retained inprevious.log
If the runtime is journald or fluentd (rare), kubectl logs
reads from the journal or talks to the fluentd socket
respectively. Most production clusters use json-file and pipe
through a node-level log shipper (Fluent Bit, Vector).
kubectl exec — run a command in a container
kubectl exec opens an interactive or one-shot session in a
container. It uses the kubelet’s container runtime exec API
(which runs runc exec on containerd or nsenter on Docker).
kubectl exec -it web-7c8 -- /bin/bash # interactive bash
kubectl exec -it web-7c8 -- /bin/sh # for distroless images
kubectl exec web-7c8 -- ls /var/log # one-shot command
kubectl exec web-7c8 -c nginx -- curl localhost:8080/healthz
kubectl exec -it web-7c8 -c nginx -- /bin/bash # multi-container
kubectl exec -n kube-system etcd-node-1 -- etcdctl ... # control plane
-i keeps stdin open, -t allocates a TTY. Together they
give you an interactive shell. Without -it, you get a one-shot
command.
The command after -- runs inside the container, not on
the host. The container’s filesystem, environment, and PID
namespace are what you see. To run on the host, SSH to the
node or use kubectl debug --profile=sysadmin (see below).
Limitations of exec
- The container must be running. If the container is in
Waiting(e.g.,CrashLoopBackOff), exec fails. Usekubectl debuginstead. - The container must have a shell or the command you want to run. Distroless images have no shell; you must exec the exact binary you need.
kubectl execdoes not work for initContainers that have completed.- The command runs as the container’s user. If the container
has
runAsNonRoot: true, you cannot run as root.
kubectl cp — file transfer
kubectl cp copies files between the local filesystem and a
container. It uses tar over the exec channel: kubectl starts
a tar process in the container, streams the data over stdin/
stdout.
kubectl cp ./local-file.txt web-7c8:/tmp/remote-file.txt
kubectl cp web-7c8:/var/log/app.log ./app.log
kubectl cp ./config.yaml web-7c8:/etc/app/config.yaml -c app
kubectl cp -n team-a-prod web-7c8:/var/log/audit.log ./audit.log
cp has limitations:
- It uses
tarin the container; if the container has notar(very minimal images), cp fails. Usekubectl exec cat < /file > local-fileas a fallback. - It is not transactional. If the transfer fails partway, you have a partial file in the container. For configuration files, prefer mounting a ConfigMap as a volume update rather than copying.
- It does not preserve ownership or special permissions.
For production config updates, never use kubectl cp as
the normal workflow. Use it for ad-hoc extraction (e.g.,
grabbing a config file to inspect), not as a deployment
mechanism.
kubectl debug — ephemeral debug containers
kubectl debug was added in Kubernetes 1.23 to address the
“how do I debug a Pod whose main container has no shell, or
is in CrashLoopBackOff, or is otherwise undebuggable” problem.
It attaches an ephemeral container to a running Pod.
kubectl debug web-7c8 -it --image=busybox --target=app
# attaches an ephemeral busybox container that shares the
# network namespace with the main 'app' container
kubectl debug web-7c8 -it --image=nicolaka/netshoot --copy-to=web-7c8-debug
# copies the Pod spec, swaps the container image, and runs a
# debuggable replica
There are several modes:
--target=<container>— attach an ephemeral container that shares the network namespace with the named container. This is the standard “debug the network” mode.--image=<image>— choose the debug image. Common choices:busybox,nicolaka/netshoot,alpine,ghcr.io/ kubernetes-debug-tools/debug-tools.--copy-to=<new-pod>— clone the Pod spec into a new Pod with one container swapped. Useful when the original Pod cannot accept an ephemeral container (e.g., it has its own resource quota).--profile=sysadmin(1.30+) — runs the debug container with host namespaces, giving you chroot, mount, and network visibility at the node level. Powerful, requires explicit RBAC.
When to use debug vs exec vs logs
| Situation | Command |
|---|---|
| Container is running, has a shell | kubectl exec |
| Container is running, no shell | kubectl exec -- <binary> |
| Container is in CrashLoopBackOff | kubectl debug with --copy-to |
| Container has no debug tools (no curl, no nslookup) | kubectl debug with netshoot image |
| Need to read kernel-level state | kubectl debug --profile=sysadmin |
| Need stdout/stderr from before the crash | kubectl logs --previous |
| Need to extract a file | kubectl cp |
Cross-course references
- The Docker course part
XXIX-Docker-Buildcoversdocker exec,docker logs,docker cp— the same set of runtime-inspection tools, before they were lifted to the cluster level. - The Linux course part
XXII-Linux-NetTroubleshootcoversss,tcpdump,strace— the system-level tools you run inside a container (via exec/debug) when triaging. - The Observability course part
LXXXIX-Kubernetes-Loggingdiscusses the cluster log pipeline; kubectl logs reads the same data the pipeline ships.
Quiz
Knowledge check · 4 questions
Q1. Which command reads the logs from the previous instance of a container that just restarted?
Q2. `kubectl exec` is read-only and does not require any RBAC beyond `pods/get`.
Q3. A Pod is in `CrashLoopBackOff`. The container image is distroless (no shell, no curl, no nslookup). The on-call engineer needs to figure out why. Walk through the read-only then debug workflow.
Pod `web-7c8` is in `CrashLoopBackOff`. The container image is `gcr.io/distroless/static-debian12`. The container runs a Go binary that listens on port 8080. The container crashes immediately on start with exit code 2. The previous logs say `bind: address already in use`. There are 5 other replicas of the same Deployment running fine on different nodes.
Q4. Describe the difference between `kubectl exec` and `kubectl debug`. When is each the right choice?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Read-only triage first. Logs, then exec, then debug. Each step is more invasive than the last.
- Always pass
--previouswhen a container is restarting. The current logs only show the new instance’s startup; the crash message is inprevious. - Use
kubectl debugwith--targetfor network-level inspection. Netshoot +--targetis the standard “what does this Pod’s network actually look like” tool. - Restrict and audit
pods/exec. A shell in a production container is a privilege; production RBAC should scope it to specific identities and audit-log every invocation. - Avoid
kubectl cpas a deployment workflow. For configuration changes, mount a ConfigMap or update the image; cp is for one-off extraction during triage.