Install Aether SD-RAN into its own namespace on the Kubernetes cluster that already runs Aether SD-Core, and confirm that its E2 termination accepts the simulated gNBs of RANSim, lists their cells, and decodes their KPM indications. The end state is a working ONOS-based RIC driving a simulated RAN. Connecting a real gNB is an open problem, described in the last step.
The previous tutorial on this step, Near-RT RIC + OCUDU, used the O-RAN Software Community (O-RAN SC) RIC — a handful of Docker containers on one host — against a real gNB. This tutorial deploys the other open RIC on COSMOS, Aether SD-RAN, which is built very differently: it is a set of ONOS microservices on Kubernetes, with its state in a replicated Atomix store and its view of the network in a topology service. Seeing both is the fastest way to understand what a RIC platform is, as distinct from the xApps that run on it.
SD-RAN is installed onto the cluster you built in Deploy Aether SD-Core, in its own namespace sdran, so the core keeps running untouched. SD-RAN brings its own RAN simulator (RANSim): simulated E2 nodes that connect to SD-RAN's E2 termination exactly as a real gNB would, report cells and UEs, and answer KPM subscriptions. That makes the whole RIC observable without reserving a radio.
What you learn to read here is the RIC's own view: which E2 nodes and cells it knows (the topology store), and whether indications arrive and decode (the E2 termination). The last step points a real OCUDU gNB at SD-RAN. That connection does not complete today — E2 Setup is never acknowledged — and the step shows you how to observe the failure precisely, which is the starting point for anyone who wants to work on it.
onos-e2t), the topology store (onos-topo), the UE store (onos-uenib), configuration (onos-config), the A1 termination (onos-a1t) and each xApp are separate pods that find each other through Kubernetes Services. The SD-RAN page describes each one and compares SD-RAN with the O-RAN SC RIC.After completing this tutorial you will be able to:
atomix, onos-operator, sd-ran) without disturbing the SD-Core namespace;onos topo get entities and interpret E2 node and cell entities;| Learning path | Step 6 of 7 — RAN intelligence. Previous: Deploy Aether SD-Core (and, on the same step, Near-RT RIC + OCUDU). Next: Amarisoft UE sweep |
| Difficulty | Intermediate (Kubernetes and Helm) |
| Duration | not timed; each Helm install waits for its pods (--wait) |
| Domain / testbed | COSMOS osc — the SD-Core core host; no radio |
| RIC | Aether SD-RAN, chart sd-ran 1.5.2 (ONOS), with RANSim |
| New interface | E2 (SCTP; onos-e2t port 36421, published on node port 36401) |
| Topic group | Cellular 4G/5G & O-RAN |
| Last verified | 2026-09-22 — SD-RAN deployed on the SD-Core RKE2 cluster; RANSim: 2 E2 nodes, 3 cells, KPM indications decoded. Real-gNB E2 Setup: open. · page written 2026-09-24 |
Background knowledge
kubectl get, kubectl exec, helm upgrade --install.Account & access
Devices / nodes
| Role | What it needs |
|---|---|
| Core host | a running Aether SD-Core (RKE2 single-node cluster, namespace aether-5gc) with internet access for the Helm charts and container images |
| gNB host (last step only) | an OCUDU gNB — the soft (ZMQ) rig from OCUDU soft-only is enough |
Disk images — the SD-Core image on the core host (see Deploy Aether SD-Core); ocudu.ndz on the gNB host for the last step. SD-RAN itself is pulled from the network by Helm.
Software components
| Component | Version | Source |
|---|---|---|
| Rancher local-path provisioner | v0.0.24 | https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml |
| Atomix | chart 1.1.2 | https://atomix.github.io/charts.atomix.io |
| ONOS operator | chart 0.5.6 | oci://ghcr.io/onosproject/charts/onos-operator |
| SD-RAN | chart 1.5.2 | oci://ghcr.io/onosproject/charts/sd-ran |
Spectrum / RF — none. RANSim is entirely software.
Figure — E2 between an E2 node and a near-RT RIC. In this tutorial the RIC is SD-RAN, drawn as the alternative RIC: its E2 termination serves RANSim's simulated nodes inside the cluster, and the real OCUDU gNB of the last step reaches it from another host.
core host — RKE2 single-node cluster
┌──────────────────────────────────────────────────────────────────────────┐
│ namespace aether-5gc: AMF SMF UPF NRF ... (SD-Core, untouched) │
│ │
│ namespace sdran: │
│ ran-simulator ──E2 (SCTP 36421)──► onos-e2t ──► onos-kpimon (xApp) │
│ │ │
│ onos-topo · onos-uenib · onos-config · onos-a1t │
│ sd-ran-consensus-0/1/2 (Atomix) onos-cli │
│ namespace kube-system: atomix · onos-operator │
└───────────────────────────────▲──────────────────────────────────────────┘
│ E2 via Service onos-e2t-external, node port 36401
│ (last step only; E2 Setup does not complete)
OCUDU gNB on another host
Walk through it from the simulator outward. RANSim runs inside the cluster and opens E2 associations to onos-e2t over the pod network; each simulated gNB sends an E2 Setup Request and onos-e2t records the node and its cells in onos-topo. onos-kpimon sees the new nodes, subscribes to E2SM-KPM on each, and onos-e2t relays the resulting indications to it. The consensus pods hold the replicated state; onos-cli is where you type commands. Nothing in sdran touches the core's user plane or control plane. A real gNB on another host reaches onos-e2t from outside the cluster through the NodePort Service onos-e2t-external (node port 36401 → pod port 36421) on the core host's data-plane address.
All commands run as root on the core host. Point kubectl and helm at the RKE2 cluster:
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml
Confirm the core is healthy before you add anything to its cluster, so that a later problem cannot be blamed on the install:
kubectl get pods -n aether-5gc
What to look for: every pod Running, with upf-0 at 5/5. Note the count (sixteen on the verified cluster); it must be the same after SD-RAN is installed.
SD-RAN's charts request persistent volumes and expect a default StorageClass to satisfy them. The SD-Core cluster has none, because SD-Core itself uses no persistent volume claims. The Rancher local-path provisioner creates volumes as directories on the node's disk; marking it the default makes SD-RAN's claims bind.
Check first:
kubectl get storageclass
Warning: Continue only if this lists no StorageClass (as on the verified cluster), or none marked
(default). On a cluster that already has a default StorageClass, do not repoint it: other workloads' volumes depend on it.
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml
kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
What to look for: kubectl get storageclass now lists local-path (default). The change is additive: it creates a provisioner and a StorageClass and alters nothing that already exists.
SD-RAN's services keep their shared state in Atomix stores, and the ONOS operator manages ONOS configuration models and applications. Both are cluster-wide platform components and go into kube-system. Versions are pinned because SD-RAN is in maintenance upstream and newer platform charts are not tested against it.
helm repo add atomix https://atomix.github.io/charts.atomix.io
helm upgrade --install atomix atomix/atomix --version 1.1.2 -n kube-system -f /tmp/sdran-platform-values.yaml --wait
helm upgrade --install onos-operator oci://ghcr.io/onosproject/charts/onos-operator --version 0.5.6 -n kube-system --wait
Info: Where the values files come from. Aether OnRamp's SD-RAN roles live in
/home/native/onramp/deps/sdranon the core host; their task list is what these commands mirror, and their templates are the source of/tmp/sdran-platform-values.yamland/tmp/sdran-values.yaml. On the COSMOS checkout the role's variables are not wired tomake aether-sdran-install, so the Helm commands are run by hand. The exact contents of the two values files used on 2026-09-22 were not recorded, and the record shows theonos-operatorinstall also took a values file whose name was not kept. If you render them yourself, keep onlyonos-kpimonamong the xApps.
The umbrella chart sd-ran deploys every ONOS service, the KPI-monitor xApp and RANSim into a new namespace sdran.
helm upgrade --install sd-ran oci://ghcr.io/onosproject/charts/sd-ran --version 1.5.2 -n sdran --create-namespace -f /tmp/sdran-values.yaml --wait
kubectl get pods -n sdran
What to look for: eleven pods Running — onos-e2t, onos-topo, onos-config, onos-uenib, onos-a1t, onos-kpimon, onos-cli, ran-simulator and sd-ran-consensus-0, -1, -2. Then re-run kubectl get pods -n aether-5gc: the SD-Core pod count and states must be unchanged.
When a RANSim node completes E2 Setup, onos-e2t writes it and its cells into onos-topo. Reading the topology is therefore the direct check that E2 Setup succeeded for the simulated nodes. The command runs the onos client inside the CLI pod, found by its label.
kubectl -n sdran exec $(kubectl -n sdran get pods -l type=cli -o name) -- onos topo get entities
# e2:1/5153 e2node onos.topo.E2Node,onos.topo.MastershipState
# e2:1/5153/1454c003 e2cell onos.topo.E2Cell
What to look for: 2 entities of kind e2node and 3 of kind e2cell. An e2node ID (e2:1/5153) names one simulated gNB; an e2cell ID extends it with the cell identity (e2:1/5153/1454c003). The MastershipState aspect records which onos-e2t instance owns the node's E2 connection — with one E2 termination, always that one.
onos-kpimon subscribes to E2SM-KPM on every E2 node in the topology; RANSim answers with a RIC Indication each reporting period, and onos-e2t decodes it and relays it to the xApp. The E2 termination's log shows both halves.
kubectl -n sdran logs deploy/onos-e2t --since=1m | grep 'RICIndication successfully decoded'
What to look for: the line RICIndication successfully decoded repeating, and onos-kpimon's subscriptions acknowledged every second. That is the proof that the full E2 path — setup, subscription, indication — works inside SD-RAN.
Info: On 2026-09-22 KPM delivery was confirmed from the E2 termination's log as above; the exact log command was not kept, only the log line. The SD-RAN documentation also describes reading
onos-kpimon's stored measurements through theonosCLI'skpimonsubcommands; that was not run on COSMOS. The values RANSim reports are simulated and are not a performance measurement.
This step reproduces the open issue: a real gNB establishes SCTP with SD-RAN but E2 Setup never completes. Do it only if you want to work on that problem; the tutorial is complete without it.
The E2 termination is reachable from outside the cluster through the NodePort Service onos-e2t-external: node port 36401 on the core host maps to onos-e2t's SCTP port 36421. On the gNB host, use the e2: block from the E2 KPM tutorial with two values changed — addrs becomes the core host's data-plane address and port becomes 36401. On the verified rig the core host was 10.59.1.17 and the soft (ZMQ) gNB host 10.59.1.18:
e2:
enable_du_e2: true
e2sm_kpm_enabled: true
e2sm_rc_enabled: true
e2sm_ccc_enabled: true
addrs: 10.59.1.17 # SD-Core / SD-RAN core host (node port below)
bind_addrs: 10.59.1.18 # this host
port: 36401 # onos-e2t-external NodePort -> 36421/SCTP
Start the gNB as its own tutorial describes, then look at both ends.
What you will see:
| Where | Observation |
|---|---|
| gNB log | E2: Connection ... established, the offered RAN functions (KPM, RC, CCC), then after five seconds E2 Setup procedure failed. cause=unknown |
| gNB socket | the SCTP association ESTAB, with the E2 Setup Request's bytes left in Send-Q, never acknowledged |
onos-e2t log |
Accepted connection from 10.57.1.17 — the node's own address, because kube-proxy source-NATs NodePort traffic — then only connection reset by peer / EOF; no decode error and no setup attempt |
The second exposure tried was a Service externalIP instead of the NodePort — the way SD-Core's AMF carries N2 over SCTP from the same gNB host:
kubectl -n sdran patch svc onos-e2t -p '{"spec":{"externalIPs":["10.59.1.17"]}}'
It fails identically. A capture at the core host then shows a healthy association — INIT, INIT ACK, COOKIE ECHO, COOKIE ACK, ten streams each way, heartbeats in both directions — and no DATA chunks, while a capture at the core host through the NodePort showed the DATA chunks arriving and being retransmitted unacknowledged. The two single-ended captures disagree. The next step is a simultaneous capture on the gNB host and the core host, which separates "the gNB never sent the request" from "the request was sent and lost in transit". The same gNB binary completes E2 Setup with the O-RAN SC RIC, so the gNB's E2 agent works.
| Check | Command | Expected |
|---|---|---|
| Core untouched | kubectl get pods -n aether-5gc |
same pods and states as before the install; upf-0 5/5 |
| Default storage | kubectl get storageclass |
local-path (default) |
| SD-RAN pods | kubectl get pods -n sdran |
11 pods Running |
| Topology | kubectl -n sdran exec $(kubectl -n sdran get pods -l type=cli -o name) -- onos topo get entities |
2 e2node, 3 e2cell |
| KPM | onos-e2t log (Step 5) |
RICIndication successfully decoded, repeating |
| Real gNB (open) | gNB log | E2 Setup procedure failed. cause=unknown — the expected result today |
The verified deployment was left running in its own namespace; it does not affect the core. To return the core host to its image state, re-image it with omf load as in Deploy Aether SD-Core — the SD-Core image does not contain SD-RAN. A Helm-level uninstall of the three releases was not recorded on COSMOS. If you ran Step 6, stop the test gNB and restore its configuration; if you applied the externalIP patch, it lives only in the sdran namespace.
Release the reservation when you are done.
| Symptom | Cause | Fix |
|---|---|---|
SD-RAN pods stay Pending; their volume claims are unbound |
the cluster has no default StorageClass | Step 1; check kubectl get storageclass first |
| Another workload's volumes misbehave after Step 1 | the cluster already had a default StorageClass and it was repointed | never repoint an existing default; undo the annotation on local-path |
onos topo get entities lists nothing |
RANSim has not completed E2 Setup, or onos-e2t / ran-simulator is not Running |
kubectl get pods -n sdran; wait for all eleven pods |
Real gNB: SCTP ESTAB but E2 Setup procedure failed. cause=unknown |
open — see Step 6 | none known; capture both ends simultaneously |
onos-e2t logs the core host's own node address as the peer |
kube-proxy source-NATs NodePort traffic | expected; filter captures on the interface, not on the gNB's address |
| SD-Core stops working after the install | not observed — the install did not touch aether-5gc |
check the core with its tutorial's Verification; never restart the SMF |
On this wiki
External
onos CLI and xApps.sd-ran chart.deps/sdran.Author(s): COSMOS team · Last verified: 2026-09-22 (SD-RAN chart 1.5.2, Atomix 1.1.2, onos-operator 0.5.6 on the SD-Core RKE2 cluster; RANSim 2 E2 nodes / 3 cells, KPM decoded; real OCUDU gNB E2 Setup open) · page written 2026-09-24 · Tested image/release: SD-Core core host image (see Deploy Aether SD-Core) + sd-ran 1.5.2 · Tags: sd-ran, onos, ric, e2, kpm, ransim, xapp, aether, kubernetes, helm