Installation#
These steps are done once per cluster, by a platform administrator, before any tenant gets an organisation. There is no per-tenant installation step.
Deploy the stack#
Loki, Mimir, Tempo, a collector and a Grafana instance must be running, with multi-tenancy enabled on Loki, Mimir and Tempo, and the MTO gateways in front of Loki and Mimir. Each component and how it can be deployed — operator or chart — is listed in How it works → what it is made of. Stakater can deploy the whole stack, or only the MTO components in front of a Grafana stack you already run.
Install the Grafana extension#
The Grafana extension (the Grafana tenant operator) derives organisations, data sources, dashboards and access from your Tenant resources. It ships separately from MTO, and once running it watches all namespaces, so the Grafana resource can live wherever you prefer — the examples here use mto-extension-grafana-system.
Install instructions coming
A public, verified install command for the Grafana tenant operator will be documented here once the operator is published. For now, contact Stakater to have the extension installed on your cluster.
Prerequisites#
Alongside the extension you will need:
- A Grafana instance managed by the Grafana Operator — a
grafana.integreatly.org/v1beta1Grafanaresource. The extension configures an existing instance; it does not install or manage Grafana itself. - The admin credentials secret the Grafana Operator creates alongside that instance:
<grafana-instance-name>-admin-credentials, holdingGF_SECURITY_ADMIN_USERandGF_SECURITY_ADMIN_PASSWORD, in the instance's namespace. The extension calls the Grafana API with those credentials. - MTO installed, with at least one
Tenant. - For user login, an OIDC provider — Dex, Keycloak, Microsoft Entra ID or any other — issuing a
groupsclaim. Without one, setsso.mode: disabledand use Grafana's built-in accounts.
Enabling the integration#
Administrators point the extension at the Grafana instance with a Grafana resource. This one resource is the whole configuration surface:
apiVersion: telemetry.tenantoperator.stakater.com/v1alpha1
kind: Grafana
metadata:
name: mto-extension-grafana
namespace: mto-extension-grafana-system
spec:
server:
name: grafana # the Grafana Operator instance to manage
namespace: telemetry
sso:
mode: secret
secretRef:
name: grafana-sso-credentials
tenantRoleMapping:
admin:
grafanaRole: grafanaadmin
pattern: clusteradmin || cluster-admin
owner:
grafanaRole: admin
pattern: tenant-{{ .Tenant }}-{{ .Role }}s
editor:
grafanaRole: editor
pattern: tenant-{{ .Tenant }}-{{ .Role }}s
viewer:
grafanaRole: viewer
pattern: tenant-{{ .Tenant }}-{{ .Role }}s
tieBreakStrategy: highest
fallback: deny
scaffolding:
mode: OnAnnotation
Two fields carry more weight than their size suggests:
spec.servernames the Grafana instance, and also fixes the namespace content is read from.GrafanaDatasource,GrafanaDashboardandGrafanaFolderresources must live inspec.server.namespace— the extension ignores them anywhere else.patternsupports Go templating with{{ .Tenant }}(the tenant name) and{{ .Role }}(owner,editororviewer), which is what lets one pattern cover every tenant, present and future.
Warning
Exactly one Grafana resource may target a given instance. Organisations, data sources and dashboards are discovered from the instance itself and carry no per-resource ownership, so two of them on one instance would delete each other's content. If a duplicate is created, the extension keeps the longest-established one reconciling — earliest metadata.creationTimestamp, ties broken on <namespace>/<name>. The newcomer is refused: its status.phase becomes Blocked, Ready goes false with reason Blocked, and a DuplicateInstanceTarget warning event is emitted. An accidental duplicate can never take the instance from the resource already managing it.
Connecting single sign-on#
spec.sso.mode takes three values:
| Mode | Use case |
|---|---|
secret (default) |
Identity-provider details come from a Kubernetes secret. Recommended for production. |
inline |
Details written directly in spec.sso.idp. Convenient for development; puts a client secret in the resource. |
disabled |
No OAuth configuration is written. Grafana's built-in accounts only, and no organisation mapping. |
In secret mode, create the secret in the same namespace as the Grafana extension resource:
apiVersion: v1
kind: Secret
metadata:
name: grafana-sso-credentials
namespace: mto-extension-grafana-system
type: Opaque
stringData:
issuer: https://dex.example.com
clientId: grafana-client
clientSecret: <client-secret>
redirectUri: https://grafana.example.com/login/generic_oauth
issuer,clientId,clientSecretandredirectUriare required.scopedefaults toopenid email profile groups offline_access.authUrl,tokenUrlandapiUrldefault to{issuer}/dex/auth,{issuer}/dex/tokenand{issuer}/dex/api— set them explicitly for any provider that is not Dex.
Warning
If you override scope, it must still contain openid, email, profile and groups. The extension validates all four and refuses to configure SSO without them — groups in particular is what the whole organisation mapping is built from.
From this the extension writes Grafana's generic_oauth settings: one org_mapping entry per tenant role — so three per tenant, for owners, editors and viewers — plus a role_attribute_path that detects cluster administrators only. Keeping tenant roles out of role_attribute_path is deliberate: Grafana would otherwise take the highest of the two and flatten per-organisation roles into one role everywhere.
You do not need to label the secret. The extension adds mto.grafana/sso-secret: "true" on first reconcile, so its informer cache watches only SSO secrets.
Previous: How it works · Next: Giving a tenant a dashboard