Logging pipeline — Vector, Loki, Logstash
The pipeline moves CRC application logs to two Loki backends with a standing, self-checking proof that they arrived. This page is the setup + usage reference; the measured results and hard-won lessons (5k lines/s ceiling, label discipline, the kubelet rotation trap) live in observability.md and are not repeated here.
Part A — How it is set up
- Collection — OpenShift Cluster Logging (Vector collectors)
with a ClusterLogForwarder whose application input covers the
log-simnamespace only. That scope is deliberate: cluster-wide forwarding is a later step, not a gap in disguise. - Two paths from the forwarder:
- Logstash (tooling plane,
observabilitynamespace) → the local Loki, plus a plain-text file archive (cluster/namespace/pod/container/log.logon a PVC) mirroring the office pattern. - The Vector aggregator — a StatefulSet in CRC’s
log-aggnamespace. Its config is one file per concern and one file per sink, so a sink is added or removed as exactly one file in git. End-to-end acknowledgements are on; every sink has a disk buffer (256 MiB) that blocks rather than drops when full.
- Logstash (tooling plane,
- The aggregator’s sinks (files in
clusters/crc/logging/vector-aggregator/config/sinks/):00-metrics— its own Prometheus exporter;10-loki— the local Loki on the tooling plane;15-logstash-fallback— Logstash over a dedicated bridge, so the archive path survives even when reached indirectly;20-grafana-cloud-loki— Grafana Cloud hosted Loki, authenticated with a write-only token delivered from Vault by a Secrets Operator tenant (secret/lab/grafana-cloud/loki-token).
- Labels are exactly
log_type, log_source, hostname, level— the allowlist is load-bearing (an unfiltered field-to-label mapping once created ~10.7k junk streams and silent drops). - Local Loki (tooling plane): upstream chart, SingleBinary,
filesystem storage on a 10 GiB PVC, 72 h retention (compactor),
max_global_streams_per_user50000. Tempo sits next to it as the tracing backend — live, datasource wired, waiting for instrumented apps. - Double storage is by design: aggregator-path events land in the local Loki twice (sink 10 direct, sink 15 via Logstash). Raw line counts lie; distinct producer-sequence counts are the truth, and every verifier below counts that way.
Part B — How to use it
- Read logs: Grafana Explore against the Loki datasource
(tools/grafana.md) with LogQL — start from
{log_type="application"}and narrow byhostname/level. The same streams exist in Grafana Cloud’s hosted Loki (sink 20) if the tooling plane is the thing that is down. - Prove delivery (do this instead of eyeballing): the
log-delivery-testCronJob inlog-simre-proves both backends every 3 hours; runbook 12 is the manual version (emit a marker, count distinct sequences in both Lokis). The health suite’sgrafana-cloud-log-deliverycheck runs the same proof hourly from the agent side. - Trigger the CronJob now:
kubectl create job --from=cronjob/log-delivery-test <name> -n log-simon CRC, then read the job’s logs — it prints both counts and exits non-zero on a miss. A failed CronJob twice in a row raises theLogDeliveryTestConsecutiveFailuresalert (see tools/prometheus-alertmanager.md). - Add a sink: one new file in the sinks directory following the existing files’ shape (buffer block included), plus one line in the aggregator’s generator list — nothing else. If the sink needs a credential, it arrives as a Vault Secrets Operator tenant (vault.md), never as a literal in the config.
- Check pipeline health: the aggregator’s own metrics (sink 00) expose per-sink sent/discarded counters; the hop where the count shrinks while its discarded counter climbs is the losing hop.
- The file archive: Logstash’s PVC holds the plain-text mirror
under
cluster/namespace/pod/container/log.logpaths — the fallback of last resort when both Lokis are suspect.