Skip to main content

KCM Proof of Concept

Repository metadataValue
HostingForgejo
Release Tagv0.0.6
Last update2026-09-22 on main
OwnerDevFW-CICD
RepositoryOpen repository

Kinds​

ResourceAPI groupOwnerScopeRole
KubernetesClusterkubernetes.edge-connect.eu/v1alpha1tenantNamespaceddesired state, node pools by capability
KubernetesClusterprovider.kubernetes.edge-connect.eu/v1alpha1schedulerNamespacedresolved projection, nodePools[].type
KubernetesClusterOfferingprovider.kubernetes.edge-connect.eu/v1alpha1platformClusterwhat one provider supports
VirtualMachineTypeprovider.edge-connect.eu/v1alpha1platformClustermachine-type catalog
Providerprovider.edge-connect.eu/v1alpha1tenantClusterprovider identity + the tenant's own credentials reference

provider.edge-connect.eu holds provider infrastructure primitives that are not Kubernetes-specific; provider.kubernetes.edge-connect.eu holds the Kubernetes-service provider layer.

None of Provider, KubernetesClusterOffering and VirtualMachineType has a status: all three are operator- or refresh-controller-authored inventory, never reconciled against a provider API by this repository.

Provider.metadata.name is the provider name — the value the scheduler writes into spec.providerRef.name and the value a provider controller filters its own work by. An offering points at a Provider through its own spec.providerRef; the offering's metadata.name is just an object name (ADR-0002).

A tenant authors its own Provider, in its own workspace, supplying its own cloud account (ADR-0004). The offering and the Provider therefore live in different workspaces, so the provider name is a vocabulary the two sides share rather than a reference resolvable in one place: the catalog says what otc can serve, a tenant's Provider/otc says that tenant can pay for it. Neither is complete alone, and nothing validates the pairing — a catalog naming a provider nobody supplies shows up as clusters reporting ProviderNotConfigured.

A VirtualMachineType's metadata.name is likewise a platform-chosen Kubernetes name. The provider's own machine-type string lives in spec.providerMachineType (an OTC ECS flavor such as s3.xlarge.2), because a provider flavor is not guaranteed to be a valid Kubernetes object name.

Scheduling contract​

  1. Read the tenant KubernetesCluster.
  2. Candidates are the KubernetesClusterOfferings whose availableRegions contain spec.region, whose availableVersions contain spec.version, and whose provider the workspace holds a Provider for. Only names are read here — never a credentials Secret.
  3. Per node pool, pick a VirtualMachineType from the candidate's availableVirtualMachineTypes whose cpu and memory satisfy the pool's capabilities. A candidate that cannot satisfy every pool is dropped whole.
  4. Write the winning offering's spec.providerRef.name — a Provider's name — onto the tenant resource's spec.providerRef.name.
  5. Emit the provider-facing KubernetesCluster with a concrete nodePools[].type per pool.

Two policy choices make the outcome reproducible, so re-running the scheduler over unchanged inputs never moves a live pool onto a different machine type:

  • among candidates, the lowest provider name wins, and the lowest offering name breaks a tie between two offerings of one provider;
  • within a candidate, a pool takes the smallest fitting type, ordered by cpu, then memory, then name.

A machine type must be available in the requested region and the offering must serve that region: the offering declares where the provider serves Kubernetes, the machine type declares where that shape exists.

Once spec.providerRef.name is set the cluster is pinned. A cheaper offering appearing elsewhere never migrates it, and a pinned provider losing its offering is reported rather than rescheduled — the nodes live at that provider.

Unschedulable requests​

An unschedulable request surfaces as a Scheduled=False condition, never a silent retry. The reasons reuse the offering-resolution error contract of platform ADR-0015:

ReasonMeaning
OfferingNotFoundno offering serves the requested region and version (or the pinned provider has none)
ProviderNotConfiguredan offering serves the request but this workspace supplies credentials for no provider that could run it; the message names the providers that would
CapabilityMismatcha candidate exists but no machine type it offers satisfies a pool; the message names the pool
CatalogUnavailablethe offerings or the catalog could not be read at all — transient, and the only one of the three that retries fast

The condition stands for as long as the request cannot be served. The catalog lives in another logical cluster this controller does not watch, so an unschedulable cluster is re-checked every --unschedulable-requeue (default 5m) — that way a provider adding a region or a machine type unsticks existing clusters without an operator touching them.

The same requeue covers ProviderNotConfigured: there is no watch on Provider, so a tenant that supplies credentials waits up to --unschedulable-requeue before anything reschedules. Nothing is wrong during that window, but it is long enough to be reported as "it did not work" by someone who created the Provider and refreshed immediately. The condition and its message stand throughout, so the cluster says what it is waiting for.

Tenant conditions​

ConditionOwnerMeaning
Scheduledcluster-schedulerprovider selected, every pool resolved
Projectedcluster-schedulerprovider-facing cluster is in sync
ProviderClusterReadycluster-statusthe projection reports itself ready
Readycluster-statusroll-up; a False Ready names the pending phases

status.ready follows Ready. The two reconcilers own disjoint conditions, so neither overwrites the other's diagnosis.

Deleting a tenant cluster deletes its projection first: the kubernetes.edge-connect.eu/cluster-projection finalizer is released only afterwards, so provider state is never orphaned behind a vanished tenant resource.