RAN CN 테스트베드 구축/OAI+Free5GC (RAN-Core)

OAI RAN을 Kubernetes 기반 Free5GC Cluster에 연결하기

gksyb4235 2026. 5. 28. 17:53

기존에는 OAI RAN과 Free5GC Docker Compose 기반 Core를 연결해서 테스트를 진행했다.

이 방식은 빠르게 실험하기에는 좋지만, AI Agent 기반의 인프라 제어 실험을 수행하기에는 한계가 있었다.

특히 Docker Compose 기반 Free5GC는 단일 서버 안에서 동작하기 때문에 다음과 같은 K8s 기반 실험을 수행하기 어렵다.

 

  • NF 단위 scaling
  • Pod migration
  • Node 분산 배치
  • Kubernetes 리소스 기반 장애 복구
  • Helm chart 기반 구성 변경
  • Monitoring stack과 연동한 실시간 관찰

 

그래서 이번에는 기존에 https://gksyb4235.tistory.com/76에서 구축해둔 Free5GC Kubernetes Cluster에
외부 OAI RAN VM을 연결하는 작업을 진행했다.

 

최종 목표는 다음과 같다.

OAI gNB / nrUE  →  Free5GC Kubernetes Cluster

 

구성은 다음과 같다.

OAI VM
  gNB/RAN IP: 192.168.56.30
  Interface: ens34

Free5GC Kubernetes Cluster
  master-node: 192.168.56.50
  free5gc-cp: 192.168.56.51
  free5gc-up: 192.168.56.52

 

 

 

 

1. VMware OAI VM과 VirtualBox Free5GC Cluster 네트워크 연결


Free5GC Cluster는 VirtualBox 위에서 동작하고 있었고, OAI VM은 VMware 위에서 동작하고 있는 상태.

VMware와 Virtualbox 간의 통신을 한다는 것은 서로 다른 Hypervisor 위의 VM을 같은 L2 네트워크에 붙여야 함을 의미한다.

 

Free5GC Cluster의 Host-only 네트워크는 다음 대역을 사용하고 있었다.

VirtualBox Host-Only Network: 192.168.56.0/24

master-node   192.168.56.50
free5gc-cp    192.168.56.51
free5gc-up    192.168.56.52

 

OAI VM도 이 네트워크에 들어가야 하므로 VMware의 vmnet0을 VirtualBox Host-only Adapter인 vboxnet0에 bridge했다.

VMware Virtual Network Editor에서 네트워크 설정을 변경한다.

 

vmnet0
  Type: Bridged
  Bridged to: vboxnet0

 

그리고 OAI VM에는 기존 NAT Adapter는 그대로 두고, 두 번째 Network Adapter를 추가한다.

Connect at power on 옵션을 체크하고, Network Connection을 Custom으로 변경하였다.

 

 

이렇게 하면 OAI VM의 두 번째 NIC가 VirtualBox Host-only 네트워크인 192.168.56.0/24에 붙는다.

구조는 다음과 같다.

VirtualBox Free5GC Cluster
  master-node   192.168.56.50
  free5gc-cp    192.168.56.51
  free5gc-up    192.168.56.52
        |
        | VirtualBox Host-Only Network: vboxnet0
        |
VMware vmnet0 bridged to vboxnet0
        |
        |
OAI VM Adapter 2
  ens34 = 192.168.56.30/24

 

 

OAI RAN IP를 고정 IP로 설정


OAI VM을 켠 뒤 ip -br addr를 확인하면 두 번째 NIC가 ens34로 잡혔다.

처음에는 VirtualBox Host-only DHCP에 의해 192.168.56.102/24 같은 주소를 자동으로 받았다.

 

 

하지만 RAN IP는 재부팅 후 바뀌면 안 된다.

gNB 설정 파일, UPF N3 설정, routing, ARP 설정이 모두 RAN IP에 의존하기 때문이다.

 

고정된 IP 설정이 재부팅 후에도 유지되도록 NetworkManager 설정을 변경했다.

sudo nmcli connection modify "유선 연결 1" \
  ipv4.method manual \
  ipv4.addresses 192.168.56.30/24 \
  ipv4.gateway "" \
  ipv4.dns "" \
  ipv4.never-default yes \
  ipv6.method ignore \
  connection.autoconnect yes

sudo nmcli connection down "유선 연결 1"
sudo nmcli connection up "유선 연결 1"

 

 

설정 확인:

nmcli connection show "유선 연결 1" | grep -E "ipv4.method|ipv4.addresses|ipv4.gateway|ipv4.dns|ipv4.never-default"
>>>
ipv4.method:        manual
ipv4.addresses:     192.168.56.30/24
ipv4.gateway:       --
ipv4.never-default: yes

 

