Log Management 2026-08-11

Kubernetes Log Management for Clusters Nobody Officially Owns

There is a specific kind of cluster this article is for: three to ten nodes, set up by someone who may have since left, running production workloads, owned by “the backend team” in the sense that nobody else will admit to it. Kubernetes log management for that cluster cannot be a platform project, because there is no platform team. It has to be a small, boring setup that one person can install in an afternoon and then mostly forget.

Here is that setup. Four rules, one agent, one config sketch, and a list of things you are explicitly allowed to skip.

Rule 1: everything logs to stdout, no exceptions

Kubernetes has one blessed logging path: containers write to stdout/stderr, the runtime writes those streams to files on the node (under /var/log/pods/, per the Kubernetes docs), and anything on the node can read them.

Every deviation from this path — apps writing to files inside the container, custom log volumes, in-process shipping to a remote endpoint — creates a snowflake you will forget about until the day its logs are missing. I have debugged an outage where the one service that “helpfully” wrote to its own logfile inside an emptyDir was, of course, the service that crashed, taking its logs with it.

So the rule is absolute: stdout, structured JSON if you can get it, one event per line. If a third-party container insists on writing files, run a minimal tail-to-stdout arrangement for that one pod and treat it as tech debt. This is the only rule that requires changing application behavior, and it is worth every argument it causes.

Rule 2: one DaemonSet, not a sidecar zoo

Collection is a node problem, not a pod problem. Run a single lightweight agent as a DaemonSet — one pod per node, reading /var/log/pods/, enriching with pod metadata, shipping everything to one central endpoint. Fluent Bit and Vector are both solid choices here; both are small (tens of MB of memory per node in typical setups), both speak every major output protocol.

Here is a trimmed Fluent Bit configuration as a sketch — treat it as a starting point to check against current docs, not something to paste blind, since input names and defaults shift between versions:

[INPUT]
    Name              tail
    Path              /var/log/pods/*/*/*.log
    Parser            cri
    Tag               kube.*
    Mem_Buf_Limit     16MB
    Skip_Long_Lines   On

[FILTER]
    Name              kubernetes
    Match             kube.*
    Merge_Log         On          # parse JSON in the log field
    Keep_Log          Off

[OUTPUT]
    Name              http
    Match             kube.*
    Host              logs.internal.example.com
    Port              443
    tls               On
    Format            json
    Retry_Limit       5

The kubernetes filter is the part that earns its keep: it attaches namespace, pod name, container name, and labels to every line, which is what makes the next rule possible.

Set memory limits on the agent and set them honestly. An unbounded log shipper on a node with a misbehaving chatty pod becomes the second incident of the night.

Rule 3: route by labels, not by parsing

You already label your workloads (or should): app, team, env. Since the DaemonSet agent attaches those labels to every log line, all your routing decisions become label matches — no regex on message bodies, no per-service parser configs.

Practical routing for a small cluster is short:

Match Action
env=prod Ship to central store, full retention
env=staging Ship, short retention (days)
namespace=kube-system Ship errors only, or sample hard
Known chatty component (ingress access logs) Sample or drop at the agent

That last row matters more than it looks. In most small clusters, the ingress controller’s access logs are half the total volume and answer questions your app logs already answer. Dropping or sampling them at the agent is the single cheapest cost cut available, and it costs one filter stanza.

Where should the central endpoint point? Anything that accepts JSON over HTTP or OTLP and lets you filter by those labels — Loki, a ClickHouse-backed store, a hosted service. If you are already running an observability stack like LogReplay for errors and session replay, pointing the agent at its log ingest means one less system to keep alive, which is the entire spirit of this exercise. The specifics of picking the destination are a separate decision — start here if you are centralizing logs for the first time.

Rule 4: capture the death rattle

The log that matters most is the one printed right before a container died, and there are two ways to lose it.

First: the crash loop. A pod restarts, and naive tooling shows you the fresh container’s logs — which are empty, because it just started. The previous container’s logs are still on the node; kubectl logs --previous reads them. But kubelet only keeps a limited number of rotated log files per container, so a pod crash-looping every ten seconds churns through that window fast. This is exactly why the DaemonSet must ship continuously: by the time you are investigating, the node-local files may be gone, but the central store has every line from every incarnation.

Second: rotation on a chatty node. Kubelet rotates container logs at a size threshold (configurable via containerLogMaxSize and containerLogMaxFiles; check your distribution’s defaults — managed offerings differ). If a pod logs faster than your agent tails during an incident, rotation can outrun collection. The Mem_Buf_Limit and file-buffer settings in your agent are the defense; make sure the agent’s own “I am dropping data” metrics go somewhere a human will see.

While you are at it, alert on one more thing: the DaemonSet itself missing from any node. A logging agent that silently isn’t running is worse than none, because you believe you have coverage.

What you are allowed to skip

Everything else, basically. Specifically and with confidence:

The whole setup is one DaemonSet manifest, one config, and a labeling convention you mostly had already. It survives node failures, catches crash loops, and needs attention roughly never — which is the correct amount for a cluster nobody officially owns.

See the bug the way your user did

LogReplay captures session replays, console output, network requests, and errors in one timeline — so you stop guessing what happened before the ticket arrived.

Try LogReplay free