Tenant provisioning
Overview
Section titled “Overview”A “tenant” on Kestrel is a Capsule Tenant backed by a Keycloak group. You do not hand-author a Tenant CR. Instead, you add a per-tenant values file under the environments hierarchy and the rcs-tenants ApplicationSet renders the Capsule Tenant, TenantOwner, and ResourcePool (plus any optional extraObjects) from the shared tenant Helm chart. The default network policies and limit ranges are not part of the chart — they are applied cluster-wide to all tenants by GlobalTenantResources in the capsule addon config.
Provisioning a new tenant means:
- Ensuring the tenant’s Keycloak group exists (authN — see the note below).
- Adding
environments/arbutus/kestrel/<cluster>/tenants/<rap-group>/values.yaml(often empty) and merging it (authZ + namespace limits, applied by the chart).
Pre-flight
Section titled “Pre-flight”Before starting, confirm:
- You have
cluster-adminaccess to the target Kestrel cluster. - The tenant name is the exact Cloud RAP POSIX group for the project. RAP/Cloud-RAP group names may contain uppercase characters — use the group as-is (real example:
CCInternal-RAID). - The requesting PI or lead has been added to the RCS intake tracker.
Step 1 — Ensure the Keycloak group exists
Section titled “Step 1 — Ensure the Keycloak group exists”The tenant’s owner group is the Keycloak group whose name matches the tenant basename. Membership is managed in Keycloak.
- Log into the Keycloak admin console for the Kestrel realm.
- Confirm a group exists matching the tenant name (e.g.
CCInternal-RAID), or create it. - Add the initial members (at minimum the requesting PI or lead).
Step 2 — Add the tenant values file
Section titled “Step 2 — Add the tenant values file”Create the per-tenant values file the rcs-tenants ApplicationSet expects:
environments/arbutus/kestrel/<cluster>/tenants/<rap-group>/values.yamlFor example, for the production cluster:
environments/arbutus/kestrel/kestrel-prod/tenants/CCInternal-RAID/values.yamlFor most tenants this file is empty (or near-empty) — the platform defaults already cover everything a standard tenant needs:
---Those defaults come from three different layers — edit the right one if a change is needed:
Set in the cluster-group tenant values (environments/arbutus/default/tenants/values.yaml, rendered by the tenant chart):
- Priority classes —
uber-user-significant,uber-user-preempt-medium,uber-user-preempt-high. - Resource pool — cluster-scoped,
resourcePool.tier: standard. The pool is shared across all of the tenant’s namespaces.
Applied cluster-wide by the capsule addon’s GlobalTenantResources (environments/arbutus/kestrel/default/addons/capsule/ — not the tenant chart):
- Network policies — inbound default-deny with pre-approved ingress allows: the
traefiknamespace, scrape from themonitoringnamespace, intra-namespace, and intra-tenant traffic. Outbound traffic is not restricted by the default policy. - Limit range defaults — limit
cpu 1/memory 1Gi; requestcpu 100m/memory 128Mi.
Supplied by cluster addons (independent of the tenant spec, which restricts neither):
- Storage classes —
csi-cinder-sc-delete(default) andcsi-cinder-sc-retain, from theopenstack-cinder-csichart installed at cluster build (theuberneteskubespray repo). - Ingress class —
traefik(the cluster default), from the Traefik addon.
Add per-tenant overrides to this file only when the tenant needs something beyond the defaults (for example, a static NFS PersistentVolume for ReadWriteMany storage, as the mlp-explorer tenant does).
Commit and merge — the rcs-tenants ApplicationSet renders an Application named tenant-<rap-group-lowercased> (via basenameNormalized, e.g. CCInternal-RAID/ → tenant-ccinternal-raid) and ArgoCD picks up the merge on its next Git poll (~3 minutes) and syncs the Tenant.
Post-provisioning checks
Section titled “Post-provisioning checks”After the ArgoCD sync completes:
kubectl get tenant <tenant-name>— status should beActive. (<tenant-name>is the lowercased basename, e.g.ccinternal-raid.)kubectl auth can-i create namespace --as=test-user --as-group=oidc:<rap-group>— should returnyes. kubectl rejects--as-groupwithout--as, so a user must be impersonated too — any placeholder username works. Theoidc:prefix is required and the basename case is preserved (e.g.--as=test-user --as-group=oidc:CCInternal-RAID).- Ask the tenant lead to log in via
kubectl oidc-loginand create a test namespace — this validates the full Keycloak → Capsule → RBAC chain. - Verify the namespaces the tenant owns:
kubectl get tenant <tenant-name> -o jsonpath='{.status.namespaces}'returns the array of namespace names;kubectl get tenant <tenant-name> -o jsonpath='{.status.size}'returns the namespace count. - Confirm the resource pool:
kubectl describe resourcepool <tenant-name>(the resource pool is cluster-scoped, so do not pass-n— it is ignored). For per-namespace usage, usekubectl describe resourcequota -n <ns>.
Deprovisioning
Section titled “Deprovisioning”To remove a tenant:
- Delete all namespaces owned by the tenant (Capsule cascades resource cleanup).
- Remove the tenant’s
tenants/<rap-group>/directory fromubernetes-applicationsand merge. This deletes the generated Application, but it does not prune the rendered resources — thercs-tenantsApplicationSet setssyncPolicy.preserveResourcesOnDeletion: true, so theTenant,ResourcePool, andTenantOwnerCRs are orphaned, not deleted. - Remove the orphaned CRs manually. Every Tenant is rendered with
preventDeletion: true, which blocks deletion until cleared:kubectl patch tenant <tenant-name> --type merge -p '{"spec":{"preventDeletion":false}}'kubectl delete tenant <tenant-name>kubectl delete resourcepool <tenant-name>kubectl delete tenantowner -l projectcapsule.dev/tenant=<tenant-name>(TenantOwner names carry a hash suffix, so delete by label).
- Verify with
kubectl get tenant— the tenant should no longer be listed. - Disable (do not delete) the Keycloak group — keeps the audit trail intact. (See the identity-rework caution in Step 1.)