Designing Multi‑Tenant Loki Logging on Kubernetes: Centralized vs Partitioned
This article explores how to implement Loki’s multi‑tenant logging on Kubernetes, comparing centralized storage (Scheme A) and partitioned storage (Scheme B), detailing required configuration flags, runtime limits, client setups with Logging Operator, Fluentd/FluentBit, and gateway routing strategies.
When looking at Loki's architecture docs, the community claims Loki supports multi‑tenant mode, but enabling it only requires two conditions: setting auth_enabled: true in the config file and sending the tenant ID in the X‑Scope‑OrgID request header.
In practice, building a multi‑tenant logging system on Kubernetes involves more considerations. Two architectural patterns are commonly used:
1. Centralized log storage (Scheme A)
Logs are written to a shared backend after validation and indexing, similar to native Loki.
2. Partitioned log storage (Scheme B)
Each tenant or project has an independent Loki instance and storage block.
Both approaches require configuring Loki’s per‑tenant limits. The main configuration blocks are query_frontend_config and limits_config.
query_frontend_config
The query frontend distributes and aggregates user queries; to prevent a single tenant from monopolising resources you can set max_outstanding_per_tenant (default 100).
[max_outstanding_per_tenant: <int> | default = 100]limits_config
Controls global flow control and per‑tenant resource allocation. Runtime limits can be supplied via the -runtime-config flag and the runtimeConfigValues struct.
type runtimeConfigValues struct {
TenantLimits map[string]*validation.Limits `yaml:"overrides"`
Multi kv.MultiRuntimeConfig `yaml:"multi_kv_config"`
}Example tenant overrides:
overrides:
tenantA:
ingestion_rate_mb: 10
max_streams_per_user: 100000
max_chunks_per_query: 100000
tenantB:
max_streams_per_user: 1000000
max_chunks_per_query: 1000000When using Scheme A, per‑tenant limits should be tuned according to log volume; Scheme B relies on the native limits_config because each tenant has a dedicated Loki instance.
Log client integration
In Kubernetes the log client must attach the tenant ID to each log stream. Two common approaches are the Logging Operator and Fluentd/Fluent Bit.
Logging Operator
The BanzaiCloud Logging Operator creates namespace‑scoped CRDs (Flow and Output) to control log collection and routing.
apiVersion: logging.banzaicloud.io/v1beta1
kind: Output
metadata:
name: loki-output
namespace: <tenantA-namespace>
spec:
loki:
url: http://loki:3100
username: <tenantA>
password: <tenantA>
tenant: <tenantA> apiVersion: logging.banzaicloud.io/v1beta1
kind: Flow
metadata:
name: flow
namespace: <tenantA-namespace>
spec:
localOutputRefs:
- loki-output
match:
- select:
labels:
app: nginx
filters:
- parser:
remove_key_name_field: true
reserve_data: true
key_name: "log"Fluentd / Fluent Bit
Both plugins support multi‑tenant configuration by extracting tenant information from Kubernetes metadata.
Fluentd example using a namespace label:
apiVersion: v1
kind: Namespace
metadata:
labels:
tenant: <tenantA>
name: <tenant-namespace> <match loki.**>
@type loki
@id loki.output
url "http://loki:3100"
# extract tenant from namespace labels
tenant ${$.kubernetes.namespace_labels.tenant}
username <username>
password <password>
<label>
tenant ${$.kubernetes.namespace_labels.tenant}
</label>
</match>Fluent Bit extracts the tenant from pod labels:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
spec:
template:
metadata:
labels:
app: nginx
tenant: <tenant-A> [FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Merge_Log On
[FILTER]
Name rewrite_tag
Match kube.*
Rule $kubernetes['labels']['tenant'] ^(.*)$ tenant.$kubernetes['labels']['tenant'].$TAG false
Emitter_Name re_emitted
[Output]
Name grafana-loki
Match tenant.tenantA.*
Url http://loki:3100/api/prom/push
TenantID "tenantA"
[Output]
Name grafana-loki
Match tenant.tenantB.*
Url http://loki:3100/api/prom/push
TenantID "tenantB"Log gateway
For Scheme A the gateway simply forwards requests with the tenant header to the shared Loki cluster. For Scheme B the gateway must route based on X‑Scope‑OrgID to the appropriate Loki instance.
# upstream addresses are rendered from CRD sidecars
upstream tenantA { server x.x.x.x:3100; }
upstream tenantB { server y.y.y.y:3100; }
server {
location / {
set $tenant $http_x_scope_orgid;
proxy_pass http://$tenant;
include proxy_params;
}
}Conclusion
The two multi‑tenant Loki architectures—centralized storage and partitioned storage—differ in Loki topology, client setup, and gateway complexity. Teams with Kubernetes operator expertise may prefer the partitioned approach, while operations‑focused teams might choose the simpler centralized storage.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Ops Development Stories
Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
