Logs pipeline¶
Part of the self-contained SRE guide
This chapter restates the starform.io/* label set (the enrichment keys) and the per-cluster auth
model inline so the guide stands alone. Owned by PRD §35.1 / §35.4 / §24.1 — the PRD wins on
conflict. The Reference collects every inlined contract with its provenance.
In plain words
Completely different mechanism from metrics — no scraping, no discovery. Kubernetes already writes every container's output to a file on the machine. An agent on each machine tails those files, stamps each line with the pod's identity, and forwards everything to one collector that is the only thing allowed to write to ClickHouse.
flowchart LR
classDef built fill:#3434DC22,stroke:#3434DC,color:#5B5EE8;
classDef third fill:transparent,stroke:#808080,color:#808080;
classDef store stroke-dasharray:4 3,stroke:#808080,color:#808080;
POD["pod stdout<br/>/stderr"]:::third
FILE["node file<br/>/var/log/containers"]:::third
FB["Fluent Bit agent<br/>tail + enrich"]:::built
VEC["Vector aggregator<br/>sole writer"]:::third
CH[("ClickHouse<br/>partition by project_id")]:::store
POD --> FILE --> FB --> VEC --> CH
linkStyle 0,1,2,3 stroke:#ED8C2B,stroke-width:1.5px;
project · environment · service from the pod labels. The aggregator is the single fan-in point and the only client ClickHouse trusts. Amber = the logs pipe.How to build it
Three pieces in a line — a store, a regional aggregator, and a thin per-node agent. In order:
1 · Stand up the store (per region). Provision the ClickHouse droplet and create
logs.customer_logs — partitioned by project_id so retention can vary per tenant
(chapter 7) — plus an ingest user (for Vector) and a
read-only user (for control-plane reads, chapter 4):
CREATE TABLE logs.customer_logs
(
project_id String,
environment LowCardinality(String),
service_id String,
ts DateTime64(3),
level LowCardinality(String),
message String
)
ENGINE = MergeTree
PARTITION BY (project_id, toDate(ts)) -- scoped by project; per-tenant retention in ch.7
ORDER BY (project_id, environment, service_id, ts);
-- ingest user — Vector writes (INSERT only)
CREATE USER vector IDENTIFIED BY '<ingest-secret>';
GRANT INSERT ON logs.customer_logs TO vector;
-- read-only user — control-plane reads over peering (SELECT only) = ch.4's "read-only ClickHouse user"
CREATE USER reader IDENTIFIED BY '<read-secret>';
GRANT SELECT ON logs.customer_logs TO reader;
2 · Stand up the Vector aggregator (per region) — the only writer ClickHouse trusts. A
fluent source receives the agents, a remap normalizes identity, and the native batching
clickhouse sink (with a 5 GiB disk buffer) is what avoids ClickHouse's "too many parts":
# receive Fluent Bit, normalize identity, batch into ClickHouse (native sink)
[sources.fb]
type = "fluent"
address = "0.0.0.0:24224"
[transforms.norm]
type = "remap"
inputs = ["fb"]
source = '''
.project_id = .kubernetes.labels."starform.io/project-id"
.environment = .kubernetes.labels."starform.io/environment"
.service_id = .kubernetes.labels."starform.io/service-id"
'''
[sinks.ch]
type = "clickhouse"
inputs = ["norm"]
endpoint = "http://clickhouse.<region>.internal:8123"
database = "logs"
table = "customer_logs"
skip_unknown_fields = true
[sinks.ch.batch] # batching is what avoids "too many parts"
max_events = 100000
timeout_secs = 10
[sinks.ch.buffer]
type = "disk"
max_size = 5368709120 # 5 GiB on-disk backpressure buffer
when_full = "block"
3 · Deploy Fluent Bit as a DaemonSet — deliberately thin (~64Mi/node), it tails the container
log files, enriches each line with the starform.io/* pod labels, and forwards to the regional
aggregator (it never writes ClickHouse itself):
# tail container logs, enrich with pod labels, forward to the regional Vector aggregator
[INPUT]
Name tail
Path /var/log/containers/*.log
multiline.parser cri
Tag kube.*
Skip_Long_Lines On
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Labels On # brings starform.io/* pod labels
K8S-Logging.Parser On
[OUTPUT]
Name forward # Fluent forward protocol → Vector `fluent` source
Match *
Host vector-agg.<region>.internal
Port 24224
tls On
Shared_Key ${CLUSTER_BEARER} # per-cluster auth, even over VPC
4 · Wire per-cluster auth. The Fluent Bit Shared_Key matches the Vector fluent source
token — even though the hop is intra-VPC. See chapter 4 · Auth.
5 · Add build logs. Starbase Worker streams build logs into the same ClickHouse, so build and runtime logs share one store. (§16.6.)
6 · Verify. A pod's stdout line lands in logs.customer_logs stamped project·env·service
within seconds; live tail streams; restart the aggregator → Fluent Bit disk-buffers and replays.
FR-049
Gotchas & what lives elsewhere
- Don't let agents write to ClickHouse directly. Frequent small inserts create too many parts and degrade ClickHouse. The aggregator exists to batch.
- Agents disk-buffer for backpressure; if the aggregator or ClickHouse is briefly down, lines queue locally and replay.
- Per-tenant log retention lives in ClickHouse (partition TTL), not Vector — see chapter 7.
PRD reference & inlined contracts
Owned by §35.1 (log flow), §35.4 (transport), §16.6 (build-log streaming), §24.1 (label set, the enrichment keys); FR-049. The label set is restated above so this guide stands alone — if it ever diverges, the PRD page wins. Canonical map: Canonical Sources.