A zero-cost, laptop-only "mini-OpenRAN lab" that demonstrates a full 4G/5G cell-site with O-RAN RIC, xApps, and modern DevOpsβall inside WSL 2.
π― Learn both telecom engineering (4G/5G protocols) and modern DevOps (Kubernetes, monitoring, CI/CD) in one integrated project!
- π‘ Radio Access Network: srsRAN gNB + UE with software-defined radio
- π§ Intelligence Layer: O-RAN RIC + xApps making real-time network decisions
- π Observability Stack: Prometheus + Grafana monitoring the entire network
- π DevOps Pipeline: Everything containerized, tested, and deployed with Helm
# Prerequisites (one-time setup)
# For WSL2 users: Ensure Docker Desktop is installed and running on Windows 11
# with WSL2 integration enabled in Docker Desktop settings
sudo apt update && sudo apt install -y curl make git
# Install kind
curl -Lo /usr/local/bin/kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64 && sudo chmod +x /usr/local/bin/kind
# Install kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# Install Helm
curl -sSL https://get.helm.sh/helm-v3.15.0-linux-amd64.tar.gz | tar xz && sudo mv linux-amd64/helm /usr/local/bin/
# Install Poetry for Python dependency management
curl -sSL https://install.python-poetry.org | python3 -
# Run the lab
git clone https://github.com/<you>/mini-openran-lab && cd mini-openran-lab
poetry install # Install Python dependencies
make kind-up # 2-3 min
helm repo add local ./charts
helm install openran local/openran -f charts/openran/values-kind.yaml
kubectl port-forward svc/grafana 3000:3000 -n monitoring
# view dashboard at http://localhost:3000 (admin/admin)
poetry run pytest -q && robot robot/e2e.robot
The Mini-OpenRAN Lab implements a complete cellular network simulation in Kubernetes, covering:
- π‘ Radio Access Network (RAN): gNB + UE communicating via software radio
- π§ Intelligence Layer: RIC + xApps making smart decisions
- π Observability Stack: Prometheus + Grafana showing what's happening
- π DevOps Pipeline: Everything packaged with Helm, tested with PyTest
Perfect for learning both telecom engineering (4G/5G protocols) and modern DevOps (Kubernetes, monitoring, CI/CD)!
A Helm Chart is like a "recipe" or "template" for deploying applications to Kubernetes. Think of it as:
- Template Package: Contains YAML templates that describe how to deploy your app
- Configurable: Uses variables (values) so you can customize deployments
- Reusable: Can deploy the same app multiple times with different settings
- Versioned: Like a software package with version numbers
In your case, the charts/openran/
directory contains templates for:
- gNB (base station)
- UE (user equipment/phone simulator)
- RIC (radio intelligence controller)
- Monitoring stack (Prometheus + Grafana)
Helm is the "package manager for Kubernetes" - like apt
for Ubuntu or brew
for macOS, but for Kubernetes applications.
# Helm commands you'll use:
helm install openran ./charts/openran # Deploy the whole lab
helm upgrade openran ./charts/openran # Update deployment
helm uninstall openran # Remove everything
Helm takes your templates + values and generates the actual Kubernetes YAML files.
Prometheus is a metrics collection and storage system. It:
- Scrapes metrics: Pulls data from
/metrics
endpoints every few seconds - Stores time-series data: Keeps track of values over time
- Provides alerts: Can notify when things go wrong
- Query language (PromQL): Search and analyze metrics
In your lab, Prometheus collects:
- Radio metrics: Signal quality (CQI), throughput, error rates
- System metrics: CPU, memory usage of pods
- Custom metrics: From your xApp and RIC
Grafana is a visualization and dashboarding tool. It:
- Connects to Prometheus: Reads the metrics data
- Creates beautiful dashboards: Graphs, charts, alerts
- Real-time monitoring: Watch your 4G/5G network in action
- Accessible via web:
http://localhost:3000
(admin/admin)
In your lab, Grafana will show:
- Signal Quality: How good the radio connection is
- Throughput: Data transfer rates
- RRC Connections: When UE connects to gNB
- xApp Actions: When AI makes network optimizations
βββββββββββ metrics βββββββββββββββ queries βββββββββββ
β srsRAN β ββββββββββββββ> β Prometheus β βββββββββββββ> β Grafana β
β (gNB/UE)β β (storage) β β (charts)β
βββββββββββ βββββββββββββββ βββββββββββ
- srsRAN pods expose metrics on
:9092/metrics
- Prometheus scrapes these metrics every 15 seconds
- Grafana queries Prometheus and shows live charts
- You watch the dashboard to see your 4G/5G network working!
Yes, Docker and Kubernetes can definitely be nested! You're actually already doing this in your Mini-OpenRAN Lab:
ββββββββββββββββββββ Your Laptop ββββββββββββββββββββ
β ββββββββββββββββ Docker Engine ββββββββββββββββββ β
β β ββββββββββββ kind Container ββββββββββββββββββ β β
β β β Kubernetes Cluster β β β
β β β βββββββββββ βββββββββββ βββββββββββ β β β
β β β βsrsran- β βsrsran- β βprometheusβ β β β
β β β βgnb β βue β β β β β β
β β β β(container) β(container) β(container) β β β
β β β βββββββββββ βββββββββββ βββββββββββ β β β
β β βββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
What's happening:
- Docker Engine runs on your laptop
- kind creates a Docker container that runs Kubernetes
- Kubernetes runs your application containers inside that container
- This gives you a full cluster experience on a single machine!
Aspect | Regular PC | Kubernetes Cluster |
---|---|---|
Scale | 1 machine | Many machines (or 1 machine pretending to be many) |
Process Communication | Localhost, pipes, shared memory | Network calls, services, DNS |
Failure Handling | Process crashes = manual restart | Pod crashes = automatic restart |
Resource Sharing | Shared CPU/RAM | Isolated CPU/RAM per container |
Networking | 127.0.0.1:port |
service-name:port |
Process Management | systemd , ps , kill |
kubectl , deployments, pods |
# On your laptop:
firefox & # Process 1234
code & # Process 1235
python app.py & # Process 1236
# They communicate via:
curl http://localhost:8080 # Same machine
# In the cluster:
kubectl get pods
# NAME READY STATUS RESTARTS
# openran-gnb-12345 1/1 Running 0
# openran-ue-67890 1/1 Running 0
# prometheus-11111 1/1 Running 0
# They communicate via:
# UE -> gNB: http://openran-gnb:2000
# Prometheus -> UE: http://openran-ue:9092
1. Self-Healing
# Regular PC:
kill 1234 # Firefox dies, stays dead
# Kubernetes:
kubectl delete pod openran-gnb-12345
# Kubernetes immediately starts openran-gnb-67891
2. Scaling
# Regular PC:
# Hard to run 10 copies of same app
# Kubernetes:
kubectl scale deployment openran-ue --replicas=10
# Now you have 10 UE simulators!
3. Resource Isolation
# Each container gets guaranteed resources:
resources:
requests:
memory: "512Mi" # Guaranteed 512MB RAM
cpu: "500m" # Guaranteed 0.5 CPU cores
limits:
memory: "1Gi" # Max 1GB RAM
cpu: "1000m" # Max 1 CPU core
# UE waits for gNB to be ready:
initContainers:
- name: wait-for-gnb
command: ['sh', '-c', 'until nc -z openran-gnb 2000; do sleep 5; done']
# Uses Kubernetes DNS: "openran-gnb" resolves to gNB pod IP
# UE connects to gNB:
env:
- name: GNB_ADDRESS
value: "openran-gnb" # Service name, not IP address!
Summary: Kubernetes is like a "distributed operating system" that makes many computers look like one big computer, with automatic networking, healing, and scaling!
Domain | Key Idea | Implementation |
---|---|---|
Radio PHY | OFDM + HARQ: srsRAN gNB & UE loop bits through an OFDM pipeline, complete with CQI feedback | charts/openran/templates/gnb.yaml (env vars choose QPSK / 16-QAM) |
Open RAN | Near-RT RIC & E2 interface: gNB sends real-time stats to the controller; xApps push policy back | charts/openran/templates/ric-plt.yaml , xapps/beam_tuner/app.py |
Control Theory / ML | Beam-tuner xApp: watches CQI; if median drops, issues "adjust MCS" E2 message | xapps/beam_tuner/logic.py |
Observability | KPIs scraped by Prometheus β Grafana panels show throughput jump when xApp kicks in | charts/openran/values-kind.yaml (prom scrape config) |
DevOps | CI builds every image, runs PyTest+Robot, publishes Helm chart; Terraform can replay on AWS | .github/workflows/ci.yml , terraform/main.tf |
mini-openran-lab/
ββ charts/openran/ # Helm bundle: gNB, UE, RIC, xApp, Prom, Grafana
ββ xapps/beam_tuner/ # FastAPI + (later) scikit-learn
ββ hack/ # kind-up.sh, load-images.sh helpers
ββ tests/ # pytest unit + Robot E2E
ββ terraform/ # spin 1 Γ t3.micro + k3s (optional)
ββ grafana/dashboards/ # Dashboard JSON configs
ββ .github/workflows/ci.yml # build, test, push, chart-lint
Phase | Task | Status |
---|---|---|
0. Bootstrap | Repo, licence, README stub | β |
Add CI badge in README | β | |
1. kind cluster | hack/kind-up.sh script | β |
CI job runs kind create cluster | β | |
2. Radio pods | Helm template for srsran-gnb | β |
Helm template for srsran-ue | β | |
PyTest: log contains "RRC CONNECTED" | β | |
Namespace separation implementation | β | |
Security audit and documentation | β | |
3. RIC | Add RIC chart dependency | β» |
Verify E2 link (grep log) | β» | |
Update architecture diagram | β» | |
4. xApp skeleton | FastAPI server returns /healthz=200 | β» |
Dockerfile builds under CI | β» | |
Helm values enable pod | β» | |
5. Observability | Prometheus scrape config | β» |
Grafana dashboard JSON (CQI, Throughput) | β» | |
Robot test checks HTTP 200 on Grafana | β» | |
6. CI/CD | GitHub Actions: build images, push to ghcr.io | β» |
Chart lint + helm template smoke in CI | β» | |
7. Terraform (optional) | main.tf t3.micro + user-data installs k3s | β» |
make deploy-cloud wrapper | β» | |
terraform destroy cleans all | β» | |
8. ML upgrade (stretch) | Collect CSV CQI samples | β» |
Train simple scikit-learn regressor | β» | |
Replace rule-based logic | β» | |
9. Release v1.0 | Tag rel/v1.0 | β» |
Attach chart .tgz + screenshot | β» | |
Write changelog | β» |
Challenge: Creating a stable Kubernetes-in-Docker environment on WSL2 that can reliably host telecom workloads.
Key Issues Solved:
- Cgroup Driver Mismatch: Fixed kubelet/container runtime incompatibility by switching from
cgroupfs
tosystemd
driver - Deprecated K8s Flags: Removed
--pod-eviction-timeout
flag that was deprecated in newer Kubernetes versions - WSL2 Resource Constraints: Optimized timeout values and swap handling for WSL2 environment
- Systematic Debugging: Used
systemctl status kubelet
andjournalctl -xeu kubelet
for root cause analysis
Technical Implementation:
- Poetry: Modern Python dependency management with virtual environments
- Comprehensive Testing: 10 pytest tests with real-time output streaming and proper cleanup
- CI/CD Integration: GitHub Actions using Poetry instead of traditional pip workflows
- WSL2 Optimization: Specialized configuration for Windows Subsystem for Linux v2 compatibility
Results: Fully functional single-node Kubernetes cluster with:
- β All control plane pods healthy (etcd, kube-apiserver, kube-controller-manager, kube-scheduler)
- β Calico CNI networking operational
- β Required namespaces created (openran, monitoring, xapps)
- β Port mappings configured (3000βGrafana, 9090βPrometheus, 8080βxApp API)
- β Node labels applied for CPU isolation
- β 100% test pass rate in ~2 minutes
Component | Role |
---|---|
srsran-gnb | Open-source 4G/5G base-station (gNodeB). Generates OFDM frames, runs MAC scheduler, exposes E2 metrics |
srsran-ue-loop | Dummy UE that connects to gNB in internal loopback; creates constant traffic so KPIs move |
ric-platform | O-RAN Software Community Near-RT RIC (controller) processing CQI, RB table, etc. |
xappmgr | Side-car inside RIC that loads/manages xApps (plugins) |
beam-tuner-xapp | FastAPI micro-service; subscribes to RIC metrics and pushes policy messages |
prometheus | Scrapes /metrics endpoints; stores time-series data |
grafana | Reads Prometheus and shows dashboards: CQI, throughput, BER, xApp actions |
Tool | Version | Purpose |
---|---|---|
kind | 0.22.0 | Kubernetes-in-Docker |
Helm | 3.15.* | Package manager |
Kubernetes | v1.30.0 | Container orchestration |
srsRAN | 22.10.* | Radio stack |
O-RAN RIC | v2.4.* | RAN controller |
FastAPI | 0.111.* | xApp framework |
scikit-learn | 1.5.* | ML (future) |
Factor | Mini-OpenRAN-Lab | Production Alternative |
---|---|---|
Radio | Software loopback (no hardware) | Real RF needs USRP-B210, antennas, timing cards |
RIC | O-RAN SC "RIC-in-Docker" | 20+ microservices, Kafka, multus CNI |
Infrastructure | kind on WSL (zero cost) | Multi-node K8s (EKS/GKE) with load balancers |
Debug | docker logs + localhost Grafana | Remote RF logs, interference, hardware failures |
Your Mini-OpenRAN Lab is completely isolated and secure - no external threats can reach your virtual radio network.
βββββββββββββ Internet ββββββββββββββ
β β β No Direct Access
β ββββββββ Your Laptop βββββββββββ β
β β β β
β β ββββ Docker Bridge ββββββββ β β π Private Network
β β β kind cluster β β β (Bridge + NAT)
β β β β β β
β β β ββ Pod Network ββββββ β β β π Internal Only
β β β β srsRAN gNB β β β β (Pod-to-Pod)
β β β β srsRAN UE β β β β
β β β β Prometheus β β β β
β β β ββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββ
Network Layer | IP Range | Access Level | Purpose |
---|---|---|---|
Pod Network | 10.244.0.0/16 |
Internal only | srsRAN gNB β UE communication |
Service Network | 10.96.0.0/12 |
Cluster internal | Service discovery (DNS names) |
Docker Bridge | 172.18.0.0/16 |
Host + containers | kind cluster networking |
Localhost Only | 127.0.0.1:3000,9090,8080 |
Your machine | Dashboard access |
- β Radio interfaces: Virtual RF between gNB/UE stays internal
- β Pod IPs: No external routing to individual containers
- β Management APIs: Kubernetes API server not externally accessible
- β Internal metrics: Prometheus endpoints only reachable within cluster
- β
Grafana Dashboard:
http://localhost:3000
(localhost-only binding) - β
Prometheus UI:
http://localhost:9090
(localhost-only binding) - β
xApp API:
http://localhost:8080
(localhost-only binding)
- Docker Bridge Isolation: Private subnet with NAT-only internet access
- Kubernetes Network Policies: Can add pod-to-pod restrictions (currently open for lab use)
- No External LoadBalancer: All services use internal ClusterIP or localhost NodePort
- WSL2 Additional Isolation: Even more network isolation through Windows Subsystem for Linux
The current configuration uses loca 85AE lhost-only bindings for security. If you need to access dashboards from other machines on your network, you can:
# In hack/kind-config.yaml, change from:
listenAddress: 127.0.0.1 # Localhost only (current)
# To:
# listenAddress: 0.0.0.0 # All interfaces (less secure)
Or use kubectl port-forward
for temporary external access:
# Temporary access from any interface
kubectl port-forward --address 0.0.0.0 svc/grafana 3000:3000 &
Bottom Line: Your virtual cellular network is completely secure and isolated. Dashboard ports are bound to localhost-only, and pod-to-pod radio communications remain completely internal.
cd terraform
terraform apply # β€ 1 min, spins t3.micro with k3s
ssh ec2-user@<ip>
helm repo add openran https://<your-gh-pages>/charts
helm install openran openran/openran -f charts/openran/values-ec2.yaml
# terraform destroy when done (stay within 750 free hours)
After setup, you'll see:
- Grafana dashboard at
http://localhost:3000
showing live CQI/throughput metrics - RRC CONNECTED logs from UE attachment
- E2 interface telemetry between gNB and RIC
- xApp policy changes affecting radio performance in real-time
- CPU pinning:
taskset -c 0 docker run ... srsran-gnb
- Port forwarding: Open 3000/tcp (Grafana) & 9090/tcp (Prometheus)
- Cost guard: AWS Budget at $0.50, cron job stops t3.micro at midnight UTC
- Debug: Check
kubectl logs -f deployment/srsran-gnb
MIT License - see LICENSE file.
- Fork the repo
- Create feature branch (
git checkout -b feature/amazing-feature
) - Commit changes (
git commit -m 'Add amazing feature'
) - Push to branch (
git push origin feature/amazing-feature
) - Open Pull Request
π― Goal: Prove you can mix telecom science (CQI, scheduler, beamforming) with modern DevOps (Docker, Helm, GitHub Actions, IaC)βwithout costing a cent.