이 상태가 되면 OAI gNB/RAN IP 192.168.56.30은 DHCP 없이 고정된 것이다.

 

 

 

 

2. Free5GC Cluster에서 OAI VM으로 ping 연결하기


IP 세팅으로 OAI VM에서 Free5GC 노드로 ping은 성공한다.

ping -c 3 192.168.56.50
ping -c 3 192.168.56.51
ping -c 3 192.168.56.52

 

하지만 반대로 Free5GC master-node에서 OAI VM으로 ping을 하면 실패했다.

ping -c 3 192.168.56.30

 

 

이 경우 OAI → Free5GC 방향은 성공하므로 L2 연결 자체가 완전히 끊어진 것은 아니다.

문제는 Free5GC 쪽에서 OAI VM의 MAC 주소를 제대로 ARP로 찾지 못하는 상황에 가까웠다.

이를 해결하기 위해서는 Free5GC 노드들에 OAI VM의 MAC 주소를 static ARP로 등록해 주어야 한다.

 

OAI VM의 ens34 MAC 주소를 확인한다.

ip link show ens34

 

 

이후 Free5GC의 모든 노드에 동일하게 아래 명령어를 지정하고 ping test를 수행한다.

sudo ip neigh replace 192.168.56.30 lladdr 00:0c:29:c1:83:83 dev enp0s8 nud permanent
ip neigh show 192.168.56.30
ping -c 3 192.168.56.30

 

 

ping 결과에서 DUP!가 보였지만,

이는 VMware vmnet0과 VirtualBox vboxnet0을 bridge하는 과정에서 ICMP Echo Reply가 중복 전달된 것이기 때문이다.

packet loss는 0%였고, 이후 SCTP 기반 NGAP 연결과 UE Registration이 정상적으로 수행되었기 때문에,

해당 현상은 본 실험의 기능 검증에는 영향을 주지 않는 것으로 보고 진행했다. (추후 문제가 생기면 수정해야 함)

 

이 과정을 통해 Free5GC 노드들에서도 OAI RAN IP로 ping이 가능해졌고,

RAN의 VM과 Core의 VM끼리 통신이 제대로 수행된 상태.

 

 

 

 

3. OAI gNB 설정 파일 변경


OAI gNB config 파일은 기존 Docker Compose 기반 OAI CN5G용 설정을 수정해서 사용했다.

파일은 다음과 같다. (gNB와 UE를 Docker로 띄우는 실행파일)

 

관련된 공식 문서 내용은 아래와 같다.

https://gitlab.eurecom.fr/oai/openairinterface5g/-/blob/develop/ci-scripts/yaml_files/5g_rfsimulator/README.md#24-deploy-second-oai-nr-ue-in-rf-simulator-mode-and-in-standalone-mode

 

/home/user/openairinterface5g/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210_docker.conf

 

핵심 값은 다음과 같이 맞췄다. (PLMN 208/93은 Free5GC의 Subscriber 기본값을 따름, OAI의 경우 001/01)

PLMN: 208/93
TAC: 1
S-NSSAI:
  SST 1 / SD 000001
  SST 2 / SD 000002
  SST 3 / SD 000003

OAI gNB IP: 192.168.56.30
AMF node:   192.168.56.51
UPF N3:     192.168.56.19

 

gNB 설정의 PLMN/S-NSSAI:

tracking_area_code  =  1;

plmn_list = ({
  mcc = 208;
  mnc = 93;
  mnc_length = 2;
  snssaiList = (
    { sst = 1; sd = 0x000001; },
    { sst = 2; sd = 0x000002; },
    { sst = 3; sd = 0x000003; }
  )
});

 

NG-C / NG-U interface는 OAI VM의 Free5GC 연결용 NIC인 ens34를 사용하도록 설정했다.

NETWORK_INTERFACES :
{
    GNB_INTERFACE_NAME_FOR_NG_AMF            = "ens34";
    GNB_IPV4_ADDRESS_FOR_NG_AMF              = "192.168.56.30/24";

    GNB_INTERFACE_NAME_FOR_NGU               = "ens34";
    GNB_IPV4_ADDRESS_FOR_NGU                 = "192.168.56.30/24";

    GNB_PORT_FOR_S1U                         = 2152;
};

 

 

최종적으로 아래의 4명의 UE를 등록해두었고, 관련된 UE config 파일도 세팅 완료했다.

 

 

 

4. OAI gNB의 AMF port 문제


