Introduction
Core concepts
Six ideas carry the whole design. If you understand these, the CRDs and the operational behavior will feel predictable.
Cell
A Cell is the unit of physical capacity: one OCM ManagedCluster bound to one host cluster, plus a single machine profile describing what the machines offer. It is the failure and isolation boundary. The spec has exactly two groups:
managedClusterRef.name— the bound OCMManagedCluster. Once any VirtualCluster has resolved this Cell, the reference is immutable.machineProfile— a name and an inline device contract. Multiple physical machines with the same profile all count as inventory; there is no separateMachineProfileCRD.
Everything else lives in status, filled in by the controller: phase, conditions, managed cluster observations, provider readiness, per-node capacity and allocatable, aggregated inventory, storage inventory, and allocated NodePorts. Never hand-write status.
apiVersion: kubecell.io/v1alpha1kind: Cellmetadata: name: cell1 namespace: kubecell-systemspec: managedClusterRef: name: cell1 # bound OCM ManagedCluster; immutable once resolved machineProfile: name: ascend-910b # single machine profile per Cell devices: - name: ascend resourceName: huawei.com/Ascend910 # exact host extended-resource keyDevice contract, not reservation
The device list is a declarative authorization. The observed allocatable of the resource key is the source of fact. If a Cell declares eight cards but the host only allocates four, the effective capacity is four and aCapabilityMismatch condition is reported.VirtualNodeClass
A VirtualNodeClass is a cluster-scoped quota tier. One name maps to one entitlement: the hard requests and limits applied to every child cluster that uses it, plus the storage class for child volumes.
- The class is applied as a Kubernetes
ResourceQuotain the child cluster's host namespace, so enforcement happens on the host, not in the child cluster. - CPU, memory, and extended resources (accelerators) all follow the same pattern: request equals limit, integer values for devices.
- Classes are platform-owned. Tenants reference them but never edit them; changing a tier means changing the class, which is a platform action.
apiVersion: kubecell.io/v1alpha1kind: VirtualNodeClassmetadata: name: ascend-910b-small # cluster-scoped; the tier name tenants referencespec: entitlement: workloadHard: requests.cpu: "4" limits.cpu: "4" requests.memory: 8Gi limits.memory: 8Gi requests.huawei.com/Ascend910: "2" limits.huawei.com/Ascend910: "2" storageClassName: topolvm-provisionerVirtualClusterPlan
A VirtualClusterPlan is a side-effect-free preflight. Its spec is isomorphic to a VirtualCluster, so you can copy a plan file into a creation request once it is accepted. The controller evaluates the request against the current Cell inventory and reports exactly one decision:
| Decision | Meaning | What to do |
|---|---|---|
| Accepted | Buildable under the current inventory snapshot. | Create the VirtualCluster (rechecked at creation time). |
| Rejected | A required resource is missing: CPU, memory, devices, storage, or ports. | Read checks and adjust the request or Cell. |
| Unknown | Inventory is unavailable or stale. | Fix freshness, then retry once observations recover. |
Acceptance is not reservation
Plans evaluate once and never refresh periodically.expiresAt marks when the conclusion becomes void, and the controller re-runs feasibility at real creation time. If someone else consumed capacity in between, creation is blocked and the recheck report appears in status.feasibilityChecks.apiVersion: kubecell.io/v1alpha1kind: VirtualClusterPlanmetadata: name: child-dev-plan namespace: kubecell-systemspec: cellRef: {name: cell1} classRef: {name: ascend-910b-small}status: decision: Accepted # Accepted | Rejected | Unknown expiresAt: "2026-09-22T10:15:00Z" # stale conclusions are void checks: [] # per-resource findings when RejectedVirtualCluster
A VirtualCluster is the product. Eight lines of spec: which Cell, which class. The controller does the rest:
- Resolves the Cell and class, then writes an immutable resolution snapshot.
- Re-runs feasibility and stops on a non-accepted conclusion.
- Creates the Foundation Work, then the Instance Work once the first is Applied.
- Discovers the child API address from host nodes and publishes the admin kubeconfig.
- Reports readiness, quota usage, and per-reflected-Pod observations in status.
Key fields are immutable: the Cell reference and class reference cannot be silently changed. To move a workload to different hardware, create a new child cluster. To change the size of a child cluster, switch to a different class.
The logical node
Every child cluster exposes exactly one node, always named kubelet. It is an entitlement view, not a physical machine. This is why there are no horizontal fields — no node count, node pools, or replicas — and why DaemonSet-style semantics are unsupported.
$ kubectl--kubeconfig child-dev.yaml get nodesNAME STATUS ROLES AGE VERSIONkubelet Ready<none> 42s v1.34.2+k3s1Reflected Pods and PVCs
K3k mirrors child-cluster Pods and PVCs into the child's host namespace. A reflected Pod is a real host Pod with identical resource requests, so the host scheduler places it and the namespace quota accounts for it. The same is true for storage: a child PVC becomes a host PVC backed by TopoLVM.
- Scheduling decisions stay in Kubernetes — there is no second scheduler to keep in sync.
hostPathvolumes are rejected at admission with a replacement hint pointing to the storage class.- Accelerator requests must use the identical extended-resource key in the child and on the host.
Foundation Work and Instance Work
The controller renders exactly two OCM ManifestWork objects per child cluster. The split is intentional: guardrails first, workload second.
Foundation Work
- Host namespace with policy labels and annotations
ResourceQuotafrom the class entitlementLimitRangewith 100m CPU / 256Mi memory defaultsNetworkPolicydenying cross-namespace traffic- Port-forward RBAC for the child cluster
Instance Work
- K3k
Clusterin Shared mode - K3k VirtualClusterPolicy
- Server Pod, data volume, NodePort Service created by K3k
- Child admin kubeconfig Secret read back through the proxy
Resolution snapshot
VirtualCluster.status.resolved is the sole basis for steady-state rendering. It records the resolved Cell, class entitlement, storage class, and the platform constants that applied at resolution time. If a class changes later, existing child clusters keep the snapshot they were built from until the operator acts — that is how KubeCell avoids surprise in-place mutations.
Platform constants vs. API fields
A field belongs in a CRD only if it has more than one safe value. Everything else is a platform constant owned by the release:
| Concern | Where it lives |
|---|---|
| K3s, K3k, OCM versions | Release bundle, verified by the installer |
| API exposure method and port range | Platform constant; endpoint is discovered into status |
| Ingress apps suffix | Platform-global fixed suffix |
| Default container requests | LimitRange injected by the Foundation Work |
| Quota tier | VirtualNodeClass (the one thing tenants choose) |
Glossary
| Management cluster | Single-node K3s carrying the OCM Hub and the KubeCell controller; runs no tenant workloads. |
| ManagedCluster | OCM registration object representing one Cell host one-to-one. |
| Cell | One ManagedCluster plus its host K3s cluster; the failure and isolation boundary. |
| VirtualNodeClass | Cluster-scoped quota tier: hard-limit numbers plus storage class. |
| VirtualCluster | The child cluster instance; key fields immutable, rich status. |
| VirtualClusterPlan | Side-effect-free preflight; one-shot evaluation, acceptance is not reservation. |
| Logical child node | The single node in the child API, always named kubelet; a view, not a machine. |
| Reflected Pod | Host Pod created by K3k mirroring a child Pod, with identical requests. |
| Cluster proxy / MSA | OCM mechanisms; the only path by which the controller reads host APIs. |
| appsSuffix | Platform-global external domain suffix; hostnames derive as <ingress>.<vc>.<suffix>. |
Next: install the management plane and onboard a host, or jump straight to the API reference for every field.