The Hitchhiker's Guide to Observability - Adding A New Tenant - Part 6
- - 3 min read
While we have created our distributed tracing infrastructure, we created two tenants as an example. In this article, I will show you how to add a new tenant and which changes must be made in the TempoStack and the OpenTelemetry Collector.
This article was mainly created as a quick reference guide to see which changes must be made when adding new tenants.
Quick Checklist for Adding a New Tenant
Use this checklist to ensure you haven’t missed any steps:
TempoStack: Add tenant to
spec.tenants.authentication[]ClusterRole (write): Add tenant name to
tempostack-traces-writeresourcesClusterRole (read): Add tenant name to
tempostack-traces-readerresourcesCentral OTC Routing: Add routing rule in
connectors.routing/traces.table[]Central OTC Exporter: Add
otlp/[tenant-name]exporter with correctX-Scope-OrgIDCentral OTC Pipeline: Add
traces/[tenant-name]pipelineNamespace: Create namespace for the team
ServiceAccount: Create ServiceAccount for local collector
Local OTC: Deploy OpenTelemetryCollector in team namespace
Application: Deploy application with OTEL configuration
Verify: Check logs, generate traces, view in Jaeger UI
You do NOT need to create new ClusterRoles for each tenant. The k8sattributes-otel-collector ClusterRole already grants the central collector cluster-wide read access to pods, namespaces, and replicasets across all namespaces. This single ClusterRole serves all tenants. |
Common Mistakes to Avoid
Mismatched Tenant Names: Ensure the tenant name in TempoStack matches the
X-Scope-OrgIDheader in the exporterWrong Namespace Attribute: The routing rule matches on
k8s.namespace.name- ensure the local collector sets this correctlyForgot RBAC Update: Without updating ClusterRoles, traces will be rejected
Typo in Pipeline Names: Pipeline names in the routing connector must match the actual pipeline definitions
Missing Exporter in Pipeline: Each new pipeline must reference the correct exporter (e.g.,
otlp/team-d)
Adding a New Tenant - team-d
Let’s add a new tenant called "team-d" with their own namespace, local collector, and route to TempoStack tenant "tenantD".
Step 1: Update TempoStack Configuration
If the tenant "tenantD" does not already exist in TempoStack, we need to add it to the tenants list.
Edit the TempoStack resource in the tempostack namespace and add the new tenant to the authentication list:
spec:
tenants:
mode: openshift
authentication:
- tenantId: 1610b0c3-c509-4592-a256-a1871353dbfc
tenantName: tenantA
- tenantId: 1610b0c3-c509-4592-a256-a1871353dbfd
tenantName: tenantB
- tenantId: 1610b0c3-c509-4592-a256-a1871353dbfe
tenantName: tenantC
# Add new tenant
- tenantId: 1610b0c3-c509-4592-a256-a1871353dbff (1)
tenantName: tenantD| 1 | Our new tenantID with the tenantName "tenantD" |
Step 2: Update RBAC Permissions - write
Update the ClusterRoles to grant write permissions for the new tenant. Edit the ClusterRole tempostack-traces-write:
oc edit clusterrole tempostack-traces-writerules:
- verbs:
- create
apiGroups:
- tempo.grafana.com
resources:
- tenantA
- tenantB
- tenantC
- tenantD (1)
resourceNames:
- traces| 1 | Our new tenant with the tenantName "tenantD" |
Step 3: Update RBAC Permissions - read
Update the ClusterRoles to grant read permissions for the new tenant. Edit the ClusterRole tempostack-traces-reader:
oc edit clusterrole tempostack-traces-readerrules:
- verbs:
- get
apiGroups:
- tempo.grafana.com
resources:
- dev
- tenantA
- tenantB
- tenantC
- tenantD (1)
resourceNames:
- traces| 1 | Our new tenant with the tenantName "tenantD" |
Step 4: Update Central OpenTelemetry Collector
The central collector needs configuration changes to route traces from the new namespace to the appropriate TempoStack tenant. Edit the OpenTelemetry Collector in the tempostack namespace and add the new routing rule:
oc edit otelcol otel -n tempostackAdd the New Routing Rule
Add the new routing rule to the connectors section:
spec:
config:
connectors:
routing/traces:
default_pipelines:
- traces/Default
error_mode: ignore
table:
- statement: route() where attributes["k8s.namespace.name"] == "team-a"
pipelines:
- traces/tenantA
- statement: route() where attributes["k8s.namespace.name"] == "team-b"
pipelines:
- traces/tenantB
- statement: route() where attributes["k8s.namespace.name"] == "team-c"
pipelines:
- traces/tenantC
# Add new routing rule
- statement: route() where attributes["k8s.namespace.name"] == "team-d" (1)
pipelines:
- traces/tenantD| 1 | Our new tenant with the tenantName "tenantD" |
Add Exporter for the New Tenant
Add the new exporter to the exporters section:
spec:
config:
exporters:
# ... existing exporters ...
# New tenant exporter
otlp/tenantD: (1)
endpoint: tempo-simplest-gateway:8090
auth:
authenticator: bearertokenauth
headers:
X-Scope-OrgID: tenantD (2)
tls:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
insecure_skip_verify: true
server_name_override: tempo-simplest-gateway.tempostack.svc.cluster.local| 1 | Our new exporter with the name "otlp/tenantD" |
| 2 | The X-Scope-OrgID header value that identifies the tenant |
Add Pipeline for the New Tenant
Add the new pipeline to the pipelines section:
spec:
config:
service:
pipelines:
# ... existing pipelines ...
# New tenant pipeline
traces/tenantD: (1)
receivers:
- routing/traces
exporters:
- otlp/tenantD| 1 | Our new tenant with the tenantName "tenantD" |
Step 5: Install the Application and Local OpenTelemetry Collector
Follow the steps from the previous article to install the application and local OpenTelemetry Collector in the new namespace.
Copyright © 2020 - 2025 Toni Schmidbauer & Thomas Jungbauer
Thomas Jungbauer
