Asterrr's Handbook

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 (default 10Mi) and containerLogMaxFiles (default 5) in the kubelet config. kubectl logs reads only the latest file.

kubectl logs, flag by flag

NeedCommand
Output of a single-container Podk logs ledger-0
One container in a multi-container Podk logs ledger-0 -c shipper
All containers at once, labelledk logs ledger-0 --all-containers --prefix
The instance that crashed before the current onek logs ledger-0 -p (--previous)
Follow livek logs -f ledger-0
Last 50 linesk logs ledger-0 --tail=50
Last 15 minutes, with timestampsk logs ledger-0 --since=15m --timestamps
Since an exact timek logs ledger-0 --since-time=2026-03-04T10:00:00Z
Every Pod with a labelk logs -l app=ledger --prefix --tail=20
One Pod of a Deployment (picked for you)k logs deploy/ledger
Every Pod of a Deploymentk logs deploy/ledger --all-pods
An init containerk 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.

-p
--previous: logs of the last terminated instance of the container.
10 lines
Default --tail when you use a label selector (-l); without one, all lines.
5
Default --max-log-requests: concurrent streams when following with a selector.
10Mi × 5
Default kubelet log rotation: file size and number of files kept per container.
/var/log/pods
Real log files on the node, one directory per Pod (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 written

kubectl 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, or sudo 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

PatternHowWhen
Node agentDaemonSet (Fluent Bit, Vector, OpenTelemetry Collector) reads /var/log/containers on every nodeDefault choice: one agent per node, apps just write to stdout
Streaming sidecarSidecar tails a file to stdoutApp can only log to files
Agent sidecarSidecar ships logs straight to the backendPer-app routing; costs a container per Pod
Direct from appApp's logging library pushes to the backendFine-grained control; ties app to backend

Scenarios

Scenario
Pod pay-relay-6c4 in namespace treasury has two containers, relay and envoy, and restarts every few minutes. `k logs pay-relay-6c4 -n treasury` prints normal start-up lines. Where are you most likely to find the error that ends each instance?
Scenario
An app writes its log only to /data/out.log inside its container. The team wants `kubectl logs` to show those lines without changing the app's image. What is the standard approach?

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.

Further reading

On this page