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-configConfigMap incentralized-unit) - NRCell status (controller-assigned identity —
gnbId,sectorId,gnbDuId,identitySource— plus NCI, pciSource, lastRetune, conditions)
Requires
racora-crds(NRCell CRD)racora-base(racora-systemanddistributed-unitnamespaces)
Install
helm install racora-controller ./charts/racora-controller
Values
| Key | Type | Default | Description |
|---|---|---|---|
| namespace | string | "racora-system" | Namespace this controller is deployed into. Must already exist (racora-base creates it). |
| duNamespace | string | "distributed-unit" | Where the controller writes generated DU resources (Deployments, ConfigMaps, Services). Must already exist (racora-base creates it). |
| cuNamespace | string | "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. |
| crdNamespace | string | "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). |
| coreNamespace | string | "5g-core" | Namespace where the bundled 5G core (Open5GS) runs. The controller derives the CU-CP's default AMF address from it (amf. |
| ueNamespace | string | "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. |
| gnbId | int | 411 | CU-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.registry | string | "" | Registry prefix for the controller image; empty = the umbrella's global.systemDefaultRegistry, re-rooted at render time. |
| image.repository | string | "racora-controller" | Controller image name. |
| image.tag | string | "v0.1.20" | Controller image tag; the distribution pins it from racora_controller in versions.yaml (make bake keeps this default in sync). |
| image.pullPolicy | string | "IfNotPresent" | Image pull policy — IfNotPresent, because the images ship with the distribution. |
| gnbImage | string | "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.triggerHandoverFromMeasurements | bool | true | Automatic 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.resyncIntervalS | int | 300 | Cadence (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. |
| cucpMetricsPeriodMs | int | 5000 | CU-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.pollIntervalS | int | 1 | Seconds between /decisions/active polls (per NRCell timer). |
| decisions.minConfidence | float | 0.95 | Minimum decision confidence to act on (deterministic functions emit 1.0; load-bearing once learned models emit decisions). |
| decisions.minStabilizationS | int | 30 | Seconds a cell's last applied change (per function) must age before the next decision for that function may apply. |
| decisions.perFunction | object | {} | Per-function overrides of the two knobs above, keyed by function name ('pci', 'anr'). |
| replicaCount | int | 1 | Single replica — kopf-based reconciler, no HA. Scale-out would require leader election which kopf supports but the controller isn't wired for it yet. |
| resources | object | {"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. |
| extraEnv | list | [] | Extra env vars (e.g., for the CU-IP Flight URL override). Each entry is rendered into the container's env list verbatim. |
| controlPlanePin | bool | true | Pin 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). |
| nodeSelector | object | {} | Node selector for the controller pod. |
| tolerations | list | [] | Tolerations for the controller pod. |
| affinity | object | {} | Affinity rules for the controller pod. |
| serviceAccount.create | bool | true | Create the ServiceAccount, ClusterRole and binding the controller needs (cross-namespace ConfigMaps, Deployments and Services; NRCell status). |
| serviceAccount.name | string | "" | ServiceAccount name; empty = the chart-generated default. |
| coreController.enabled | bool | true | Deploy racora-core-controller (the Subscriber reconciler and core provider adapter). |
| coreController.image.registry | string | "" | Registry prefix for the core controller image; empty = global.systemDefaultRegistry, re-rooted at render time. |
| coreController.image.repository | string | "racora-core-controller" | Core controller image name. |
| coreController.image.tag | string | "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.pullPolicy | string | "IfNotPresent" | Image pull policy — IfNotPresent, because the images ship with the distribution. |
| coreController.resyncSeconds | int | 300 | Seconds between resyncs that re-apply every Subscriber to the core (the provider's apply is an upsert, so a wiped core database heals). |
| coreController.retrySeconds | int | 30 | Seconds before a Subscriber whose provider was not ready (or whose adapter failed) is retried. |
| coreController.resources | object | {"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.