Free5GC의 공식 문서에서 AMF NodePort가 31412였기 때문에 OAI gNB config에 다음과 같이 설정했다.

amf_ip_address = ({ ipv4 = "192.168.56.51"; port = 31412; });

 

하지만 tcpdump로 확인해보니 OAI gNB는 실제로 31412가 아니라 38412로 SCTP 연결을 시도하고 있었다.

OAI VM에서 tcpdump:

sudo tcpdump -ni ens34 'host 192.168.56.51 and (sctp or port 31412 or port 38412)'

 

문제 상황:

192.168.56.30.46815 > 192.168.56.51.38412: SCTP INIT
192.168.56.51.38412 > 192.168.56.30.46815: SCTP ABORT

 

즉 현재 사용한 OAI gNB 빌드에서는 amf_ip_addressport = 31412 설정이 반영되지 않았다.

OAI는 기본 NGAP port인 38412로 접속하려고 했다.

 

그렇다고 Free5GC AMF NodePort를 38412로 바꾸는 것도 불가능했는데,

Kubernetes NodePort 기본 범위는 30000-32767이기 때문이다.

 

위와 같이 설정하고 Helm upgrade 시 다음 오류가 발생했다.

Service "free5gc-free5gc-amf-amf-n2" is invalid:
spec.ports[0].nodePort: Invalid value: 38412:
provided port is not in the valid range.
The range of valid ports is 30000-32767

 

따라서 NodePort 대신 AMF Pod에 hostPort: 38412를 추가하는 방식으로 우회했다.

 

 

Free5GC AMF에 hostPort 38412 추가


수정한 파일:

~/towards5gs-helm/charts/free5gc/charts/free5gc-amf/templates/amf-deployment.yaml

 

기존:

ports:
  - name: namf
    containerPort: {{ .service.port }}
{{- if $.Values.isNgapNeeded }}
  - name: n2
    containerPort: {{ $.Values.global.amf.service.ngap.port }}
    protocol: {{ $.Values.global.amf.service.ngap.protocol }}
{{- end }}

 

수정 후:

ports:
  - name: namf
    containerPort: {{ .service.port }}
{{- if $.Values.isNgapNeeded }}
  - name: n2
    containerPort: {{ $.Values.global.amf.service.ngap.port }}
    hostPort: {{ $.Values.global.amf.service.ngap.port }}
    protocol: {{ $.Values.global.amf.service.ngap.protocol }}
{{- end }}

 

 

 

즉 실제로는 다음이 추가된다.

hostPort: 38412
protocol: SCTP

 

이 설정을 통해 free5gc-cp 노드의 192.168.56.51:38412/SCTP로 들어오는 SCTP 트래픽이

AMF 컨테이너의 38412/SCTP로 직접 들어가게 된다.

 

이후 Helm upgrade를 수행했다.

cd ~/towards5gs-helm/charts/free5gc
helm upgrade free5gc . -n free5gc -f services-enabled-values.yaml

 

hostPort가 Pod spec에 반영됐는지 확인했다.

kubectl get pod -n free5gc -l nf=amf -o yaml | grep -A12 -B4 "containerPort: 38412"

 

정상 반영 상태:

- containerPort: 38412
  hostPort: 38412
  name: n2
  protocol: SCTP

 

 

이제 RAN의 설정 파일에서 amf_ip_address에서 port 부분을 제외하고 실행했다.

 

 

최종적으로 tcpdump 결과는 다음처럼 바뀌었다.

192.168.56.30.50726 > 192.168.56.51.38412: SCTP INIT
192.168.56.51.38412 > 192.168.56.30.50726: SCTP INIT ACK
192.168.56.30.50726 > 192.168.56.51.38412: SCTP COOKIE ECHO
192.168.56.51.38412 > 192.168.56.30.50726: SCTP COOKIE ACK

 

gNB와 AMF 로그에서도 정상적으로 NGSetupRequest가 처리됐다.

 

<gNB 로그>

 

<AMF 로그>

[AMF][Ngap] [AMF] SCTP Accept from: 192.168.56.30:50726
[AMF][Ngap] Create a new NG connection for: 192.168.56.30:50726
[AMF][Ngap] Handle NGSetupRequest
[AMF][Ngap] Send NG-Setup response

 

 

 

 

 

5. Free5GC NF 설정 변경


다음으로 Free5GC에 설치된 towards5gs-helm chart의 내용을 변경해야 한다.

 

Free5GC SMF 설정 변경


SMF에서는 UE가 요청할 S-NSSAI와 DNN을 지원하도록 설정해야 한다.

