Skip to main content
Version: 2.0

App Controller

Component: app-controller (Kubernetes operator) · Metrics: :8080 · Health probes: :8081

app-controller is the Kubernetes operator that turns a KubeOperaApp resource into a running application inside a tenant's vCluster. It doesn't apply anything to the tenant cluster directly: it generates manifests, commits them to Git, and makes sure Flux inside the vCluster picks them up.

kubeopera-api creates and updates KubeOperaApp resources; app-controller acts on them.

Why GitOps?​

Every tenant vCluster already runs Flux, so using it for apps gives the whole platform one delivery mechanism. It also means every app's desired state has a durable, reviewable history in Git — rollback is just returning to an earlier state — and the cluster continuously converges to it.

The KubeOperaApp resource​

apiVersion: kubeopera.io/v1alpha1
kind: KubeOperaApp
metadata:
name: hello
namespace: kubeopera-system
spec:
appID: 5c1e… # the app's ID in kubeopera-api
cloudSpaceID: b1f0…
tenantCRName: alice-default # optional: target a specific vCluster
schema: # the app definition — see App lifecycle
app:
name: hello
namespace: hello
containers:
- name: hello
image: ghcr.io/stefanprodan/podinfo:6.7.0
ports: [{ containerPort: 9898 }]
ingress:
enabled: true
host: hello-alice.apps.kubeopera.io
tls: true
registry:
secretRef: hello-registry # optional: private registry credentials
git:
url: https://github.com/acme/fleet-repo
branch: main
secretRef: fleet-git-credentials
flux:
interval: 1m
targetNamespace: hello
prune: true
status:
phase: Ready # Pending | Generating | Syncing | Ready | Failed | Deleting
gitPath: tenants/alice/apps/hello
gitCommit: 3f9c2e1
fluxKustomization: hello-sync
lastSyncedAt: "2026-09-26T14:23:01Z"
message: "Flux applied 3f9c2e1"

What happens on reconcile​

  1. Find the tenant's vCluster. If the resource names a tenantCRName, the controller loads that Tenant and verifies it belongs to the app's CloudSpace; otherwise it uses the CloudSpace's default vCluster. It builds a client for that vCluster from the tenant's kubeconfig Secret.
  2. Prepare the namespace. It creates the app's namespace in the vCluster up front, so nothing later has to wait for Flux to create it.
  3. Copy registry credentials. If spec.registry.secretRef is set, it copies that Secret into the app's namespace so pods can pull private images.
  4. Generate manifests. It asks the app lifecycle service to turn the spec into a Deployment, Service, Ingress (with a cert-manager annotation when TLS is on), HorizontalPodAutoscaler and anything else the spec needs.
  5. Commit to Git. It pushes the manifests to the tenant's path in the fleet repository and records the commit on the resource's status.
  6. Point Flux at it. It makes sure a Flux GitRepository and Kustomization exist in the vCluster for that path, with prune: true.
  7. Track progress. It watches the Flux Kustomization and updates the resource's phase to Ready once the commit is applied.

Deletion​

Every KubeOperaApp carries the kubeopera.io/app-cleanup finalizer, so deleting it is thorough:

  1. The controller removes the app's manifests from the fleet repository.
  2. It deletes the app's Flux Kustomization and GitRepository in the vCluster. Because the Kustomization prunes, this removes the Deployment, Service, Ingress and everything else it applied.
  3. Only then does it remove the finalizer, letting the resource disappear.

If the vCluster is briefly unreachable, the controller retries every 30 seconds; resources that are already gone count as cleaned up, so a retry after a partial cleanup always completes.

Configuration​

Almost everything app-controller needs travels on the KubeOperaApp resource itself, set by kubeopera-api. The controller takes the standard controller-runtime flags:

Flag / variableDefaultDescription
--metrics-bind-address:8080Prometheus metrics.
--health-probe-bind-address:8081/healthz and /readyz.
APP_SERVICE_BASE_URLhttp://app-service:8112The manifest generator.
LOG_LEVELinfoLogging verbosity.

Permissions​

The controller can manage KubeOperaApp resources (including deletion, for the finalizer), read Tenant resources and their kubeconfig Secrets (to reach each vCluster), and read and copy Secrets (for registry credentials).

Next steps​