Kubernetes log collection comparison¶
This page records measured KAN-86 results for the in-cluster Fluent Bit and external Grafana Alloy collection paths. It is deliberately separate from the design and the operations runbook: designs state intended behavior, while this page records what was observed.
Status¶
The production topology, mandatory Loki mTLS, Grafana access, Pod replacement, Loki outage, one collector-restart trial, and a live receipt-worker event were verified on 2026-09-14. KAN-86 was accepted as complete after both collection methods returned the same live application event through Grafana. The broader benchmark tests listed below were deliberately deferred and are not required for this functional-verification scope.
Test environment¶
| Item | Value |
|---|---|
| Cluster | brownrook-k3s1 |
| Namespace | home-budget |
| K3s worker nodes | One measured node, k3s1 |
| Agent path | Fluent Bit 4.2.0 DaemonSet |
| API path | Grafana Alloy 1.19.2 systemd service |
| Backend | Single-process filesystem Loki on the monitoring host |
| Query path | Grafana datasource proxy using datasource UID cfy4qg8i178jkb |
| Security state | Loki RequireAndVerifyClientCert plus nftables source allowlist |
Synthetic tests emitted a monotonically increasing sequence=N once per
second. After stopping the source and allowing delivery to settle, the same
Grafana query was grouped by collection. Missing values, duplicates, and
path-only values were calculated from the unique integer sequences.
Run the committed analyzer for another sequenced marker:
scripts/compare_log_collection_probe.sh MARKER 1h
It prompts for the Grafana administrator password through curl; the password
is not accepted as a command-line argument or stored in Git.
Results summary¶
| Test | Fluent Bit | Kubernetes API and Alloy | Observation |
|---|---|---|---|
| Initial dual delivery | Probe present | Probe present | Grafana returned both collection values |
| Live receipt-worker event | Exact terminal event present | Exact terminal event present | Both paths returned the same receipt hash and status=review_required through Grafana |
| Application Pod replacement | 46 old-Pod and 50 replacement-Pod records | 46 old-Pod and 50 replacement-Pod records | Exact counts for both Pod identities |
| Loki outage and recovery | 269/269 unique, no gaps or duplicates | 269/269 unique, no gaps or duplicates | Exact sequence sets matched after recovery |
| Collector restart | 166/167 unique, sequence 114 missing, no duplicates | 167/167 unique, no gaps or duplicates | One Fluent Bit record gap in this trial |
These are small controlled samples, not service-level guarantees. The collector-restart result must be repeated before treating its one-record difference as representative.
Resource and latency baseline¶
The following are single-point observations taken after both collectors were active. They are not normalized benchmark results.
| Measurement | Fluent Bit | Alloy |
|---|---|---|
| Placement | One Pod on k3s1 |
One process on monitoring host |
| CPU observation | 3m from Kubernetes metrics |
0.4% from ps |
| Resident memory | 32 MiB |
65,856 KiB from ps; exporter later reported about 73.3 MiB |
| Delivered volume at sample | 1,521 records / 497,152 bytes | 118,580 records / 8,960,756 bytes |
| Dropped records | 0 | 0 for every reported reason |
| Historical retries before outage test | 0 | 24 batches, corresponding to 24 HTTP 500 responses |
| Mean reported propagation/output latency | About 0.500 seconds | About 0.391 seconds |
| Records within one second | 1,382/1,384, about 99.9% | 110,699/118,580, about 93.4% |
The counters cover different process lifetimes and workloads. Fluent Bit cost is per node and scales with cluster size; Alloy is centralized and its process also performs discovery, relabeling, processing, and WAL management. The CPU units come from different tools and should not be compared as if they were the same sample. A synchronized sustained-load test is still required.
Pod replacement test¶
A controlled Deployment emitted KAN86_RESTART_PROBE every two seconds. Its
original Pod was deleted while logging, and Kubernetes created a replacement.
Grafana returned:
| Collection | Deleted Pod | Replacement Pod |
|---|---|---|
fluent-bit |
46 | 50 |
kubernetes-api |
46 | 50 |
Both collectors retained the observed tail of the deleted Pod and began collecting the replacement. This test did not restart either collector.
Loki outage test¶
A second Deployment emitted KAN86_LOKI_OUTAGE_PROBE every second. Loki was
stopped while the source continued from sequence 32 through at least sequence
85. Loki was then started and returned HTTP 200 from /ready after its ring
stabilized.
The final Grafana result was identical for both paths:
- sequences 0 through 268;
- 269 unique records;
- zero missing sequences;
- zero duplicates; and
- zero records present in only one collection.
Fluent Bit retries rose from zero to 134 during the outage and to 221 after recovery. Its filesystem chunks rose from one to 64, then drained back to one. Output errors, abandoned retries, and dropped records remained zero.
Alloy batch retries rose from 24 to 30. Sent entries rose from 118,580 to
120,006, all dropped-entry counters remained zero, and its WAL watcher remained
active. Application-side kubectl logs continued working while Loki was down.
Collector restart test¶
A third Deployment emitted KAN86_COLLECTOR_RESTART_PROBE every second. Alloy
was restarted and reported ready. The Fluent Bit Pod was then deleted and
recreated by its DaemonSet. The source was removed and the result was queried
again after the final Alloy record had settled.
| Collection | Range | Unique records | Missing | Duplicates |
|---|---|---|---|---|
fluent-bit |
0–166 | 166 | 114 | 0 |
kubernetes-api |
0–166 | 167 | None | 0 |
Sequence 114 appeared only in the Kubernetes API collection. An early query temporarily lacked Alloy's final sequence 166, but that record appeared on the settled query. The final observation is therefore one Fluent Bit gap and no Alloy gaps or duplicates in this single trial.
Live receipt-worker event¶
During a real OCR reprocessing run, the worker emitted a terminal event for
receipt hash
0774172aab99fddba9b689b7b62b23db7ac0f23ae263d79c7bcfb8f2227cbc96
with status=review_required. A Grafana datasource-proxy query filtered on the
receipt-worker container, exact hash, and exact status. Grafana returned the
same event from Pod receipt-worker-65888f8744-tf226 in both the fluent-bit
and kubernetes-api collections. The API-collected form retained a trailing
newline; the application message content otherwise matched exactly.
This proves that both paths can carry and search a real application terminal event through the user-facing Grafana query path, not only synthetic probes.
Security and operational comparison¶
| Dimension | Fluent Bit node agent | External Alloy API collector |
|---|---|---|
| Installation boundary | Requires a DaemonSet in the application cluster | No collector workload in the application cluster |
| Data access | Reads node /var/log through hostPath |
Reads only Pods and pods/log in home-budget |
| Kubernetes identity | Namespace Role for Pod metadata | Namespace Role for Pod discovery and log reads |
| Secret access | Denied | Denied |
| Loki identity | Dedicated fluent-bit-loki-client certificate |
Dedicated alloy-loki-client certificate |
| Local recovery state | Per-node tail database and filesystem chunks | Central WAL on monitoring host |
| Scaling cost | Collector CPU/memory/storage on every node | One external collector process; API and network cost grow with streams |
| Vendor-managed suitability | Not suitable when agents or node access are prohibited | Suitable if a stable API endpoint and least-privilege credential are available |
Both approaches add operational work beyond application logging. Fluent Bit adds Kubernetes workload lifecycle, per-node storage, and node filesystem privilege. Alloy adds external credential rotation, API reachability, and dependence on terminated-Pod log availability. Both require Loki client certificate rotation for this deployment.
Interim findings¶
- Both paths deliver useful, bounded Kubernetes metadata and remain searchable with application identifiers kept in message content.
- Both paths returned the same real
receipt-workerterminal event when filtered by its exact receipt hash andstatus=review_requiredin Grafana. - Both paths recovered an exact record set after the controlled Loki outage.
- Both paths followed an application Pod replacement without loss in the measured window.
- The single collector-restart trial favored Alloy by one record, but the sample is too small for a general reliability conclusion.
- Fluent Bit used less memory in the observed single-node baseline, but its cost repeats on every node. The Alloy process used more memory but stays outside the application cluster.
- Method B satisfies the stated vendor boundary; Method A does not when in-cluster software or node-log access is prohibited.
Both methods are operationally viable for BrownRook-managed clusters based on the functional checks in this comparison. This story does not select one as the preferred managed-cluster collector because the deferred benchmark work is needed for that decision. For vendor-managed clusters that prohibit agents, the Kubernetes API path is the only tested design that satisfies the installation boundary, subject to the vendor providing the required API access and retention behavior.
Deferred tests¶
The following measurements were left for future work and did not block closing KAN-86 after the agreed Grafana verification:
- Emit and compare a Python multiline traceback.
- Repeat collector restarts enough times to estimate loss and recovery delay.
- Interrupt Kubernetes API access and compare continued collection behavior.
- Run a synchronized sustained-load window and capture CPU, memory, storage, network, delivery latency, and completeness from aligned counters.
- Select a preferred collector for BrownRook-managed clusters if that decision becomes necessary.