Tenant Controller
Component: tenant-controller (Kubernetes operator) · Namespace: kubeopera-system
tenant-controller creates and manages CloudSpaces. It watches Tenant resources and, for each one, provisions an isolated vCluster, installs Flux and the tenant's App Advisor inside it, applies the tenant's quota, and keeps it healthy — or tears it all down cleanly when the CloudSpace is deleted.
The Tenant resource
Group/version: kubeopera.io/v1alpha1 · Kind: Tenant · Short name: kt
apiVersion: kubeopera.io/v1alpha1
kind: Tenant
metadata:
name: alice-default
namespace: kubeopera-system
spec:
userID: "3f1a2b4c-..." # the owning user
tenantName: "alice-default" # used for release and namespace names
cloudSpaceID: "7e9d3a1b-..." # the CloudSpace in kubeopera-api
quota:
cpu: "4" # total CPU for the CloudSpace's workloads
memory: "8Gi" # total memory for the CloudSpace's workloads
status:
phase: Ready # Pending | Provisioning | InstallingFlux | Ready | Failed | Deleting
vclusterName: vc-alice-default
namespace: vcluster-alice-default
kubeconfigSecret: tenant-7e9d3a1b-kubeconfig
message: "vCluster, Flux and App Advisor ready"
lastSyncedAt: "2026-09-26T14:05:00Z"
Naming:
| Resource | Name |
|---|---|
| vCluster Helm release | vc-{tenantName} |
| Host namespace | vcluster-{tenantName} |
| Kubeconfig Secret | tenant-{cloudSpaceID}-kubeconfig in kubeopera-system |
tenantName must match ^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$.
Lifecycle
Provisioning
- Create the host namespace
vcluster-{tenantName}. - Install (or upgrade) the vCluster Helm chart without blocking, then check readiness every 15 seconds.
- When the release is deployed, read the kubeconfig vCluster exports and store it as
tenant-{cloudSpaceID}-kubeconfig, labelledkubeopera.io/tenant-idandkubeopera.io/managed-by=tenant-controller.
InstallingFlux
- Install Flux inside the vCluster and wait for it to be healthy.
- Create an
AdvisorAgentso advisor-controller deploys the tenant's App Advisor. - Apply the tenant's quota as a
ResourceQuotainside the vCluster, alongside the control plane's own limits.
Ready
- Tell kubeopera-api the CloudSpace is active. If kubeopera-api is briefly unavailable, the update is retried until it succeeds.
Once Ready, further reconciles are cheap no-ops unless the spec (for example the quota) changes.
Failures and retries
Any failure sets the phase to Failed with a message explaining the cause, and the controller retries automatically with exponential backoff. An install that failed partway is resumed rather than restarted: the controller detects an existing release and upgrades it. Administrators can also retry immediately from the dashboard or by setting the phase to Pending.
Deletion
Every Tenant carries a finalizer. Deleting one:
- uninstalls the vCluster's Helm release;
- waits until the host namespace is actually gone, not just marked for deletion;
- removes the kubeconfig Secret and the
AdvisorAgent; - removes the finalizer.
If namespace deletion stalls — for example on a volume that won't release — the phase stays Deleting with a message naming what's blocking it.
vCluster configuration
The controller installs oci://ghcr.io/loft-sh/charts/vcluster with these values for every CloudSpace:
controlPlane:
distro:
k3s:
enabled: true
statefulSet:
resources:
requests: { cpu: "500m", memory: "1Gi" }
limits: { cpu: "2", memory: "4Gi" }
sync:
toHost:
ingresses: { enabled: true } # so cert-manager and external-dns on the host serve tenant apps
serviceAccounts: { enabled: true }
exportKubeConfig:
enabled: true # writes the kubeconfig to Secret vc-{releaseName}
The Helm SDK runs in-cluster through a custom RESTClientGetter wrapping the controller's own REST config, so installs work the same in the cluster and in local development. Charts are cached in an emptyDir volume so they aren't re-downloaded on every reconcile.
Permissions
rules:
- apiGroups: [kubeopera.io]
resources: [tenants, tenants/status, tenants/finalizers, advisoragents]
verbs: [get, list, watch, create, update, patch, delete]
- apiGroups: [""]
resources: [namespaces, secrets, configmaps, events]
verbs: [get, list, watch, create, update, patch, delete]
- apiGroups: ["*"]
resources: ["*"]
verbs: [get, list] # read-only, for Helm's discovery
Operating it
kubectl get tenants -n kubeopera-system # every CloudSpace and its phase
kubectl describe tenant alice-default -n kubeopera-system # details and recent events
kubectl logs -n kubeopera-system -l app=tenant-controller -f
Metrics are on :8080/metrics (including controller_runtime_reconcile_total, …_errors_total and …_time_seconds), and probes on :8081/healthz and :8081/readyz.
Configuration
| Variable | Default | Description |
|---|---|---|
KUBEOPERA_API_BASE_URL | http://kubeopera-api:8090 | Where CloudSpace status is reported. |
VCLUSTER_CHART_VERSION | 0.21.2 | vCluster chart version. |
ADVISOR_IMAGE | — | App Advisor image for new tenants. |
SERVICE_CLIENT_ID / SERVICE_CLIENT_SECRET | — | The controller's service identity. |
ENVIRONMENT | production | Added to Helm values as a label. |