Skip to content

Architecture and Components

Plethos gives members of your organization self-service Kubernetes clusters, provisioned as isolated vCluster instances. You only need to run one real Kubernetes cluster. This page explains what Plethos creates in it and how the parts interact, so you can operate, debug and contribute to it.

Plethos architecture: the portal and controller communicate through VClusterClaim resources and the controller manages one namespace per vCluster

Plethos consists of two independent services:

  • Portal: the web app users log into to create and manage their clusters. It is a FastAPI + HTMX server.
  • Controller: a Kubernetes controller, built with Kopf, that creates and manages the actual vClusters.

The two never talk to each other directly. They communicate through a custom resource called VClusterClaim:

  1. The portal creates, updates and deletes VClusterClaim objects to describe the desired state of a cluster: who owns it, who it is shared with and whether it should be running or asleep.
  2. The controller watches these objects and makes the real vCluster match.
  3. The controller writes the result back into the object’s status, which the portal reads to show the cluster’s state to the user.

There is no database. Ownership, sharing and lifecycle state all live in Kubernetes resources, so the Kubernetes API is the single source of truth for both services.

The portal handles everything users and admins see:

  • Login: a standard OIDC authorization-code flow, so any compliant identity provider works. A user’s OIDC sub claim is stored as spec.ownerSubject on their claims. It is the stable identifier a claim is scoped to, as explained later in Ownership Labels.
  • Cluster management: create, sleep, wake and delete clusters, download the kubeconfig, view logs, export PVC data, share clusters with other users by email and see the public app host.
  • Admin dashboard: admins see all clusters and owners and can edit portal-wide settings. “Admin” is not an OIDC role but instead a membership in the adminEmails list of the settings ConfigMap.
  • Emails: member invites and expiry notifications are sent through your SMTP server.

The controller watches VClusterClaim resources in its namespace (plethos by default) and reconciles each one. It runs on two timers: a slow one (every 3 hours by default) that corrects drift on every claim and a fast one (every 10 seconds) that only polls claims in transitional phases, so users see a cluster become Running quickly.

Each vCluster has an expiration date (by default 150 days after creation or last renewal). A daily CronJob checks for expiring clusters, sends warnings, puts them to sleep and finally deletes them. It reuses the portal’s image and ServiceAccount, so nothing extra is deployed. For a more detailed explanation see Cluster expiry.

A claim is the contract between portal and controller. The portal writes the spec and the labels (what the user wants and who it belongs to), the controller writes the status (what is actually happening). This is an example of a real VClusterClaim, trimmed slightly:

apiVersion: plethos.cs.hm.edu/v1alpha1
kind: VClusterClaim
metadata:
name: uad50034fa11850c4-abcdefg
namespace: plethos-system
generation: 1
finalizers:
- kopf.zalando.org/KopfFinalizerMarker
labels:
plethos.cs.hm.edu/owner-subject-hash: ad50034fa11850c4
plethos.cs.hm.edu/member-6c006363d263546e: "true"
spec: # written by the portal
name: abcdefg
ownerSubject: 3ce80ebb-bb1c-4a35-9247-0a1b2c3d4e5f
ownerEmail: testuser@example.com
vClusterVersion: 0.35.0
state: Running # Running or Sleeping
members: # users the cluster is shared with
- email: otheruser@example.com
status: # written by the controller
phase: Running
message: vCluster is ready.
observedGeneration: 1
conditions:
- type: Ready
status: "True"
reason: Running
message: vCluster is ready.
observedGeneration: 1
lastTransitionTime: "2026-10-01T07:56:54Z"

A few things worth knowing:

  • Naming: spec.name is the name the user chose. The claim’s metadata.name is generated from the owner’s hash and that name (u<owner-subject-hash>-<name>), so different users can pick the same cluster name. The claim name is also what appears in the cluster’s hostnames.
  • Immutability: after creation only state and members can change, all other spec fields are immutable.
  • Observed generation: if status.observedGeneration is lower than metadata.generation, the controller hasn’t processed the latest change to the claim yet.

You can use kubectl to query and list all VClusterClaims. For example

Terminal
kubectl get vcc -n plethos

The portal doesn’t keep an index of who owns or shares what. It finds a user’s clusters by querying VClusterClaims using label selectors:

Label Meaning
plethos.cs.hm.edu/owner-subject-hash Used to select all clusters a user owns. Its value is the hash of the owner’s ownerSubject (i.e. OIDC sub).
plethos.cs.hm.edu/member-<hash> Used to select all clusters shared with a user. One label per shared member with the value set to "true". The hash is generated from the user’s email.

status.phase shows where the actual state of vCluster and status.message gives a human-readable description:

Phase Meaning
Provisioning The Helm install Job is being created or is still running.
Pending The vCluster StatefulSet exists but isn’t ready yet.
Running The vCluster is up.
Sleeping Scaled to zero, workload pods deleted.
Waking Scaling back up after sleep.
Error Unexpected problem, such as an unsupported version or a failed resource creation.
Deleting The claim and vCluster are being deleted.

The Ready condition is True only when the vCluster is actually up. Otherwise it is False with the current phase as the reason.

These are created once when deploying the Plethos Helm chart in the dedicated namespace:

Resource Purpose
Portal Deployment, Service and Ingress The web app and its public entry point.
Controller Deployment The controller.
CronJob <release>-inactivity-check Daily expiry check. Runs Forbid concurrency, so runs never overlap.
ConfigMap plethos-portal-settings Stores admin emails and quotas. Seeded from your values on first install, afterwards managed through the admin UI.
ServiceAccounts and RBAC Permissions for the portal and controller, plus the plethos-vcluster-installer ClusterRole used by install Jobs.
VClusterClaim CRD Installed on first install.

You can find all manifest templates here.

For every VClusterClaim, the controller creates and maintains:

Resource Purpose
Namespace One dedicated namespace per vCluster. All of its resources live here. Named vcluster-<claim-name>.
ServiceAccount helm-installer Bound to the plethos-vcluster-installer ClusterRole, so install and uninstall Jobs get only the permissions the vCluster chart needs in that one namespace.
Helm install / uninstall Jobs Run the vCluster Helm chart to create or remove the cluster.
vCluster StatefulSet The vCluster’s control plane.
Synced workload pods Pods that users deploy inside their vCluster are synced into this namespace, where they actually run.
API Ingress Exposes the vCluster’s Kubernetes API at <claim-name>.<vClusterApiBaseDomain> using SSL passthrough.
App Ingress Only if exposedAppsBaseDomain is set. Exposes <claim-name>.<exposedAppsBaseDomain>.
NetworkPolicy Allows the ingress controller to reach the vCluster’s workloads and prevents vClusters from reaching each other.
  • vCluster API: traffic goes through your ingress controller straight to the vCluster, which terminates TLS itself (SSL passthrough). That is why no certificate is needed for the API domain.
  • Public apps: the app Ingress routes https://<claim-name>.<exposedAppsBaseDomain> to a Service named http in the vCluster’s default namespace on port 80. This is HTTP only. TLS is handled by the wildcard certificate configured as the ingress controller’s default.
  • Isolation: each vCluster namespace has a NetworkPolicy. It lets the ingress controller through, identified by controller.ingressNamespace or, with hostNetwork, by controller.ingressControllerIpRanges.