Skip to main content

Generated at build time from charts/racora-controller/README.md (helm-docs: values.yaml + README.md.gotmpl); edit the source, not this page.

racora-controller

The Racora NRCell controller — a kopf-based K8s operator that reconciles NRCell CRDs into:

  • DU Deployments + ConfigMaps + Services in distributed-unit
  • CU-CP mobility config overlay (nrcell-cu-cp-config ConfigMap in centralized-unit)
  • NRCell status (controller-assigned identity — gnbId, sectorId, gnbDuId, identitySource — plus NCI, pciSource, lastRetune, conditions)

Requires​

  • racora-crds (NRCell CRD)
  • racora-base (racora-system and distributed-unit namespaces)

Install​

helm install racora-controller ./charts/racora-controller

Values​

KeyTypeDefaultDescription
namespacestring"racora-system"Namespace this controller is deployed into. Must already exist (racora-base creates it).
duNamespacestring"distributed-unit"Where the controller writes generated DU resources (Deployments, ConfigMaps, Services). Must already exist (racora-base creates it).
cuNamespacestring"centralized-unit"Where the controller writes the cu-cp mobility overlay ConfigMap (nrcell-cu-cp-config). This MUST match the namespace where cu-cp is deployed — the DU and CU are separate planes with separate knobs.
crdNamespacestring"racora-system"Where the controller watches for NRCell CRs. Today the controller watches a single namespace; this aligns with how kopf is invoked in main.py (kopf run ... --namespace=racora-system).
coreNamespacestring"5g-core"Namespace where the bundled 5G core (Open5GS) runs. The controller derives the CU-CP's default AMF address from it (amf..svc.cluster.local). Must match racora-core's namespace. A customer core is addressed with the umbrella's global.core.amfAddr instead (env RACORA_AMF_ADDR), which overrides this.
ueNamespacestring"user-equipment"Namespace where UE simulator pods (srsUE) run. Used as the default ZMQ rx_port host when an NRCell with ruType=zmq doesn't override spec.radioBackend.zmq.rxPort.
gnbIdint411CU-wide gNodeB ID. One gNB per CU in the current software, so every cell shares this value — the controller stamps it into each cell's status and uses it for every NCI (NCI = gnbId << 14
image.registrystring""Registry prefix for the controller image; empty = the umbrella's global.systemDefaultRegistry, re-rooted at render time.
image.repositorystring"racora-controller"Controller image name.
image.tagstring"v0.1.20"Controller image tag; the distribution pins it from racora_controller in versions.yaml (make bake keeps this default in sync).
image.pullPolicystring"IfNotPresent"Image pull policy — IfNotPresent, because the images ship with the distribution.
gnbImagestring"gnb:v0.4.6"OCUDU gNB image (repository:tag) the controller plants into every DU Deployment it generates (passed as the GNB_IMAGE env var, consumed by du_deployment_generator.py). Re-rooted at render time through the same registry prefix as the controller image (global.systemDefaultRegistry), so the DU pulls the same racora/gnb image the CU does.
mobility.triggerHandoverFromMeasurementsbooltrueAutomatic A3/measurement-driven handover at the CU-CP. CU-wide and boot-only in OCUDU: the controller bakes it into the CU-CP overlay, so a change takes effect at the next cu-cp restart — which is why it lives here and not on the NRCell spec.
mobility.resyncIntervalSint300Cadence (seconds) of the idempotent upsert-only mobility re-push to the running CU-CP — belt-and-braces convergence for edge cases the per-reconcile runtime sync can miss.
cucpMetricsPeriodMsint5000CU-CP metrics report period (ms), baked into the CU-CP overlay. Without it the CU-CP emits no periodic RRC/NGAP counter lines (handover requested/successful etc.) and the monitoring dashboards starve. Boot-only: takes effect at the next cu-cp restart.
decisions.pollIntervalSint1Seconds between /decisions/active polls (per NRCell timer).
decisions.minConfidencefloat0.95Minimum decision confidence to act on (deterministic functions emit 1.0; load-bearing once learned models emit decisions).
decisions.minStabilizationSint30Seconds a cell's last applied change (per function) must age before the next decision for that function may apply.
decisions.perFunctionobject{}Per-function overrides of the two knobs above, keyed by function name ('pci', 'anr').
replicaCountint1Single replica — kopf-based reconciler, no HA. Scale-out would require leader election which kopf supports but the controller isn't wired for it yet.
resourcesobject{"limits":{"memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}Resource requests/limits. The controller is light: kopf event loop + small in-memory state per NRCell. Defaults sized for ~50 cells.
extraEnvlist[]Extra env vars (e.g., for the CU-IP Flight URL override). Each entry is rendered into the container's env list verbatim.
controlPlanePinbooltruePin both reconcilers (racora-controller, racora-core-controller) to the control node, like the CU planes and the in-cluster core: "first node is the brain", additional nodes are DU/RF capacity that comes and goes with the radios. Adds node-role.kubernetes.io/control-plane=true to the nodeSelector; platforms with tainted control planes set it false and pin with nodeSelector instead (platforms/kubernetes/values.yaml).
nodeSelectorobject{}Node selector for the controller pod.
tolerationslist[]Tolerations for the controller pod.
affinityobject{}Affinity rules for the controller pod.
serviceAccount.createbooltrueCreate the ServiceAccount, ClusterRole and binding the controller needs (cross-namespace ConfigMaps, Deployments and Services; NRCell status).
serviceAccount.namestring""ServiceAccount name; empty = the chart-generated default.
coreController.enabledbooltrueDeploy racora-core-controller (the Subscriber reconciler and core provider adapter).
coreController.image.registrystring""Registry prefix for the core controller image; empty = global.systemDefaultRegistry, re-rooted at render time.
coreController.image.repositorystring"racora-core-controller"Core controller image name.
coreController.image.tagstring"v0.1.2"Core controller image tag; the distribution pins it from racora_core_controller in versions.yaml (make bake keeps this default in sync).
coreController.image.pullPolicystring"IfNotPresent"Image pull policy — IfNotPresent, because the images ship with the distribution.
coreController.resyncSecondsint300Seconds between resyncs that re-apply every Subscriber to the core (the provider's apply is an upsert, so a wiped core database heals).
coreController.retrySecondsint30Seconds before a Subscriber whose provider was not ready (or whose adapter failed) is retried.
coreController.resourcesobject{"limits":{"memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}}Resources for the core controller: a light kopf loop.

Verify​

kubectl get pods -n racora-system -l app=racora-controller
kubectl logs -n racora-system -l app=racora-controller --tail=20

# Apply a test NRCell to confirm reconciliation (see the repo README
# for a copy-pasteable example cell):
kubectl get nrcell -n racora-system
kubectl get deployment -n distributed-unit

RBAC​

The controller's ClusterRole grants cross-namespace writes for ConfigMaps + Deployments + Services. Today scoped cluster-wide for simplicity; in a tighter security posture this could be narrowed to just distributed-unit + centralized-unit via RoleBindings.

The core controller​

The same chart deploys racora-core-controller, the core-side reconciler: a second, small kopf service owning the Subscriber CRD. Each Subscriber is provisioned into the selected core provider through the adapter the provider declares (the racora-core-provider ConfigMap in coreNamespace; see cores/README.md), retried while the provider is not ready, and re-applied on a cadence (coreController.resyncSeconds) so a wiped core database heals. Its RBAC is separate: Subscribers, the credentials Secrets in crdNamespace, and pods/exec in coreNamespace — the RAN controller holds none of those.