Container logs
Reading container stdout and stderr with kubectl logs (previous instances, multi-container Pods, selectors, time windows), where the logs live on the node, rotation, and streaming file logs through a sidecar.
Exam tasks: 5.4 (manage and evaluate container output streams)
The decision: can kubectl logs reach the output you need (right container, right instance, right time
window), or do you have to read it on the node, or first make the app's output visible at all?
Where container output goes
- Only stdout and stderr reach
kubectl logs. A file the app writes inside its filesystem doesn't. - The kubelet keeps the current container's log and the log of the previous instance after a restart. When the Pod is deleted, its log files go too.
- The kubelet rotates logs:
containerLogMaxSize(default10Mi) andcontainerLogMaxFiles(default5) in the kubelet config.kubectl logsreads only the latest file.
kubectl logs, flag by flag
| Need | Command |
|---|---|
| Output of a single-container Pod | k logs ledger-0 |
| One container in a multi-container Pod | k logs ledger-0 -c shipper |
| All containers at once, labelled | k logs ledger-0 --all-containers --prefix |
| The instance that crashed before the current one | k logs ledger-0 -p (--previous) |
| Follow live | k logs -f ledger-0 |
| Last 50 lines | k logs ledger-0 --tail=50 |
| Last 15 minutes, with timestamps | k logs ledger-0 --since=15m --timestamps |
| Since an exact time | k logs ledger-0 --since-time=2026-03-04T10:00:00Z |
| Every Pod with a label | k logs -l app=ledger --prefix --tail=20 |
| One Pod of a Deployment (picked for you) | k logs deploy/ledger |
| Every Pod of a Deployment | k logs deploy/ledger --all-pods |
| An init container | k logs ledger-0 -c fetch-schema |
Exam signal
If a Pod has more than one container and you don't pass -c, kubectl uses the default container (the
kubectl.kubernetes.io/default-container annotation, otherwise the first) and prints a notice listing the
others. The answer the task wants is often in the other container. List them with
k get pod ledger-0 -o jsonpath='{.spec.containers[*].name}'.
Logs of a crash loop
k logs on a Pod in CrashLoopBackOff often shows nothing or only start-up lines, because the current
instance has just started (or is waiting to). The error that killed it is in k logs --previous. If even that is
empty, the process died before printing anything: check k describe pod for the exit code and reason.
--previous: logs of the last terminated instance of the container.--tail when you use a label selector (-l); without one, all lines.--max-log-requests: concurrent streams when following with a selector.namespace_pod_uid).Saving logs for the grader
Tasks often say "write the log lines containing ERROR to /opt/out/errors.log".
k logs ledger-0 -c api -n vault | grep ERROR > /opt/out/errors.log
k logs ledger-0 -c api -n vault --previous > /opt/out/crash.log 2>&1
wc -l /opt/out/errors.log # verify something was writtenkubectl logs writes the log to stdout and its own warnings (such as the default-container notice) to stderr.
Redirect only stdout unless the task asks for everything.
When kubectl logs can't help
- API server down: read files directly on the node:
sudo tail -50 /var/log/pods/kube-system_kube-apiserver-*/kube-apiserver/*.log, orsudo crictl logs <container-id>. See Troubleshooting the control plane. - Node services aren't containers: kubelet and containerd log to the systemd journal.
sudo journalctl -u kubelet --since "30 min ago",sudo journalctl -u containerd -e. - The app writes to a file: add a sidecar that streams the file to its own stdout.
Streaming a log file through a sidecar
A native sidecar is an init container with restartPolicy: Always. It starts before the app, keeps running
alongside it, and doesn't block Pod completion.
apiVersion: v1
kind: Pod
metadata:
name: tally
namespace: vault
spec:
initContainers:
- name: log-tail
image: busybox:1.37
restartPolicy: Always # makes it a sidecar
command: ["sh", "-c", "exec tail -F /var/log/tally/app.log"]
volumeMounts:
- name: logs
mountPath: /var/log/tally
containers:
- name: tally
image: busybox:1.37
command: ["sh", "-c", "while true; do echo \"$(date) tally tick\" >> /var/log/tally/app.log; sleep 5; done"]
volumeMounts:
- name: logs
mountPath: /var/log/tally
volumes:
- name: logs
emptyDir: {}Then k logs tally -n vault -c log-tail -f. A sidecar under containers (the older pattern) works the same for
logs; the native form is preferred when the Pod belongs to a Job, because it won't keep the Job from finishing.
Cluster-level logging patterns
| Pattern | How | When |
|---|---|---|
| Node agent | DaemonSet (Fluent Bit, Vector, OpenTelemetry Collector) reads /var/log/containers on every node | Default choice: one agent per node, apps just write to stdout |
| Streaming sidecar | Sidecar tails a file to stdout | App can only log to files |
| Agent sidecar | Sidecar ships logs straight to the backend | Per-app routing; costs a container per Pod |
| Direct from app | App's logging library pushes to the backend | Fine-grained control; ties app to backend |
Scenarios
The current instance only just started, so its log can't show why the previous one died; --previous reads the
terminated instance. With two containers you also need -c to read the one that actually restarts. More lines
from the current instance don't help, the other Pods aren't the problem, and container logs live on the node
running the Pod, not the control plane.
A streaming sidecar turns the file into stdout, which the runtime and kubelet then capture. terminationMessagePath
only feeds the short termination message shown in describe. Mounting node log directories into the app
doesn't make it write to stdout, and rotation size is irrelevant when nothing reaches stdout.
Drill
kubectl config use-context lab-logs. Pod quarry in namespace strata runs two containers. Save the log lines
containing WARN from the previous instance of whichever container has restarted to /opt/drill/warn.log.
k get pod quarry -n strata \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\n"}{end}'
# example output: digger 0, sorter 4 -> sorter is the one restarting
k logs quarry -n strata -c sorter --previous | grep WARN > /opt/drill/warn.logVerify:
head -3 /opt/drill/warn.log
grep -vc WARN /opt/drill/warn.log # 0: only WARN lines were savedFurther reading
Resource usage
Measuring what nodes, Pods and containers actually consume with metrics-server and kubectl top, telling usage apart from requests, and using events to explain evictions and pressure.
Troubleshooting applications
Reading Pod status and events to fix Pending, ImagePullBackOff, CreateContainerConfigError, CrashLoopBackOff, OOMKilled and not-ready Pods, plus kubectl exec and kubectl debug for containers without a shell.