Tenant#
A Tenant is the single declarative definition of one team's slice of the cluster. In one object you describe who the team is, where they run, and what they may consume — and the operator turns that into the namespaces, RBAC, resource quota, metadata and admission guardrails that make it real, keeping them in sync as the Tenant changes.
Because everything derives from one object, the pieces cannot drift apart: membership, namespaces, allocation and standards always agree with each other.
What the operator creates from a Tenant#
flowchart TB
Admin["Platform admin"] -->|defines| T["Tenant CR<br/>one per team"]
T --> NS["Namespaces<br/>prefixed · shared · sandboxes"]
T --> RBAC["RBAC bindings<br/>owners · editors · viewers"]
T --> Q["Linked Quota<br/>spec.quota → Quota CR"]
T --> MD["Namespace metadata<br/>labels · annotations"]
T --> G["Admission guardrails<br/>storage · ingress · registries<br/>service accounts · priority · hostnames"]
NS --> K["Kubernetes / OpenShift"]
RBAC --> K
Q --> K
MD --> K
G --> K
A minimal Tenant#
The smallest valid Tenant needs only a quota, which caps what the team can
consume:
apiVersion: tenantoperator.stakater.com/v1beta3
kind: Tenant
metadata:
name: alpha
spec:
quota: small
Note
A minimal Tenant does not set storageClasses, ingressClasses or
podPriorityClasses. Those guardrails are disabled by default, which
leaves RBAC for those cluster-scoped resources to the platform
administrator. See Resource guardrails to enable
them.
The spec at a glance#
Only quota is required; every other field is optional. Listed in the order
they appear in the manifest:
| Field | Required | What it controls |
|---|---|---|
quota |
Yes | Name of the Quota CR that caps the tenant's resource usage. |
desc |
No | Human-readable description of the tenant. |
accessControl |
No | Owners, editors and viewers of the tenant. |
namespaces |
No | Which namespaces the tenant gets, and their metadata. |
storageClasses |
No | StorageClasses the tenant may use (allow-list). |
ingressClasses |
No | IngressClasses the tenant may use (allow-list). |
podPriorityClasses |
No | PriorityClasses the tenant may use (allow-list). |
imageRegistries |
No | Registries the tenant may pull from (allow-list). |
serviceAccounts |
No | Service accounts the tenant may not use (deny-list). |
hostValidationConfig |
No | Allowed ingress and route host names. |
Each field is described below, in the same order. For a manifest you can adapt, see A complete example; the full generated schema is in the API reference.
A complete example#
apiVersion: tenantoperator.stakater.com/v1beta3
kind: Tenant
metadata:
name: tenant-sample
spec:
quota: small
desc: "This is a sample tenant setup for the v1beta3 version."
accessControl:
owners:
users:
- kubeadmin
groups:
- admin-group
editors:
users:
- devuser1
- devuser2
groups:
- dev-group
viewers:
users:
- viewuser
groups:
- view-group
namespaces:
sandboxes:
enabled: true
private: true
withoutTenantPrefix:
- analytics
- marketing
withTenantPrefix:
- dev
- staging
onDeletePurgeNamespaces: true
metadata:
common:
labels:
common-label: common-value
annotations:
common-annotation: common-value
sandbox:
labels:
sandbox-label: sandbox-value
annotations:
sandbox-annotation: sandbox-value
specific:
- namespaces:
- tenant-sample-dev
labels:
specific-label: specific-dev-value
annotations:
specific-annotation: specific-dev-value
storageClasses:
allowed:
- staging
- dev
ingressClasses:
allowed:
- nginx
- traefik
podPriorityClasses:
allowed:
- high-priority
imageRegistries:
allowed:
- ghcr.io
- docker.io
serviceAccounts:
denied:
- service-user-1
- service-user-2
hostValidationConfig:
allowed:
- gateway.saap.dev
- console.saap.dev
- "*.saap.prod"
allowedRegex: ^[a-zA-Z0-9-]+\.saap\.dev$
denyWildcards: false
Quota#
spec.quota names the Quota CR that caps the tenant's resource
usage — CPU, memory and more — so no single tenant can consume a
disproportionate share. A Quota is a separate, reusable object that many
tenants can reference by name; see the Quota concept for how it is
defined and enforced.
Description#
desc is a human-readable description of the tenant, which helps documentation
and at-a-glance understanding of its purpose.
Access Control#
Access control divides tenant users into three roles, each with its own privileges, so you can manage permissions precisely:
- Owners — full administrative rights, including resource management and namespace creation. Suited to high-level management of the tenant.
- Editors — may modify resources, supporting day-to-day operations without full administrative access.
- Viewers — read-only access, suited to oversight and auditing.
Link users and groups to a role by listing their usernames or group names under
owners, editors and viewers. The concrete permissions behind each role
come from IntegrationConfig's TenantRoles field.
Namespaces#
namespaces controls which namespaces the tenant gets and how they are
labelled:
sandboxes— personal namespaces for tenant members.- When enabled, a sandbox is created per user with the naming convention {TenantName}-{UserName}-sandbox. For a group, one is created for each member.
private: truemakes each sandbox visible only to its owner. By default sandboxes are visible to all tenant members.
withTenantPrefix— namespaces created with the tenant name as a prefix, for easy identification and isolation.withoutTenantPrefix— namespaces created without the prefix, useful for shared or common resources.onDeletePurgeNamespaces— whether the tenant's namespaces are deleted when the tenant is deleted. Defaults tofalse.metadata— labels and annotations applied to the tenant's namespaces:common— applied across all of the tenant's namespaces.sandbox— applied across sandbox namespaces. Supports templating: the placeholder{{ TENANT.USERNAME }}is replaced with the owning user, e.g.username: {{ TENANT.USERNAME }}.specific— applied to the named namespaces only, for custom configurations.
Metadata precedence
When the same label or annotation key is set by more than one mechanism,
the highest precedence wins in this order: namespaces.metadata.specific,
then namespaces.metadata.common, then
openshift.project.labels / openshift.project.annotations from the
IntegrationConfig.
Resource guardrails#
Beyond quota, a Tenant can constrain which cluster-scoped resources its workloads may use. There are two enforcement styles.
Allow-lists with secure-by-default RBAC — storageClasses,
ingressClasses and podPriorityClasses. Each follows the same three-state
model, where configuring the field both filters usage and sets up the RBAC:
| Spec state | Behaviour |
|---|---|
Field omitted (nil) |
Disabled — the operator configures no RBAC; access is left to the platform administrator. |
Empty ({} or {allowed: []}) |
Allow all — the operator configures RBAC so the tenant may use any class in the cluster. |
Listed ({allowed: [...]}) |
Only the listed — the operator configures RBAC so the tenant may use only those classes. |
Admission guardrails, always enforced when set — imageRegistries
(allow-list) and serviceAccounts (deny-list). When present, every matching
workload is validated at admission.
podPriorityClasses, imageRegistries and serviceAccounts are validated on
the standard workload-bearing resources — Pods, Deployments, StatefulSets,
ReplicaSets, Jobs, CronJobs and DaemonSets. storageClasses are evaluated
when a PersistentVolumeClaim is created, and ingressClasses when an Ingress is
created.
Storage classes#
storageClasses:
allowed:
- staging-fast
- shared
The class of a PersistentVolumeClaim is resolved as follows:
- If
storageClassNameis set on the PVC, it is evaluated directly. - If only
volumeNameis given, the operator inspects that PersistentVolume to determine its class. - If neither is given, evaluation is deferred until a default storage class is set.
- An empty string for
storageClassName(as opposed to null) is treated as the literal value"".
Tip
Tenant users can list their available StorageClasses with the
kubectl-tenant plugin:
kubectl tenant get storageclasses <tenant-name>.
Ingress classes#
Note
This field applies to Kubernetes only. See the Ingress Sharding guide for details.
ingressClasses:
allowed:
- nginx
- traefik
If an Ingress specifies no IngressClass, it is treated as ""; include "" in
the allow-list to permit such Ingresses.
Pod priority classes#
podPriorityClasses:
allowed:
- high-priority
If a workload omits priorityClassName, it is treated as ""; include "" in
the allow-list to permit workloads without a priority class.
Image registries#
imageRegistries:
allowed:
- ghcr.io
- docker.io
allowed lists the registries the tenant may pull from; images from any other
registry are blocked. To allow images referenced without a registry name
(pulled from the container runtime's default registry), include the empty
string "" in the list.
Service accounts#
serviceAccounts:
denied:
- service-user-1
- service-user-2
denied blocks the listed service accounts from being used by the tenant's
workloads. A workload with no service account (or "") is treated as using the
default service account; add default to the deny-list to block workloads
that fall back to it.
Hostname validation#
hostValidationConfig validates host names on Ingresses and Routes:
allowed— exact host names that are permitted. Wildcards are compared literally; useallowedRegexfor pattern matching.allowedRegex— host names matching this regular expression are permitted.denyWildcards— whentrue, wildcard host names in Ingresses are rejected.
Related guides#
- Create a Tenant
- Delete a Tenant
- Create Namespaces
- Create Sandbox Namespaces
- Assign Metadata
- Templated values in Labels and Annotations
- Changing the default access level for tenant owners
- Extending the default access level for tenant members
- Hibernate a Tenant
The per-field restriction guides each build on a section above: