Kubernetes環境でfree 5GCを動かすまで

修士の研究でfree 5GCをKubernetes上で動かす必要があったが、どちらも全く触ったことがなかったので備忘録として手順を残しておく。

ポートとかファイアウォールとかセキュリティに関してはザルなので(とにかく動くこと優先)、その辺は参考にしないように。

2台のPCを使って同一LAN内で環境構築することを前提とする。



# マシンの準備
何はともあれマシンを準備する。
研究室でおねだりした結果、ノートPCとデスクトップの2台のPCを借りられたのでノートPCをMaster Node, デスクトップをWorker Nodeとして使う。
- Master Node(DELL XPS-13-9305)
    CPU: 第11世代 インテル® Core™ i7-1165G7
    メモリ: 16GB 4267MHz LPDDR4x メモリー
    
- Worker Node(研究室で余ってたやつ)
    CPU: インテル® Core™ i7-11700
    メモリ: 32GB 8GB*4枚


# OSのセッティング
ubuntuをインストールしてカーネルのバージョンを下げる。

## Ubuntuのインストール
今回はUbuntu 20.04 LTSをインストールする。Worker Nodeには何のOSも入っていなかったので、何の工夫もなくインストールできたが、Master Nodeには元々windowsがインストールされているのでデュアルブートする必要がある。
https://www.pc-koubou.jp/magazine/35542 などを参考にデュアルブートしてください。(ミスるとwindowsごとぶっ壊す可能性があるので自己責任で)

## カーネルのダウングレード
5GCのパケット処理に使われるカーネルモジュールgtp5gはlinuxのカーネルバージョンが5.4以下でないと動作しないので、カーネルをダウングレードする。

https://kazuhira-r.hatenablog.com/entry/2020/03/08/141730　
https://level69.net/archives/31142

この辺の記事を参考にカーネルバージョンを下げる。




# 各種モジュールのインストール
free5gcのパケット処理に使われるgtp5g、kubernetesのコンテナランタイムとなるdocker、kubernetes環境を構築するためのツールkubeadmをインストールする。　

## gtp5gのインストール
リポジトリを参考にインストール

https://github.com/free5gc/gtp5g 

```
git clone https://github.com/free5gc/gtp5g.git
cd gtp5g
make clean && make
sudo make install
```

## dockerのインストール
kubernetesのコンテナランタイム(CRI)にはdocker, containerd, CRI-Oの三つがあるが、今回はdockerを採用する。理由としてはcadvisorでコンテナメトリクスを収集するのにdockerである必要があるから。 

ちなみにkubernetesではdockerが非推奨になっているっぽい。dockerの内部にはcontainerdがあるため、dockerとcontainerdがどちらも入っているとdockerが優先される。 

こちらを参考にインストール

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04-ja

```
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt update
apt-cache policy docker-ce
```
今回インストールしたdockerのバージョンは23.0.1。サーバーとクライアントのバージョンは揃えているが、揃わないと動かないかは知らん。 


最後にこのコマンドでインストールできる。
```
sudo apt install docker-ce=23.0.1
```

dockerがインストールできているか確認する。 
```
sudo docker version
```

ちゃんとインストールされていれば次のように出力される。　
```
Client: Docker Engine - Community
 Version:           23.0.1
 API version:       1.42
 Go version:        go1.19.5
 Git commit:        a5ee5b1
 Built:             Thu Feb  9 19:46:56 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.1
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.5
  Git commit:       bc3805a
  Built:            Thu Feb  9 19:46:56 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.16
  GitCommit:        31aa4358a36870b21a992d3ad2bef29e1d693bec
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

## kubeadm, kubelet, kubectlのインストール 
kubeadmはkubernetesクラスターの構築をめちゃめちゃ簡単にできるツールで、`kubeadm init`と`kubeadm join`の二つのコマンドを提供する。 
`kubeadm init`は最初にクラスターを作成する時に使い、(今回のように2台構成なら)Master Nodeで打つ。
`kubeadm join`はクラスターにノードを追加する時に使い、Worker Nodeや2つ目以降のMaster Nodeで打つ。

kubeletはクラスター内の全てのマシンで実行され、podの起動などを請け負っている。(ユーザーがこのコマンドを使うことはあんまりない)

kubectlはクラスターを操作するコマンド。 デプロイや削除など基本的にはこのコマンドで全てを行う。

kubeadmをインストールする前にいくつか準備が必要。

### MACアドレスとproduct_uuidが全てのノードでユニークであることの検証
`ip link`もしくは`ifconfig -a`でMACアドレスを確認。
また、`sudo cat /sys/class/dmi/id/product_uuid`でproduct_uuidを確認。
今回はハードウェアが異なるので、どちらもユニークだったが、VMの場合はこれらが同じになる場合がある。

### iptablesがブリッジを通過するトラフィックを処理できるようにする
何してるかはよくわからんが、公式ページに書いてあったのでやっておく。(通信についての設定かな？)

```
modprobe br_netfilter

cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
```

### 必須ポートの確認
このサイトの通りにポートを開いておく。

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
　
開いているポートの確認は`sudo ufw status`でできる。
ポートの解放は`sudo ufw allow <開きたいポート番号>`でできる。

### スワップをオフにする
kubernetesではメモリのスワップがあると動作しないのでMasterもWorkerもどちらも`swapoff -a`でスワップをオフにする。

### kubeadm, kubelet, kubectlのインストール
```
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
```
```
sudo curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
```
```
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
```
```
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
```

# kubeadmでクラスター作成
kubernetesクラスターの作成にはkubeadmを使う。

## kubeadm init
Master Nodeで`kubeadm init`をしてクラスターを作成する。
```
kubeadm init --apiserver-advertise-address=172.19.109.201 --pod-network-cidr=10.244.0.0/16 --cri-socket=unix:///var/run/cri-dockerd.sock
```
apiserver-advertise-addressはクラスター内で使うipアドレスを指定している。Master Nodeのipアドレスを指定しておく。

pod-network-cidrはpod間通信をするためのCNIが利用するipアドレス。

cri-socketはdockerdのソケットのパスを渡す。

成功すると、次のコマンドを打つよう指示されるのでコマンドを打つ。
```
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
```

## CNI(flannel)の導入 
pod間通信を可能にする仮想ネットワークのアドオンであるflannelを導入する。
```
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
```

動いたか確認

```
kubectl get nodes
```

## kubeadm joinでWorker Nodeを追加
`kubeadm init`した際にWorker Nodeを追加する用のコマンドが出力されるので、それをWorker Nodeで打つ。
```
kubeadm join 172.19.109.201:6443 --token <表示されるtoken> --discovery-token-ca-cert-hash sha256:...
```

追加されたら、Master Nodeで確認 
```
kubectl get nodes
```

二つのノードが表示されたらOK


# クラスターのリセット
色々試しててクラスターがぐちゃぐちゃになってしまった時はクラスターをリセットする。(マジで役に立った)

Master NodeでWorker Nodeを削除する
```
kubectl delete node <worker nodeの名前>
```

Worker NodeとMaster Nodeからreset
```
kubeadm reset --force
systemctl stop kubelet
rm -rf /etc/kubernetes/
rm -rf ~/.kube/
rm -rf /var/lib/kubelet/
rm -rf /var/lib/cni/
rm -rf /etc/cni/
rm -rf /var/lib/etcd/
iptables -F && iptables -X
```
注) 最後のiptablesは実行するとwifiや有線LANが繋がらなくなるかもしれないので、繋がらなくなったら再起動しましょう。

ノードを再起動したら、
これで完全にリセットされるので、再度クラスターを作成する。

# デプロイ
free5gcやらcadvisorやらprometheusやら色々デプロイしていく。

## free5gcのデプロイ

ここにfree5gcのマニフェストファイルがあるので、これを利用する。(stage-3.1.1を利用するが、確認すると無くなってる)

https://github.com/free5gmano/free5gmano/tree/master/deploy

マニフェストファイルの詳しい説明は省略するが、podの様式をyamlで書いておいて簡単にデプロイできるようにしたもの。

このfree5gcのcnfをクラスター上にデプロイしていく。

以降のコマンドは全てMaster Nodeで実行する。
まず、namespaceを作成する。
```
kubectl create namespace free5gc
```
namespaceはクラスター上の仮想環境みたいなもの。

free5gcは次の順番でデプロイしていく。
NRF > UDR > UDM > AUSF > NSSF > AMF > PCF > UPF > SMF
必ず、前のpodがrunningになったことを確認してから次のNFをデプロイする。

```
kubectl apply -f 03-free5gc-nrf.yaml -n free5gc
kubectl apply -f 06-free5gc-udr.yaml -n free5gc
kubectl apply -f 08-free5gc-udm.yaml -n free5gc
kubectl apply -f 10-free5gc-ausf.yaml -n free5gc
kubectl apply -f 09-free5gc-nssf.yaml -n free5gc
kubectl apply -f 04-free5gc-amf.yaml -n free5gc
kubectl apply -f 07-free5gc-pcf.yaml -n free5gc
kubectl apply -f 02-free5gc-upf.yaml -n free5gc
kubectl apply -f 05-free5gc-smf.yaml -n free5gc
kubectl apply -f 01-free5gc-mongodb.yaml -n free5gc
kubectl apply -f 11-free5gc-webui.yaml -n free5gc
```

正常に動作しているか確認。
```
>> kubectl get pods -n free5gc


NAME                                        READY   STATUS    RESTARTS   AGE
free5gc-amf-deployment-5b94ff96d-f5vjh      1/1     Running   0          3m7s
free5gc-ausf-deployment-5548b989-nvc9f      1/1     Running   0          3m39s
free5gc-mongodb-7d954666d4-mvmnz            1/1     Running   0          62s
free5gc-nrf-deployment-5f9666c4f4-8l9m7     1/1     Running   0          4m42s
free5gc-nssf-deployment-586cb84fcd-zfzzn    1/1     Running   0          3m23s
free5gc-pcf-deployment-6f99976745-pp9tq     1/1     Running   0          2m48s
free5gc-smf-deployment-7647c6787-hm9c8      1/1     Running   0          2m17s
free5gc-udm-deployment-6f5f499c5f-pkktr     1/1     Running   0          3m58s
free5gc-udr-deployment-5749586f77-dqnrl     1/1     Running   0          4m13s
free5gc-upf-deployment-5f575485fc-tkwrh     1/1     Running   0          2m32s
free5gc-webui-deployment-57cc7b5498-gpdfr   1/1     Running   0          27s
```

web uiの確認
クラスターのPCから確認する場合は、http://\<web uiのpodのipアドレス>:5000

podのipアドレスは次のコマンドで確認できる。

```
>> kubectl get pods -n free5gc -o wide


NAME                                        READY   STATUS    RESTARTS   AGE     IP            NODE                  NOMINATED NODE   READINESS GATES
free5gc-amf-deployment-5b94ff96d-f5vjh      1/1     Running   0          9m20s   10.244.1.7    system-product-name   <none>           <none>
free5gc-ausf-deployment-5548b989-nvc9f      1/1     Running   0          9m52s   10.244.1.5    system-product-name   <none>           <none>
free5gc-mongodb-7d954666d4-mvmnz            1/1     Running   0          7m15s   10.244.1.11   system-product-name   <none>           <none>
free5gc-nrf-deployment-5f9666c4f4-8l9m7     1/1     Running   0          10m     10.244.1.2    system-product-name   <none>           <none>
free5gc-nssf-deployment-586cb84fcd-zfzzn    1/1     Running   0          9m36s   10.244.1.6    system-product-name   <none>           <none>
free5gc-pcf-deployment-6f99976745-pp9tq     1/1     Running   0          9m1s    10.244.1.8    system-product-name   <none>           <none>
free5gc-smf-deployment-7647c6787-hm9c8      1/1     Running   0          8m30s   10.244.1.10   system-product-name   <none>           <none>
free5gc-udm-deployment-6f5f499c5f-pkktr     1/1     Running   0          10m     10.244.1.4    system-product-name   <none>           <none>
free5gc-udr-deployment-5749586f77-dqnrl     1/1     Running   0          10m     10.244.1.3    system-product-name   <none>           <none>
free5gc-upf-deployment-5f575485fc-tkwrh     1/1     Running   0          8m45s   10.244.1.9    system-product-name   <none>           <none>
free5gc-webui-deployment-57cc7b5498-gpdfr   1/1     Running   0          6m40s   10.244.1.12   system-product-name   <none>           <none>
```

クラスター外(同一LAN内)から確認する場合
http://\<Worker Nodeのローカルipアドレス>:31111

web uiはID/pass：admin/free5gc でログインできる。

![スクリーンショット 2023-03-07 21.15.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3243092/35482223-f475-0b9d-bed9-593976ad970e.png)




## UERANSIMのデプロイ
UERANSIMはその名の通りUEと基地局のシミュレータで、いろんなシミュレーションができる。

まず、UERANSIMのマニフェストファイルを書き換える。
gnbのマニフェストファイルを以下の部分を書き換える。

```diff_yaml
  name: ueransim-gnb-configmap
data:
  free5gc-gnb.yaml: |
    mcc: '208'          # Mobile Country Code value
    mnc: '93'           # Mobile Network Code value (2 or 3 digits)

    nci: '0x000000010'  # NR Cell Identity (36-bit)
    idLength: 32        # NR gNB ID length in bits [22...32]
    tac: 1              # Tracking Area Code

-    linkIp: 10.244.3.31  # gNB's local IP address for Radio Link Simulation (Usually same with local IP)
-    ngapIp: 10.244.3.31   # gNB's local IP address for N2 Interface (Usually same with local IP)    
-    gtpIp: 10.244.3.31    # gNB's local IP address for N3 Interface (Usually same with local IP)
+    linkIp: 10.244.1.13  # gnbのpodのipアドレス
+    ngapIp: 10.244.1.13  # gnbのpodのipアドレス
+    gtpIp: 10.244.1.13   # gnbのpodのipアドレス

    # List of AMF address information
    amfConfigs:
-      - address: 10.244.3.22
-        port: 38412
+      - address: 10.244.1.7  # AMFのpodのipアドレス
+        port: 38412 # AMFのSCTPプロトコルのポート
```

kubernetesのpodのipアドレスは一つずつインクリメントされるため、最後にデプロイされたpodのipアドレス(ここではweb ui)に1足したものを入力する。

変更したらデプロイする。
```
kubectl apply -f ueransim/ueransim-gnb.yaml -n free5gc
```

podの中に入って、is-ngap-up: trueになることを確認。
```
kubectl -n free5gc exec -it ueransim-gnb-deployment-7cf855fc85-z998r -- bash

/UERANSIM# cd build
/UERANSIM/build# ./nr-cli UERANSIM-gnb-208-93-1
-------------------------------------------------------------------------------------
$ status
is-ngap-up: true
-------------------------------------------------------------------------------------
```

次にueransim-ue.yamlの次の部分を書き換える。
```diff_yaml
data:
  free5gc-ue.yaml: |
    # IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 digits)
    supi: 'imsi-208930000000003'
    # Mobile Country Code value of HPLMN
    mcc: '208'
    # Mobile Network Code value of HPLMN (2 or 3 digits)
    mnc: '93'

    # Permanent subscription key
    key: '8baf473f2f8fd09487cccbd7097c6862'
    # Operator code (OP or OPC) of the UE
    op: '8e27b6af0e692e750f32667a3b14605d'
    # This value specifies the OP type and it can be either 'OP' or 'OPC'
    opType: 'OP'
    # Authentication Management Field (AMF) value
    amf: '8000'
    # IMEI number of the device. It is used if no SUPI is provided
    imei: '356938035643803'
    # IMEISV number of the device. It is used if no SUPI and IMEI is provided
    imeiSv: '4370816125816151'

    # List of gNB IP addresses for Radio Link Simulation
    gnbSearchList:
-      - 10.244.3.31
+      - 10.244.1.13 # gnbのpodのipアドレス
```

書き換えたらデプロイ
```
kubectl apply -f ueransim/ueransim-ue.yaml -n free5gc
```

podの中に入って確認
```
kubectl -n free5gc exec -it ueransim-ue-deployment-dd445b88d-wcmm9 -- bash

/UERANSIM# cd build
/UERANSIM/build# ./nr-cli -d imsi-208930000000003
imsi-208930000000003
/UERANSIM/build# ./nr-cli imsi-208930000000003
-------------------------------------------------------------------------------------
$ status
cm-state: CM-CONNECTED
rm-state: RM-REGISTERED
mm-state: MM-REGISTERED/NORMAL-SERVICE
5u-state: 5U1-UPDATED
sim-inserted: true
selected-plmn: 208/93
current-cell: 1
current-plmn: 208/93
current-tac: 1
last-tai: PLMN[208/93] TAC[1]
stored-suci: no-identity
stored-guti: 
 plmn: 208/93
 amf-region-id: 0xca
 amf-set-id: 1016
 amf-pointer: 0
 tmsi: 0x00000001
has-emergency: false
-------------------------------------------------------------------------------------
```


cm-state: CM-CONNECTEDになっていることを確認

## cAdvisorのデプロイ
dockerのコンテナメトリクスを収集できるcAdvisorを導入する。
まずはnamespaceの作成。
```
kubectl create namespace cadvisor
```
cadvisorのgithubにkubernetes用のマニフェストファイルがあるので、これを利用。


https://github.com/google/cadvisor/tree/master/deploy/kubernetes/base

daemonset.yamlの最後に次のコードを追加

```yaml
---          
apiVersion: v1
kind: Service
metadata:
  name: cadvisor
  namespace: cadvisor
spec:
  selector:
    app: cadvisor
  ports:
  - protocol: TCP
    port: 8080
    nodePort: 30080
  type: NodePort
```
これによってサービスとして外部に公開できる。

kustomizeによってデプロイする。

```
kubectl apply -k cadvisor/ -n cadvisor
```

デプロイできたか確認
```
>> kubectl get pod -n cadvisor
NAME             READY   STATUS    RESTARTS   AGE
cadvisor-z6v7l   1/1     Running   0          21s


>> kubectl get service -n cadvisor
NAME       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
cadvisor   NodePort   10.102.196.48   <none>        8080:30080/TCP   33s


>> kubectl get daemonset -n cadvisor
NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
cadvisor   1         1         1       1            1           <none>          42s
```

uiにはfree5gcのweb uiと同様にアクセスできる。

![スクリーンショット 2023-03-07 21.15.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3243092/4a0821ab-1563-4b03-3a91-f440bc40f489.png)


## prometheusのデプロイ
httpプル方式でメトリクスを蓄積、監視するprometheusを導入して、cadvisorのメトリクスをこちらに流す。

https://uzimihsr.github.io/post/2022-11-28-kubernetes-prometheus-kube-state-metrics-cadvisor-ja/
こちらを参考にprometheusのマニフェストファイルとコンフィグファイルをgistにしたので、これを使えばできるはず。

```
kubectl -n prometheus create serviceaccount prometheus
kubectl create clusterrole prometheus --verb=get,list,watch --resource=pods,services,endpoints
kubectl create clusterrolebinding prometheus --clusterrole=prometheus --serviceaccount=prometheus:prometheus
kubectl apply -f configmap.yaml -n prometheus
kubectl apply -f deployment.yaml -n prometheus
```

uiにアクセスできるか確認。
![スクリーンショット 2023-03-07 21.38.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3243092/77421260-a360-5235-c07f-48249e4b8004.png)

service discoveryにcadvisorがあることを確認。
![スクリーンショット 2023-03-07 21.40.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3243092/4d93c55f-0e10-5ceb-943b-0dc7cc9c6044.png)


https://gist.github.com/hakiitakeru/55b957b1b5cf71eb25131b12b000709f

https://gist.github.com/hakiitakeru/006afcf172177f3ca54d037f5766006e

# デプロイメントの削除
デプロイしたものを削除したいときは次のコードで削除できる。
```
kubectl delete -f 01-free5gc-mongodb.yaml -n free5gc
```