수정 대상: ~/towards5gs-helm/charts/free5gc/charts/free5gc-smf/values.yaml

 

UE1은 다음 값을 사용한다.

DNN = internet
SST = 1
SD  = 000001

따라서 SMF의 snssaiInfossNssaiUpfInfos에 동일한 S-NSSAI를 넣었다.

 

예:

snssaiInfos:
  - sNssai:
      sst: 1
      sd: 000001
    dnnInfos:
      - dnn: internet
        dns:
          ipv4: 8.8.8.8
          ipv6: 2001:4860:4860::8888

 

UPF 정보도 동일하게 맞춘다.

sNssaiUpfInfos:
  - sNssai:
      sst: 1
      sd: 000001
    dnnUpfInfoList:
      - dnn: internet
        pools:
          - cidr: 10.1.0.0/17
        staticPools:
          - cidr: 10.1.64.0/24

 

추가 slice를 사용할 경우 다음처럼 확장할 수 있다.

- sNssai:
    sst: 2
    sd: 000002
  dnnUpfInfoList:
    - dnn: internet
      pools:
        - cidr: 10.1.128.0/17
      staticPools:
        - cidr: 10.1.192.0/24

- sNssai:
    sst: 3
    sd: 000003
  dnnUpfInfoList:
    - dnn: internet
      pools:
        - cidr: 10.2.0.0/17
      staticPools:
        - cidr: 10.2.64.0/24

 

UPF N3 endpoint는 다음 값을 유지했다.

interfaces:
  - interfaceType: N3
    endpoints:
      - 192.168.56.19
    networkInstances:
      - internet

 

여기서 192.168.56.19는 OAI RAN IP가 아니다.

UPF의 N3/GTP-U endpoint로 사용되는 주소이므로 192.168.56.30으로 바꾸면 안 된다.

(GPT는 항상 이 부분을 잘못 알려준다. 아직 학습이 덜 된 듯;)

 

Free5GC AMF Allowed NSSAI 설정 변경


SMF만 수정하면 PDU Session이 생성되지 않았다. UE 로그에 다음 에러가 발생했다.

[NAS] NSSAI parameters not match with allowed NSSAI. Couldn't request PDU session.

 

이 에러는 UE가 요청한 S-NSSAI와 AMF가 Registration Accept에서 내려준 Allowed NSSAI가 일치하지 않는다는 뜻이다. 즉 OAI nrUE가 PDU Session Establishment Request를 보내기도 전에 스스로 중단한 것이다.

UE는 다음 S-NSSAI를 요청하고 있었다.

SST = 1
SD  = 000001
DNN = internet

 

하지만 AMF values.yaml에서도 plmn과 snssai 관련된 부분을 수정한다.

~/towards5gs-helm/charts/free5gc/charts/free5gc-amf/values.yaml

 

다음과 같이 수정 :

plmnSupportList:
  - plmnId:
      mcc: 208
      mnc: 93
    snssaiList:
      - sst: 1
        sd: 000001
      - sst: 2
        sd: 000002
      - sst: 3
        sd: 000003

 

 

 

SNSSAI 부분도 아래와 같이 수정한다.

 

 

Helm upgrade 후 AMF ConfigMap 반영 여부를 확인했다.

kubectl get configmap -n free5gc -o yaml | grep -A8 -B4 "plmnSupportList"

 

정상 반영 예:

plmnSupportList:
  - plmnId:
      mcc: 208
      mnc: 93
    snssaiList:
      - sst: 1
        sd: 000001
      - sst: 2
        sd: 000002

 

이후 AMF를 재시작해서 제대로 반영될 수 있도록 한다.

kubectl rollout restart deployment -n free5gc -l nf=amf

 

 

 

6. gNB와 UE1 실행


gNB 실행 명령:

docker run --rm -it \
  --name oai-gnb \
  --privileged \
  --cap-add NET_ADMIN \
  --cap-add NET_RAW \
  --device /dev/net/tun:/dev/net/tun \
  --network host \
  -v /home/user/openairinterface5g/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210_docker.conf:/opt/oai-gnb/etc/gnb.conf \
  oai-gnb:latest \
  /opt/oai-gnb/bin/nr-softmodem \
  -O /opt/oai-gnb/etc/gnb.conf \
  --gNBs.[0].min_rxtxtime 6 \
  --rfsim

 

gNB 정상 로그:

[NGAP] Received NGSetupResponse from AMF
[GNB_APP] [gNB 0] Received NGAP_REGISTER_GNB_CNF: associated AMF 0
[HW] Running as server waiting opposite rfsimulators to connect

 

 

UE1 실행 명령:

docker run --rm -it \
  --name oai-nr-ue1 \
  --privileged \
  --cap-add NET_ADMIN \
  --cap-add NET_RAW \
  --device /dev/net/tun:/dev/net/tun \
  --network host \
  -v /home/user/openairinterface5g/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue1.conf:/opt/oai-nr-ue/etc/nr-ue.conf \
  oai-nr-ue:latest \
  /opt/oai-nr-ue/bin/nr-uesoftmodem \
  -r 106 \
  --numerology 1 \
  --band 78 \
  -C 3619200000 \
  --rfsim \
  --uicc0.imsi 208930000000001 \
  --rfsimulator.serveraddr 127.0.0.1 \
  --telnetsrv \
  --telnetsrv.listenport 9095

 

OAI gNB와 UE 모두 --network host로 실행했기 때문에 RF simulator server address는

Docker container name인 oai-gnb가 아니라 127.0.0.1을 사용했다.

--rfsimulator.serveraddr 127.0.0.1

 

기존 Docker Compose 기반 OAI CN5G 환경에서는 gNB와 UE가 같은 Docker bridge network에 있었기 때문에

oai-gnb라는 Docker DNS 이름이 동작했다.

하지만 host network 모드에서는 Docker DNS를 사용하지 않으므로 127.0.0.1로 연결해야 한다.

 

Registration 성공 확인


UE 로그에서는 다음 흐름을 확인할 수 있다.

Connection to 127.0.0.1:4043 established
Initial sync successful
SIB1 decoded
4-Step RA procedure succeeded
State = NR_RRC_CONNECTED
Generate Initial NAS Message: Registration Request
Received NAS_DOWNLINK_DATA_IND type FGS_AUTHENTICATION_REQUEST
Received NAS_DOWNLINK_DATA_IND type FGS_SECURITY_MODE_COMMAND
Received NAS_DOWNLINK_DATA_IND type FGS_REGISTRATION_ACCEPT
Received Registration Accept with result 3GPP
Send NAS_UPLINK_DATA_REQ message(RegistrationComplete)

 

AMF 로그에서는 다음 흐름이 확인된다.

Handle InitialUEMessage
Handle Registration Request
Send Authentication Request
Handle Authentication Response
Send Security Mode Command
Handle Security Mode Complete
Send Registration Accept
Send Initial Context Setup Request
Handle InitialContextSetupResponse
Handle Registration Complete
transition from [ContextSetup] to [Registered]

 

즉 여기까지는 다음이 모두 성공한 것이다.

OAI UE ↔ OAI gNB RRC 연결 성공
OAI gNB ↔ Free5GC AMF NGAP 연결 성공
UE Authentication 성공
Security Mode 성공
Registration Accept 수신 성공
UE Registered 상태 진입

 

 

 

7. PDU Session 확인


Registration 이후 PDU Session이 생성되면 SMF/UPF 로그가 움직인다.

 

SMF 확인:

kubectl logs -n free5gc -l nf=smf --since=5m | grep -Ei "PDU|Session|SMContext|PFCP|SEID|DNN|snssai|error|fail"

 

UPF 확인:

kubectl logs -n free5gc -l nf=upf --since=5m | grep -Ei "PFCP|Session|SEID|PDR|FAR|QER|error|fail"

 

SMF와 UPF가 정상 준비된 경우 부팅 시점에는 다음과 같은 로그가 보인다.

[SMF][PFCP] Listen on 10.100.50.244:8805
[SMF][Main] Sending PFCP Association Request to UPF[10.100.50.241]
[SMF][Main] Received PFCP Association Setup Accepted Response from UPF[10.100.50.241]

 

UPF 로그:

[UPF][Main] GTP Address: "192.168.56.19:2152"
[UPF][PFCP][LAddr:10.100.50.241:8805] pfcp server started
[UPF][Main] UPF started
[UPF][PFCP] handleAssociationSetupRequest

 

PDU Session Establishment가 성공하면 UE 터널 인터페이스가 OAI VM에 생성된다.

OAI VM에서 확인:

ip -br addr | grep -Ei "oaitun|tun|nr"
ip route

 

그럼 다음과 같이 oaitun_ue1이라는 PDU Session이 생성됨을 확인할 수 있다.

oaitun_ue1  UNKNOWN  10.1.x.x/...

 

 

 

ping 테스트:

ping -I oaitun_ue1 -c 4 8.8.8.8

 

 

이후 Grafana에서 전체적인 Traffic 흐름을 확인할 수 있다.